From 7f810052fd0e3cac0fe0d6ab4605f0e1ae96f0ff Mon Sep 17 00:00:00 2001 From: Thorsten Ludewig Date: Fri, 17 Jun 2022 13:13:28 +0200 Subject: [PATCH 01/97] mongoose webserver submodule added --- .gitmodules | 3 +++ configure.ac | 1 + gphoto2/mongoose | 1 + 3 files changed, 5 insertions(+) create mode 100644 .gitmodules create mode 160000 gphoto2/mongoose diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..d1064c64 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "gphoto2/mongoose"] + path = gphoto2/mongoose + url = https://github.com/cesanta/mongoose.git diff --git a/configure.ac b/configure.ac index 621bb347..6e82ff43 100644 --- a/configure.ac +++ b/configure.ac @@ -19,6 +19,7 @@ AM_INIT_AUTOMAKE([ dist-bzip2 dist-xz check-news + subdir-objects ]) GP_CHECK_SHELL_ENVIRONMENT diff --git a/gphoto2/mongoose b/gphoto2/mongoose new file mode 160000 index 00000000..3331ed2f --- /dev/null +++ b/gphoto2/mongoose @@ -0,0 +1 @@ +Subproject commit 3331ed2f491e81bca4bde817813044c370e87f9e From a3ea8d143727a03c10a401279f28ad5aa918bc83 Mon Sep 17 00:00:00 2001 From: Thorsten Ludewig Date: Fri, 17 Jun 2022 13:14:10 +0200 Subject: [PATCH 02/97] mongoose subproject --- gphoto2/mongoose | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gphoto2/mongoose b/gphoto2/mongoose index 3331ed2f..d5993ba2 160000 --- a/gphoto2/mongoose +++ b/gphoto2/mongoose @@ -1 +1 @@ -Subproject commit 3331ed2f491e81bca4bde817813044c370e87f9e +Subproject commit d5993ba27ece4b406c230eca63b76dd5d2c28a2d From dfe9116059558515bcfdc9aaba752bfe7963a7c0 Mon Sep 17 00:00:00 2001 From: Thorsten Ludewig Date: Fri, 17 Jun 2022 13:14:53 +0200 Subject: [PATCH 03/97] additional binary gphoto2-webapi configured --- gphoto2/Makefile.am | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/gphoto2/Makefile.am b/gphoto2/Makefile.am index 88ff9cf2..15fc1eae 100644 --- a/gphoto2/Makefile.am +++ b/gphoto2/Makefile.am @@ -7,7 +7,7 @@ EXTRA_DIST = \ gphoto2-cmd-capture.c gphoto2-cmd-capture.h \ i18n.h test-hook.sh -bin_PROGRAMS = gphoto2 +bin_PROGRAMS = gphoto2 gphoto2-webapi doc_DATA = test-hook.sh @@ -37,6 +37,21 @@ gphoto2_SOURCES = \ range.c range.h \ shell.c shell.h +gphoto2_webapi_SOURCES = \ + $(AA_FILES) \ + $(CDK_FILES) \ + $(NO_POPT_FILES) \ + actions.c actions.h \ + foreach.c foreach.h \ + globals.h \ + gp-params.c gp-params.h \ + spawnve.c spawnve.h \ + gphoto2-webapi.c gphoto2-webapi.h \ + version.c version.h \ + range.c range.h \ + mongoose/mongoose.c mongoose/mongoose.h \ + server.c server.h + #gphoto2_LDFLAGS = -export-dynamic gphoto2_CFLAGS = \ @@ -45,11 +60,20 @@ gphoto2_CFLAGS = \ $(CDK_CFLAGS) $(AA_CFLAGS) \ $(LIBEXIF_CFLAGS) $(RL_CFLAGS) $(POPT_CFLAGS) +gphoto2_webapi_CFLAGS = \ + -DWEBAPI -I$(top_srcdir) -I$(top_srcdir)/gphoto2 \ + $(LIBGPHOTO2_CFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(CDK_CFLAGS) $(AA_CFLAGS) \ + $(LIBEXIF_CFLAGS) $(RL_CFLAGS) $(POPT_CFLAGS) gphoto2_LDADD = \ $(LIBGPHOTO2_LIBS) $(CDK_LIBS) $(AA_LIBS) $(JPEG_LIBS) \ $(PTHREAD_LIBS) $(LIBEXIF_LIBS) $(INTLLIBS) $(RL_LIBS) $(POPT_LIBS) +gphoto2_webapi_LDADD = \ + $(LIBGPHOTO2_LIBS) $(CDK_LIBS) $(AA_LIBS) $(JPEG_LIBS) \ + $(PTHREAD_LIBS) $(LIBEXIF_LIBS) $(INTLLIBS) $(RL_LIBS) $(POPT_LIBS) + TESTS = spawntest TESTS_ENVIRONMENT = GPHOTO_HOOK=$(srcdir)/test-hook.sh From 022f86ea04b424cad1d4f99aae1e002423bb07c0 Mon Sep 17 00:00:00 2001 From: Thorsten Ludewig Date: Fri, 17 Jun 2022 13:15:41 +0200 Subject: [PATCH 04/97] json output for webapi added --- gphoto2/actions.c | 200 ++++++++++++++++++++++++++++++++++++++++------ gphoto2/actions.h | 21 ++++- 2 files changed, 193 insertions(+), 28 deletions(-) diff --git a/gphoto2/actions.c b/gphoto2/actions.c index f4d37e13..cedfc791 100644 --- a/gphoto2/actions.c +++ b/gphoto2/actions.c @@ -76,7 +76,11 @@ #define __unused__ #endif +#ifndef WEBAPI static int print_widget (GPParams *p, const char*name, CameraWidget *widget); +#else +static int print_widget (struct mg_connection *c, GPParams *p, const char*name, CameraWidget *widget); +#endif static long timediff_now (struct timeval *target) { @@ -663,8 +667,14 @@ list_ports_action (GPParams *p) } -int -auto_detect_action (GPParams *p) + +#ifndef WEBAPI +int +auto_detect_action(GPParams *p) +#else +int +auto_detect_action(struct mg_connection *c, GPParams *p) +#endif { int x, count; CameraList *list; @@ -674,19 +684,35 @@ auto_detect_action (GPParams *p) count = gp_port_info_list_count (p->portinfo_list); CR (gp_list_new (&list)); - gp_abilities_list_detect (gp_params_abilities_list(p), p->portinfo_list, list, p->context); + gp_abilities_list_detect (gp_params_abilities_list(p), p->portinfo_list, list, p->context); - CL (count = gp_list_count (list), list); + CL (count = gp_list_count (list), list); + +#ifdef WEBAPI + char *firstChar = " "; + JSON_PRINTF( c, "{\"result\":[", 0 ); +#else + printf(_("%-30s %-16s\n"), _("Model"), _("Port")); + printf(_("----------------------------------------------------------\n")); +#endif + + for (x = 0; x < count; x++) { + CL (gp_list_get_name (list, x, &name), list); + CL (gp_list_get_value (list, x, &value), list); +#ifdef WEBAPI + JSON_PRINTF( c, "%s{\"model\":\"%s\",\"port\":\"%s\"}\n", firstChar, name, value ); + firstChar = ","; +#else + printf(_("%-30s %-16s\n"), name, value); +#endif + } + +#ifdef WEBAPI + JSON_PRINTF( c, "],\"return_code\": 0}\n" ); +#endif - printf(_("%-30s %-16s\n"), _("Model"), _("Port")); - printf(_("----------------------------------------------------------\n")); - for (x = 0; x < count; x++) { - CL (gp_list_get_name (list, x, &name), list); - CL (gp_list_get_value (list, x, &value), list); - printf(_("%-30s %-16s\n"), name, value); - } gp_list_free (list); - return GP_OK; + return GP_OK; } int @@ -1544,8 +1570,14 @@ debug_action (GPParams *p, const char *debug_loglevel, const char *debug_logfile return GP_OK; } +#ifdef WEBAPI static void -display_widgets (GPParams *p, CameraWidget *widget, char *prefix, int dumpval) { +display_widgets (struct mg_connection *c, char **firstChar, GPParams *p, CameraWidget *widget, char *prefix, int dumpval) +#else +static void +display_widgets (GPParams *p, CameraWidget *widget, char *prefix, int dumpval) +#endif +{ int ret, n, i; char *newprefix; const char *label, *name, *uselabel; @@ -1570,8 +1602,20 @@ display_widgets (GPParams *p, CameraWidget *widget, char *prefix, int dumpval) { sprintf(newprefix,"%s/%s",prefix,uselabel); if ((type != GP_WIDGET_WINDOW) && (type != GP_WIDGET_SECTION)) { +#ifndef WEBAPI printf("%s\n",newprefix); if (dumpval) print_widget (p, newprefix, widget); +#else + if (dumpval) { + JSON_PRINTF(c,"%s{\"path\":\"%s\"",*firstChar,newprefix); + print_widget (c, p, newprefix, widget); + JSON_PRINTF(c,"}\n"); + } + else { + JSON_PRINTF(c,"%s\"%s\"\n",*firstChar,newprefix); + } + *firstChar = ","; +#endif } for (i=0; icamera, &rootconfig, p->context); - if (ret != GP_OK) return ret; - display_widgets (p, rootconfig, "", 1); - gp_widget_free (rootconfig); - return GP_OK; + + if (ret == GP_OK) + { +#ifdef WEBAPI + char *firstChar = " "; + JSON_PRINTF( c, "\"result\":[" ); + display_widgets (c, &firstChar, p, rootconfig, "", 1); + JSON_PRINTF( c, "]," ); +#else + display_widgets (p, rootconfig, "", 1); +#endif + gp_widget_free (rootconfig); + } + + return ret; } + +#ifndef WEBAPI int -list_config_action (GPParams *p) { +list_config_action (GPParams *p) +#else +int +list_config_action (struct mg_connection *c, GPParams *p) +#endif +{ CameraWidget *rootconfig; int ret; ret = gp_camera_get_config (p->camera, &rootconfig, p->context); - if (ret != GP_OK) return ret; - display_widgets (p, rootconfig, "", 0); - gp_widget_free (rootconfig); - return GP_OK; + + if (ret == GP_OK) + { +#ifdef WEBAPI + char *firstChar = " "; + JSON_PRINTF( c, "\"result\":[" ); + display_widgets (c, &firstChar, p, rootconfig, "", 0); + JSON_PRINTF( c, "]," ); +#else + display_widgets (p, rootconfig, "", 0); +#endif + gp_widget_free (rootconfig); + } + + return ret; } static int @@ -1680,8 +1764,14 @@ my_strftime(char *s, size_t max, const char *fmt, const struct tm *tm) return strftime(s, max, fmt, tm); } +#ifndef WEBAPI static int -print_widget (GPParams *p, const char *name, CameraWidget *widget) { +print_widget (GPParams *p, const char *name, CameraWidget *widget) +#else +static int +print_widget (struct mg_connection *c, GPParams *p, const char *name, CameraWidget *widget) +#endif +{ const char *label; CameraWidgetType type; int ret, readonly; @@ -1697,16 +1787,26 @@ print_widget (GPParams *p, const char *name, CameraWidget *widget) { if (ret != GP_OK) return ret; +#ifndef WEBAPI printf ("Label: %s\n", label); /* "Label:" is not i18ned, the "label" variable is */ printf ("Readonly: %d\n", readonly); +#else + JSON_PRINTF(c,", \"label\": \"%s\"", label); /* "Label:" is not i18ned, the "label" variable is */ + JSON_PRINTF(c,", \"readonly\": %s", (readonly) ? "true" : "false" ); +#endif switch (type) { case GP_WIDGET_TEXT: { /* char * */ char *txt; ret = gp_widget_get_value (widget, &txt); if (ret == GP_OK) { +#ifndef WEBAPI printf ("Type: TEXT\n"); /* parsed by scripts, no i18n */ printf ("Current: %s\n",txt); +#else + JSON_PRINTF(c,", \"type\": \"TEXT\""); /* parsed by scripts, no i18n */ + JSON_PRINTF(c,", \"current\": \"%s\"", txt); +#endif } else { gp_context_error (p->context, _("Failed to retrieve value of text widget %s."), name); } @@ -1719,11 +1819,19 @@ print_widget (GPParams *p, const char *name, CameraWidget *widget) { if (ret == GP_OK) ret = gp_widget_get_value (widget, &f); if (ret == GP_OK) { +#ifndef WEBAPI printf ("Type: RANGE\n"); /* parsed by scripts, no i18n */ printf ("Current: %g\n", f); /* parsed by scripts, no i18n */ printf ("Bottom: %g\n", b); /* parsed by scripts, no i18n */ printf ("Top: %g\n", t); /* parsed by scripts, no i18n */ printf ("Step: %g\n", s); /* parsed by scripts, no i18n */ +#else + JSON_PRINTF(c,", \"type\": \"RANGE\""); + JSON_PRINTF(c,", \"current\": %g\n", f); + JSON_PRINTF(c,", \"bottom\": %g\n", b); + JSON_PRINTF(c,", \"top\": %g\n", t); + JSON_PRINTF(c,", \"step\": %g\n", s); +#endif } else { gp_context_error (p->context, _("Failed to retrieve values of range widget %s."), name); } @@ -1734,8 +1842,13 @@ print_widget (GPParams *p, const char *name, CameraWidget *widget) { ret = gp_widget_get_value (widget, &t); if (ret == GP_OK) { +#ifndef WEBAPI printf ("Type: TOGGLE\n"); printf ("Current: %d\n",t); +#else + JSON_PRINTF(c,", \"type\": \"TOGGLE\""); + JSON_PRINTF(c,", \"current\": %d",t); +#endif } else { gp_context_error (p->context, _("Failed to retrieve values of toggle widget %s."), name); } @@ -1755,10 +1868,17 @@ print_widget (GPParams *p, const char *name, CameraWidget *widget) { xtime = t; xtm = localtime (&xtime); ret = my_strftime (timebuf, sizeof(timebuf), "%c", xtm); +#ifndef WEBAPI printf ("Type: DATE\n"); printf ("Current: %d\n", t); printf ("Printable: %s\n", timebuf); printf ("Help: %s\n", _("Use 'now' as the current time when setting.\n")); +#else + JSON_PRINTF(c,", \"type\": \"DATE\""); + JSON_PRINTF(c,", \"current\": %d", t); + JSON_PRINTF(c,", \"printable\": \"%s\"", timebuf); + JSON_PRINTF(c,", \"help\": \"%s\"", _("Use 'now' as the current time when setting.")); +#endif break; } case GP_WIDGET_MENU: @@ -1770,15 +1890,38 @@ print_widget (GPParams *p, const char *name, CameraWidget *widget) { if (ret == GP_OK) { cnt = gp_widget_count_choices (widget); if (type == GP_WIDGET_MENU) +#ifndef WEBAPI printf ("Type: MENU\n"); +#else + JSON_PRINTF(c,", \"type\": \"MENU\""); +#endif else +#ifndef WEBAPI printf ("Type: RADIO\n"); +#else + JSON_PRINTF(c,", \"type\": \"MENU\""); +#endif +#ifndef WEBAPI printf ("Current: %s\n",current); +#else + JSON_PRINTF(c,", \"current\": \"%s\"",current); + JSON_PRINTF(c,", \"choice\": ["); +#endif for ( i=0; icontext, _("Failed to retrieve values of radio widget %s."), name); } @@ -1791,8 +1934,11 @@ print_widget (GPParams *p, const char *name, CameraWidget *widget) { case GP_WIDGET_BUTTON: break; } - + +#ifndef WEBAPI printf ("END\n"); +#endif + return GP_OK; } @@ -1805,7 +1951,11 @@ get_config_action (GPParams *p, const char *name) { ret = _find_widget_by_name (p, name, &child, &rootconfig); if (ret != GP_OK) return ret; + +#ifndef WEBAPI ret = print_widget (p, name, child); +#endif + gp_widget_free (rootconfig); return ret; } diff --git a/gphoto2/actions.h b/gphoto2/actions.h index a5798f16..5c640be1 100644 --- a/gphoto2/actions.h +++ b/gphoto2/actions.h @@ -26,6 +26,14 @@ #include +#ifdef WEBAPI +#include "mongoose/mongoose.h" +#define JSON_PRINTF mg_http_printf_chunk +#else +#define JSON_PRINTF(x,...) +#endif + + enum wait_type { WAIT_TIME, WAIT_EVENTS, @@ -85,15 +93,12 @@ int action_camera_wait_event (GPParams *, enum download_type dt, const char /* Other actions */ int list_cameras_action (GPParams *); int list_ports_action (GPParams *); -int auto_detect_action (GPParams *); int set_folder_action (GPParams *, const char *folder); int set_filename_action (GPParams *, const char *filename); int print_version_action (GPParams *); int override_usbids_action (GPParams *, int usb_vendor, int usb_product, int usb_vendor_modified, int usb_product_modified); int debug_action (GPParams *, const char *debug_loglevel, const char *debug_logfile_name); -int list_config_action (GPParams *); -int list_all_config_action (GPParams *); int get_config_action (GPParams *, const char *name); int set_config_action (GPParams *, const char *name, const char *value); int set_config_index_action (GPParams *, const char *name, const char *value); @@ -102,6 +107,16 @@ int print_storage_info (GPParams *); void _get_portinfo_list (GPParams *p); +#ifdef WEBAPI +int auto_detect_action (struct mg_connection *, GPParams *); +int list_config_action (struct mg_connection *, GPParams *); +int list_all_config_action (struct mg_connection *, GPParams *); +#else +int auto_detect_action (GPParams *); +int list_config_action (GPParams *); +int list_all_config_action (GPParams *); +#endif + #endif /* !defined(GPHOTO2_ACTIONS_H) */ From d03c01c75075efb13eebc1efb39f60d907e94ac9 Mon Sep 17 00:00:00 2001 From: Thorsten Ludewig Date: Fri, 17 Jun 2022 13:16:00 +0200 Subject: [PATCH 05/97] webapi initial commit --- gphoto2/gphoto2-webapi.c | 2560 ++++++++++++++++++++++++++++++++++++++ gphoto2/gphoto2-webapi.h | 61 + gphoto2/server.c | 239 ++++ gphoto2/server.h | 22 + 4 files changed, 2882 insertions(+) create mode 100644 gphoto2/gphoto2-webapi.c create mode 100644 gphoto2/gphoto2-webapi.h create mode 100644 gphoto2/server.c create mode 100644 gphoto2/server.h diff --git a/gphoto2/gphoto2-webapi.c b/gphoto2/gphoto2-webapi.c new file mode 100644 index 00000000..4e47320e --- /dev/null +++ b/gphoto2/gphoto2-webapi.c @@ -0,0 +1,2560 @@ +/* + * Copyright 2002 Lutz Mueller + * Copyright 2004-2013 Marcus Meissner + * Copyright 2022 Thorsten Ludewig + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +#define _DARWIN_C_SOURCE +#define _XOPEN_SOURCE 500 + +#include "config.h" +#if defined (HAVE_SIGNAL_H) +#include +#endif +#include "actions.h" +#include "foreach.h" +#include +#include +#include +#include "gp-params.h" +#include "i18n.h" +#include "gphoto2-webapi.h" +#include "range.h" +#include "server.h" + +#ifdef HAVE_CDK +# include "gphoto2-cmd-config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#ifdef HAVE_RL +# include +#endif + +#ifdef HAVE_PTHREAD +# include +#endif + +#ifndef WIN32 +# include +#endif + +#if defined(_MSC_VER) +#include +#define getcwd _getcwd +#elif defined(__GNUC__) +#include +#endif + +#include "mongoose/mongoose.h" + +#ifndef MAX +# define MAX(a, b) ((a) > (b) ? (a) : (b)) +#endif +#ifndef MIN +# define MIN(a, b) ((a) < (b) ? (a) : (b)) +#endif + +#define CR(result) {int r = (result); if (r < 0) return (r);} + +#ifdef __GNUC__ +#define __unused__ __attribute__((unused)) +#else +#define __unused__ +#endif + +static int debug_option_given = 0; +char glob_cancel = 0; +static int glob_frames = 0; +static int glob_interval = 0; +static int glob_bulblength = 0; + +GPParams gp_params; + +/* flag for SIGUSR1 handler */ +volatile int capture_now = 0; + +/* flag for SIGUSR2 handler */ +volatile int end_next = 0; + +/*! \brief Copy string almost like strncpy, converting to lower case. + * + * This function behaves like strncpy, but + * - convert chars to lower case + * - ensures the dst buffer is terminated with a '\0' + * (even if that means cutting the string short) + * - limits the string copy at a reasonable size (32K) + * + * Relies on tolower() which may be locale specific, but cannot be + * multibyte encoding safe. + */ + +static size_t +strncpy_lower(char *dst, const char *src, size_t count) +{ + unsigned int i; + if ((dst == NULL) || (src == NULL) + || (((unsigned long)count)>= 0x7fff)) { + return -1; + } + for (i=0; (itm_hour % 12; + if (hour12 == 0) { + hour12 = 12; + } + + /* + * If the user didn't specify a filename, use the original name + * (and prefix). + */ + if (!gp_params.filename || !strcmp (gp_params.filename, "")) { + if (file) { + return gp_file_get_name_by_type (file, name, type, path); + } else if ((type == GP_FILE_TYPE_NORMAL) && strchr(name,'.')) { + /* return the original name */ + *path = strdup(name); + return (GP_OK); + } else + /* Download required to get the path from CameraFile *file */ + return (GP_ERROR_BAD_PARAMETERS); + } + + /* The user did specify a filename. Use it. */ + b[sizeof (b) - 1] = '\0'; + for (i = 0; i < strlen (gp_params.filename); i++) { + if (gp_params.filename[i] == '%') { + char padding = '0'; /* default padding character */ + int precision = 0; /* default: no padding */ + i++; + /* determine padding character */ + switch (gp_params.filename[i]) { + /* case ' ': + * spaces are not supported everywhere, so we + * restrict ourselves to padding with zeros. + */ + case '0': + padding = gp_params.filename[i]; + precision = 1; /* do padding */ + i++; + break; + } + /* determine padding width */ + if (isdigit((int)gp_params.filename[i])) { + char *cp; + long int _prec; + _prec = strtol(&gp_params.filename[i], + &cp, 10); + if (_prec < 1) + precision = 1; + else if (_prec > 20) + precision = 20; + else + precision = _prec; + if (*cp != 'n') { + /* make sure this is %n */ + gp_context_error (gp_params.context, + _("Zero padding numbers " + "in file names is only " + "possible with %%n.")); + return GP_ERROR_BAD_PARAMETERS; + } + /* go to first non-digit character */ + i += (cp - &gp_params.filename[i]); + } else if (precision && ( gp_params.filename[i] != 'n')) { + gp_context_error (gp_params.context, + _("You cannot use %%n " + "zero padding " + "without a " + "precision value!" + )); + return GP_ERROR_BAD_PARAMETERS; + } + switch (gp_params.filename[i]) { + case 'n': + /* + * Previously this used an folder index number. + * Now this uses a linear increasing number. + */ + if (precision > 1) { + char padfmt[16]; + strcpy(padfmt, "%!.*i"); + padfmt[1] = padding; + snprintf (b, sizeof (b), padfmt, + precision, gp_params.filenr); + } else { + snprintf (b, sizeof (b), "%i", + gp_params.filenr); + } + gp_params.filenr++; + break; + + case 'C': + /* Get the suffix of the original name */ + s = strrchr (name, '.'); + if (!s) { + free (*path); + *path = NULL; + gp_context_error (gp_params.context, + _("The filename provided " + "by the camera ('%s') " + "does not contain a " + "suffix!"), name); + return (GP_ERROR_BAD_PARAMETERS); + } + strncpy (b, s + 1, sizeof (b) - 1); + break; + + case 'f': + + /* Get the file name without suffix */ + s = strrchr (name, '.'); + if (!s) + strncpy (b, name, sizeof (b) - 1); + else { + l = MIN ((unsigned long)(sizeof(b) - 1), + (unsigned long)(s - name)); + strncpy (b, name, l); + b[l] = '\0'; + } + break; + + case 'F': + /* Get the folder name */ + { + const char *f = folder ? folder : ""; + if (f[0] == '/') f++; /* Skip first '/' */ + if (!f[0]) f = "."; /* replace empty folder name by '.' */ + + strncpy (b, f, sizeof (b) - 1 - strlen(b)); + b[sizeof(b) - 1] = '\0'; + } + break; + + case 'a': + case 'A': + case 'b': + case 'B': + case 'd': + case 'H': + case 'k': + case 'I': + case 'l': + case 'j': + case 'm': + case 'M': + case 'S': + case 'y': + case 'Y': + { + char fmt[3] = { '%', '\0', '\0' }; + if (!file) return (GP_ERROR_BAD_PARAMETERS); /* mtime unknown */ + + fmt[1] = gp_params.filename[i]; /* the letter of this 'case' */ + strftime(b, sizeof (b), fmt, tm); + break; + } + case '%': + strcpy (b, "%"); + break; + case ':': + strncpy_lower(b, name, sizeof(b)); + b[sizeof(b)-1] = '\0'; + break; + default: + free (*path); + *path = NULL; + gp_context_error (gp_params.context, + _("Invalid format '%s' (error at " + "position %i)."), gp_params.filename, + i + 1); + return (GP_ERROR_BAD_PARAMETERS); + } + } else { + b[0] = gp_params.filename[i]; + b[1] = '\0'; + } + + s = *path ? realloc (*path, strlen (*path) + strlen (b) + 1) : + malloc (strlen (b) + 1); + if (!s) { + free (*path); + *path = NULL; + return (GP_ERROR_NO_MEMORY); + } + if (*path) { + *path = s; + strcat (*path, b); + } else { + *path = s; + strcpy (*path, b); + } + } + + /** + * If the file is a capture_preview, + * apply prefix over the calculated basename + */ + if (type == GP_FILE_TYPE_PREVIEW) { + return gp_file_get_name_by_type (file, s, type, path); + } + + return (GP_OK); +} + + +int +save_camera_file_to_file ( + const char *folder, const char *name, CameraFileType type, CameraFile *file, const char *curname +) { + char *path = NULL, s[1024], c[1024]; + int res; + time_t mtime; + struct utimbuf u; + + CR (get_path_for_file (folder, name, type, file, &path)); + strncpy (s, path, sizeof (s) - 1); + s[sizeof (s) - 1] = '\0'; + free (path); + path = NULL; + + if ((gp_params.flags & FLAGS_SKIP_EXISTING) && gp_system_is_file (s)) { + if ((gp_params.flags & FLAGS_QUIET) == 0) { + printf (_("Skip existing file %s\n"), s); + fflush (stdout); + } + if (curname) + unlink (curname); + return (GP_OK); + } + if ((gp_params.flags & FLAGS_QUIET) == 0) { + while ((gp_params.flags & FLAGS_FORCE_OVERWRITE) == 0 && + gp_system_is_file (s)) { + do { + putchar ('\007'); + printf (_("File %s exists. Overwrite? [y|n] "), + s); + fflush (stdout); + if (NULL == fgets (c, sizeof (c) - 1, stdin)) + return GP_ERROR; + } while ((c[0]!='y')&&(c[0]!='Y')&& + (c[0]!='n')&&(c[0]!='N')); + + if ((c[0]=='y') || (c[0]=='Y')) + break; + + do { + printf (_("Specify new filename? [y|n] ")); + fflush (stdout); + if (NULL == fgets (c, sizeof (c) - 1, stdin)) + return GP_ERROR; + } while ((c[0]!='y')&&(c[0]!='Y')&& + (c[0]!='n')&&(c[0]!='N')); + + if (!((c[0]=='y') || (c[0]=='Y'))) { + if (curname) unlink (curname); + return (GP_OK); + } + + printf (_("Enter new filename: ")); + fflush (stdout); + if (NULL == fgets (s, sizeof (s) - 1, stdin)) + return GP_ERROR; + s[strlen (s) - 1] = 0; + } + printf (_("Saving file as %s\n"), s); + fflush (stdout); + } + path = s; + while ((path = strchr (path, gp_system_dir_delim))){ + *path = '\0'; + if(!gp_system_is_dir (s)) + gp_system_mkdir (s); + *path++ = gp_system_dir_delim; + } + if (curname) { + int x; + + unlink(s); + if (-1 == rename (curname, s)) { + /* happens if the user specified a absolute path with --filename */ + /* EPERM happens on windows, see https://github.com/gphoto/libgphoto2/issues/97 */ + if ((errno == EXDEV) || (errno == EPERM)) { + char buf[8192]; + int in_fd, out_fd; + + in_fd = open(curname, O_RDONLY); + if (in_fd < 0) + perror("Can't open file for reading"); + + out_fd = open(s, O_CREAT | O_WRONLY, 0644); + if (out_fd < 0) + perror("Can't open file for writing"); + + while (1) { + ssize_t result = read(in_fd, buf, sizeof(buf)); + if (!result) break; + if (-1 == write(out_fd, buf, result)) { + perror("write"); + break; + } + } + close(out_fd); + close(in_fd); + unlink(curname); + } else + perror("rename"); + } + x = umask(0022); /* get umask */ + umask(x);/* set it back to the old value */ + chmod(s,0666 & ~x); + } + res = gp_file_get_mtime (file, &mtime); + if ((res == GP_OK) && (mtime)) { + u.actime = mtime; + u.modtime = mtime; + utime (s, &u); + } + gp_params_run_hook(&gp_params, "download", s); + return (GP_OK); +} + +int +camera_file_exists (Camera *camera, GPContext *context, const char *folder, + const char *filename, CameraFileType type) +{ + CameraFileInfo info; + CR (gp_camera_file_get_info (camera, folder, filename, &info, + context)); + switch (type) { + case GP_FILE_TYPE_METADATA: + return TRUE; + case GP_FILE_TYPE_AUDIO: + return (info.audio.fields != 0); + case GP_FILE_TYPE_PREVIEW: + return (info.preview.fields != 0); + case GP_FILE_TYPE_RAW: + case GP_FILE_TYPE_NORMAL: + return (info.file.fields != 0); + default: + gp_context_error (context, "Unknown file type in camera_file_exists: %d", type); + return FALSE; + } +} + +struct privstr { + int fd; +}; + +static int x_size(void*priv,uint64_t *size) { + struct privstr *ps = priv; + int fd = ps->fd; + off_t res; + + gp_log (GP_LOG_DEBUG, "x_size","(%p,%u)", priv, (unsigned int)*size); + res = lseek (fd, 0, SEEK_END); + if (res == -1) { + perror ("x_size: lseek SEEK_END"); + return GP_ERROR_IO; + } + res = lseek (fd, 0, SEEK_CUR); + if (res == -1) { + perror ("x_size: lseek SEEK_CUR"); + return GP_ERROR_IO; + } + *size = res; + res = lseek (fd, 0, SEEK_SET); + if (res == -1) { + perror ("x_size: lseek SEEK_SET"); + return GP_ERROR_IO; + } + return GP_OK; +} + +static int x_read(void*priv,unsigned char *data, uint64_t *size) { + struct privstr *ps = priv; + int fd = ps->fd; + uint64_t curread = 0, xsize, res; + + gp_log (GP_LOG_DEBUG, "x_read", "(%p,%p,%u)", priv, data, (unsigned int)*size); + xsize = *size; + while (curread < xsize) { + res = read (fd, data+curread, xsize-curread); + if (res == -1) return GP_ERROR_IO_READ; + if (!res) break; + curread += res; + } + *size = curread; + return GP_OK; +} + +static int x_write(void*priv,unsigned char *data, uint64_t *size) { + struct privstr *ps = priv; + int fd = ps->fd; + uint64_t curwritten = 0, xsize, res; + + gp_log (GP_LOG_DEBUG, "x_write","(%p,%p,%u)", priv, data, (unsigned int)*size); + xsize = *size; + while (curwritten < xsize) { + res = write (fd, data+curwritten, xsize-curwritten); + if (res == -1) return GP_ERROR_IO_WRITE; + if (!res) break; + curwritten += res; + } + *size = curwritten; + return GP_OK; +} + +static CameraFileHandler xhandler = { x_size, x_read, x_write }; + +int +save_file_to_file (Camera *camera, GPContext *context, Flags flags, + const char *folder, const char *filename, + CameraFileType type) +{ + int fd, res; + CameraFile *file; + char tmpname[20], *tmpfilename; + struct privstr *ps = NULL; + + if (flags & FLAGS_SKIP_EXISTING && !(flags & FLAGS_STDOUT)) { + char *path = NULL; + /* Check if the file is present before downloading it. */ + res = get_path_for_file (folder, filename, type, NULL, &path); + if (res == (GP_OK) && gp_system_is_file (path)) { + /* File name pattern do not require CameraFile and target file + exists: Skip this file. */ + if ((gp_params.flags & FLAGS_QUIET) == 0) { + printf (_("Skip existing file %s\n"), path); + fflush (stdout); + } + return (GP_OK); + } + } + + if (flags & FLAGS_NEW) { + CameraFileInfo info; + + CR (gp_camera_file_get_info (camera, folder, filename, + &info, context)); + switch (type) { + case GP_FILE_TYPE_PREVIEW: + if (info.preview.fields & GP_FILE_INFO_STATUS && + info.preview.status == GP_FILE_STATUS_DOWNLOADED) + return (GP_OK); + break; + case GP_FILE_TYPE_NORMAL: + case GP_FILE_TYPE_RAW: + case GP_FILE_TYPE_EXIF: + if (info.file.fields & GP_FILE_INFO_STATUS && + info.file.status == GP_FILE_STATUS_DOWNLOADED) + return (GP_OK); + break; + case GP_FILE_TYPE_AUDIO: + if (info.audio.fields & GP_FILE_INFO_STATUS && + info.audio.status == GP_FILE_STATUS_DOWNLOADED) + return (GP_OK); + break; + default: + return (GP_ERROR_NOT_SUPPORTED); + } + } + + strcpy (tmpname, "tmpfileXXXXXX"); + fd = mkstemp(tmpname); + + if (fd == -1) { + if (errno == EACCES) { + gp_context_error (context, _("Permission denied")); + return GP_ERROR; + } + CR (gp_file_new (&file)); + tmpfilename = NULL; + } else { + if (time(NULL) & 1) { /* to test both methods. */ + gp_log (GP_LOG_DEBUG, "save_file_to_file","using fd method"); + res = gp_file_new_from_fd (&file, fd); + if (res < GP_OK) { + close (fd); + unlink (tmpname); + return res; + } + } else { + gp_log (GP_LOG_DEBUG, "save_file_to_file","using handler method"); + ps = malloc (sizeof(*ps)); + if (!ps) return GP_ERROR_NO_MEMORY; + ps->fd = fd; + /* just pass in the file pointer as private */ + res = gp_file_new_from_handler (&file, &xhandler, ps); + if (res < GP_OK) { + close (fd); + unlink (tmpname); + return res; + } + } + tmpfilename = tmpname; + } + + res = gp_camera_file_get (camera, folder, filename, type, file, context); + + if (res < GP_OK) { + free (ps); + gp_file_unref (file); + if (tmpfilename) unlink (tmpfilename); + return res; + } + + if (flags & FLAGS_STDOUT) { + const char *data; + unsigned long int size; + + CR (gp_file_get_data_and_size (file, &data, &size)); + + if (flags & FLAGS_STDOUT_SIZE) /* this will be difficult in fd mode */ + printf ("%li\n", size); + if (1!=fwrite (data, size, 1, stdout)) + fprintf(stderr,"fwrite failed writing to stdout.\n"); + if (ps && ps->fd) close (ps->fd); + free (ps); + gp_file_unref (file); + unlink (tmpname); + return (GP_OK); + } + res = save_camera_file_to_file (folder, filename, type, file, tmpfilename); + if (ps && ps->fd) close (ps->fd); + free (ps); + gp_file_unref (file); + if ((res!=GP_OK) && tmpfilename) + unlink (tmpfilename); + return (res); +} + +static void +dissolve_filename ( + const char *folder, const char *filename, + char **newfolder, char **newfilename +) { + char *nfolder, *s; + + s = strrchr (filename, '/'); + if (!s) { + *newfolder = strdup (folder); + *newfilename = strdup (filename); + return; + } + while (filename[0] == '/') + filename++; + nfolder = malloc (strlen (folder) + 1 + (s-filename) + 1); + strcpy (nfolder, folder); + if (strcmp (nfolder, "/")) strcat (nfolder, "/"); /* if its not the root directory, append / */ + memcpy (nfolder+strlen(nfolder), filename, (s-filename)); + nfolder[strlen (folder) + 1 + (s-filename)-1] = '\0'; + *newfolder = nfolder; + *newfilename = strdup (s+1); +#if 0 + fprintf (stderr, "%s - %s dissolved to %s - %s\n", folder, filename, *newfolder, *newfilename); +#endif +} + + + +/*! \brief parse range, download specified files, or their + * thumbnails according to thumbnail argument, and save to files. + */ + +int +get_file_common (const char *arg, CameraFileType type ) +{ + unsigned int mightberange = 1, i; + gp_log (GP_LOG_DEBUG, "main", "Getting '%s'...", arg); + + gp_params.download_type = type; /* remember for multi download */ + /* + * If the user specified the file directly (and not a number), + * get that file. + */ + for (i=0;i '9'))) { + mightberange = 0; + break; + } + } + + if (!mightberange) { + int ret; + char *newfolder, *newfilename; + + dissolve_filename (gp_params.folder, arg, &newfolder, &newfilename); + ret = save_file_to_file (gp_params.camera, gp_params.context, gp_params.flags, + newfolder, newfilename, type); + free (newfolder); free (newfilename); + return ret; + } + + switch (type) { + case GP_FILE_TYPE_PREVIEW: + CR (for_each_file_in_range (&gp_params, save_thumbnail_action, arg)); + break; + case GP_FILE_TYPE_NORMAL: + CR (for_each_file_in_range (&gp_params, save_file_action, arg)); + break; + case GP_FILE_TYPE_RAW: + CR (for_each_file_in_range (&gp_params, save_raw_action, arg)); + break; + case GP_FILE_TYPE_AUDIO: + CR (for_each_file_in_range (&gp_params, save_audio_action, arg)); + break; + case GP_FILE_TYPE_EXIF: + CR (for_each_file_in_range (&gp_params, save_exif_action, arg)); + break; + case GP_FILE_TYPE_METADATA: + CR (for_each_file_in_range (&gp_params, save_meta_action, arg)); + break; + default: + return (GP_ERROR_NOT_SUPPORTED); + } + + return (GP_OK); +} + +static void +sig_handler_capture_now (int sig_num) +{ + signal (SIGUSR1, sig_handler_capture_now); + capture_now = 1; +} + +static void +sig_handler_end_next (int sig_num) +{ + signal (SIGUSR2, sig_handler_end_next); + end_next = 1; +} + +/* temp test function */ +int +trigger_capture (void) { + int result = gp_camera_trigger_capture (gp_params.camera, gp_params.context); + if (result != GP_OK) { + cli_error_print(_("Could not trigger capture.")); + return (result); + } + return GP_OK; +} + +static long +timediff_now (struct timeval *target) { + struct timeval now; + + gettimeofday (&now, NULL); + return (target->tv_sec-now.tv_sec)*1000+ + (target->tv_usec-now.tv_usec)/1000; +} + +static int +save_captured_file (struct mg_connection *c, CameraFilePath *path, int download) { + char *pathsep; + static CameraFilePath last; + int result; + + if (strcmp(path->folder, "/") == 0) + pathsep = ""; + else + pathsep = "/"; + + if (gp_params.flags & FLAGS_QUIET) { + if (!(gp_params.flags & (FLAGS_STDOUT|FLAGS_STDOUT_SIZE))) + printf ("%s%s%s\n", path->folder, pathsep, path->name); + } else { + printf (_("New file is in location %s%s%s on the camera\n"), + path->folder, pathsep, path->name); + } + + JSON_PRINTF( c, "\"image_name\": \"%s\",", path->name ); + JSON_PRINTF( c, "\"camera_folder\": \"%s\",", path->folder ); + JSON_PRINTF( c, "\"download\": %s,", (download) ? "true" : "false" ); + + if (download) { + char *cwd; + if( (cwd=getcwd(NULL, 0)) != NULL) { + JSON_PRINTF( c, "\"local_folder\": \"%s\",", cwd ); + free(cwd); + } + + if (strcmp(path->folder, last.folder)) { + memcpy(&last, path, sizeof(last)); + + result = set_folder_action(&gp_params, path->folder); + if (result != GP_OK) { + cli_error_print(_("Could not set folder.")); + return (result); + } + } + if ( (gp_params.flags & FLAGS_KEEP_RAW) && + (!strstr(path->name,".jpg") && !strstr(path->name,".JPG")) + ) { + if (!(gp_params.flags & FLAGS_QUIET)) + printf (_("Keeping file %s%s%s on the camera\n"), + path->folder, pathsep, path->name); + return GP_OK; + } + + result = get_file_common (path->name, GP_FILE_TYPE_NORMAL); + if (result != GP_OK) { + cli_error_print (_("Could not get image.")); + if(result == GP_ERROR_FILE_NOT_FOUND) { + /* Buggy libcanon.so? + * Can happen if this was the first capture after a + * CF card format, or during a directory roll-over, + * ie: CANON100 -> CANON101 + */ + cli_error_print ( _("Buggy libcanon.so?")); + } + return (result); + } + + JSON_PRINTF( c, "\"keeping_file_on_camera\": %s,", (gp_params.flags & FLAGS_KEEP) ? "true" : "false" ); + + if (!(gp_params.flags & FLAGS_KEEP)) { + if (!(gp_params.flags & FLAGS_QUIET)) + printf (_("Deleting file %s%s%s on the camera\n"), + path->folder, pathsep, path->name); + + result = delete_file_action (&gp_params, path->folder, path->name); + if (result != GP_OK) { + cli_error_print ( _("Could not delete image.")); + return (result); + } + } else { + if (!(gp_params.flags & FLAGS_QUIET)) + printf (_("Keeping file %s%s%s on the camera\n"), + path->folder, pathsep, path->name); + } + } + return GP_OK; +} + +static int +wait_and_handle_event (struct mg_connection *c, long waittime, CameraEventType *type, int download) { + int result; + CameraEventType evtype; + void *data; + CameraFilePath *path; + + if (!type) type = &evtype; + evtype = GP_EVENT_UNKNOWN; + data = NULL; + result = gp_camera_wait_for_event(gp_params.camera, waittime, type, &data, gp_params.context); + if (result == GP_ERROR_NOT_SUPPORTED) { + *type = GP_EVENT_TIMEOUT; + usleep(waittime*1000); + return GP_OK; + } + if (result != GP_OK) + return result; + path = data; + switch (*type) { + case GP_EVENT_TIMEOUT: + break; + case GP_EVENT_CAPTURE_COMPLETE: + break; + case GP_EVENT_FOLDER_ADDED: + if (!(gp_params.flags & FLAGS_QUIET)) + printf (_("Event FOLDER_ADDED %s/%s during wait, ignoring.\n"), path->folder, path->name); + free (data); + break; + case GP_EVENT_FILE_CHANGED: + if (!(gp_params.flags & FLAGS_QUIET)) + printf (_("Event FILE_CHANGED %s/%s during wait, ignoring.\n"), path->folder, path->name); + free (data); + break; + case GP_EVENT_FILE_ADDED: + result = save_captured_file (c, path, download); + free (data); + /* result will fall through to final return */ + break; + case GP_EVENT_UNKNOWN: +#if 0 /* too much spam for the common usage */ + printf (_("Event UNKNOWN %s during wait, ignoring.\n"), (char*)data); +#endif + free (data); + break; + default: + if (!(gp_params.flags & FLAGS_QUIET)) + printf (_("Unknown event type %d during bulb wait, ignoring.\n"), *type); + break; + } + return result; +} + +int +capture_generic (struct mg_connection *c, CameraCaptureType type, const char __unused__ *name, int download) +{ + CameraFilePath path; + int result, frames = 0; + CameraAbilities a; + CameraEventType evtype; + long waittime; + struct timeval next_pic_time, expose_end_time; + + result = gp_camera_get_abilities (gp_params.camera, &a); + if (result != GP_OK) { + cli_error_print(_("Could not get capabilities?")); + return (result); + } + gettimeofday (&next_pic_time, NULL); + next_pic_time.tv_sec += glob_interval; + if(glob_interval) { + if (!(gp_params.flags & FLAGS_QUIET)) { + if (glob_interval != -1) + printf (_("Time-lapse mode enabled (interval: %ds).\n"), + glob_interval); + else + printf (_("Standing by waiting for SIGUSR1 to capture.\n")); + } + } + + if(glob_bulblength) { + if (!(gp_params.flags & FLAGS_QUIET)) { + printf (_("Bulb mode enabled (exposure time: %ds).\n"), + glob_bulblength); + } + } + + while(++frames) { + if (!(gp_params.flags & FLAGS_QUIET) && glob_interval) { + if(!glob_frames) + printf (_("Capturing frame #%d...\n"), frames); + else + printf (_("Capturing frame #%d/%d...\n"), frames, glob_frames); + } + + fflush(stdout); + + /* Now handle the different capture methods */ + if(glob_bulblength) { + /* Bulb mode is special ... we enable it, wait disable it */ + result = set_config_action (&gp_params, "bulb", "1"); + if (result != GP_OK) { + cli_error_print(_("Could not set bulb capture, result %d."), result); + return (result); + } + gettimeofday (&expose_end_time, NULL); + expose_end_time.tv_sec += glob_bulblength; + waittime = timediff_now (&expose_end_time); + while(waittime > 0) { + result = wait_and_handle_event(c,waittime, &evtype, download); + if (result != GP_OK) + return result; + waittime = timediff_now (&expose_end_time); + } + result = set_config_action (&gp_params, "bulb", "0"); + if (result != GP_OK) { + cli_error_print(_("Could not end capture (bulb mode).")); + return (result); + } + /* The actual download will happen down below in the interval wait + * or the loop exit. + */ + } else { + result = GP_ERROR_NOT_SUPPORTED; +#if 0 + /* Not a good idea, as we do not know how long to wait after capture ... */ + if (a.operations & GP_OPERATION_TRIGGER_CAPTURE) { + result = gp_camera_trigger_capture (gp_params.camera, gp_params.context); + if ((result != GP_OK) && (result != GP_ERROR_NOT_SUPPORTED)) + cli_error_print(_("Could not trigger image capture.")); + /* The downloads will be handled by wait_event */ + } +#endif + if (result == GP_ERROR_NOT_SUPPORTED) { + result = gp_camera_capture (gp_params.camera, type, &path, gp_params.context); + if (result != GP_OK) { + printf( "(%s:%d) result=%d\n", __FILE__, __LINE__, result ); + cli_error_print(_("Could not capture image.")); + } else { + /* If my Canon EOS 10D is set to auto-focus and it is unable to + * get focus lock - it will return with *UNKNOWN* as the filename. + */ + if (glob_interval && strcmp(path.name, "*UNKNOWN*") == 0) { + if (!(gp_params.flags & FLAGS_QUIET)) { + printf (_("Capture failed (auto-focus problem?)...\n")); + sleep(1); + continue; + } + } + result = save_captured_file (c, &path, download); + if (result != GP_OK) + break; + } + } + if (result != GP_OK) { + printf( "(%s:%d) result=%d\n", __FILE__, __LINE__, result ); + cli_error_print(_("Could not capture.")); + if ( (result == GP_ERROR_NOT_SUPPORTED) || + (result == GP_ERROR_NO_MEMORY) || + (result == GP_ERROR_CANCEL) || + (result == GP_ERROR_NO_SPACE) || + (result == GP_ERROR_IO_USB_CLAIM) || + (result == GP_ERROR_IO_LOCK) || + (result == GP_ERROR_CAMERA_BUSY) || + (result == GP_ERROR_OS_FAILURE) || + (result == -2) + ) + + JSON_PRINTF( c, "\"error_message\": \"Could not capture.\"," ); + + return (result); + } + } + + /* Break if we've reached the requested number of frames + * to capture. + */ + if(!glob_interval) break; + + if(glob_frames && frames == glob_frames) break; + + /* Break if we've been told to end before the next frame */ + if(end_next) break; + + /* + * Even if the interval is set to -1, it is better to take a + * picture first to prepare the camera driver for faster + * response when a signal is caught. + * [alesan] + */ + if (glob_interval != -1) { + waittime = timediff_now (&next_pic_time); + result = GP_OK; + if (waittime > 0) { + if (!(gp_params.flags & FLAGS_QUIET) && glob_interval) + printf (_("Waiting for next capture slot %ld seconds...\n"), waittime/1000); + /* We're not sure about sleep() semantics when catching a signal */ + do { + /* granularity in which we can receive signals is 200 */ + if (waittime > 200) waittime = 200; + result = wait_and_handle_event (c,waittime, NULL, download); + if (result != GP_OK) + break; + if (capture_now && !(gp_params.flags & FLAGS_QUIET) && glob_interval) { + printf (_("Awakened by SIGUSR1...\n")); + break; + } + waittime = timediff_now (&next_pic_time); + } while (waittime > 0); + } else { + /* drain the queue first though, even though there is no time. */ + while (1) { + result = wait_and_handle_event (c,1, &evtype, download); + if ((result != GP_OK) || (evtype == GP_EVENT_TIMEOUT)) + break; + } + if (!(gp_params.flags & FLAGS_QUIET) && glob_interval) + printf (_("not sleeping (%ld seconds behind schedule)\n"), -waittime/1000); + } + if (result != GP_OK) break; + if (capture_now && (gp_params.flags & FLAGS_RESET_CAPTURE_INTERVAL)) { + gettimeofday (&next_pic_time, NULL); + next_pic_time.tv_sec += glob_interval; + } + else if (!capture_now) { + /* + * In the case of a (huge) time-sync while gphoto is running, + * gphoto could percieve an extremely large amount of time and + * stay "behind schedule" quite forever. That's why I reduce the + * difference of time with the following loop. + * [alesan] + */ + do { + next_pic_time.tv_sec += glob_interval; + } while (glob_interval && (timediff_now (&next_pic_time) < 0)); + } + capture_now = 0; + } else { + /* wait indefinitely for SIGUSR1 */ + do { + result = wait_and_handle_event (c,200, &evtype, download); + } while(!capture_now && (result == GP_OK)); + if (result != GP_OK) + break; + capture_now = 0; + if (!(gp_params.flags & FLAGS_QUIET)) + printf (_("Awakened by SIGUSR1...\n")); + } + } + /* The final capture will fall out of the loop into this case, + * so make sure we wait a bit for the the camera to finish stuff. + */ + gettimeofday (&expose_end_time, NULL); + waittime = 3000; + /* tricky, this loop might need to download both JPG and RAW. so wait longer. */ + /*if (glob_frames || end_next || !glob_interval || glob_bulblength) waittime = 2000;*/ + /* Drain the event queue at the end and download left over added images */ + while (1) { + int realwait = waittime - (-timediff_now(&expose_end_time)); + if (realwait < -3) break; /* wait at most 6 seconds */ + + if (realwait < 0) realwait = 0; /* just drain the queue now */ + result = wait_and_handle_event(c,realwait, &evtype, download); + if ((result != GP_OK) || (evtype == GP_EVENT_TIMEOUT)) { + /*printf("Timeout or error, leaving loop.\n");*/ + break; + } + if (evtype == GP_EVENT_CAPTURE_COMPLETE) { + /*printf("Capture complete, waiting final 0.1s.\n");*/ + waittime = 100; + } + if (evtype == GP_EVENT_FILE_ADDED) { /* Restart timer, more image data might come. */ + /*printf("File added, waiting more %gs.\n", waittime/1000.0);*/ + gettimeofday (&expose_end_time, NULL); + } + } + return GP_OK; +} + + +/* Set/init global variables */ +/* ------------------------------------------------------------ */ + +#ifdef HAVE_PTHREAD + +typedef struct _ThreadData ThreadData; +struct _ThreadData { + Camera *camera; + time_t timeout; + CameraTimeoutFunc func; +}; + +static void +thread_cleanup_func (void *data) +{ + ThreadData *td = data; + + free (td); +} + +static void * +thread_func (void *data) +{ + ThreadData *td = data; + time_t t, last; + + pthread_cleanup_push (thread_cleanup_func, td); + + last = time (NULL); + while (1) { + t = time (NULL); + if (t - last > td->timeout) { + td->func (td->camera, NULL); + last = t; + } + pthread_testcancel (); + } + + pthread_cleanup_pop (1); +} + +static unsigned int +start_timeout_func (Camera *camera, unsigned int timeout, + CameraTimeoutFunc func, void __unused__ *data) +{ + pthread_t tid; + ThreadData *td; + + td = malloc (sizeof (ThreadData)); + if (!td) + return 0; + memset (td, 0, sizeof (ThreadData)); + td->camera = camera; + td->timeout = timeout; + td->func = func; + + pthread_create (&tid, NULL, thread_func, td); + + return (tid); +} + +static void +stop_timeout_func (Camera __unused__ *camera, unsigned int id, + void __unused__ *data) +{ + pthread_t tid = id; + + pthread_cancel (tid); + pthread_join (tid, NULL); +} + +#endif + +/* Misc functions */ +/* ------------------------------------------------------------------ */ + +void +cli_error_print (char *format, ...) +{ + va_list pvar; + + fprintf(stderr, _("ERROR: ")); + va_start(pvar, format); + vfprintf(stderr, format, pvar); + va_end(pvar); + fprintf(stderr, "\n"); +} + +static void +signal_resize (int __unused__ signo) +{ + const char *columns; + + columns = getenv ("COLUMNS"); + if (columns && atoi (columns)) + gp_params.cols = atoi (columns); +} + +static void +signal_exit (int __unused__ signo) +{ + /* If we already were told to cancel, abort. */ + if (glob_cancel) { + if ((gp_params.flags & FLAGS_QUIET) == 0) + printf (_("\nAborting...\n")); + if (gp_params.camera) + gp_camera_unref (gp_params.camera); + if (gp_params.context) + gp_context_unref (gp_params.context); + if ((gp_params.flags & FLAGS_QUIET) == 0) + printf (_("Aborted.\n")); + exit (EXIT_FAILURE); + } + + if ((gp_params.flags & FLAGS_QUIET) == 0) + printf (_("\nCancelling...\n")); + + glob_cancel = 1; + glob_interval = 0; +} + +/* Main :) */ +/* -------------------------------------------------------------------- */ + +typedef enum { + ARG_ABILITIES, + ARG_ABOUT, + ARG_SHOW_PREVIEW, + ARG_CONFIG, + ARG_DEBUG, + ARG_DEBUG_LOGLEVEL, + ARG_DEBUG_LOGFILE, + ARG_DELETE_ALL_FILES, + ARG_DELETE_FILE, + ARG_FILENAME, + ARG_FILENUMBER, + ARG_FOLDER, + ARG_FORCE_OVERWRITE, + ARG_GET_ALL_AUDIO_DATA, + ARG_GET_ALL_FILES, + ARG_GET_ALL_METADATA, + ARG_GET_ALL_RAW_DATA, + ARG_GET_ALL_THUMBNAILS, + ARG_GET_AUDIO_DATA, + ARG_GET_CONFIG, + ARG_SET_CONFIG, + ARG_SET_CONFIG_INDEX, + ARG_SET_CONFIG_VALUE, + ARG_GET_FILE, + ARG_GET_METADATA, + ARG_GET_RAW_DATA, + ARG_GET_THUMBNAIL, + ARG_HELP, + ARG_HOOK_SCRIPT, + ARG_KEEP, + ARG_KEEP_RAW, + ARG_LIST_CAMERAS, + ARG_LIST_FILES, + ARG_LIST_FOLDERS, + ARG_LIST_PORTS, + ARG_MANUAL, + ARG_MKDIR, + ARG_MODEL, + ARG_NEW, + ARG_NO_KEEP, + ARG_NO_RECURSE, + ARG_NUM_FILES, + ARG_PORT, + ARG_QUIET, + ARG_RECURSE, + ARG_RESET, + ARG_RESET_INTERVAL, + ARG_RMDIR, + ARG_SERVER, + ARG_SERVER_URL, + ARG_SHOW_EXIF, + ARG_SHOW_INFO, + ARG_PARSABLE, + ARG_SKIP_EXISTING, + ARG_SPEED, + ARG_STDOUT, + ARG_STDOUT_SIZE, + ARG_STORAGE_INFO, + ARG_SUMMARY, + ARG_UPLOAD_FILE, + ARG_UPLOAD_METADATA, + ARG_USAGE, + ARG_USBID, + ARG_VERSION, + ARG_WAIT_EVENT +} Arg; + +typedef enum { + CALLBACK_PARAMS_TYPE_NONE, + CALLBACK_PARAMS_TYPE_PREINITIALIZE, + CALLBACK_PARAMS_TYPE_INITIALIZE, + CALLBACK_PARAMS_TYPE_QUERY, + CALLBACK_PARAMS_TYPE_RUN +} CallbackParamsType; + +typedef struct _CallbackParams CallbackParams; +struct _CallbackParams { + CallbackParamsType type; + union { + /* + * CALLBACK_PARAMS_TYPE_RUN, + * CALLBACK_PARAMS_TYPE_INITIALIZE, + * CALLBACK_PARAMS_TYPE_PREINITIALIZE, + */ + int r; + + /* CALLBACK_PARAMS_TYPE_QUERY */ + struct { + Arg arg; + char found; + } q; + + /* CALLBACK_PARAMS_TYPE_NONE */ + } p; +}; + + +/*! \brief popt callback with type CALLBACK_PARAMS_TYPE_QUERY + */ + +static void +cb_arg_query (poptContext __unused__ ctx, + enum poptCallbackReason __unused__ reason, + const struct poptOption *opt, const char __unused__ *arg, + CallbackParams *params) +{ + /* p.q.arg is an enum, but opt->val is an int */ + if (opt->val == (int)(params->p.q.arg)) + params->p.q.found = 1; +} + + +/*! \brief popt callback with type CALLBACK_PARAMS_TYPE_PREINITIALIZE + */ + +static void +cb_arg_preinit (poptContext __unused__ ctx, + enum poptCallbackReason __unused__ reason, + const struct poptOption *opt, const char *arg, + CallbackParams *params) +{ + int usb_product, usb_vendor; + int usb_product_modified, usb_vendor_modified; + switch (opt->val) { + case ARG_USBID: + gp_log (GP_LOG_DEBUG, "main", "Overriding USB " + "IDs to '%s'...", arg); + if (sscanf (arg, "0x%x:0x%x=0x%x:0x%x", + &usb_vendor_modified, + &usb_product_modified, &usb_vendor, + &usb_product) != 4) { + printf (_("Use the following syntax a:b=c:d " + "to treat any USB device detected " + "as a:b as c:d instead. " + "a b c d should be hexadecimal " + "numbers beginning with '0x'.\n")); + params->p.r = GP_ERROR_BAD_PARAMETERS; + break; + } + params->p.r = override_usbids_action (&gp_params, usb_vendor, + usb_product, usb_vendor_modified, + usb_product_modified); + break; + default: + break; + } +} + + +/*! \brief popt callback with type CALLBACK_PARAMS_TYPE_INITIALIZE + */ + +static void +cb_arg_init (poptContext __unused__ ctx, + enum poptCallbackReason __unused__ reason, + const struct poptOption *opt, const char *arg, + CallbackParams *params) +{ + switch (opt->val) { + + case ARG_FILENAME: + params->p.r = set_filename_action (&gp_params, arg); + break; + case ARG_FILENUMBER: + gp_params.filenr = atoi (arg); + params->p.r = GP_OK; + break; + case ARG_FOLDER: + params->p.r = set_folder_action (&gp_params, arg); + break; + + case ARG_FORCE_OVERWRITE: + gp_params.flags |= FLAGS_FORCE_OVERWRITE; + break; + case ARG_NEW: + gp_params.flags |= FLAGS_NEW; + break; + case ARG_KEEP_RAW: + gp_params.flags |= FLAGS_KEEP_RAW; + break; + + case ARG_KEEP: + gp_params.flags |= FLAGS_KEEP; + break; + case ARG_NO_KEEP: + gp_params.flags &= ~FLAGS_KEEP; + break; + + case ARG_NO_RECURSE: + gp_params.flags &= ~FLAGS_RECURSE; + break; + case ARG_RECURSE: + gp_params.flags |= FLAGS_RECURSE; + break; + + case ARG_MODEL: + gp_log (GP_LOG_DEBUG, "main", "Processing 'model' " + "option ('%s')...", arg); + params->p.r = action_camera_set_model (&gp_params, arg); + break; + case ARG_PORT: + gp_log (GP_LOG_DEBUG, "main", "Processing 'port' " + "option ('%s')...", arg); + params->p.r = action_camera_set_port (&gp_params, arg); + break; + + case ARG_SKIP_EXISTING: + gp_params.flags |= FLAGS_SKIP_EXISTING; + break; + + case ARG_SPEED: + params->p.r = action_camera_set_speed (&gp_params, atoi (arg)); + break; + + case ARG_QUIET: + gp_params.flags |= FLAGS_QUIET; + break; + + case ARG_PARSABLE: + gp_params.flags |= FLAGS_QUIET; + gp_params.flags |= FLAGS_PARSABLE; + break; + + case ARG_RESET_INTERVAL: + gp_params.flags |= FLAGS_RESET_CAPTURE_INTERVAL; + break; + + case ARG_HOOK_SCRIPT: + do { + const size_t sz = strlen(arg); + char *copy = malloc(sz+1); + if (!copy) { + perror("malloc error"); + exit (EXIT_FAILURE); + } + gp_params.hook_script = strcpy(copy, arg); + /* Run init hook */ + if (0!=gp_params_run_hook(&gp_params, "init", NULL)) { + fprintf(stderr, + "Hook script \"%s\" init failed. Aborting.\n", + gp_params.hook_script); + exit(3); + } + } while (0); + break; + + case ARG_STDOUT: + gp_params.flags |= FLAGS_QUIET | FLAGS_STDOUT; + break; + case ARG_STDOUT_SIZE: + gp_params.flags |= FLAGS_QUIET | FLAGS_STDOUT + | FLAGS_STDOUT_SIZE; + break; + + case ARG_VERSION: + params->p.r = print_version_action (&gp_params); + printf( "%-16s%-15s%s\n", "mongoose", MG_VERSION, "webserver" ); + break; + default: + break; + } +} + +/*! \brief popt callback with type CALLBACK_PARAMS_TYPE_RUN + */ + +static void +cb_arg_run (poptContext __unused__ ctx, + enum poptCallbackReason __unused__ reason, + const struct poptOption *opt, const char *arg, + CallbackParams *params) +{ + char *newfilename = NULL, *newfolder = NULL; + + switch (opt->val) { + case ARG_ABILITIES: + params->p.r = action_camera_show_abilities (&gp_params); + break; + case ARG_ABOUT: + params->p.r = action_camera_about (&gp_params); + break; + case ARG_SHOW_PREVIEW: + params->p.r = action_camera_show_preview (&gp_params); + break; + case ARG_CONFIG: +#ifdef HAVE_CDK + params->p.r = gp_cmd_config (gp_params.camera, gp_params.context); +#else + gp_context_error (gp_params.context, + _("gphoto2 has been compiled without " + "support for CDK.")); + params->p.r = GP_ERROR_NOT_SUPPORTED; +#endif + break; + case ARG_DELETE_ALL_FILES: + params->p.r = for_each_folder (&gp_params, delete_all_action); + break; + case ARG_DELETE_FILE: + gp_params.multi_type = MULTI_DELETE; + /* Did the user specify a file or a range? */ + if (strchr (arg, '.')) { + dissolve_filename (gp_params.folder, arg, &newfolder, &newfilename); + params->p.r = delete_file_action (&gp_params, newfolder, newfilename); + free (newfolder); free (newfilename); + break; + } + params->p.r = for_each_file_in_range (&gp_params, + delete_file_action, arg); + break; + case ARG_GET_ALL_AUDIO_DATA: + params->p.r = for_each_file (&gp_params, save_all_audio_action); + break; + case ARG_GET_ALL_FILES: + params->p.r = for_each_file (&gp_params, save_file_action); + break; + case ARG_GET_ALL_METADATA: + params->p.r = for_each_file (&gp_params, save_meta_action); + break; + case ARG_GET_ALL_RAW_DATA: + params->p.r = for_each_file (&gp_params, save_raw_action); + break; + case ARG_GET_ALL_THUMBNAILS: + params->p.r = for_each_file (&gp_params, save_thumbnail_action); + break; + case ARG_GET_AUDIO_DATA: + gp_params.multi_type = MULTI_DOWNLOAD; + params->p.r = get_file_common (arg, GP_FILE_TYPE_AUDIO); + break; + case ARG_GET_METADATA: + gp_params.multi_type = MULTI_DOWNLOAD; + params->p.r = get_file_common (arg, GP_FILE_TYPE_METADATA); + break; + case ARG_GET_FILE: + gp_params.multi_type = MULTI_DOWNLOAD; + params->p.r = get_file_common (arg, GP_FILE_TYPE_NORMAL); + break; + case ARG_GET_THUMBNAIL: + gp_params.multi_type = MULTI_DOWNLOAD; + params->p.r = get_file_common (arg, GP_FILE_TYPE_PREVIEW); + break; + case ARG_GET_RAW_DATA: + gp_params.multi_type = MULTI_DOWNLOAD; + params->p.r = get_file_common (arg, GP_FILE_TYPE_RAW); + break; + case ARG_LIST_CAMERAS: + params->p.r = list_cameras_action (&gp_params); + break; + case ARG_LIST_FILES: + params->p.r = for_each_folder (&gp_params, list_files_action); + break; + case ARG_LIST_FOLDERS: + params->p.r = for_each_folder (&gp_params, list_folders_action); + break; + case ARG_LIST_PORTS: + params->p.r = list_ports_action (&gp_params); + break; + case ARG_RESET: { + GPPort *port; + GPPortInfo info; + + /* If a camera is already open, close it, as we need a new port */ + if (gp_params.camera) { + gp_camera_exit (gp_params.camera, gp_params.context); + /* exit, not free. will reopen on next command. */ + } + + params->p.r = gp_port_new (&port); + if (params->p.r != GP_OK) { + gp_log(GP_LOG_ERROR,"port_reset", "new failed %d", params->p.r); + break; + } + params->p.r = gp_camera_get_port_info (gp_params.camera, &info); + if (params->p.r != GP_OK) { + gp_log(GP_LOG_ERROR,"port_reset", "camera_get_port_info failed"); + break; + } + params->p.r = gp_port_set_info (port, info); + if (params->p.r != GP_OK) { + gp_log(GP_LOG_ERROR,"port_reset", "port_set_info failed"); + break; + } + params->p.r = gp_port_open (port); + if (params->p.r != GP_OK) { + gp_log(GP_LOG_ERROR,"port_reset", "open failed %d", params->p.r); + break; + } + params->p.r = gp_port_reset (port); + gp_port_close (port); + gp_port_free (port); + break; + } + case ARG_MANUAL: + params->p.r = action_camera_manual (&gp_params); + break; + case ARG_RMDIR: + dissolve_filename (gp_params.folder, arg, &newfolder, &newfilename); + params->p.r = gp_camera_folder_remove_dir (gp_params.camera, + newfolder, newfilename, gp_params.context); + free (newfolder); free (newfilename); + break; + case ARG_NUM_FILES: + params->p.r = num_files_action (&gp_params); + break; + case ARG_MKDIR: + dissolve_filename (gp_params.folder, arg, &newfolder, &newfilename); + params->p.r = gp_camera_folder_make_dir (gp_params.camera, + newfolder, newfilename, gp_params.context); + free (newfolder); free (newfilename); + break; + case ARG_SERVER_URL: + strncpy( webcfg.server_url, arg, WEBCFG_STR_LEN); + webcfg.server_url[WEBCFG_STR_LEN] = 0; + break; + case ARG_SERVER: + params->p.r = webapi_server (&gp_params); + break; + case ARG_SHOW_EXIF: + /* Did the user specify a file or a range? */ + if (strchr (arg, '.')) { + dissolve_filename (gp_params.folder, arg, &newfolder, &newfilename); + params->p.r = print_exif_action (&gp_params, newfolder, newfilename); + free (newfolder); free (newfilename); + break; + } + params->p.r = for_each_file_in_range (&gp_params, + print_exif_action, arg); + break; + case ARG_SHOW_INFO: + /* Did the user specify a file or a range? */ + if (strchr (arg, '.')) { + dissolve_filename (gp_params.folder, arg, &newfolder, &newfilename); + params->p.r = print_info_action (&gp_params, newfolder, newfilename); + free (newfolder); free (newfilename); + break; + } + params->p.r = for_each_file_in_range (&gp_params, + print_info_action, arg); + break; + case ARG_SUMMARY: + params->p.r = action_camera_summary (&gp_params); + break; + case ARG_UPLOAD_FILE: + gp_params.multi_type = MULTI_UPLOAD; + /* Note: do not normalize folder/filename, as -u allows local filenames with paths */ + params->p.r = action_camera_upload_file (&gp_params, gp_params.folder, arg); + break; + case ARG_UPLOAD_METADATA: + gp_params.multi_type = MULTI_UPLOAD_META; + /* Note: do not normalize folder/filename, as -u-meta allows local filenames with paths */ + params->p.r = action_camera_upload_metadata (&gp_params, gp_params.folder, arg); + break; + case ARG_GET_CONFIG: + params->p.r = get_config_action (&gp_params, arg); + break; + case ARG_SET_CONFIG: { + char *name, *value; + + if (strchr (arg, '=') == NULL) { + params->p.r = GP_ERROR_BAD_PARAMETERS; + break; + } + name = strdup (arg); + value = strchr (name, '='); + *value = '\0'; + value++; + params->p.r = set_config_action (&gp_params, name, value); + free (name); + break; + } + case ARG_SET_CONFIG_INDEX: { + char *name, *value; + + if (strchr (arg, '=') == NULL) { + params->p.r = GP_ERROR_BAD_PARAMETERS; + break; + } + name = strdup (arg); + value = strchr (name, '='); + *value = '\0'; + value++; + params->p.r = set_config_index_action (&gp_params, name, value); + free (name); + break; + } + case ARG_SET_CONFIG_VALUE: { + char *name, *value; + + if (strchr (arg, '=') == NULL) { + params->p.r = GP_ERROR_BAD_PARAMETERS; + break; + } + name = strdup (arg); + value = strchr (name, '='); + *value = '\0'; + value++; + params->p.r = set_config_value_action (&gp_params, name, value); + free (name); + break; + } + case ARG_WAIT_EVENT: + params->p.r = action_camera_wait_event (&gp_params, DT_NO_DOWNLOAD, arg); + break; + case ARG_STORAGE_INFO: + params->p.r = print_storage_info (&gp_params); + break; + default: + break; + }; +} + + +/*! \brief Callback function called while parsing command line options. + * + * This callback function is called multiple times in multiple + * phases. That should probably become separate functions. + */ + +static void +cb_arg (poptContext __unused__ ctx, + enum poptCallbackReason __unused__ reason, + const struct poptOption *opt, const char *arg, + void *data) +{ + CallbackParams *params = (CallbackParams *) data; + + /* Check if we are only to query. */ + switch (params->type) { + case CALLBACK_PARAMS_TYPE_NONE: + /* do nothing */ + break; + case CALLBACK_PARAMS_TYPE_QUERY: + cb_arg_query (ctx, reason, opt, arg, params); + break; + case CALLBACK_PARAMS_TYPE_PREINITIALIZE: + cb_arg_preinit (ctx, reason, opt, arg, params); + break; + case CALLBACK_PARAMS_TYPE_INITIALIZE: + /* Check if we are only to initialize. */ + cb_arg_init (ctx, reason, opt, arg, params); + break; + case CALLBACK_PARAMS_TYPE_RUN: + cb_arg_run (ctx, reason, opt, arg, params); + break; + } +} + + +static void +report_failure (int result, int argc, char **argv) +{ + if (result >= 0) + return; + + if (result == GP_ERROR_CANCEL) { + fprintf (stderr, _("Operation cancelled.\n")); + return; + } + if (result == -2000) { + fprintf (stderr, _("*** Error: No camera found. ***\n\n")); + } else { + fprintf (stderr, _("*** Error (%i: '%s') *** \n\n"), result, + gp_result_as_string (result)); + } + if (!debug_option_given) { + int n; + printf (_("For debugging messages, please use " + "the --debug option.\n" + "Debugging messages may help finding a " + "solution to your problem.\n" + "If you intend to send any error or " + "debug messages to the gphoto\n" + "developer mailing list " + ", please run\n" + "gphoto2 as follows:\n\n")); + + /* + * Print the exact command line to assist bugreporters + */ + printf (" env LANG=C gphoto2 --debug --debug-logfile=my-logfile.txt"); + for (n = 1; n < argc; n++) { + if (strchr(argv[n], ' ') == NULL) + printf(" %s",argv[n]); + else + printf(" \"%s\"",argv[n]); + } + printf ("\n\n"); + printf (_("Please make sure there is sufficient quoting " + "around the arguments.\n\n")); + } +} + +#define CR_MAIN(result) \ + do { \ + int r = (result); \ + \ + if (r < 0) { \ + report_failure (r, argc, argv); \ + \ + /* Run stop hook */ \ + gp_params_run_hook(&gp_params, "stop", NULL); \ + \ + gp_params_exit (&gp_params); \ + poptFreeContext(ctx); \ + return (EXIT_FAILURE); \ + } \ + } while (0) + + +#define GPHOTO2_POPT_CALLBACK \ + {NULL, '\0', POPT_ARG_CALLBACK, \ + (void *) &cb_arg, 0, (char *) &cb_params, NULL}, + +/*! main function: parse command line arguments and call actions + * + * Perhaps we should use the following code for parsing command line + * options: + + poptGetContext(NULL, argc, argv, poptOptions, 0); + while ((rc = poptGetNextOpt(poptcon)) > 0) { + switch (rc) { + ARG_FOO: + printf("foo = %s\n", poptGetOptArg(poptcon)); + break; + } + } + poptFreeContext(poptcon); + * + * Regardless of whether we do this or not, we should get rid of those + * legions of poptResetContext() calls followed by lots of + * poptGetNextOpt() calls. + * + * At least we should get rid of all those stages. Probably two stages + * are sufficient: + * -# look for --help, --debug, --debug-logfile, --quiet + * -# repeat this until command line has been used up + * -# go through all command line options + * -# ignore those from above + * -# if setting for command, store its value + * -# if command, execute command + */ + + +int +main (int argc, char **argv, char **envp) +{ + CallbackParams cb_params; + poptContext ctx; + int i, help_option_given = 0; + int usage_option_given = 0; + char *debug_logfile_name = NULL, *debug_loglevel = NULL; + const struct poptOption generalOptions[] = { + GPHOTO2_POPT_CALLBACK + {"help", '?', POPT_ARG_NONE, (void *) &help_option_given, ARG_HELP, + N_("Print complete help message on program usage"), NULL}, + {"usage", '\0', POPT_ARG_NONE, (void *) &usage_option_given, ARG_USAGE, + N_("Print short message on program usage"), NULL}, + {"debug", '\0', POPT_ARG_NONE, (void *) &debug_option_given, ARG_DEBUG, + N_("Turn on debugging"), NULL}, + {"debug-loglevel", '\0', POPT_ARG_STRING, (void *) &debug_loglevel, ARG_DEBUG_LOGLEVEL, + N_("Set debug level [error|debug|data|all]"), NULL}, + {"debug-logfile", '\0', POPT_ARG_STRING, (void *) &debug_logfile_name, ARG_DEBUG_LOGFILE, + N_("Name of file to write debug info to"), N_("FILENAME")}, + {"quiet", 'q', POPT_ARG_NONE, NULL, ARG_QUIET, + N_("Quiet output (default=verbose)"), NULL}, + {"parsable", '\0', POPT_ARG_NONE, NULL, ARG_PARSABLE, + N_("Simple parsable output (implies quiet)"), NULL}, + {"hook-script", '\0', POPT_ARG_STRING, NULL, ARG_HOOK_SCRIPT, + N_("Hook script to call after downloads, captures, etc."), + N_("FILENAME")}, + POPT_TABLEEND + }; + const struct poptOption cameraOptions[] = { + GPHOTO2_POPT_CALLBACK + {"port", '\0', POPT_ARG_STRING, NULL, ARG_PORT, + N_("Specify device port"), N_("FILENAME")}, + {"speed", '\0', POPT_ARG_INT, NULL, ARG_SPEED, + N_("Specify serial transfer speed"), N_("SPEED")}, + {"camera", '\0', POPT_ARG_STRING, NULL, ARG_MODEL, + N_("Specify camera model"), N_("MODEL")}, + {"usbid", '\0', POPT_ARG_STRING, NULL, ARG_USBID, + N_("(expert only) Override USB IDs"), N_("USBIDs")}, + POPT_TABLEEND + }; + const struct poptOption infoOptions[] = { + GPHOTO2_POPT_CALLBACK + {"version", 'v', POPT_ARG_NONE, NULL, ARG_VERSION, + N_("Display version and exit"), NULL}, + {"list-cameras", '\0', POPT_ARG_NONE, NULL, ARG_LIST_CAMERAS, + N_("List supported camera models"), NULL}, + {"list-ports", '\0', POPT_ARG_NONE, NULL, ARG_LIST_PORTS, + N_("List supported port devices"), NULL}, + {"abilities", 'a', POPT_ARG_NONE, NULL, ARG_ABILITIES, + N_("Display the camera/driver abilities in the libgphoto2 database"), NULL}, + POPT_TABLEEND + }; + const struct poptOption configOptions[] = { + GPHOTO2_POPT_CALLBACK +#ifdef HAVE_CDK + {"config", '\0', POPT_ARG_NONE, NULL, ARG_CONFIG, + N_("Configure"), NULL}, +#endif + {"get-config", '\0', POPT_ARG_STRING, NULL, ARG_GET_CONFIG, + N_("Get configuration value"), NULL}, + {"set-config", '\0', POPT_ARG_STRING, NULL, ARG_SET_CONFIG, + N_("Set configuration value or index in choices"), NULL}, + {"set-config-index", '\0', POPT_ARG_STRING, NULL, ARG_SET_CONFIG_INDEX, + N_("Set configuration value index in choices"), NULL}, + {"set-config-value", '\0', POPT_ARG_STRING, NULL, ARG_SET_CONFIG_VALUE, + N_("Set configuration value"), NULL}, + {"reset", '\0', POPT_ARG_NONE, NULL, ARG_RESET, + N_("Reset device port"), NULL}, + POPT_TABLEEND + }; + const struct poptOption captureOptions[] = { + GPHOTO2_POPT_CALLBACK + {"keep", '\0', POPT_ARG_NONE, NULL, ARG_KEEP, + N_("Keep images on camera after capturing"), NULL}, + {"keep-raw", '\0', POPT_ARG_NONE, NULL, ARG_KEEP_RAW, + N_("Keep RAW images on camera after capturing"), NULL}, + {"no-keep", '\0', POPT_ARG_NONE, NULL, ARG_NO_KEEP, + N_("Remove images from camera after capturing"), NULL}, + {"wait-event", '\0', POPT_ARG_STRING|POPT_ARGFLAG_OPTIONAL, NULL, ARG_WAIT_EVENT, + N_("Wait for event(s) from camera"), N_("EVENT")}, + {"show-preview", '\0', POPT_ARG_NONE, NULL, + ARG_SHOW_PREVIEW, + N_("Show a quick preview as Ascii Art"), NULL}, + {"reset-interval", '\0', POPT_ARG_NONE, NULL, ARG_RESET_INTERVAL, + N_("Reset capture interval on signal (default=no)"), NULL}, + POPT_TABLEEND + }; + const struct poptOption fileOptions[] = { + GPHOTO2_POPT_CALLBACK + {"list-folders", 'l', POPT_ARG_NONE, NULL, ARG_LIST_FOLDERS, + N_("List folders in folder"), NULL}, + {"list-files", 'L', POPT_ARG_NONE, NULL, ARG_LIST_FILES, + N_("List files in folder"), NULL}, + {"mkdir", 'm', POPT_ARG_STRING, NULL, ARG_MKDIR, + N_("Create a directory"), N_("DIRNAME")}, + {"rmdir", 'r', POPT_ARG_STRING, NULL, ARG_RMDIR, + N_("Remove a directory"), N_("DIRNAME")}, + {"num-files", 'n', POPT_ARG_NONE, NULL, ARG_NUM_FILES, + N_("Display number of files"), NULL}, + {"get-file", 'p', POPT_ARG_STRING, NULL, ARG_GET_FILE, + N_("Get files given in range"), N_("RANGE")}, + {"get-all-files", 'P', POPT_ARG_NONE, NULL, ARG_GET_ALL_FILES, + N_("Get all files from folder"), NULL}, + {"get-thumbnail", 't', POPT_ARG_STRING, NULL, ARG_GET_THUMBNAIL, + N_("Get thumbnails given in range"), N_("RANGE")}, + {"get-all-thumbnails", 'T', POPT_ARG_NONE, 0, + ARG_GET_ALL_THUMBNAILS, + N_("Get all thumbnails from folder"), NULL}, + {"get-metadata", '\0', POPT_ARG_STRING, NULL, ARG_GET_METADATA, + N_("Get metadata given in range"), N_("RANGE")}, + {"get-all-metadata", '\0', POPT_ARG_NONE, NULL, ARG_GET_ALL_METADATA, + N_("Get all metadata from folder"), NULL}, + {"upload-metadata", '\0', POPT_ARG_STRING, NULL, ARG_UPLOAD_METADATA, + N_("Upload metadata for file"), NULL}, + {"get-raw-data", '\0', POPT_ARG_STRING, NULL, + ARG_GET_RAW_DATA, + N_("Get raw data given in range"), N_("RANGE")}, + {"get-all-raw-data", '\0', POPT_ARG_NONE, NULL, + ARG_GET_ALL_RAW_DATA, + N_("Get all raw data from folder"), NULL}, + {"get-audio-data", '\0', POPT_ARG_STRING, NULL, + ARG_GET_AUDIO_DATA, + N_("Get audio data given in range"), N_("RANGE")}, + {"get-all-audio-data", '\0', POPT_ARG_NONE, NULL, + ARG_GET_ALL_AUDIO_DATA, + N_("Get all audio data from folder"), NULL}, + {"delete-file", 'd', POPT_ARG_STRING, NULL, ARG_DELETE_FILE, + N_("Delete files given in range"), N_("RANGE")}, + {"delete-all-files", 'D', POPT_ARG_NONE, NULL, + ARG_DELETE_ALL_FILES, N_("Delete all files in folder (--no-recurse by default)"), NULL}, + {"upload-file", 'u', POPT_ARG_STRING, NULL, ARG_UPLOAD_FILE, + N_("Upload a file to camera"), N_("FILENAME")}, + {"filename", '\0', POPT_ARG_STRING, NULL, ARG_FILENAME, + N_("Specify a filename or filename pattern"), N_("FILENAME_PATTERN")}, + {"filenumber", '\0', POPT_ARG_INT, NULL, ARG_FILENUMBER, + N_("Specify the number a filename %%n will starts with (default 1)"), N_("NUMBER")}, + {"folder", 'f', POPT_ARG_STRING, NULL, ARG_FOLDER, + N_("Specify camera folder (default=\"/\")"), N_("FOLDER")}, + {"recurse", 'R', POPT_ARG_NONE, NULL, ARG_RECURSE, + N_("Recursion (default for download)"), NULL}, + {"no-recurse", '\0', POPT_ARG_NONE, NULL, ARG_NO_RECURSE, + N_("No recursion (default for deletion)"), NULL}, + {"new", '\0', POPT_ARG_NONE, NULL, ARG_NEW, + N_("Process new files only"), NULL}, + {"force-overwrite", '\0', POPT_ARG_NONE, NULL, + ARG_FORCE_OVERWRITE, N_("Overwrite files without asking"), NULL}, + {"skip-existing", '\0', POPT_ARG_NONE, NULL, + ARG_SKIP_EXISTING, N_("Skip existing files"), NULL}, + POPT_TABLEEND + }; + const struct poptOption miscOptions[] = { + GPHOTO2_POPT_CALLBACK + {"stdout", '\0', POPT_ARG_NONE, NULL, ARG_STDOUT, + N_("Send file to stdout"), NULL}, + {"stdout-size", '\0', POPT_ARG_NONE, NULL, ARG_STDOUT_SIZE, + N_("Print filesize before data"), NULL}, + +#ifdef HAVE_LIBEXIF + {"show-exif", '\0', POPT_ARG_STRING, NULL, ARG_SHOW_EXIF, + N_("Show EXIF information of JPEG images"), NULL}, +#endif + {"show-info", '\0', POPT_ARG_STRING, NULL, ARG_SHOW_INFO, + N_("Show image information, like width, height, and capture time"), NULL}, + {"summary", '\0', POPT_ARG_NONE, NULL, ARG_SUMMARY, + N_("Show camera summary"), NULL}, + {"manual", '\0', POPT_ARG_NONE, NULL, ARG_MANUAL, + N_("Show camera driver manual"), NULL}, + {"about", '\0', POPT_ARG_NONE, NULL, ARG_ABOUT, + N_("About the camera driver manual"), NULL}, + {"storage-info", '\0', POPT_ARG_NONE, NULL, ARG_STORAGE_INFO, + N_("Show storage information"), NULL}, + {"server", '\0', POPT_ARG_NONE, NULL, ARG_SERVER, + N_("gPhoto webapi server"), NULL}, + {"server-url", '\0', POPT_ARG_STRING, NULL, ARG_SERVER_URL, + N_("Server URL - e.g http://0.0.0.0:8866"), NULL}, + POPT_TABLEEND + }; + const struct poptOption options[] = { + GPHOTO2_POPT_CALLBACK + {NULL, '\0', POPT_ARG_INCLUDE_TABLE, (void *) &generalOptions, 0, + N_("Common options"), NULL}, + {NULL, '\0', POPT_ARG_INCLUDE_TABLE, (void *) &miscOptions, 0, + N_("Miscellaneous options (unsorted)"), NULL}, + {NULL, '\0', POPT_ARG_INCLUDE_TABLE, (void *) &infoOptions, 0, + N_("Get information on software and host system (not from the camera)"), NULL}, + {NULL, '\0', POPT_ARG_INCLUDE_TABLE, (void *) &cameraOptions, 0, + N_("Specify the camera to use"), NULL}, + {NULL, '\0', POPT_ARG_INCLUDE_TABLE, (void *) &configOptions, 0, + N_("Camera and software configuration"), NULL}, + {NULL, '\0', POPT_ARG_INCLUDE_TABLE, (void *) &captureOptions, 0, + N_("Capture an image from or on the camera"), NULL}, + {NULL, '\0', POPT_ARG_INCLUDE_TABLE, (void *) &fileOptions, 0, + N_("Downloading, uploading and manipulating files"), NULL}, + POPT_TABLEEND + }; + CameraAbilities a; + GPPortInfo info; + GPPortType type; + int result = GP_OK; + cb_params.type = CALLBACK_PARAMS_TYPE_NONE; + + /* For translation */ + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + webapi_server_initialize(); + + /* These are for people signaling us */ + capture_now = 0; + signal(SIGUSR1, sig_handler_capture_now); + end_next = 0; + signal(SIGUSR2, sig_handler_end_next); + + /* Create/Initialize the global variables before we first use + * them. And the signal handlers and popt callback functions + * do use them. */ + gp_params_init (&gp_params, envp); + + /* Figure out the width of the terminal and watch out for changes */ + signal_resize (0); +#ifdef SIGWINCH + signal (SIGWINCH, signal_resize); +#endif + + /* Prepare processing options. */ + ctx = poptGetContext (PACKAGE, argc, (const char **) argv, options, 0); + if (argc <= 1) { + poptPrintUsage (ctx, stdout, 0); + gp_params_exit (&gp_params); + poptFreeContext(ctx); + return (0); + } + + /* + * Do we need debugging output? While we are at it, scan the + * options for bad ones. + */ + poptResetContext (ctx); + while ((result = poptGetNextOpt (ctx)) >= 0); + if (result == POPT_ERROR_BADOPT) { + poptPrintUsage (ctx, stderr, 0); + gp_params_exit (&gp_params); + poptFreeContext(ctx); + return (EXIT_FAILURE); + } + if (help_option_given) { + poptPrintHelp(ctx, stdout, 0); + fprintf(stdout, "\n%s", + _("EVENT can be either COUNT, SECONDS, MILLISECONDS, or MATCHSTRING.\n")); + gp_params_exit (&gp_params); + poptFreeContext(ctx); + return 0; + } + if (usage_option_given) { + poptPrintUsage(ctx, stdout, 0); + gp_params_exit (&gp_params); + poptFreeContext(ctx); + return 0; + } + if (debug_option_given) { + CR_MAIN (debug_action (&gp_params, debug_loglevel, debug_logfile_name)); + } + + gp_log (GP_LOG_DEBUG, "main", "invoked with following arguments:"); + for (i=1;i= GP_OK) && (poptGetNextOpt (ctx) >= 0)); + + CR_MAIN (cb_params.p.r); + + cb_params.type = CALLBACK_PARAMS_TYPE_INITIALIZE; + poptResetContext (ctx); + while ((cb_params.p.r >= GP_OK) && (poptGetNextOpt (ctx) >= 0)); + /* Load default values for --filename and --hook-script if not + * explicitely specified + */ + if (!gp_params.filename) { + char buf[256]; + if (gp_setting_get("gphoto2","filename",buf)>=0) { + set_filename_action(&gp_params,buf); + } + } + if (!gp_params.hook_script) { + char buf[PATH_MAX]; + if (gp_setting_get("gphoto2","hook-script",buf)>=0) { + gp_params.hook_script = strdup(buf); + /* Run init hook */ + if (0!=gp_params_run_hook(&gp_params, "init", NULL)) { + fprintf(stdout, + "Hook script \"%s\" init failed. Aborting.\n", + gp_params.hook_script); + exit(3); + } + } + } + CR_MAIN (cb_params.p.r); + +#define CHECK_OPT(o) \ + if (!cb_params.p.q.found) { \ + cb_params.p.q.arg = (o); \ + poptResetContext (ctx); \ + while (poptGetNextOpt (ctx) >= 0); \ + } + + /* If we need a camera, make sure we've got one. */ + CR_MAIN (gp_camera_get_abilities (gp_params.camera, &a)); + CR_MAIN (gp_camera_get_port_info (gp_params.camera, &info)); + + /* Determine which command is given on command line */ + cb_params.type = CALLBACK_PARAMS_TYPE_QUERY; + cb_params.p.q.found = 0; + CHECK_OPT (ARG_ABILITIES); + CHECK_OPT (ARG_SHOW_PREVIEW); + CHECK_OPT (ARG_CONFIG); + CHECK_OPT (ARG_DELETE_ALL_FILES); + CHECK_OPT (ARG_DELETE_FILE); + CHECK_OPT (ARG_GET_ALL_AUDIO_DATA); + CHECK_OPT (ARG_GET_ALL_FILES); + CHECK_OPT (ARG_GET_ALL_RAW_DATA); + CHECK_OPT (ARG_GET_ALL_THUMBNAILS); + CHECK_OPT (ARG_GET_AUDIO_DATA); + CHECK_OPT (ARG_GET_CONFIG); + CHECK_OPT (ARG_GET_FILE); + CHECK_OPT (ARG_GET_RAW_DATA); + CHECK_OPT (ARG_GET_THUMBNAIL); + CHECK_OPT (ARG_LIST_FILES); + CHECK_OPT (ARG_LIST_FOLDERS); + CHECK_OPT (ARG_MANUAL); + CHECK_OPT (ARG_MKDIR); + CHECK_OPT (ARG_NUM_FILES); + CHECK_OPT (ARG_RESET); + CHECK_OPT (ARG_RMDIR); + CHECK_OPT (ARG_SET_CONFIG); + CHECK_OPT (ARG_SET_CONFIG_INDEX); + CHECK_OPT (ARG_SET_CONFIG_VALUE); + CHECK_OPT (ARG_SERVER); + CHECK_OPT (ARG_SERVER_URL); + CHECK_OPT (ARG_SHOW_EXIF); + CHECK_OPT (ARG_SHOW_INFO); + CHECK_OPT (ARG_STORAGE_INFO); + CHECK_OPT (ARG_SUMMARY); + CHECK_OPT (ARG_UPLOAD_FILE); + CHECK_OPT (ARG_UPLOAD_METADATA); + CHECK_OPT (ARG_WAIT_EVENT); + gp_port_info_get_type (info, &type); + if (cb_params.p.q.found && + (!strcmp (a.model, "") || (type == GP_PORT_NONE))) { + int count; + const char *model = NULL, *path = NULL; + CameraList *list; + char buf[1024]; + int use_auto = 1; + + gp_log (GP_LOG_DEBUG, "main", "The user has not specified " + "both a model and a port. Try to figure them out."); + + + _get_portinfo_list(&gp_params); + CR_MAIN (gp_list_new (&list)); /* no freeing below */ + CR_MAIN (gp_abilities_list_detect (gp_params_abilities_list(&gp_params), + gp_params.portinfo_list, + list, gp_params.context)); + CR_MAIN (count = gp_list_count (list)); + if (count == 1) { + /* Exactly one camera detected */ + CR_MAIN (gp_list_get_name (list, 0, &model)); + CR_MAIN (gp_list_get_value (list, 0, &path)); + if (a.model[0] && strcmp(a.model,model)) { + CameraAbilities alt; + int m; + + CR_MAIN (m = gp_abilities_list_lookup_model ( + gp_params_abilities_list(&gp_params), + model)); + CR_MAIN (gp_abilities_list_get_abilities ( + gp_params_abilities_list(&gp_params), + m, &alt)); + + if ((a.port == GP_PORT_USB) && (alt.port == GP_PORT_USB)) { + if ( (a.usb_vendor == alt.usb_vendor) && + (a.usb_product == alt.usb_product) + ) + use_auto = 0; + } + } + + if (use_auto) { + CR_MAIN (action_camera_set_model (&gp_params, model)); + } + if (type == GP_PORT_USB) { + char *xpath; + + gp_port_info_get_path (info, &xpath); + if (strcmp(xpath, "usb:") && strcmp(xpath, path)) { + fprintf (stderr, _("Port %s not found\n"), xpath ); + CR_MAIN (GP_ERROR_UNKNOWN_PORT); + } + } + CR_MAIN (action_camera_set_port (&gp_params, path)); + + } else if (!count) { + int ret; + /* + * No camera detected. Have a look at the settings. + * Ignore errors here, it might be a serial one. + */ + if (gp_setting_get ("gphoto2", "model", buf) >= 0) + action_camera_set_model (&gp_params, buf); + if (gp_setting_get ("gphoto2", "port", buf) >= 0) + action_camera_set_port (&gp_params, buf); + ret = gp_camera_init (gp_params.camera, gp_params.context); + if (ret != GP_OK) { + if (ret == GP_ERROR_BAD_PARAMETERS) + ret = -2000; + CR_MAIN (ret); + } + } else { + /* If --port override, search the model with the same port. */ + if (type != GP_PORT_NONE) { + int i; + char *xpath, *xname; + + gp_port_info_get_path (info, &xpath); + gp_port_info_get_name (info, &xname); + gp_log (GP_LOG_DEBUG, "gphoto2", "Looking for port ...\n"); + gp_log (GP_LOG_DEBUG, "gphoto2", "info.type = %d\n", type); + gp_log (GP_LOG_DEBUG, "gphoto2", "info.name = %s\n", xname); + gp_log (GP_LOG_DEBUG, "gphoto2", "info.path = %s\n", xpath); + + for (i=0;i= 0); + if (!cb_params.p.q.found) { + cb_params.p.q.arg = ARG_DELETE_ALL_FILES; + poptResetContext (ctx); + while (poptGetNextOpt (ctx) >= 0); + } + if (cb_params.p.q.found) { + cb_params.p.q.found = 0; + cb_params.p.q.arg = ARG_RECURSE; + poptResetContext (ctx); + while (poptGetNextOpt (ctx) >= 0); + if (!cb_params.p.q.found) + gp_params.flags &= ~FLAGS_RECURSE; + } + + signal (SIGINT, signal_exit); + signal (SIGTERM, signal_exit); + + /* If we are told to be quiet, be so. * + cb_params.type = CALLBACK_PARAMS_TYPE_QUERY; + cb_params.p.q.found = 0; + cb_params.p.q.arg = ARG_QUIET; + poptResetContext (ctx); + while (poptGetNextOpt (ctx) >= 0); + if (cb_params.p.q.found) { + gp_params.flags |= FLAGS_QUIET; + } + */ + + /* Run startup hook */ + gp_params_run_hook(&gp_params, "start", NULL); + + /* Go! */ + cb_params.type = CALLBACK_PARAMS_TYPE_RUN; + poptResetContext (ctx); + cb_params.p.r = GP_OK; + while ((cb_params.p.r >= GP_OK) && (poptGetNextOpt (ctx) >= 0)); + + switch (gp_params.multi_type) { + case MULTI_UPLOAD: { + const char *arg; + + while ((cb_params.p.r >= GP_OK) && (NULL != (arg = poptGetArg (ctx)))) { + CR_MAIN (action_camera_upload_file (&gp_params, gp_params.folder, arg)); + } + break; + } + case MULTI_UPLOAD_META: { + const char *arg; + + while ((cb_params.p.r >= GP_OK) && (NULL != (arg = poptGetArg (ctx)))) { + CR_MAIN (action_camera_upload_metadata (&gp_params, gp_params.folder, arg)); + } + break; + } + case MULTI_DELETE: { + const char *arg; + + while ((cb_params.p.r >= GP_OK) && (NULL != (arg = poptGetArg (ctx)))) { + CR_MAIN (delete_file_action (&gp_params, gp_params.folder, arg)); + } + break; + } + case MULTI_DOWNLOAD: { + const char *arg; + + while ((cb_params.p.r >= GP_OK) && (NULL != (arg = poptGetArg (ctx)))) { + CR_MAIN (get_file_common (arg, gp_params.download_type )); + } + break; + } + default: + break; + } + + CR_MAIN (cb_params.p.r); + + /* Run stop hook */ + gp_params_run_hook(&gp_params, "stop", NULL); + + /* FIXME: Env var checks (e.g. for Windows, OS/2) should happen before + * we load the camlibs */ + + gp_params_exit (&gp_params); + poptFreeContext(ctx); + return EXIT_SUCCESS; +} + + +/* + * Local Variables: + * c-file-style:"linux" + * indent-tabs-mode:t + * End: + */ diff --git a/gphoto2/gphoto2-webapi.h b/gphoto2/gphoto2-webapi.h new file mode 100644 index 00000000..b4f846d6 --- /dev/null +++ b/gphoto2/gphoto2-webapi.h @@ -0,0 +1,61 @@ +/* gphoto2-webapi.h + * + * Copyright © 2002 Lutz Müller + * Copyright © 2022 Thorsten Ludewig + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +#ifndef GPHOTO2_WEBAPI_H +#define GPHOTO2_WEBAPI_H + +#include +#include +#include + +#include "mongoose/mongoose.h" +#define JSON_PRINTF mg_http_printf_chunk + +#define MAX_IMAGE_NUMBER 65536 + +#ifdef WIN32 +#include +#define VERSION "2" +#endif + +void cli_error_print(char *format, ...); + +int camera_file_exists (Camera *camera, GPContext *context, + const char *folder, const char *filename, + CameraFileType type); +int save_file_to_file (Camera *camera, GPContext *context, Flags flags, + const char *folder, const char *filename, + CameraFileType type); +int save_camera_file_to_file (const char *folder, const char *fn, CameraFileType type, CameraFile *file, const char *tmpname); +int capture_generic (struct mg_connection *c, CameraCaptureType type, const char *name, int download); +int get_file_common (const char *arg, CameraFileType type ); + +int trigger_capture (void); + +#endif /* !defined(GPHOTO2_WEBAPI_H) */ + + +/* + * Local Variables: + * c-file-style:"linux" + * indent-tabs-mode:t + * End: + */ diff --git a/gphoto2/server.c b/gphoto2/server.c new file mode 100644 index 00000000..a6c5322d --- /dev/null +++ b/gphoto2/server.c @@ -0,0 +1,239 @@ +/* + * Copyright 2022 Thorsten Ludewig + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +#define _DARWIN_C_SOURCE + +#ifndef WEBAPI +#define WEBAPI +#endif + +#include "config.h" +#include "actions.h" +#include "globals.h" +#include "i18n.h" +#include "gphoto2-webapi.h" +#include "server.h" +#include "mongoose/mongoose.h" +#include "version.h" + +#include +#include +#include +#include + +#ifdef HAVE_UNISTD_H +#include +#endif + +#define CHECK_NULL(x) { if (x == NULL) { return(-1); } } + +#ifndef MIN +# define MIN(a, b) ((a) < (b) ? (a) : (b)) +#endif + +////////////////////////////////////////////////////////////////////////////////////// + +WebAPIServerConfig webcfg; + +static GPParams *p = NULL; +static const char *s_http_addr = "http://0.0.0.0:8866"; + +static const char *http_chunked_header = "HTTP/1.1 200 OK\r\n" + "Content-Type: application/json\r\n" + "Transfer-Encoding: chunked\r\n\r\n"; + +static const char *content_type_application_json = "Content-Type: application/json\r\n"; + +#define MG_HTTP_CHUNK_START mg_printf(c, http_chunked_header) +#define MG_HTTP_CHUNK_END mg_http_printf_chunk(c, "") + + +static int +server_http_version(struct mg_connection *c) +{ + MG_HTTP_CHUNK_START; + mg_http_printf_chunk(c, "{ \"result\": [{ \"name\": \"mongoose\", \"version\": \"%s\"}", MG_VERSION); + + int n; + + for (n = 0; module_versions[n].name != NULL; n++) + { + int i; + const char **v = NULL; + char *name = module_versions[n].name; + GPVersionFunc func = module_versions[n].version_func; + CHECK_NULL(name); + CHECK_NULL(func); + v = func(GP_VERSION_SHORT); + CHECK_NULL(v); + CHECK_NULL(v[0]); + + mg_http_printf_chunk(c, ", { \"name\": \"%s\", \"version\": \"%s\", \"libs\": \"", name, v[0]); + + for (i = 1; v[i] != NULL; i++) + { + if (v[i + 1] != NULL) + mg_http_printf_chunk(c, "%s, ", v[i]); + else + mg_http_printf_chunk(c, "%s", v[i]); + } + + mg_http_printf_chunk(c, "\"}"); + } + + mg_http_printf_chunk(c, "]}\n"); + MG_HTTP_CHUNK_END; + return 0; +} + +static void +fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) +{ + if (ev == MG_EV_HTTP_MSG) + { + struct mg_http_message *hm = (struct mg_http_message *)ev_data; + + if (mg_http_match_uri(hm, "/")) + { + server_http_version(c); + } + + else if (mg_http_match_uri(hm, "/api/version")) + { + server_http_version(c); + } + + else if (mg_http_match_uri(hm, "/api/server/shutdown")) + { + mg_http_reply(c, 200, content_type_application_json, "{\"return_code\":0}\n" ); + webcfg.server_done = TRUE; + } + + else if (mg_http_match_uri(hm, "/api/auto-detect")) + { + MG_HTTP_CHUNK_START; + auto_detect_action(c, p); + MG_HTTP_CHUNK_END; + } + + else if (mg_http_match_uri(hm, "/api/trigger-capture")) + { + MG_HTTP_CHUNK_START; + mg_http_printf_chunk(c, "{\"return_code\":%d}\n", gp_camera_trigger_capture(p->camera, p->context)); + MG_HTTP_CHUNK_END; + } + + else if (mg_http_match_uri(hm, "/api/list-config")) + { + MG_HTTP_CHUNK_START; + mg_http_printf_chunk(c, "{" ); + mg_http_printf_chunk(c, "\"return_code\":%d}", list_config_action(c, p)); + MG_HTTP_CHUNK_END; + } + + else if (mg_http_match_uri(hm, "/api/list-all-config")) + { + MG_HTTP_CHUNK_START; + mg_http_printf_chunk(c, "{" ); + mg_http_printf_chunk(c, "\"return_code\":%d}", list_all_config_action(c, p)); + MG_HTTP_CHUNK_END; + } + + else if (mg_http_match_uri( hm, "/api/config/set-index/#")) + { + int ret = -1; + + if ( hm->query.len >= 3 && hm->query.ptr[0] == 'i' + && hm->query.ptr[1] == '=' && hm->uri.len >= 22 ) + { + char buffer[256]; + char buffer2[6]; + strncpy( buffer, hm->uri.ptr, MIN((int)hm->uri.len,255)); + strncpy( buffer2, hm->query.ptr, MIN((int)hm->query.len,5)); + buffer[MIN((int)hm->uri.len,255)] = 0; + buffer2[MIN((int)hm->query.len,5)] = 0; + char *name = buffer+21; + char *value = buffer2+2; + ret = set_config_index_action( p, name, value ); + } + + mg_http_reply(c, 200, content_type_application_json, "{\"return_code\":%d}\n", ret ); + } + + + else if (mg_http_match_uri(hm, "/api/capture-image")) + { + MG_HTTP_CHUNK_START; + mg_http_printf_chunk(c, "{" ); + mg_http_printf_chunk(c, "\"return_code\":%d}\n", capture_generic(c, GP_CAPTURE_IMAGE, NULL, 0 )); + MG_HTTP_CHUNK_END; + } + + else if (mg_http_match_uri(hm, "/api/capture-image-download")) + { + MG_HTTP_CHUNK_START; + mg_http_printf_chunk(c, "{" ); + mg_http_printf_chunk(c, "\"return_code\":%d}\n", capture_generic(c, GP_CAPTURE_IMAGE, NULL, 1 )); + MG_HTTP_CHUNK_END; + } + + + else + { + mg_http_reply(c, 404, "", "Page not found.\n" ); + } + } +} + +void webapi_server_initialize(void) +{ + webcfg.server_url[0] = 0; + webcfg.api_user[0] = 0; + webcfg.api_password[0] = 0; + webcfg.server_done = FALSE; +} + +int webapi_server(GPParams *params) +{ + struct mg_mgr mgr; + + p = params; + + if (!webcfg.server_url[0]) + { + strcpy(webcfg.server_url, s_http_addr); + } + + printf("Starting GPhoto2 " VERSION " WebAPI server - %s\n", webcfg.server_url); + + mg_log_set("2"); + mg_mgr_init(&mgr); + mg_http_listen(&mgr, webcfg.server_url, fn, NULL); + + do + { + mg_mgr_poll(&mgr, 1000); + } while (webcfg.server_done == FALSE); + + mg_mgr_poll(&mgr, 1000); + mg_mgr_free(&mgr); + + puts("WebAPI server stopped."); + return GP_OK; +} diff --git a/gphoto2/server.h b/gphoto2/server.h new file mode 100644 index 00000000..e3e90a5d --- /dev/null +++ b/gphoto2/server.h @@ -0,0 +1,22 @@ +#ifndef GPHOTO2_SERVER_H +#define GPHOTO2_SERVER_H + +#include + +#define WEBCFG_STR_LEN 63 + +typedef struct _webapi_server_config +{ + char server_url[WEBCFG_STR_LEN+1]; + char api_user[WEBCFG_STR_LEN+1]; + char api_password[WEBCFG_STR_LEN+1]; + int server_done; +} WebAPIServerConfig; + + +extern void webapi_server_initialize(void); +extern int webapi_server(GPParams *params); + +extern WebAPIServerConfig webcfg; + +#endif From 92783ba42d08511f7cbdbb2a4a8691d19e47f318 Mon Sep 17 00:00:00 2001 From: Thorsten Ludewig Date: Fri, 17 Jun 2022 13:31:37 +0200 Subject: [PATCH 06/97] WEBAPI README added --- README_WEBAPI_SERVER.md | 82 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 README_WEBAPI_SERVER.md diff --git a/README_WEBAPI_SERVER.md b/README_WEBAPI_SERVER.md new file mode 100644 index 00000000..de6eece0 --- /dev/null +++ b/README_WEBAPI_SERVER.md @@ -0,0 +1,82 @@ +# GPhoto2 WEBAPI Server + +I need to control my camera via a small embedded controller so i decided to implement this little server direct into the wonderful gphoto2 application. + +2022 June, Thorsten Ludewig (t.ludewig@gmail.com) + +## Run server + +`gphoto2-webapi --server` default port 8866 + +binding to a different port + +`gphoto2-webapi --server-url=http://0.0.0.0:9999 --server` + +binding to a specific ip and or port + +`gphoto2-webapi --server-url=http://127.0.0.1:8866 --server` + +## API Calls - all HTTP GET + +All responses are [JSON](https://json.org) formated. + +- `http://:8866/` + + version information for now + +- `http://:8866/api/server/shutdown` + + shutdown http server + +- `http://:8866/api/version` + + version information + +- `http://:8866/api/auto-detect` + + show detected camera info + +- `http://:8866/api/trigger-capture` + + trigger capture image + +- `http://:8866/api/config/list` + + list available config names + +- `http://:8866/api/config/list/all` + + list config including current and available config values + +- `http://:8866/api/config/set-index/?i=` + + set the `config name` to the specified `index value` + +- `http://:8866/api/capture-image` + +- `http://:8866/api/capture-image-download` + + +## Mongoose HTTP Server - LICENSE + +https://github.com/cesanta/mongoose + + +``` +Copyright (c) 2004-2013 Sergey Lyubka +Copyright (c) 2013-2021 Cesanta Software Limited +All rights reserved + +This software is dual-licensed: you can redistribute it and/or modify +it under the terms of the GNU General Public License version 2 as +published by the Free Software Foundation. For the terms of this +license, see . + +You are free to use this software under the terms of the GNU General +Public License, but WITHOUT ANY WARRANTY; without even the implied +warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +See the GNU General Public License for more details. + +Alternatively, you can license this software under a commercial +license, as set out in . +``` From 454b129457230c6b1a3dc6ca1c9452552e645a59 Mon Sep 17 00:00:00 2001 From: Thorsten Ludewig Date: Fri, 17 Jun 2022 13:37:47 +0200 Subject: [PATCH 07/97] describe clone with submodule --- README_WEBAPI_SERVER.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README_WEBAPI_SERVER.md b/README_WEBAPI_SERVER.md index de6eece0..f74e6c4d 100644 --- a/README_WEBAPI_SERVER.md +++ b/README_WEBAPI_SERVER.md @@ -4,6 +4,10 @@ I need to control my camera via a small embedded controller so i decided to impl 2022 June, Thorsten Ludewig (t.ludewig@gmail.com) +## clone with submodule (mongoose webserver) + +`git clone --recurse-submodules ` + ## Run server `gphoto2-webapi --server` default port 8866 From 1b2ef40a71c3c342ebd26212cdfbab5641123aa6 Mon Sep 17 00:00:00 2001 From: Thorsten Ludewig Date: Sat, 18 Jun 2022 09:18:28 +0200 Subject: [PATCH 08/97] gphoto2-webapi binary added to ignore list --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 9303377d..1fb2b245 100644 --- a/.gitignore +++ b/.gitignore @@ -44,6 +44,7 @@ snap/.snapcraft *.x86_64 *.hex gphoto2/gphoto2 +gphoto2/gphoto2-webapi # Debug files *.dSYM/ From bcd3cf33902d61deb55bec0711cd553d809bba27 Mon Sep 17 00:00:00 2001 From: Thorsten Ludewig Date: Sat, 18 Jun 2022 09:38:49 +0200 Subject: [PATCH 09/97] downloaded images, add image info to json response --- gphoto2/gphoto2-webapi.c | 182 ++++----------------------------------- gphoto2/gphoto2-webapi.h | 4 +- 2 files changed, 21 insertions(+), 165 deletions(-) diff --git a/gphoto2/gphoto2-webapi.c b/gphoto2/gphoto2-webapi.c index 4e47320e..1967e19d 100644 --- a/gphoto2/gphoto2-webapi.c +++ b/gphoto2/gphoto2-webapi.c @@ -582,7 +582,7 @@ static int x_write(void*priv,unsigned char *data, uint64_t *size) { static CameraFileHandler xhandler = { x_size, x_read, x_write }; int -save_file_to_file (Camera *camera, GPContext *context, Flags flags, +save_file_to_file (struct mg_connection *c, Camera *camera, GPContext *context, Flags flags, const char *folder, const char *filename, CameraFileType type) { @@ -606,11 +606,20 @@ save_file_to_file (Camera *camera, GPContext *context, Flags flags, } } + CameraFileInfo info; + + CR (gp_camera_file_get_info (camera, folder, filename, &info, context)); + + JSON_PRINTF( c, "\"image_info\":{" ); + JSON_PRINTF( c, "\"mtime\":%ld,", info.file.mtime ); + JSON_PRINTF( c, "\"size\":%ld,", info.file.size ); + JSON_PRINTF( c, "\"height\":%d,", info.file.height ); + JSON_PRINTF( c, "\"width\":%d,", info.file.width ); + JSON_PRINTF( c, "\"type\":\"%s\"\n", info.file.type ); + JSON_PRINTF( c, "}," ); + if (flags & FLAGS_NEW) { - CameraFileInfo info; - CR (gp_camera_file_get_info (camera, folder, filename, - &info, context)); switch (type) { case GP_FILE_TYPE_PREVIEW: if (info.preview.fields & GP_FILE_INFO_STATUS && @@ -737,7 +746,7 @@ dissolve_filename ( */ int -get_file_common (const char *arg, CameraFileType type ) +get_file_common (struct mg_connection *c, const char *arg, CameraFileType type ) { unsigned int mightberange = 1, i; gp_log (GP_LOG_DEBUG, "main", "Getting '%s'...", arg); @@ -759,7 +768,7 @@ get_file_common (const char *arg, CameraFileType type ) char *newfolder, *newfilename; dissolve_filename (gp_params.folder, arg, &newfolder, &newfilename); - ret = save_file_to_file (gp_params.camera, gp_params.context, gp_params.flags, + ret = save_file_to_file ( c, gp_params.camera, gp_params.context, gp_params.flags, newfolder, newfilename, type); free (newfolder); free (newfilename); return ret; @@ -873,7 +882,7 @@ save_captured_file (struct mg_connection *c, CameraFilePath *path, int download) return GP_OK; } - result = get_file_common (path->name, GP_FILE_TYPE_NORMAL); + result = get_file_common ( c, path->name, GP_FILE_TYPE_NORMAL); if (result != GP_OK) { cli_error_print (_("Could not get image.")); if(result == GP_ERROR_FILE_NOT_FOUND) { @@ -1320,26 +1329,14 @@ typedef enum { ARG_DEBUG, ARG_DEBUG_LOGLEVEL, ARG_DEBUG_LOGFILE, - ARG_DELETE_ALL_FILES, - ARG_DELETE_FILE, ARG_FILENAME, ARG_FILENUMBER, ARG_FOLDER, ARG_FORCE_OVERWRITE, - ARG_GET_ALL_AUDIO_DATA, - ARG_GET_ALL_FILES, - ARG_GET_ALL_METADATA, - ARG_GET_ALL_RAW_DATA, - ARG_GET_ALL_THUMBNAILS, - ARG_GET_AUDIO_DATA, ARG_GET_CONFIG, ARG_SET_CONFIG, ARG_SET_CONFIG_INDEX, ARG_SET_CONFIG_VALUE, - ARG_GET_FILE, - ARG_GET_METADATA, - ARG_GET_RAW_DATA, - ARG_GET_THUMBNAIL, ARG_HELP, ARG_HOOK_SCRIPT, ARG_KEEP, @@ -1372,8 +1369,6 @@ typedef enum { ARG_STDOUT_SIZE, ARG_STORAGE_INFO, ARG_SUMMARY, - ARG_UPLOAD_FILE, - ARG_UPLOAD_METADATA, ARG_USAGE, ARG_USBID, ARG_VERSION, @@ -1607,56 +1602,6 @@ cb_arg_run (poptContext __unused__ ctx, params->p.r = GP_ERROR_NOT_SUPPORTED; #endif break; - case ARG_DELETE_ALL_FILES: - params->p.r = for_each_folder (&gp_params, delete_all_action); - break; - case ARG_DELETE_FILE: - gp_params.multi_type = MULTI_DELETE; - /* Did the user specify a file or a range? */ - if (strchr (arg, '.')) { - dissolve_filename (gp_params.folder, arg, &newfolder, &newfilename); - params->p.r = delete_file_action (&gp_params, newfolder, newfilename); - free (newfolder); free (newfilename); - break; - } - params->p.r = for_each_file_in_range (&gp_params, - delete_file_action, arg); - break; - case ARG_GET_ALL_AUDIO_DATA: - params->p.r = for_each_file (&gp_params, save_all_audio_action); - break; - case ARG_GET_ALL_FILES: - params->p.r = for_each_file (&gp_params, save_file_action); - break; - case ARG_GET_ALL_METADATA: - params->p.r = for_each_file (&gp_params, save_meta_action); - break; - case ARG_GET_ALL_RAW_DATA: - params->p.r = for_each_file (&gp_params, save_raw_action); - break; - case ARG_GET_ALL_THUMBNAILS: - params->p.r = for_each_file (&gp_params, save_thumbnail_action); - break; - case ARG_GET_AUDIO_DATA: - gp_params.multi_type = MULTI_DOWNLOAD; - params->p.r = get_file_common (arg, GP_FILE_TYPE_AUDIO); - break; - case ARG_GET_METADATA: - gp_params.multi_type = MULTI_DOWNLOAD; - params->p.r = get_file_common (arg, GP_FILE_TYPE_METADATA); - break; - case ARG_GET_FILE: - gp_params.multi_type = MULTI_DOWNLOAD; - params->p.r = get_file_common (arg, GP_FILE_TYPE_NORMAL); - break; - case ARG_GET_THUMBNAIL: - gp_params.multi_type = MULTI_DOWNLOAD; - params->p.r = get_file_common (arg, GP_FILE_TYPE_PREVIEW); - break; - case ARG_GET_RAW_DATA: - gp_params.multi_type = MULTI_DOWNLOAD; - params->p.r = get_file_common (arg, GP_FILE_TYPE_RAW); - break; case ARG_LIST_CAMERAS: params->p.r = list_cameras_action (&gp_params); break; @@ -1754,16 +1699,6 @@ cb_arg_run (poptContext __unused__ ctx, case ARG_SUMMARY: params->p.r = action_camera_summary (&gp_params); break; - case ARG_UPLOAD_FILE: - gp_params.multi_type = MULTI_UPLOAD; - /* Note: do not normalize folder/filename, as -u allows local filenames with paths */ - params->p.r = action_camera_upload_file (&gp_params, gp_params.folder, arg); - break; - case ARG_UPLOAD_METADATA: - gp_params.multi_type = MULTI_UPLOAD_META; - /* Note: do not normalize folder/filename, as -u-meta allows local filenames with paths */ - params->p.r = action_camera_upload_metadata (&gp_params, gp_params.folder, arg); - break; case ARG_GET_CONFIG: params->p.r = get_config_action (&gp_params, arg); break; @@ -2055,39 +1990,6 @@ main (int argc, char **argv, char **envp) N_("Remove a directory"), N_("DIRNAME")}, {"num-files", 'n', POPT_ARG_NONE, NULL, ARG_NUM_FILES, N_("Display number of files"), NULL}, - {"get-file", 'p', POPT_ARG_STRING, NULL, ARG_GET_FILE, - N_("Get files given in range"), N_("RANGE")}, - {"get-all-files", 'P', POPT_ARG_NONE, NULL, ARG_GET_ALL_FILES, - N_("Get all files from folder"), NULL}, - {"get-thumbnail", 't', POPT_ARG_STRING, NULL, ARG_GET_THUMBNAIL, - N_("Get thumbnails given in range"), N_("RANGE")}, - {"get-all-thumbnails", 'T', POPT_ARG_NONE, 0, - ARG_GET_ALL_THUMBNAILS, - N_("Get all thumbnails from folder"), NULL}, - {"get-metadata", '\0', POPT_ARG_STRING, NULL, ARG_GET_METADATA, - N_("Get metadata given in range"), N_("RANGE")}, - {"get-all-metadata", '\0', POPT_ARG_NONE, NULL, ARG_GET_ALL_METADATA, - N_("Get all metadata from folder"), NULL}, - {"upload-metadata", '\0', POPT_ARG_STRING, NULL, ARG_UPLOAD_METADATA, - N_("Upload metadata for file"), NULL}, - {"get-raw-data", '\0', POPT_ARG_STRING, NULL, - ARG_GET_RAW_DATA, - N_("Get raw data given in range"), N_("RANGE")}, - {"get-all-raw-data", '\0', POPT_ARG_NONE, NULL, - ARG_GET_ALL_RAW_DATA, - N_("Get all raw data from folder"), NULL}, - {"get-audio-data", '\0', POPT_ARG_STRING, NULL, - ARG_GET_AUDIO_DATA, - N_("Get audio data given in range"), N_("RANGE")}, - {"get-all-audio-data", '\0', POPT_ARG_NONE, NULL, - ARG_GET_ALL_AUDIO_DATA, - N_("Get all audio data from folder"), NULL}, - {"delete-file", 'd', POPT_ARG_STRING, NULL, ARG_DELETE_FILE, - N_("Delete files given in range"), N_("RANGE")}, - {"delete-all-files", 'D', POPT_ARG_NONE, NULL, - ARG_DELETE_ALL_FILES, N_("Delete all files in folder (--no-recurse by default)"), NULL}, - {"upload-file", 'u', POPT_ARG_STRING, NULL, ARG_UPLOAD_FILE, - N_("Upload a file to camera"), N_("FILENAME")}, {"filename", '\0', POPT_ARG_STRING, NULL, ARG_FILENAME, N_("Specify a filename or filename pattern"), N_("FILENAME_PATTERN")}, {"filenumber", '\0', POPT_ARG_INT, NULL, ARG_FILENUMBER, @@ -2281,17 +2183,7 @@ main (int argc, char **argv, char **envp) CHECK_OPT (ARG_ABILITIES); CHECK_OPT (ARG_SHOW_PREVIEW); CHECK_OPT (ARG_CONFIG); - CHECK_OPT (ARG_DELETE_ALL_FILES); - CHECK_OPT (ARG_DELETE_FILE); - CHECK_OPT (ARG_GET_ALL_AUDIO_DATA); - CHECK_OPT (ARG_GET_ALL_FILES); - CHECK_OPT (ARG_GET_ALL_RAW_DATA); - CHECK_OPT (ARG_GET_ALL_THUMBNAILS); - CHECK_OPT (ARG_GET_AUDIO_DATA); CHECK_OPT (ARG_GET_CONFIG); - CHECK_OPT (ARG_GET_FILE); - CHECK_OPT (ARG_GET_RAW_DATA); - CHECK_OPT (ARG_GET_THUMBNAIL); CHECK_OPT (ARG_LIST_FILES); CHECK_OPT (ARG_LIST_FOLDERS); CHECK_OPT (ARG_MANUAL); @@ -2308,8 +2200,6 @@ main (int argc, char **argv, char **envp) CHECK_OPT (ARG_SHOW_INFO); CHECK_OPT (ARG_STORAGE_INFO); CHECK_OPT (ARG_SUMMARY); - CHECK_OPT (ARG_UPLOAD_FILE); - CHECK_OPT (ARG_UPLOAD_METADATA); CHECK_OPT (ARG_WAIT_EVENT); gp_port_info_get_type (info, &type); if (cb_params.p.q.found && @@ -2459,6 +2349,7 @@ main (int argc, char **argv, char **envp) * Recursion is too dangerous for deletion. Only turn it on if * explicitely specified. */ + /* cb_params.type = CALLBACK_PARAMS_TYPE_QUERY; cb_params.p.q.found = 0; cb_params.p.q.arg = ARG_DELETE_FILE; @@ -2469,6 +2360,8 @@ main (int argc, char **argv, char **envp) poptResetContext (ctx); while (poptGetNextOpt (ctx) >= 0); } + */ + if (cb_params.p.q.found) { cb_params.p.q.found = 0; cb_params.p.q.arg = ARG_RECURSE; @@ -2501,43 +2394,6 @@ main (int argc, char **argv, char **envp) cb_params.p.r = GP_OK; while ((cb_params.p.r >= GP_OK) && (poptGetNextOpt (ctx) >= 0)); - switch (gp_params.multi_type) { - case MULTI_UPLOAD: { - const char *arg; - - while ((cb_params.p.r >= GP_OK) && (NULL != (arg = poptGetArg (ctx)))) { - CR_MAIN (action_camera_upload_file (&gp_params, gp_params.folder, arg)); - } - break; - } - case MULTI_UPLOAD_META: { - const char *arg; - - while ((cb_params.p.r >= GP_OK) && (NULL != (arg = poptGetArg (ctx)))) { - CR_MAIN (action_camera_upload_metadata (&gp_params, gp_params.folder, arg)); - } - break; - } - case MULTI_DELETE: { - const char *arg; - - while ((cb_params.p.r >= GP_OK) && (NULL != (arg = poptGetArg (ctx)))) { - CR_MAIN (delete_file_action (&gp_params, gp_params.folder, arg)); - } - break; - } - case MULTI_DOWNLOAD: { - const char *arg; - - while ((cb_params.p.r >= GP_OK) && (NULL != (arg = poptGetArg (ctx)))) { - CR_MAIN (get_file_common (arg, gp_params.download_type )); - } - break; - } - default: - break; - } - CR_MAIN (cb_params.p.r); /* Run stop hook */ diff --git a/gphoto2/gphoto2-webapi.h b/gphoto2/gphoto2-webapi.h index b4f846d6..31d3ebbe 100644 --- a/gphoto2/gphoto2-webapi.h +++ b/gphoto2/gphoto2-webapi.h @@ -41,12 +41,12 @@ void cli_error_print(char *format, ...); int camera_file_exists (Camera *camera, GPContext *context, const char *folder, const char *filename, CameraFileType type); -int save_file_to_file (Camera *camera, GPContext *context, Flags flags, +int save_file_to_file (struct mg_connection *c, Camera *camera, GPContext *context, Flags flags, const char *folder, const char *filename, CameraFileType type); int save_camera_file_to_file (const char *folder, const char *fn, CameraFileType type, CameraFile *file, const char *tmpname); int capture_generic (struct mg_connection *c, CameraCaptureType type, const char *name, int download); -int get_file_common (const char *arg, CameraFileType type ); +int get_file_common (struct mg_connection *c, const char *arg, CameraFileType type ); int trigger_capture (void); From 80014e82d3380bfb65b96881265f1ce41d417e2d Mon Sep 17 00:00:00 2001 From: Thorsten Ludewig Date: Sat, 18 Jun 2022 10:58:13 +0200 Subject: [PATCH 10/97] add image_info also to capture-image --- gphoto2/gphoto2-webapi.c | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/gphoto2/gphoto2-webapi.c b/gphoto2/gphoto2-webapi.c index 1967e19d..73c2b10c 100644 --- a/gphoto2/gphoto2-webapi.c +++ b/gphoto2/gphoto2-webapi.c @@ -606,20 +606,10 @@ save_file_to_file (struct mg_connection *c, Camera *camera, GPContext *context, } } - CameraFileInfo info; - - CR (gp_camera_file_get_info (camera, folder, filename, &info, context)); - - JSON_PRINTF( c, "\"image_info\":{" ); - JSON_PRINTF( c, "\"mtime\":%ld,", info.file.mtime ); - JSON_PRINTF( c, "\"size\":%ld,", info.file.size ); - JSON_PRINTF( c, "\"height\":%d,", info.file.height ); - JSON_PRINTF( c, "\"width\":%d,", info.file.width ); - JSON_PRINTF( c, "\"type\":\"%s\"\n", info.file.type ); - JSON_PRINTF( c, "}," ); - if (flags & FLAGS_NEW) { - + CameraFileInfo info; + CR (gp_camera_file_get_info (camera, folder, filename, &info, context)); + switch (type) { case GP_FILE_TYPE_PREVIEW: if (info.preview.fields & GP_FILE_INFO_STATUS && @@ -853,8 +843,18 @@ save_captured_file (struct mg_connection *c, CameraFilePath *path, int download) path->folder, pathsep, path->name); } - JSON_PRINTF( c, "\"image_name\": \"%s\",", path->name ); - JSON_PRINTF( c, "\"camera_folder\": \"%s\",", path->folder ); + CameraFileInfo info; + CR (gp_camera_file_get_info (gp_params.camera, path->folder, path->name, &info, gp_params.context)); + + JSON_PRINTF( c, "\"image_info\":{" ); + JSON_PRINTF( c, "\"name\": \"%s\",", path->name ); + JSON_PRINTF( c, "\"folder\": \"%s\",", path->folder ); + JSON_PRINTF( c, "\"mtime\":%ld,", info.file.mtime ); + JSON_PRINTF( c, "\"size\":%ld,", info.file.size ); + JSON_PRINTF( c, "\"height\":%d,", info.file.height ); + JSON_PRINTF( c, "\"width\":%d,", info.file.width ); + JSON_PRINTF( c, "\"type\":\"%s\"\n", info.file.type ); + JSON_PRINTF( c, "}," ); JSON_PRINTF( c, "\"download\": %s,", (download) ? "true" : "false" ); if (download) { @@ -883,6 +883,7 @@ save_captured_file (struct mg_connection *c, CameraFilePath *path, int download) } result = get_file_common ( c, path->name, GP_FILE_TYPE_NORMAL); + if (result != GP_OK) { cli_error_print (_("Could not get image.")); if(result == GP_ERROR_FILE_NOT_FOUND) { From 2c48beac69538bfa349fc0023e0be3414b42e3fa Mon Sep 17 00:00:00 2001 From: Thorsten Ludewig Date: Sat, 18 Jun 2022 12:22:22 +0200 Subject: [PATCH 11/97] WEBAPI added to flags --- gphoto2/gp-params.h | 1 + 1 file changed, 1 insertion(+) diff --git a/gphoto2/gp-params.h b/gphoto2/gp-params.h index 3339efe2..41f6536b 100644 --- a/gphoto2/gp-params.h +++ b/gphoto2/gp-params.h @@ -38,6 +38,7 @@ typedef enum { FLAGS_KEEP_RAW = 1 << 9, FLAGS_SKIP_EXISTING = 1 << 10, FLAGS_PARSABLE = 1 << 11, + FLAGS_WEBAPI = 1 << 12, } Flags; typedef enum { From e6533454f33ca52033ff0dba3426524baa76339f Mon Sep 17 00:00:00 2001 From: Thorsten Ludewig Date: Sat, 18 Jun 2022 12:24:38 +0200 Subject: [PATCH 12/97] get-file api implemented --- gphoto2/gphoto2-webapi.c | 67 +++++++++++++++++++++++++++++++++++----- gphoto2/gphoto2-webapi.h | 1 + gphoto2/server.c | 11 +++++++ 3 files changed, 71 insertions(+), 8 deletions(-) diff --git a/gphoto2/gphoto2-webapi.c b/gphoto2/gphoto2-webapi.c index 73c2b10c..7536932f 100644 --- a/gphoto2/gphoto2-webapi.c +++ b/gphoto2/gphoto2-webapi.c @@ -586,8 +586,8 @@ save_file_to_file (struct mg_connection *c, Camera *camera, GPContext *context, const char *folder, const char *filename, CameraFileType type) { - int fd, res; - CameraFile *file; + int fd, res; + CameraFile *file; char tmpname[20], *tmpfilename; struct privstr *ps = NULL; @@ -678,22 +678,45 @@ save_file_to_file (struct mg_connection *c, Camera *camera, GPContext *context, } if (flags & FLAGS_STDOUT) { - const char *data; - unsigned long int size; + const char *data; + unsigned long int size; - CR (gp_file_get_data_and_size (file, &data, &size)); + CR (gp_file_get_data_and_size (file, &data, &size)); if (flags & FLAGS_STDOUT_SIZE) /* this will be difficult in fd mode */ - printf ("%li\n", size); - if (1!=fwrite (data, size, 1, stdout)) + printf ("%li\n", size); + + if (1!=fwrite (data, size, 1, stdout)) fprintf(stderr,"fwrite failed writing to stdout.\n"); - if (ps && ps->fd) close (ps->fd); + + if (ps && ps->fd) + close (ps->fd); + + free (ps); + gp_file_unref (file); + unlink (tmpname); + return (GP_OK); + } + + if (flags & FLAGS_WEBAPI) { + const char *data; + unsigned long int size; + + CR (gp_file_get_data_and_size (file, &data, &size)); + + mg_send( c, data, size ); + + if (ps && ps->fd) + close (ps->fd); + free (ps); gp_file_unref (file); unlink (tmpname); return (GP_OK); } + res = save_camera_file_to_file (folder, filename, type, file, tmpfilename); + if (ps && ps->fd) close (ps->fd); free (ps); gp_file_unref (file); @@ -730,6 +753,34 @@ dissolve_filename ( } +int +get_file_http_common (struct mg_connection *c, const char *path, CameraFileType type ) +{ + gp_log (GP_LOG_DEBUG, "gphoto2-webapi", "Getting '%s'...", path ); + + gp_params.download_type = type; + gp_params.flags |= FLAGS_WEBAPI; + + int ret; + char *newfolder, *newfilename; + + dissolve_filename (gp_params.folder, path, &newfolder, &newfilename); + + CameraFileInfo info; + CR (gp_camera_file_get_info (gp_params.camera, newfolder, newfilename, &info, gp_params.context)); + + const char *http_header = "HTTP/1.1 200 OK\r\n" + "Content-Type: %s\r\n" + "Content-Length: %ld\r\n\r\n"; + + mg_printf( c, http_header, info.file.type, info.file.size); + + ret = save_file_to_file (c, gp_params.camera, gp_params.context, gp_params.flags, + newfolder, newfilename, type); + free (newfolder); free (newfilename); + return ret; +} + /*! \brief parse range, download specified files, or their * thumbnails according to thumbnail argument, and save to files. diff --git a/gphoto2/gphoto2-webapi.h b/gphoto2/gphoto2-webapi.h index 31d3ebbe..2c222753 100644 --- a/gphoto2/gphoto2-webapi.h +++ b/gphoto2/gphoto2-webapi.h @@ -47,6 +47,7 @@ int save_file_to_file (struct mg_connection *c, Camera *camera, GPContext *conte int save_camera_file_to_file (const char *folder, const char *fn, CameraFileType type, CameraFile *file, const char *tmpname); int capture_generic (struct mg_connection *c, CameraCaptureType type, const char *name, int download); int get_file_common (struct mg_connection *c, const char *arg, CameraFileType type ); +int get_file_http_common (struct mg_connection *c, const char *arg, CameraFileType type ); int trigger_capture (void); diff --git a/gphoto2/server.c b/gphoto2/server.c index a6c5322d..79873bc6 100644 --- a/gphoto2/server.c +++ b/gphoto2/server.c @@ -193,6 +193,17 @@ fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) MG_HTTP_CHUNK_END; } + else if (mg_http_match_uri(hm, "/api/get-file/#")) + { + if ( hm->uri.len >= 14 ) + { + char buffer[256]; + strncpy( buffer, hm->uri.ptr, MIN((int)hm->uri.len,255)); + buffer[MIN((int)hm->uri.len,255)] = 0; + char *path = buffer+13; + get_file_http_common ( c, path, GP_FILE_TYPE_NORMAL ); + } + } else { From f7967d73e04c1809c65c3105e7479cd71f0d0185 Mon Sep 17 00:00:00 2001 From: Thorsten Ludewig Date: Sat, 18 Jun 2022 12:34:38 +0200 Subject: [PATCH 13/97] a few responses added --- README_WEBAPI_SERVER.md | 45 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/README_WEBAPI_SERVER.md b/README_WEBAPI_SERVER.md index f74e6c4d..375b3923 100644 --- a/README_WEBAPI_SERVER.md +++ b/README_WEBAPI_SERVER.md @@ -56,10 +56,53 @@ All responses are [JSON](https://json.org) formated. set the `config name` to the specified `index value` -- `http://:8866/api/capture-image` +- `http://:8866/api/capture-image` + +response + +```json +{ + image_info: { + name: "IMG_0264.JPG", + folder: "/store_00020001/DCIM/100CANON", + mtime: 1655542624, + size: 4838064, + height: 3456, + width: 5184, + type: "image/jpeg" + }, + download: false, + return_code: 0 +} +``` - `http://:8866/api/capture-image-download` +response + +```json +{ + image_info: { + name: "IMG_0265.JPG", + folder: "/store_00020001/DCIM/100CANON", + mtime: 1655542624, + size: 4838064, + height: 3456, + width: 5184, + type: "image/jpeg" + }, + download: true, + local_folder: "/home/user/Projects/gphoto2", + keeping_file_on_camera: true, + return_code: 0 +} +``` + +- `http://:8866/api/get-file/` + +response +- native file + ## Mongoose HTTP Server - LICENSE From e66d85a7b840a78eeb1b8eb00b34ab95a2a178fd Mon Sep 17 00:00:00 2001 From: Thorsten Ludewig Date: Sat, 18 Jun 2022 12:40:37 +0200 Subject: [PATCH 14/97] using jsonc tag --- README_WEBAPI_SERVER.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README_WEBAPI_SERVER.md b/README_WEBAPI_SERVER.md index 375b3923..e782e8eb 100644 --- a/README_WEBAPI_SERVER.md +++ b/README_WEBAPI_SERVER.md @@ -60,7 +60,7 @@ All responses are [JSON](https://json.org) formated. response -```json +```jsonc { image_info: { name: "IMG_0264.JPG", @@ -80,7 +80,7 @@ response response -```json +```jsonc { image_info: { name: "IMG_0265.JPG", From e236a53e8ac392670d6193e1e690cbb55c8e5597 Mon Sep 17 00:00:00 2001 From: Thorsten Ludewig Date: Sat, 18 Jun 2022 17:17:05 +0200 Subject: [PATCH 15/97] get-exif added --- README_WEBAPI_SERVER.md | 6 ++- gphoto2/actions.c | 88 +++++++++++++++++++++++++++------------- gphoto2/actions.h | 3 +- gphoto2/gphoto2-webapi.c | 20 +-------- gphoto2/gphoto2-webapi.h | 3 +- 5 files changed, 68 insertions(+), 52 deletions(-) diff --git a/README_WEBAPI_SERVER.md b/README_WEBAPI_SERVER.md index 375b3923..f21545e2 100644 --- a/README_WEBAPI_SERVER.md +++ b/README_WEBAPI_SERVER.md @@ -60,7 +60,7 @@ All responses are [JSON](https://json.org) formated. response -```json +```jsonc { image_info: { name: "IMG_0264.JPG", @@ -80,7 +80,7 @@ response response -```json +```jsonc { image_info: { name: "IMG_0265.JPG", @@ -103,6 +103,8 @@ response response - native file +- `http://:8866/api/get-exif/` + ## Mongoose HTTP Server - LICENSE diff --git a/gphoto2/actions.c b/gphoto2/actions.c index cedfc791..f8ec9e86 100644 --- a/gphoto2/actions.c +++ b/gphoto2/actions.c @@ -490,39 +490,56 @@ delete_file_action (GPParams *p, const char *folder, const char *filename) } #ifdef HAVE_LIBEXIF + +#ifdef WEBAPI static void -show_ifd (ExifContent *content) +show_ifd(struct mg_connection *c, ExifContent *content) +#else +static void +show_ifd(ExifContent *content) +#endif { - ExifEntry *e; - unsigned int i; - - for (i = 0; i < content->count; i++) { - e = content->entries[i]; - printf ("%-20.20s", exif_tag_get_name (e->tag)); - printf ("|"); - {char b[1024]; - printf ("%-59.59s", exif_entry_get_value (e, b, sizeof (b))); - } - printf ("\n"); - } + ExifEntry *e; + unsigned int i; + + for (i = 0; i < content->count; i++) + { + char b[1024]; + e = content->entries[i]; +#ifndef WEBAPI + printf("%-20.20s", exif_tag_get_name(e->tag)); + printf("|"); + printf("%-59.59s", exif_entry_get_value(e, b, sizeof(b))); + printf("\n"); +#else + JSON_PRINTF( c, "\"%s\":\"%s\",", exif_tag_get_name(e->tag), exif_entry_get_value(e, b, sizeof(b))); +#endif + } } +#ifndef WEBAPI static void -print_hline (void) +print_hline(void) { - int i; + int i; - for (i = 0; i < 20; i++) - putchar ('-'); - printf ("+"); - for (i = 0; i < 59; i++) - putchar ('-'); - putchar ('\n'); + for (i = 0; i < 20; i++) + putchar('-'); + printf("+"); + for (i = 0; i < 59; i++) + putchar('-'); + putchar('\n'); } #endif +#endif +#ifdef WEBAPI +int +print_exif_action (struct mg_connection *c, GPParams *p, const char *folder, const char *filename) +#else int print_exif_action (GPParams *p, const char *folder, const char *filename) +#endif { #ifdef HAVE_LIBEXIF CameraFile *file; @@ -542,6 +559,7 @@ print_exif_action (GPParams *p, const char *folder, const char *filename) return GP_ERROR; } +#ifndef WEBAPI printf (_("EXIF tags:")); putchar ('\n'); print_hline (); @@ -550,19 +568,31 @@ print_exif_action (GPParams *p, const char *folder, const char *filename) printf ("%-59.59s", _("Value")); putchar ('\n'); print_hline (); +#endif + for (i = 0; i < EXIF_IFD_COUNT; i++) + { if (ed->ifd[i]) + { +#ifndef WEBAPI show_ifd (ed->ifd[i]); - print_hline (); - if (ed->size) { - printf (_("EXIF data contains a thumbnail (%i bytes)."), - ed->size); - putchar ('\n'); - } +#else + show_ifd (c, ed->ifd[i]); +#endif + } + } - exif_data_unref (ed); +#ifndef WEBAPI + print_hline (); + if (ed->size) { + printf (_("EXIF data contains a thumbnail (%i bytes)."), ed->size); + putchar ('\n'); + } +#endif - return GP_OK; + exif_data_unref (ed); + + return GP_OK; #else gp_context_error (p->context, _("gphoto2 has been compiled without " "EXIF support.")); diff --git a/gphoto2/actions.h b/gphoto2/actions.h index 5c640be1..9b01561c 100644 --- a/gphoto2/actions.h +++ b/gphoto2/actions.h @@ -55,7 +55,6 @@ struct waitparams { /* Image actions */ typedef int FileAction (GPParams *, const char *folder, const char *filename); int print_file_action (GPParams *, const char *folder, const char *filename); -int print_exif_action (GPParams *, const char *folder, const char *filename); int print_info_action (GPParams *, const char *folder, const char *filename); int save_file_action (GPParams *, const char *folder, const char *filename); int save_thumbnail_action (GPParams *, const char *folder, const char *filename); @@ -111,10 +110,12 @@ void _get_portinfo_list (GPParams *p); int auto_detect_action (struct mg_connection *, GPParams *); int list_config_action (struct mg_connection *, GPParams *); int list_all_config_action (struct mg_connection *, GPParams *); +int print_exif_action (struct mg_connection *, GPParams *, const char *folder, const char *filename); #else int auto_detect_action (GPParams *); int list_config_action (GPParams *); int list_all_config_action (GPParams *); +int print_exif_action (GPParams *, const char *folder, const char *filename); #endif #endif /* !defined(GPHOTO2_ACTIONS_H) */ diff --git a/gphoto2/gphoto2-webapi.c b/gphoto2/gphoto2-webapi.c index 7536932f..670a3d9a 100644 --- a/gphoto2/gphoto2-webapi.c +++ b/gphoto2/gphoto2-webapi.c @@ -725,7 +725,7 @@ save_file_to_file (struct mg_connection *c, Camera *camera, GPContext *context, return (res); } -static void +void dissolve_filename ( const char *folder, const char *filename, char **newfolder, char **newfilename @@ -1412,7 +1412,6 @@ typedef enum { ARG_RMDIR, ARG_SERVER, ARG_SERVER_URL, - ARG_SHOW_EXIF, ARG_SHOW_INFO, ARG_PARSABLE, ARG_SKIP_EXISTING, @@ -1726,17 +1725,6 @@ cb_arg_run (poptContext __unused__ ctx, case ARG_SERVER: params->p.r = webapi_server (&gp_params); break; - case ARG_SHOW_EXIF: - /* Did the user specify a file or a range? */ - if (strchr (arg, '.')) { - dissolve_filename (gp_params.folder, arg, &newfolder, &newfilename); - params->p.r = print_exif_action (&gp_params, newfolder, newfilename); - free (newfolder); free (newfilename); - break; - } - params->p.r = for_each_file_in_range (&gp_params, - print_exif_action, arg); - break; case ARG_SHOW_INFO: /* Did the user specify a file or a range? */ if (strchr (arg, '.')) { @@ -2066,11 +2054,6 @@ main (int argc, char **argv, char **envp) N_("Send file to stdout"), NULL}, {"stdout-size", '\0', POPT_ARG_NONE, NULL, ARG_STDOUT_SIZE, N_("Print filesize before data"), NULL}, - -#ifdef HAVE_LIBEXIF - {"show-exif", '\0', POPT_ARG_STRING, NULL, ARG_SHOW_EXIF, - N_("Show EXIF information of JPEG images"), NULL}, -#endif {"show-info", '\0', POPT_ARG_STRING, NULL, ARG_SHOW_INFO, N_("Show image information, like width, height, and capture time"), NULL}, {"summary", '\0', POPT_ARG_NONE, NULL, ARG_SUMMARY, @@ -2248,7 +2231,6 @@ main (int argc, char **argv, char **envp) CHECK_OPT (ARG_SET_CONFIG_VALUE); CHECK_OPT (ARG_SERVER); CHECK_OPT (ARG_SERVER_URL); - CHECK_OPT (ARG_SHOW_EXIF); CHECK_OPT (ARG_SHOW_INFO); CHECK_OPT (ARG_STORAGE_INFO); CHECK_OPT (ARG_SUMMARY); diff --git a/gphoto2/gphoto2-webapi.h b/gphoto2/gphoto2-webapi.h index 2c222753..285e737d 100644 --- a/gphoto2/gphoto2-webapi.h +++ b/gphoto2/gphoto2-webapi.h @@ -36,7 +36,8 @@ #define VERSION "2" #endif -void cli_error_print(char *format, ...); +void cli_error_print(char *format, ...); +void dissolve_filename ( const char *folder, const char *filename, char **newfolder, char **newfilename ); int camera_file_exists (Camera *camera, GPContext *context, const char *folder, const char *filename, From 2ba73812c31e670d8c9830528aeaa775024ec30b Mon Sep 17 00:00:00 2001 From: Thorsten Ludewig Date: Sat, 18 Jun 2022 17:21:06 +0200 Subject: [PATCH 16/97] get-exif added --- README_WEBAPI_SERVER.md | 3 +-- gphoto2/server.c | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/README_WEBAPI_SERVER.md b/README_WEBAPI_SERVER.md index f21545e2..cbac1d08 100644 --- a/README_WEBAPI_SERVER.md +++ b/README_WEBAPI_SERVER.md @@ -100,8 +100,7 @@ response - `http://:8866/api/get-file/` -response -- native file +response - native file - `http://:8866/api/get-exif/` diff --git a/gphoto2/server.c b/gphoto2/server.c index 79873bc6..82904aa2 100644 --- a/gphoto2/server.c +++ b/gphoto2/server.c @@ -205,6 +205,28 @@ fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) } } +#ifdef HAVE_LIBEXIF + else if (mg_http_match_uri(hm, "/api/get-exif/#")) + { + if ( hm->uri.len >= 14 ) + { + char *newfilename = NULL, *newfolder = NULL; + + char buffer[256]; + strncpy( buffer, hm->uri.ptr, MIN((int)hm->uri.len,255)); + buffer[MIN((int)hm->uri.len,255)] = 0; + char *path = buffer+13; + + dissolve_filename (p->folder, path, &newfolder, &newfilename); + MG_HTTP_CHUNK_START; + mg_http_printf_chunk(c, "{" ); + mg_http_printf_chunk(c, "\"return_code\":%d}\n", print_exif_action (c, p, newfolder, newfilename)); + MG_HTTP_CHUNK_END; + free (newfolder); free (newfilename); + } + } +#endif + else { mg_http_reply(c, 404, "", "Page not found.\n" ); From bc9ca863c35f98b8798e455a5c8ed5758cfc2590 Mon Sep 17 00:00:00 2001 From: Thorsten Ludewig Date: Sat, 18 Jun 2022 17:33:43 +0200 Subject: [PATCH 17/97] remove several cli params --- gphoto2/gphoto2-webapi.c | 191 +-------------------------------------- 1 file changed, 5 insertions(+), 186 deletions(-) diff --git a/gphoto2/gphoto2-webapi.c b/gphoto2/gphoto2-webapi.c index 670a3d9a..a03286f8 100644 --- a/gphoto2/gphoto2-webapi.c +++ b/gphoto2/gphoto2-webapi.c @@ -1381,43 +1381,26 @@ typedef enum { ARG_DEBUG, ARG_DEBUG_LOGLEVEL, ARG_DEBUG_LOGFILE, - ARG_FILENAME, - ARG_FILENUMBER, - ARG_FOLDER, - ARG_FORCE_OVERWRITE, ARG_GET_CONFIG, ARG_SET_CONFIG, ARG_SET_CONFIG_INDEX, ARG_SET_CONFIG_VALUE, ARG_HELP, - ARG_HOOK_SCRIPT, ARG_KEEP, ARG_KEEP_RAW, ARG_LIST_CAMERAS, - ARG_LIST_FILES, - ARG_LIST_FOLDERS, ARG_LIST_PORTS, - ARG_MANUAL, - ARG_MKDIR, ARG_MODEL, - ARG_NEW, ARG_NO_KEEP, ARG_NO_RECURSE, - ARG_NUM_FILES, ARG_PORT, ARG_QUIET, - ARG_RECURSE, ARG_RESET, ARG_RESET_INTERVAL, - ARG_RMDIR, ARG_SERVER, ARG_SERVER_URL, ARG_SHOW_INFO, - ARG_PARSABLE, - ARG_SKIP_EXISTING, ARG_SPEED, - ARG_STDOUT, - ARG_STDOUT_SIZE, ARG_STORAGE_INFO, ARG_SUMMARY, ARG_USAGE, @@ -1519,23 +1502,6 @@ cb_arg_init (poptContext __unused__ ctx, { switch (opt->val) { - case ARG_FILENAME: - params->p.r = set_filename_action (&gp_params, arg); - break; - case ARG_FILENUMBER: - gp_params.filenr = atoi (arg); - params->p.r = GP_OK; - break; - case ARG_FOLDER: - params->p.r = set_folder_action (&gp_params, arg); - break; - - case ARG_FORCE_OVERWRITE: - gp_params.flags |= FLAGS_FORCE_OVERWRITE; - break; - case ARG_NEW: - gp_params.flags |= FLAGS_NEW; - break; case ARG_KEEP_RAW: gp_params.flags |= FLAGS_KEEP_RAW; break; @@ -1550,9 +1516,6 @@ cb_arg_init (poptContext __unused__ ctx, case ARG_NO_RECURSE: gp_params.flags &= ~FLAGS_RECURSE; break; - case ARG_RECURSE: - gp_params.flags |= FLAGS_RECURSE; - break; case ARG_MODEL: gp_log (GP_LOG_DEBUG, "main", "Processing 'model' " @@ -1565,10 +1528,6 @@ cb_arg_init (poptContext __unused__ ctx, params->p.r = action_camera_set_port (&gp_params, arg); break; - case ARG_SKIP_EXISTING: - gp_params.flags |= FLAGS_SKIP_EXISTING; - break; - case ARG_SPEED: params->p.r = action_camera_set_speed (&gp_params, atoi (arg)); break; @@ -1577,42 +1536,10 @@ cb_arg_init (poptContext __unused__ ctx, gp_params.flags |= FLAGS_QUIET; break; - case ARG_PARSABLE: - gp_params.flags |= FLAGS_QUIET; - gp_params.flags |= FLAGS_PARSABLE; - break; - case ARG_RESET_INTERVAL: gp_params.flags |= FLAGS_RESET_CAPTURE_INTERVAL; break; - case ARG_HOOK_SCRIPT: - do { - const size_t sz = strlen(arg); - char *copy = malloc(sz+1); - if (!copy) { - perror("malloc error"); - exit (EXIT_FAILURE); - } - gp_params.hook_script = strcpy(copy, arg); - /* Run init hook */ - if (0!=gp_params_run_hook(&gp_params, "init", NULL)) { - fprintf(stderr, - "Hook script \"%s\" init failed. Aborting.\n", - gp_params.hook_script); - exit(3); - } - } while (0); - break; - - case ARG_STDOUT: - gp_params.flags |= FLAGS_QUIET | FLAGS_STDOUT; - break; - case ARG_STDOUT_SIZE: - gp_params.flags |= FLAGS_QUIET | FLAGS_STDOUT - | FLAGS_STDOUT_SIZE; - break; - case ARG_VERSION: params->p.r = print_version_action (&gp_params); printf( "%-16s%-15s%s\n", "mongoose", MG_VERSION, "webserver" ); @@ -1656,12 +1583,6 @@ cb_arg_run (poptContext __unused__ ctx, case ARG_LIST_CAMERAS: params->p.r = list_cameras_action (&gp_params); break; - case ARG_LIST_FILES: - params->p.r = for_each_folder (&gp_params, list_files_action); - break; - case ARG_LIST_FOLDERS: - params->p.r = for_each_folder (&gp_params, list_folders_action); - break; case ARG_LIST_PORTS: params->p.r = list_ports_action (&gp_params); break; @@ -1700,24 +1621,6 @@ cb_arg_run (poptContext __unused__ ctx, gp_port_free (port); break; } - case ARG_MANUAL: - params->p.r = action_camera_manual (&gp_params); - break; - case ARG_RMDIR: - dissolve_filename (gp_params.folder, arg, &newfolder, &newfilename); - params->p.r = gp_camera_folder_remove_dir (gp_params.camera, - newfolder, newfilename, gp_params.context); - free (newfolder); free (newfilename); - break; - case ARG_NUM_FILES: - params->p.r = num_files_action (&gp_params); - break; - case ARG_MKDIR: - dissolve_filename (gp_params.folder, arg, &newfolder, &newfilename); - params->p.r = gp_camera_folder_make_dir (gp_params.camera, - newfolder, newfilename, gp_params.context); - free (newfolder); free (newfilename); - break; case ARG_SERVER_URL: strncpy( webcfg.server_url, arg, WEBCFG_STR_LEN); webcfg.server_url[WEBCFG_STR_LEN] = 0; @@ -1952,11 +1855,6 @@ main (int argc, char **argv, char **envp) N_("Name of file to write debug info to"), N_("FILENAME")}, {"quiet", 'q', POPT_ARG_NONE, NULL, ARG_QUIET, N_("Quiet output (default=verbose)"), NULL}, - {"parsable", '\0', POPT_ARG_NONE, NULL, ARG_PARSABLE, - N_("Simple parsable output (implies quiet)"), NULL}, - {"hook-script", '\0', POPT_ARG_STRING, NULL, ARG_HOOK_SCRIPT, - N_("Hook script to call after downloads, captures, etc."), - N_("FILENAME")}, POPT_TABLEEND }; const struct poptOption cameraOptions[] = { @@ -2018,48 +1916,13 @@ main (int argc, char **argv, char **envp) N_("Reset capture interval on signal (default=no)"), NULL}, POPT_TABLEEND }; - const struct poptOption fileOptions[] = { - GPHOTO2_POPT_CALLBACK - {"list-folders", 'l', POPT_ARG_NONE, NULL, ARG_LIST_FOLDERS, - N_("List folders in folder"), NULL}, - {"list-files", 'L', POPT_ARG_NONE, NULL, ARG_LIST_FILES, - N_("List files in folder"), NULL}, - {"mkdir", 'm', POPT_ARG_STRING, NULL, ARG_MKDIR, - N_("Create a directory"), N_("DIRNAME")}, - {"rmdir", 'r', POPT_ARG_STRING, NULL, ARG_RMDIR, - N_("Remove a directory"), N_("DIRNAME")}, - {"num-files", 'n', POPT_ARG_NONE, NULL, ARG_NUM_FILES, - N_("Display number of files"), NULL}, - {"filename", '\0', POPT_ARG_STRING, NULL, ARG_FILENAME, - N_("Specify a filename or filename pattern"), N_("FILENAME_PATTERN")}, - {"filenumber", '\0', POPT_ARG_INT, NULL, ARG_FILENUMBER, - N_("Specify the number a filename %%n will starts with (default 1)"), N_("NUMBER")}, - {"folder", 'f', POPT_ARG_STRING, NULL, ARG_FOLDER, - N_("Specify camera folder (default=\"/\")"), N_("FOLDER")}, - {"recurse", 'R', POPT_ARG_NONE, NULL, ARG_RECURSE, - N_("Recursion (default for download)"), NULL}, - {"no-recurse", '\0', POPT_ARG_NONE, NULL, ARG_NO_RECURSE, - N_("No recursion (default for deletion)"), NULL}, - {"new", '\0', POPT_ARG_NONE, NULL, ARG_NEW, - N_("Process new files only"), NULL}, - {"force-overwrite", '\0', POPT_ARG_NONE, NULL, - ARG_FORCE_OVERWRITE, N_("Overwrite files without asking"), NULL}, - {"skip-existing", '\0', POPT_ARG_NONE, NULL, - ARG_SKIP_EXISTING, N_("Skip existing files"), NULL}, - POPT_TABLEEND - }; + const struct poptOption miscOptions[] = { GPHOTO2_POPT_CALLBACK - {"stdout", '\0', POPT_ARG_NONE, NULL, ARG_STDOUT, - N_("Send file to stdout"), NULL}, - {"stdout-size", '\0', POPT_ARG_NONE, NULL, ARG_STDOUT_SIZE, - N_("Print filesize before data"), NULL}, {"show-info", '\0', POPT_ARG_STRING, NULL, ARG_SHOW_INFO, N_("Show image information, like width, height, and capture time"), NULL}, {"summary", '\0', POPT_ARG_NONE, NULL, ARG_SUMMARY, N_("Show camera summary"), NULL}, - {"manual", '\0', POPT_ARG_NONE, NULL, ARG_MANUAL, - N_("Show camera driver manual"), NULL}, {"about", '\0', POPT_ARG_NONE, NULL, ARG_ABOUT, N_("About the camera driver manual"), NULL}, {"storage-info", '\0', POPT_ARG_NONE, NULL, ARG_STORAGE_INFO, @@ -2070,6 +1933,7 @@ main (int argc, char **argv, char **envp) N_("Server URL - e.g http://0.0.0.0:8866"), NULL}, POPT_TABLEEND }; + const struct poptOption options[] = { GPHOTO2_POPT_CALLBACK {NULL, '\0', POPT_ARG_INCLUDE_TABLE, (void *) &generalOptions, 0, @@ -2084,8 +1948,6 @@ main (int argc, char **argv, char **envp) N_("Camera and software configuration"), NULL}, {NULL, '\0', POPT_ARG_INCLUDE_TABLE, (void *) &captureOptions, 0, N_("Capture an image from or on the camera"), NULL}, - {NULL, '\0', POPT_ARG_INCLUDE_TABLE, (void *) &fileOptions, 0, - N_("Downloading, uploading and manipulating files"), NULL}, POPT_TABLEEND }; CameraAbilities a; @@ -2219,13 +2081,7 @@ main (int argc, char **argv, char **envp) CHECK_OPT (ARG_SHOW_PREVIEW); CHECK_OPT (ARG_CONFIG); CHECK_OPT (ARG_GET_CONFIG); - CHECK_OPT (ARG_LIST_FILES); - CHECK_OPT (ARG_LIST_FOLDERS); - CHECK_OPT (ARG_MANUAL); - CHECK_OPT (ARG_MKDIR); - CHECK_OPT (ARG_NUM_FILES); CHECK_OPT (ARG_RESET); - CHECK_OPT (ARG_RMDIR); CHECK_OPT (ARG_SET_CONFIG); CHECK_OPT (ARG_SET_CONFIG_INDEX); CHECK_OPT (ARG_SET_CONFIG_VALUE); @@ -2378,46 +2234,9 @@ main (int argc, char **argv, char **envp) } gp_list_free (list); } - - /* - * Recursion is too dangerous for deletion. Only turn it on if - * explicitely specified. - */ - /* - cb_params.type = CALLBACK_PARAMS_TYPE_QUERY; - cb_params.p.q.found = 0; - cb_params.p.q.arg = ARG_DELETE_FILE; - poptResetContext (ctx); - while (poptGetNextOpt (ctx) >= 0); - if (!cb_params.p.q.found) { - cb_params.p.q.arg = ARG_DELETE_ALL_FILES; - poptResetContext (ctx); - while (poptGetNextOpt (ctx) >= 0); - } - */ - - if (cb_params.p.q.found) { - cb_params.p.q.found = 0; - cb_params.p.q.arg = ARG_RECURSE; - poptResetContext (ctx); - while (poptGetNextOpt (ctx) >= 0); - if (!cb_params.p.q.found) - gp_params.flags &= ~FLAGS_RECURSE; - } - - signal (SIGINT, signal_exit); - signal (SIGTERM, signal_exit); - - /* If we are told to be quiet, be so. * - cb_params.type = CALLBACK_PARAMS_TYPE_QUERY; - cb_params.p.q.found = 0; - cb_params.p.q.arg = ARG_QUIET; - poptResetContext (ctx); - while (poptGetNextOpt (ctx) >= 0); - if (cb_params.p.q.found) { - gp_params.flags |= FLAGS_QUIET; - } - */ + + signal (SIGINT, signal_exit); + signal (SIGTERM, signal_exit); /* Run startup hook */ gp_params_run_hook(&gp_params, "start", NULL); From 84d7b40ecda1c122499f8d334ce946f55d96cee2 Mon Sep 17 00:00:00 2001 From: Thorsten Ludewig Date: Sat, 18 Jun 2022 17:48:01 +0200 Subject: [PATCH 18/97] exif name to lower --- gphoto2/actions.c | 6 +++++- gphoto2/gphoto2-webapi.c | 2 +- gphoto2/gphoto2-webapi.h | 1 + 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/gphoto2/actions.c b/gphoto2/actions.c index f8ec9e86..260450f8 100644 --- a/gphoto2/actions.c +++ b/gphoto2/actions.c @@ -492,6 +492,8 @@ delete_file_action (GPParams *p, const char *folder, const char *filename) #ifdef HAVE_LIBEXIF #ifdef WEBAPI +size_t strncpy_lower(char *dst, const char *src, size_t count); + static void show_ifd(struct mg_connection *c, ExifContent *content) #else @@ -512,7 +514,9 @@ show_ifd(ExifContent *content) printf("%-59.59s", exif_entry_get_value(e, b, sizeof(b))); printf("\n"); #else - JSON_PRINTF( c, "\"%s\":\"%s\",", exif_tag_get_name(e->tag), exif_entry_get_value(e, b, sizeof(b))); + char bn[128]; + strncpy_lower(bn, exif_tag_get_name(e->tag), 127 ); + JSON_PRINTF( c, "\"%s\":\"%s\",", bn, exif_entry_get_value(e, b, sizeof(b))); #endif } } diff --git a/gphoto2/gphoto2-webapi.c b/gphoto2/gphoto2-webapi.c index a03286f8..20446e32 100644 --- a/gphoto2/gphoto2-webapi.c +++ b/gphoto2/gphoto2-webapi.c @@ -118,7 +118,7 @@ volatile int end_next = 0; * multibyte encoding safe. */ -static size_t +size_t strncpy_lower(char *dst, const char *src, size_t count) { unsigned int i; diff --git a/gphoto2/gphoto2-webapi.h b/gphoto2/gphoto2-webapi.h index 285e737d..ee3e9fea 100644 --- a/gphoto2/gphoto2-webapi.h +++ b/gphoto2/gphoto2-webapi.h @@ -38,6 +38,7 @@ void cli_error_print(char *format, ...); void dissolve_filename ( const char *folder, const char *filename, char **newfolder, char **newfilename ); +size_t strncpy_lower(char *dst, const char *src, size_t count); int camera_file_exists (Camera *camera, GPContext *context, const char *folder, const char *filename, From 9b7c75c93f3cdee70948f913ebcb3d2890de2413 Mon Sep 17 00:00:00 2001 From: Thorsten Ludewig Date: Sat, 18 Jun 2022 20:30:53 +0200 Subject: [PATCH 19/97] remove webapi flag --- gphoto2/gp-params.h | 1 - 1 file changed, 1 deletion(-) diff --git a/gphoto2/gp-params.h b/gphoto2/gp-params.h index 41f6536b..3339efe2 100644 --- a/gphoto2/gp-params.h +++ b/gphoto2/gp-params.h @@ -38,7 +38,6 @@ typedef enum { FLAGS_KEEP_RAW = 1 << 9, FLAGS_SKIP_EXISTING = 1 << 10, FLAGS_PARSABLE = 1 << 11, - FLAGS_WEBAPI = 1 << 12, } Flags; typedef enum { From aa782c83cc25116efb27b57b8f976b7583b9e129 Mon Sep 17 00:00:00 2001 From: Thorsten Ludewig Date: Sat, 18 Jun 2022 20:31:33 +0200 Subject: [PATCH 20/97] use webapi flag as parameter --- gphoto2/gphoto2-webapi.c | 48 ++++++++++++++++++++++++---------------- gphoto2/gphoto2-webapi.h | 3 +-- 2 files changed, 30 insertions(+), 21 deletions(-) diff --git a/gphoto2/gphoto2-webapi.c b/gphoto2/gphoto2-webapi.c index 20446e32..bd5c4c89 100644 --- a/gphoto2/gphoto2-webapi.c +++ b/gphoto2/gphoto2-webapi.c @@ -584,7 +584,7 @@ static CameraFileHandler xhandler = { x_size, x_read, x_write }; int save_file_to_file (struct mg_connection *c, Camera *camera, GPContext *context, Flags flags, const char *folder, const char *filename, - CameraFileType type) + CameraFileType type, int webapi ) { int fd, res; CameraFile *file; @@ -698,7 +698,7 @@ save_file_to_file (struct mg_connection *c, Camera *camera, GPContext *context, return (GP_OK); } - if (flags & FLAGS_WEBAPI) { + if (webapi) { const char *data; unsigned long int size; @@ -752,36 +752,45 @@ dissolve_filename ( #endif } - -int -get_file_http_common (struct mg_connection *c, const char *path, CameraFileType type ) +int +get_file_http_common(struct mg_connection *c, const char *path, CameraFileType type) { - gp_log (GP_LOG_DEBUG, "gphoto2-webapi", "Getting '%s'...", path ); + gp_log(GP_LOG_DEBUG, "gphoto2-webapi", "Getting '%s'...", path); gp_params.download_type = type; - gp_params.flags |= FLAGS_WEBAPI; int ret; + char *buffer; char *newfolder, *newfilename; - dissolve_filename (gp_params.folder, path, &newfolder, &newfilename); + buffer = strdup(path); + char *lr = strrchr(buffer, '/'); + *lr = 0; + newfolder = buffer; + newfilename = lr + 1; - CameraFileInfo info; - CR (gp_camera_file_get_info (gp_params.camera, newfolder, newfilename, &info, gp_params.context)); + if (strlen(newfilename) > 0) + { + CameraFileInfo info; + CR(gp_camera_file_get_info(gp_params.camera, newfolder, newfilename, &info, gp_params.context)); - const char *http_header = "HTTP/1.1 200 OK\r\n" - "Content-Type: %s\r\n" - "Content-Length: %ld\r\n\r\n"; + const char *http_header = "HTTP/1.1 200 OK\r\n" + "Content-Type: %s\r\n" + "Content-Length: %ld\r\n\r\n"; - mg_printf( c, http_header, info.file.type, info.file.size); + mg_printf(c, http_header, info.file.type, info.file.size); - ret = save_file_to_file (c, gp_params.camera, gp_params.context, gp_params.flags, - newfolder, newfilename, type); - free (newfolder); free (newfilename); + ret = save_file_to_file(c, gp_params.camera, gp_params.context, gp_params.flags, + newfolder, newfilename, type, TRUE ); + } + else + { + ret = -1; + } + free(buffer); return ret; } - /*! \brief parse range, download specified files, or their * thumbnails according to thumbnail argument, and save to files. */ @@ -810,7 +819,7 @@ get_file_common (struct mg_connection *c, const char *arg, CameraFileType type ) dissolve_filename (gp_params.folder, arg, &newfolder, &newfilename); ret = save_file_to_file ( c, gp_params.camera, gp_params.context, gp_params.flags, - newfolder, newfilename, type); + newfolder, newfilename, type, FALSE); free (newfolder); free (newfilename); return ret; } @@ -900,6 +909,7 @@ save_captured_file (struct mg_connection *c, CameraFilePath *path, int download) JSON_PRINTF( c, "\"image_info\":{" ); JSON_PRINTF( c, "\"name\": \"%s\",", path->name ); JSON_PRINTF( c, "\"folder\": \"%s\",", path->folder ); + JSON_PRINTF( c, "\"path\": \"%s%s%s\",", path->folder, pathsep, path->name ); JSON_PRINTF( c, "\"mtime\":%ld,", info.file.mtime ); JSON_PRINTF( c, "\"size\":%ld,", info.file.size ); JSON_PRINTF( c, "\"height\":%d,", info.file.height ); diff --git a/gphoto2/gphoto2-webapi.h b/gphoto2/gphoto2-webapi.h index ee3e9fea..59b7700f 100644 --- a/gphoto2/gphoto2-webapi.h +++ b/gphoto2/gphoto2-webapi.h @@ -44,8 +44,7 @@ int camera_file_exists (Camera *camera, GPContext *context, const char *folder, const char *filename, CameraFileType type); int save_file_to_file (struct mg_connection *c, Camera *camera, GPContext *context, Flags flags, - const char *folder, const char *filename, - CameraFileType type); + const char *folder, const char *filename, CameraFileType type, int webapi); int save_camera_file_to_file (const char *folder, const char *fn, CameraFileType type, CameraFile *file, const char *tmpname); int capture_generic (struct mg_connection *c, CameraCaptureType type, const char *name, int download); int get_file_common (struct mg_connection *c, const char *arg, CameraFileType type ); From 81dea6eedd22b6fac9e5e05ba067e6f569ec8f2a Mon Sep 17 00:00:00 2001 From: Thorsten Ludewig Date: Sat, 18 Jun 2022 20:31:58 +0200 Subject: [PATCH 21/97] own init of path and name --- gphoto2/server.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/gphoto2/server.c b/gphoto2/server.c index 82904aa2..a5de1fd6 100644 --- a/gphoto2/server.c +++ b/gphoto2/server.c @@ -217,12 +217,22 @@ fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) buffer[MIN((int)hm->uri.len,255)] = 0; char *path = buffer+13; - dissolve_filename (p->folder, path, &newfolder, &newfilename); + char *lr = strrchr( path, '/' ); + *lr=0; + newfolder = path; + newfilename = lr+1; + MG_HTTP_CHUNK_START; - mg_http_printf_chunk(c, "{" ); - mg_http_printf_chunk(c, "\"return_code\":%d}\n", print_exif_action (c, p, newfolder, newfilename)); + if ( strlen(newfilename) > 0 ) + { + mg_http_printf_chunk(c, "{" ); + mg_http_printf_chunk(c, "\"return_code\":%d}\n", print_exif_action (c, p, newfolder, newfilename)); + } + else + { + mg_http_printf_chunk(c, "{ \"return_code\": -1 }\n"); + } MG_HTTP_CHUNK_END; - free (newfolder); free (newfilename); } } #endif From 83b2eff943c95e7240e96918444f5c07c4a0c828 Mon Sep 17 00:00:00 2001 From: Thorsten Ludewig Date: Sat, 18 Jun 2022 20:35:58 +0200 Subject: [PATCH 22/97] change api to match description --- README_WEBAPI_SERVER.md | 2 +- gphoto2/server.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README_WEBAPI_SERVER.md b/README_WEBAPI_SERVER.md index cbac1d08..5e512735 100644 --- a/README_WEBAPI_SERVER.md +++ b/README_WEBAPI_SERVER.md @@ -48,7 +48,7 @@ All responses are [JSON](https://json.org) formated. list available config names -- `http://:8866/api/config/list/all` +- `http://:8866/api/config/list-all` list config including current and available config values diff --git a/gphoto2/server.c b/gphoto2/server.c index a5de1fd6..4d300d11 100644 --- a/gphoto2/server.c +++ b/gphoto2/server.c @@ -139,7 +139,7 @@ fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) MG_HTTP_CHUNK_END; } - else if (mg_http_match_uri(hm, "/api/list-config")) + else if (mg_http_match_uri(hm, "/api/config/list")) { MG_HTTP_CHUNK_START; mg_http_printf_chunk(c, "{" ); @@ -147,7 +147,7 @@ fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) MG_HTTP_CHUNK_END; } - else if (mg_http_match_uri(hm, "/api/list-all-config")) + else if (mg_http_match_uri(hm, "/api/config/list-all")) { MG_HTTP_CHUNK_START; mg_http_printf_chunk(c, "{" ); From f322505bc32d258dee844d135668d9e6529b78b7 Mon Sep 17 00:00:00 2001 From: Thorsten Ludewig Date: Sat, 18 Jun 2022 21:07:34 +0200 Subject: [PATCH 23/97] additional check for to short param --- gphoto2/gphoto2-webapi.c | 314 +++++++++++++++++++-------------------- gphoto2/server.c | 11 +- 2 files changed, 159 insertions(+), 166 deletions(-) diff --git a/gphoto2/gphoto2-webapi.c b/gphoto2/gphoto2-webapi.c index bd5c4c89..b394c813 100644 --- a/gphoto2/gphoto2-webapi.c +++ b/gphoto2/gphoto2-webapi.c @@ -280,7 +280,6 @@ get_path_for_file (const char *folder, const char *name, CameraFileType type, Ca break; case 'f': - /* Get the file name without suffix */ s = strrchr (name, '.'); if (!s) @@ -376,84 +375,94 @@ get_path_for_file (const char *folder, const char *name, CameraFileType type, Ca return (GP_OK); } - -int -save_camera_file_to_file ( - const char *folder, const char *name, CameraFileType type, CameraFile *file, const char *curname -) { +int save_camera_file_to_file( + const char *folder, const char *name, CameraFileType type, CameraFile *file, const char *curname) +{ char *path = NULL, s[1024], c[1024]; int res; time_t mtime; struct utimbuf u; - CR (get_path_for_file (folder, name, type, file, &path)); - strncpy (s, path, sizeof (s) - 1); - s[sizeof (s) - 1] = '\0'; - free (path); + CR(get_path_for_file(folder, name, type, file, &path)); + strncpy(s, path, sizeof(s) - 1); + s[sizeof(s) - 1] = '\0'; + free(path); path = NULL; - if ((gp_params.flags & FLAGS_SKIP_EXISTING) && gp_system_is_file (s)) { - if ((gp_params.flags & FLAGS_QUIET) == 0) { - printf (_("Skip existing file %s\n"), s); - fflush (stdout); + if ((gp_params.flags & FLAGS_SKIP_EXISTING) && gp_system_is_file(s)) + { + if ((gp_params.flags & FLAGS_QUIET) == 0) + { + printf(_("Skip existing file %s\n"), s); + fflush(stdout); } if (curname) - unlink (curname); + unlink(curname); return (GP_OK); } - if ((gp_params.flags & FLAGS_QUIET) == 0) { - while ((gp_params.flags & FLAGS_FORCE_OVERWRITE) == 0 && - gp_system_is_file (s)) { - do { - putchar ('\007'); - printf (_("File %s exists. Overwrite? [y|n] "), - s); - fflush (stdout); - if (NULL == fgets (c, sizeof (c) - 1, stdin)) + if ((gp_params.flags & FLAGS_QUIET) == 0) + { + while ((gp_params.flags & FLAGS_FORCE_OVERWRITE) == 0 && + gp_system_is_file(s)) + { + do + { + putchar('\007'); + printf(_("File %s exists. Overwrite? [y|n] "), + s); + fflush(stdout); + if (NULL == fgets(c, sizeof(c) - 1, stdin)) return GP_ERROR; - } while ((c[0]!='y')&&(c[0]!='Y')&& - (c[0]!='n')&&(c[0]!='N')); + } while ((c[0] != 'y') && (c[0] != 'Y') && + (c[0] != 'n') && (c[0] != 'N')); - if ((c[0]=='y') || (c[0]=='Y')) + if ((c[0] == 'y') || (c[0] == 'Y')) break; - do { - printf (_("Specify new filename? [y|n] ")); - fflush (stdout); - if (NULL == fgets (c, sizeof (c) - 1, stdin)) + do + { + printf(_("Specify new filename? [y|n] ")); + fflush(stdout); + if (NULL == fgets(c, sizeof(c) - 1, stdin)) return GP_ERROR; - } while ((c[0]!='y')&&(c[0]!='Y')&& - (c[0]!='n')&&(c[0]!='N')); + } while ((c[0] != 'y') && (c[0] != 'Y') && + (c[0] != 'n') && (c[0] != 'N')); - if (!((c[0]=='y') || (c[0]=='Y'))) { - if (curname) unlink (curname); + if (!((c[0] == 'y') || (c[0] == 'Y'))) + { + if (curname) + unlink(curname); return (GP_OK); } - printf (_("Enter new filename: ")); - fflush (stdout); - if (NULL == fgets (s, sizeof (s) - 1, stdin)) + printf(_("Enter new filename: ")); + fflush(stdout); + if (NULL == fgets(s, sizeof(s) - 1, stdin)) return GP_ERROR; - s[strlen (s) - 1] = 0; - } - printf (_("Saving file as %s\n"), s); - fflush (stdout); - } + s[strlen(s) - 1] = 0; + } + printf(_("Saving file as %s\n"), s); + fflush(stdout); + } path = s; - while ((path = strchr (path, gp_system_dir_delim))){ + while ((path = strchr(path, gp_system_dir_delim))) + { *path = '\0'; - if(!gp_system_is_dir (s)) - gp_system_mkdir (s); + if (!gp_system_is_dir(s)) + gp_system_mkdir(s); *path++ = gp_system_dir_delim; } - if (curname) { + if (curname) + { int x; unlink(s); - if (-1 == rename (curname, s)) { + if (-1 == rename(curname, s)) + { /* happens if the user specified a absolute path with --filename */ /* EPERM happens on windows, see https://github.com/gphoto/libgphoto2/issues/97 */ - if ((errno == EXDEV) || (errno == EPERM)) { + if ((errno == EXDEV) || (errno == EPERM)) + { char buf[8192]; int in_fd, out_fd; @@ -465,10 +474,13 @@ save_camera_file_to_file ( if (out_fd < 0) perror("Can't open file for writing"); - while (1) { + while (1) + { ssize_t result = read(in_fd, buf, sizeof(buf)); - if (!result) break; - if (-1 == write(out_fd, buf, result)) { + if (!result) + break; + if (-1 == write(out_fd, buf, result)) + { perror("write"); break; } @@ -476,31 +488,33 @@ save_camera_file_to_file ( close(out_fd); close(in_fd); unlink(curname); - } else + } + else perror("rename"); } x = umask(0022); /* get umask */ - umask(x);/* set it back to the old value */ - chmod(s,0666 & ~x); + umask(x); /* set it back to the old value */ + chmod(s, 0666 & ~x); + } + res = gp_file_get_mtime(file, &mtime); + if ((res == GP_OK) && (mtime)) + { + u.actime = mtime; + u.modtime = mtime; + utime(s, &u); } - res = gp_file_get_mtime (file, &mtime); - if ((res == GP_OK) && (mtime)) { - u.actime = mtime; - u.modtime = mtime; - utime (s, &u); - } gp_params_run_hook(&gp_params, "download", s); return (GP_OK); } -int -camera_file_exists (Camera *camera, GPContext *context, const char *folder, - const char *filename, CameraFileType type) +int camera_file_exists(Camera *camera, GPContext *context, const char *folder, + const char *filename, CameraFileType type) { CameraFileInfo info; - CR (gp_camera_file_get_info (camera, folder, filename, &info, - context)); - switch (type) { + CR(gp_camera_file_get_info(camera, folder, filename, &info, + context)); + switch (type) + { case GP_FILE_TYPE_METADATA: return TRUE; case GP_FILE_TYPE_AUDIO: @@ -511,51 +525,60 @@ camera_file_exists (Camera *camera, GPContext *context, const char *folder, case GP_FILE_TYPE_NORMAL: return (info.file.fields != 0); default: - gp_context_error (context, "Unknown file type in camera_file_exists: %d", type); + gp_context_error(context, "Unknown file type in camera_file_exists: %d", type); return FALSE; } } -struct privstr { +struct privstr +{ int fd; }; -static int x_size(void*priv,uint64_t *size) { +static int x_size(void *priv, uint64_t *size) +{ struct privstr *ps = priv; int fd = ps->fd; off_t res; - gp_log (GP_LOG_DEBUG, "x_size","(%p,%u)", priv, (unsigned int)*size); - res = lseek (fd, 0, SEEK_END); - if (res == -1) { - perror ("x_size: lseek SEEK_END"); + gp_log(GP_LOG_DEBUG, "x_size", "(%p,%u)", priv, (unsigned int)*size); + res = lseek(fd, 0, SEEK_END); + if (res == -1) + { + perror("x_size: lseek SEEK_END"); return GP_ERROR_IO; } - res = lseek (fd, 0, SEEK_CUR); - if (res == -1) { - perror ("x_size: lseek SEEK_CUR"); + res = lseek(fd, 0, SEEK_CUR); + if (res == -1) + { + perror("x_size: lseek SEEK_CUR"); return GP_ERROR_IO; } *size = res; - res = lseek (fd, 0, SEEK_SET); - if (res == -1) { - perror ("x_size: lseek SEEK_SET"); + res = lseek(fd, 0, SEEK_SET); + if (res == -1) + { + perror("x_size: lseek SEEK_SET"); return GP_ERROR_IO; } return GP_OK; } -static int x_read(void*priv,unsigned char *data, uint64_t *size) { - struct privstr *ps = priv; - int fd = ps->fd; - uint64_t curread = 0, xsize, res; +static int x_read(void *priv, unsigned char *data, uint64_t *size) +{ + struct privstr *ps = priv; + int fd = ps->fd; + uint64_t curread = 0, xsize, res; - gp_log (GP_LOG_DEBUG, "x_read", "(%p,%p,%u)", priv, data, (unsigned int)*size); + gp_log(GP_LOG_DEBUG, "x_read", "(%p,%p,%u)", priv, data, (unsigned int)*size); xsize = *size; - while (curread < xsize) { - res = read (fd, data+curread, xsize-curread); - if (res == -1) return GP_ERROR_IO_READ; - if (!res) break; + while (curread < xsize) + { + res = read(fd, data + curread, xsize - curread); + if (res == -1) + return GP_ERROR_IO_READ; + if (!res) + break; curread += res; } *size = curread; @@ -725,28 +748,29 @@ save_file_to_file (struct mg_connection *c, Camera *camera, GPContext *context, return (res); } -void -dissolve_filename ( - const char *folder, const char *filename, - char **newfolder, char **newfilename -) { +void dissolve_filename( + const char *folder, const char *filename, + char **newfolder, char **newfilename) +{ char *nfolder, *s; - s = strrchr (filename, '/'); - if (!s) { - *newfolder = strdup (folder); - *newfilename = strdup (filename); + s = strrchr(filename, '/'); + if (!s) + { + *newfolder = strdup(folder); + *newfilename = strdup(filename); return; } while (filename[0] == '/') filename++; - nfolder = malloc (strlen (folder) + 1 + (s-filename) + 1); - strcpy (nfolder, folder); - if (strcmp (nfolder, "/")) strcat (nfolder, "/"); /* if its not the root directory, append / */ - memcpy (nfolder+strlen(nfolder), filename, (s-filename)); - nfolder[strlen (folder) + 1 + (s-filename)-1] = '\0'; - *newfolder = nfolder; - *newfilename = strdup (s+1); + nfolder = malloc(strlen(folder) + 1 + (s - filename) + 1); + strcpy(nfolder, folder); + if (strcmp(nfolder, "/")) + strcat(nfolder, "/"); /* if its not the root directory, append / */ + memcpy(nfolder + strlen(nfolder), filename, (s - filename)); + nfolder[strlen(folder) + 1 + (s - filename) - 1] = '\0'; + *newfolder = nfolder; + *newfilename = strdup(s + 1); #if 0 fprintf (stderr, "%s - %s dissolved to %s - %s\n", folder, filename, *newfolder, *newfilename); #endif @@ -759,33 +783,32 @@ get_file_http_common(struct mg_connection *c, const char *path, CameraFileType t gp_params.download_type = type; - int ret; + int ret = -1; char *buffer; char *newfolder, *newfilename; buffer = strdup(path); char *lr = strrchr(buffer, '/'); - *lr = 0; - newfolder = buffer; - newfilename = lr + 1; - - if (strlen(newfilename) > 0) + if (lr != NULL) { - CameraFileInfo info; - CR(gp_camera_file_get_info(gp_params.camera, newfolder, newfilename, &info, gp_params.context)); + *lr = 0; + newfolder = buffer; + newfilename = lr + 1; - const char *http_header = "HTTP/1.1 200 OK\r\n" - "Content-Type: %s\r\n" - "Content-Length: %ld\r\n\r\n"; + if (strlen(newfilename) > 0) + { + CameraFileInfo info; + CR(gp_camera_file_get_info(gp_params.camera, newfolder, newfilename, &info, gp_params.context)); - mg_printf(c, http_header, info.file.type, info.file.size); + const char *http_header = "HTTP/1.1 200 OK\r\n" + "Content-Type: %s\r\n" + "Content-Length: %ld\r\n\r\n"; - ret = save_file_to_file(c, gp_params.camera, gp_params.context, gp_params.flags, - newfolder, newfilename, type, TRUE ); - } - else - { - ret = -1; + mg_printf(c, http_header, info.file.type, info.file.size); + + ret = save_file_to_file(c, gp_params.camera, gp_params.context, gp_params.flags, + newfolder, newfilename, type, TRUE); + } } free(buffer); return ret; @@ -1808,41 +1831,10 @@ report_failure (int result, int argc, char **argv) } \ } while (0) - #define GPHOTO2_POPT_CALLBACK \ {NULL, '\0', POPT_ARG_CALLBACK, \ (void *) &cb_arg, 0, (char *) &cb_params, NULL}, -/*! main function: parse command line arguments and call actions - * - * Perhaps we should use the following code for parsing command line - * options: - - poptGetContext(NULL, argc, argv, poptOptions, 0); - while ((rc = poptGetNextOpt(poptcon)) > 0) { - switch (rc) { - ARG_FOO: - printf("foo = %s\n", poptGetOptArg(poptcon)); - break; - } - } - poptFreeContext(poptcon); - * - * Regardless of whether we do this or not, we should get rid of those - * legions of poptResetContext() calls followed by lots of - * poptGetNextOpt() calls. - * - * At least we should get rid of all those stages. Probably two stages - * are sufficient: - * -# look for --help, --debug, --debug-logfile, --quiet - * -# repeat this until command line has been used up - * -# go through all command line options - * -# ignore those from above - * -# if setting for command, store its value - * -# if command, execute command - */ - - int main (int argc, char **argv, char **envp) { @@ -1851,6 +1843,7 @@ main (int argc, char **argv, char **envp) int i, help_option_given = 0; int usage_option_given = 0; char *debug_logfile_name = NULL, *debug_loglevel = NULL; + const struct poptOption generalOptions[] = { GPHOTO2_POPT_CALLBACK {"help", '?', POPT_ARG_NONE, (void *) &help_option_given, ARG_HELP, @@ -1867,6 +1860,7 @@ main (int argc, char **argv, char **envp) N_("Quiet output (default=verbose)"), NULL}, POPT_TABLEEND }; + const struct poptOption cameraOptions[] = { GPHOTO2_POPT_CALLBACK {"port", '\0', POPT_ARG_STRING, NULL, ARG_PORT, @@ -1879,6 +1873,7 @@ main (int argc, char **argv, char **envp) N_("(expert only) Override USB IDs"), N_("USBIDs")}, POPT_TABLEEND }; + const struct poptOption infoOptions[] = { GPHOTO2_POPT_CALLBACK {"version", 'v', POPT_ARG_NONE, NULL, ARG_VERSION, @@ -1891,6 +1886,7 @@ main (int argc, char **argv, char **envp) N_("Display the camera/driver abilities in the libgphoto2 database"), NULL}, POPT_TABLEEND }; + const struct poptOption configOptions[] = { GPHOTO2_POPT_CALLBACK #ifdef HAVE_CDK @@ -1960,6 +1956,7 @@ main (int argc, char **argv, char **envp) N_("Capture an image from or on the camera"), NULL}, POPT_TABLEEND }; + CameraAbilities a; GPPortInfo info; GPPortType type; @@ -2102,6 +2099,7 @@ main (int argc, char **argv, char **envp) CHECK_OPT (ARG_SUMMARY); CHECK_OPT (ARG_WAIT_EVENT); gp_port_info_get_type (info, &type); + if (cb_params.p.q.found && (!strcmp (a.model, "") || (type == GP_PORT_NONE))) { int count; @@ -2269,11 +2267,3 @@ main (int argc, char **argv, char **envp) poptFreeContext(ctx); return EXIT_SUCCESS; } - - -/* - * Local Variables: - * c-file-style:"linux" - * indent-tabs-mode:t - * End: - */ diff --git a/gphoto2/server.c b/gphoto2/server.c index 4d300d11..ed14c578 100644 --- a/gphoto2/server.c +++ b/gphoto2/server.c @@ -218,12 +218,15 @@ fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) char *path = buffer+13; char *lr = strrchr( path, '/' ); - *lr=0; - newfolder = path; - newfilename = lr+1; + if ( lr != NULL ) + { + *lr=0; + newfolder = path; + newfilename = lr+1; + } MG_HTTP_CHUNK_START; - if ( strlen(newfilename) > 0 ) + if ( newfilename != NULL && strlen(newfilename) > 0 ) { mg_http_printf_chunk(c, "{" ); mg_http_printf_chunk(c, "\"return_code\":%d}\n", print_exif_action (c, p, newfolder, newfilename)); From 0209958ef097ee71ae5160191b6b00ab3f0ffecf Mon Sep 17 00:00:00 2001 From: Thorsten Ludewig Date: Sun, 19 Jun 2022 10:28:17 +0200 Subject: [PATCH 24/97] list-files added to webapi --- README_WEBAPI_SERVER.md | 17 +++ gphoto2/server.c | 250 ++++++++++++++++++++++++++-------------- 2 files changed, 179 insertions(+), 88 deletions(-) diff --git a/README_WEBAPI_SERVER.md b/README_WEBAPI_SERVER.md index 5e512735..355e116c 100644 --- a/README_WEBAPI_SERVER.md +++ b/README_WEBAPI_SERVER.md @@ -104,6 +104,23 @@ response - native file - `http://:8866/api/get-exif/` +- `http://:8866/api/list-files/` + +response + +```jsonc +{ + path: "/store_00020001/DCIM/", + files: [ + { + name: "100CANON", + isFolder: true + } + ], + entries: 1, + return_code: 0 +} +``` ## Mongoose HTTP Server - LICENSE diff --git a/gphoto2/server.c b/gphoto2/server.c index ed14c578..79b34311 100644 --- a/gphoto2/server.c +++ b/gphoto2/server.c @@ -41,10 +41,32 @@ #include #endif -#define CHECK_NULL(x) { if (x == NULL) { return(-1); } } +#define CHECK_NULL(x) \ + { \ + if (x == NULL) \ + { \ + return (-1); \ + } \ + } + +#define CHECK(result) \ + { \ + int r = (result); \ + if (r < 0) \ + return (r); \ + } +#define CL(result, list) \ + { \ + int r = (result); \ + if (r < 0) \ + { \ + gp_list_free(list); \ + return (r); \ + } \ + } #ifndef MIN -# define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define MIN(a, b) ((a) < (b) ? (a) : (b)) #endif ////////////////////////////////////////////////////////////////////////////////////// @@ -63,7 +85,6 @@ static const char *content_type_application_json = "Content-Type: application/js #define MG_HTTP_CHUNK_START mg_printf(c, http_chunked_header) #define MG_HTTP_CHUNK_END mg_http_printf_chunk(c, "") - static int server_http_version(struct mg_connection *c) { @@ -102,6 +123,39 @@ server_http_version(struct mg_connection *c) return 0; } +static int list_files(struct mg_connection *c, const char *path) +{ + CameraList *list; + const char *name; + + CHECK(gp_list_new(&list)); + CL(gp_camera_folder_list_folders(p->camera, path, list, p->context), list); + + JSON_PRINTF(c, "\"path\":\"%s\",\"files\":[", path); + + int sizeD = gp_list_count(list); + for (int i = 0; i < sizeD; i++) + { + CL(gp_list_get_name(list, i, &name), list); + JSON_PRINTF(c, "%s{ \"name\":\"%s\",\"isFolder\": true }", (i > 0) ? "," : "", name); + } + + CL(gp_camera_folder_list_files(p->camera, path, list, p->context), list); + + int sizeF = gp_list_count(list); + for (int i = 0; i < sizeF; i++) + { + CL(gp_list_get_name(list, i, &name), list); + JSON_PRINTF(c, "%s{ \"name\":\"%s\",\"isFolder\": false }", (i > 0) ? "," : "", name); + } + + JSON_PRINTF(c, "],"); + JSON_PRINTF(c, "\"entries\":%d,", sizeD + sizeF) ; + + gp_list_free(list); + return GP_OK; +} + static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) { @@ -114,135 +168,155 @@ fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) server_http_version(c); } - else if (mg_http_match_uri(hm, "/api/version")) + else if (mg_http_match_uri(hm, "/api/version")) { - server_http_version(c); - } - - else if (mg_http_match_uri(hm, "/api/server/shutdown")) + server_http_version(c); + } + + else if (mg_http_match_uri(hm, "/api/server/shutdown")) { - mg_http_reply(c, 200, content_type_application_json, "{\"return_code\":0}\n" ); + mg_http_reply(c, 200, content_type_application_json, "{\"return_code\":0}\n"); webcfg.server_done = TRUE; - } + } - else if (mg_http_match_uri(hm, "/api/auto-detect")) + else if (mg_http_match_uri(hm, "/api/auto-detect")) { - MG_HTTP_CHUNK_START; - auto_detect_action(c, p); + MG_HTTP_CHUNK_START; + auto_detect_action(c, p); MG_HTTP_CHUNK_END; - } + } - else if (mg_http_match_uri(hm, "/api/trigger-capture")) - { - MG_HTTP_CHUNK_START; - mg_http_printf_chunk(c, "{\"return_code\":%d}\n", gp_camera_trigger_capture(p->camera, p->context)); + else if (mg_http_match_uri(hm, "/api/trigger-capture")) + { + MG_HTTP_CHUNK_START; + mg_http_printf_chunk(c, "{\"return_code\":%d}\n", gp_camera_trigger_capture(p->camera, p->context)); MG_HTTP_CHUNK_END; - } + } - else if (mg_http_match_uri(hm, "/api/config/list")) + else if (mg_http_match_uri(hm, "/api/config/list")) { - MG_HTTP_CHUNK_START; - mg_http_printf_chunk(c, "{" ); - mg_http_printf_chunk(c, "\"return_code\":%d}", list_config_action(c, p)); + MG_HTTP_CHUNK_START; + mg_http_printf_chunk(c, "{"); + mg_http_printf_chunk(c, "\"return_code\":%d}", list_config_action(c, p)); MG_HTTP_CHUNK_END; - } + } - else if (mg_http_match_uri(hm, "/api/config/list-all")) + else if (mg_http_match_uri(hm, "/api/config/list-all")) { - MG_HTTP_CHUNK_START; - mg_http_printf_chunk(c, "{" ); - mg_http_printf_chunk(c, "\"return_code\":%d}", list_all_config_action(c, p)); + MG_HTTP_CHUNK_START; + mg_http_printf_chunk(c, "{"); + mg_http_printf_chunk(c, "\"return_code\":%d}", list_all_config_action(c, p)); MG_HTTP_CHUNK_END; - } + } - else if (mg_http_match_uri( hm, "/api/config/set-index/#")) - { + else if (mg_http_match_uri(hm, "/api/config/set-index/#")) + { int ret = -1; - if ( hm->query.len >= 3 && hm->query.ptr[0] == 'i' - && hm->query.ptr[1] == '=' && hm->uri.len >= 22 ) + if (hm->query.len >= 3 && hm->query.ptr[0] == 'i' && hm->query.ptr[1] == '=' && hm->uri.len >= 22) { - char buffer[256]; - char buffer2[6]; - strncpy( buffer, hm->uri.ptr, MIN((int)hm->uri.len,255)); - strncpy( buffer2, hm->query.ptr, MIN((int)hm->query.len,5)); - buffer[MIN((int)hm->uri.len,255)] = 0; - buffer2[MIN((int)hm->query.len,5)] = 0; - char *name = buffer+21; - char *value = buffer2+2; - ret = set_config_index_action( p, name, value ); + char buffer[256]; + char buffer2[6]; + strncpy(buffer, hm->uri.ptr, MIN((int)hm->uri.len, 255)); + strncpy(buffer2, hm->query.ptr, MIN((int)hm->query.len, 5)); + buffer[MIN((int)hm->uri.len, 255)] = 0; + buffer2[MIN((int)hm->query.len, 5)] = 0; + char *name = buffer + 21; + char *value = buffer2 + 2; + ret = set_config_index_action(p, name, value); } - mg_http_reply(c, 200, content_type_application_json, "{\"return_code\":%d}\n", ret ); - } - + mg_http_reply(c, 200, content_type_application_json, "{\"return_code\":%d}\n", ret); + } - else if (mg_http_match_uri(hm, "/api/capture-image")) - { - MG_HTTP_CHUNK_START; - mg_http_printf_chunk(c, "{" ); - mg_http_printf_chunk(c, "\"return_code\":%d}\n", capture_generic(c, GP_CAPTURE_IMAGE, NULL, 0 )); + else if (mg_http_match_uri(hm, "/api/capture-image")) + { + MG_HTTP_CHUNK_START; + mg_http_printf_chunk(c, "{"); + mg_http_printf_chunk(c, "\"return_code\":%d}\n", capture_generic(c, GP_CAPTURE_IMAGE, NULL, 0)); MG_HTTP_CHUNK_END; - } + } - else if (mg_http_match_uri(hm, "/api/capture-image-download")) - { - MG_HTTP_CHUNK_START; - mg_http_printf_chunk(c, "{" ); - mg_http_printf_chunk(c, "\"return_code\":%d}\n", capture_generic(c, GP_CAPTURE_IMAGE, NULL, 1 )); + else if (mg_http_match_uri(hm, "/api/capture-image-download")) + { + MG_HTTP_CHUNK_START; + mg_http_printf_chunk(c, "{"); + mg_http_printf_chunk(c, "\"return_code\":%d}\n", capture_generic(c, GP_CAPTURE_IMAGE, NULL, 1)); MG_HTTP_CHUNK_END; - } + } - else if (mg_http_match_uri(hm, "/api/get-file/#")) - { - if ( hm->uri.len >= 14 ) + else if (mg_http_match_uri(hm, "/api/get-file/#")) + { + if (hm->uri.len >= 14) { - char buffer[256]; - strncpy( buffer, hm->uri.ptr, MIN((int)hm->uri.len,255)); - buffer[MIN((int)hm->uri.len,255)] = 0; - char *path = buffer+13; - get_file_http_common ( c, path, GP_FILE_TYPE_NORMAL ); + char buffer[256]; + strncpy(buffer, hm->uri.ptr, MIN((int)hm->uri.len, 255)); + buffer[MIN((int)hm->uri.len, 255)] = 0; + char *path = buffer + 13; + get_file_http_common(c, path, GP_FILE_TYPE_NORMAL); } - } + } #ifdef HAVE_LIBEXIF - else if (mg_http_match_uri(hm, "/api/get-exif/#")) - { - if ( hm->uri.len >= 14 ) + else if (mg_http_match_uri(hm, "/api/get-exif/#")) + { + if (hm->uri.len >= 14) { char *newfilename = NULL, *newfolder = NULL; - char buffer[256]; - strncpy( buffer, hm->uri.ptr, MIN((int)hm->uri.len,255)); - buffer[MIN((int)hm->uri.len,255)] = 0; - char *path = buffer+13; + char buffer[256]; + strncpy(buffer, hm->uri.ptr, MIN((int)hm->uri.len, 255)); + buffer[MIN((int)hm->uri.len, 255)] = 0; + char *path = buffer + 13; - char *lr = strrchr( path, '/' ); - if ( lr != NULL ) + char *lr = strrchr(path, '/'); + if (lr != NULL) { - *lr=0; - newfolder = path; - newfilename = lr+1; + *lr = 0; + newfolder = path; + newfilename = lr + 1; } - MG_HTTP_CHUNK_START; - if ( newfilename != NULL && strlen(newfilename) > 0 ) + MG_HTTP_CHUNK_START; + if (newfilename != NULL && strlen(newfilename) > 0) { - mg_http_printf_chunk(c, "{" ); - mg_http_printf_chunk(c, "\"return_code\":%d}\n", print_exif_action (c, p, newfolder, newfilename)); + mg_http_printf_chunk(c, "{"); + mg_http_printf_chunk(c, "\"return_code\":%d}\n", print_exif_action(c, p, newfolder, newfilename)); } else { - mg_http_printf_chunk(c, "{ \"return_code\": -1 }\n"); + mg_http_printf_chunk(c, "{ \"return_code\": -1 }\n"); } - MG_HTTP_CHUNK_END; + MG_HTTP_CHUNK_END; } - } + } #endif - else + else if (mg_http_match_uri(hm, "/api/list-files/#")) + { + if (hm->uri.len >= 16) + { + char buffer[256]; + strncpy(buffer, hm->uri.ptr, MIN((int)hm->uri.len, 255)); + buffer[MIN((int)hm->uri.len, 255)] = 0; + char *path = buffer + 15; + char *lastChar = path + strlen(path)-1; + + if( *lastChar != '/' ) + { + strcat( lastChar, "/" ); + } + + MG_HTTP_CHUNK_START; + mg_http_printf_chunk(c, "{"); + mg_http_printf_chunk(c, "\"return_code\":%d}\n", list_files(c, path)); + MG_HTTP_CHUNK_END; + } + } + + else { - mg_http_reply(c, 404, "", "Page not found.\n" ); + mg_http_reply(c, 404, "", "Page not found.\n"); } } } @@ -280,6 +354,6 @@ int webapi_server(GPParams *params) mg_mgr_poll(&mgr, 1000); mg_mgr_free(&mgr); - puts("WebAPI server stopped."); + puts("WebAPI server stopped."); return GP_OK; } From 4e7845060f81cde83580ed56b2fe22f297380e55 Mon Sep 17 00:00:00 2001 From: Thorsten Ludewig Date: Sun, 19 Jun 2022 10:55:10 +0200 Subject: [PATCH 25/97] cleanup webapi --- README_WEBAPI_SERVER.md | 60 ++++++++++++++++++++++++++++------------- gphoto2/server.c | 53 ++++++++++++++++++------------------ 2 files changed, 68 insertions(+), 45 deletions(-) diff --git a/README_WEBAPI_SERVER.md b/README_WEBAPI_SERVER.md index 355e116c..d2342e75 100644 --- a/README_WEBAPI_SERVER.md +++ b/README_WEBAPI_SERVER.md @@ -24,14 +24,12 @@ binding to a specific ip and or port All responses are [JSON](https://json.org) formated. +### general + - `http://:8866/` version information for now -- `http://:8866/api/server/shutdown` - - shutdown http server - - `http://:8866/api/version` version information @@ -44,18 +42,6 @@ All responses are [JSON](https://json.org) formated. trigger capture image -- `http://:8866/api/config/list` - - list available config names - -- `http://:8866/api/config/list-all` - - list config including current and available config values - -- `http://:8866/api/config/set-index/?i=` - - set the `config name` to the specified `index value` - - `http://:8866/api/capture-image` response @@ -98,13 +84,49 @@ response } ``` -- `http://:8866/api/get-file/` + +### server + +- `http://:8866/api/server/shutdown` + + shutdown http server + +### config + +- `http://:8866/api/config/list` + + list available config names + +- `http://:8866/api/config/list-all` + + list config including current and available config values + +- `http://:8866/api/config/set-index/?i=` + + set the `config name` to the specified `index value` + +### file + +- `http://:8866/api/file/get/` response - native file -- `http://:8866/api/get-exif/` +- `http://:8866/api/file/exif/` + +response + +```jsonc +{ + make: "Canon", + model: "Canon EOS 550D", + orientation: "Oben links", + xresolution: "72", + yresolution: "72", +... +} +``` -- `http://:8866/api/list-files/` +- `http://:8866/api/file/list/` response diff --git a/gphoto2/server.c b/gphoto2/server.c index 79b34311..96d7a14f 100644 --- a/gphoto2/server.c +++ b/gphoto2/server.c @@ -245,7 +245,7 @@ fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) MG_HTTP_CHUNK_END; } - else if (mg_http_match_uri(hm, "/api/get-file/#")) + else if (mg_http_match_uri(hm, "/api/file/get/#")) { if (hm->uri.len >= 14) { @@ -253,21 +253,44 @@ fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) strncpy(buffer, hm->uri.ptr, MIN((int)hm->uri.len, 255)); buffer[MIN((int)hm->uri.len, 255)] = 0; char *path = buffer + 13; + puts( path ); get_file_http_common(c, path, GP_FILE_TYPE_NORMAL); } } + else if (mg_http_match_uri(hm, "/api/file/list/#")) + { + if (hm->uri.len >= 16) + { + char buffer[256]; + strncpy(buffer, hm->uri.ptr, MIN((int)hm->uri.len, 255)); + buffer[MIN((int)hm->uri.len, 255)] = 0; + char *path = buffer + 14; + char *lastChar = path + strlen(path)-1; + + if( *lastChar != '/' ) + { + strcat( lastChar, "/" ); + } + + MG_HTTP_CHUNK_START; + mg_http_printf_chunk(c, "{"); + mg_http_printf_chunk(c, "\"return_code\":%d}\n", list_files(c, path)); + MG_HTTP_CHUNK_END; + } + } + #ifdef HAVE_LIBEXIF - else if (mg_http_match_uri(hm, "/api/get-exif/#")) + else if (mg_http_match_uri(hm, "/api/file/exif/#")) { - if (hm->uri.len >= 14) + if (hm->uri.len >= 15) { char *newfilename = NULL, *newfolder = NULL; char buffer[256]; strncpy(buffer, hm->uri.ptr, MIN((int)hm->uri.len, 255)); buffer[MIN((int)hm->uri.len, 255)] = 0; - char *path = buffer + 13; + char *path = buffer + 14; char *lr = strrchr(path, '/'); if (lr != NULL) @@ -292,28 +315,6 @@ fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) } #endif - else if (mg_http_match_uri(hm, "/api/list-files/#")) - { - if (hm->uri.len >= 16) - { - char buffer[256]; - strncpy(buffer, hm->uri.ptr, MIN((int)hm->uri.len, 255)); - buffer[MIN((int)hm->uri.len, 255)] = 0; - char *path = buffer + 15; - char *lastChar = path + strlen(path)-1; - - if( *lastChar != '/' ) - { - strcat( lastChar, "/" ); - } - - MG_HTTP_CHUNK_START; - mg_http_printf_chunk(c, "{"); - mg_http_printf_chunk(c, "\"return_code\":%d}\n", list_files(c, path)); - MG_HTTP_CHUNK_END; - } - } - else { mg_http_reply(c, 404, "", "Page not found.\n"); From f4238de42d24e49698cf7f81bf11dfdf652e932c Mon Sep 17 00:00:00 2001 From: Thorsten Ludewig Date: Sun, 19 Jun 2022 11:00:49 +0200 Subject: [PATCH 26/97] clean up --- README_WEBAPI_SERVER.md | 143 +++++++++++++++++++++------------------- 1 file changed, 77 insertions(+), 66 deletions(-) diff --git a/README_WEBAPI_SERVER.md b/README_WEBAPI_SERVER.md index d2342e75..e3157f17 100644 --- a/README_WEBAPI_SERVER.md +++ b/README_WEBAPI_SERVER.md @@ -22,13 +22,13 @@ binding to a specific ip and or port ## API Calls - all HTTP GET -All responses are [JSON](https://json.org) formated. +Nearly all responses are [JSON](https://json.org) formated. ### general - `http://:8866/` - version information for now + version information for now - `http://:8866/api/version` @@ -37,6 +37,17 @@ All responses are [JSON](https://json.org) formated. - `http://:8866/api/auto-detect` show detected camera info + ```jsonc + { + result: [ + { + model: "Canon EOS 550D", + port: "usb:003,005" + } + ], + return_code: 0 + } + ``` - `http://:8866/api/trigger-capture` @@ -44,45 +55,45 @@ All responses are [JSON](https://json.org) formated. - `http://:8866/api/capture-image` -response - -```jsonc -{ - image_info: { - name: "IMG_0264.JPG", - folder: "/store_00020001/DCIM/100CANON", - mtime: 1655542624, - size: 4838064, - height: 3456, - width: 5184, - type: "image/jpeg" - }, - download: false, - return_code: 0 -} -``` + response + + ```jsonc + { + image_info: { + name: "IMG_0264.JPG", + folder: "/store_00020001/DCIM/100CANON", + mtime: 1655542624, + size: 4838064, + height: 3456, + width: 5184, + type: "image/jpeg" + }, + download: false, + return_code: 0 + } + ``` - `http://:8866/api/capture-image-download` -response - -```jsonc -{ - image_info: { - name: "IMG_0265.JPG", - folder: "/store_00020001/DCIM/100CANON", - mtime: 1655542624, - size: 4838064, - height: 3456, - width: 5184, - type: "image/jpeg" - }, - download: true, - local_folder: "/home/user/Projects/gphoto2", - keeping_file_on_camera: true, - return_code: 0 -} -``` + response + + ```jsonc + { + image_info: { + name: "IMG_0265.JPG", + folder: "/store_00020001/DCIM/100CANON", + mtime: 1655542624, + size: 4838064, + height: 3456, + width: 5184, + type: "image/jpeg" + }, + download: true, + local_folder: "/home/user/Projects/gphoto2", + keeping_file_on_camera: true, + return_code: 0 + } + ``` ### server @@ -109,40 +120,40 @@ response - `http://:8866/api/file/get/` -response - native file + response - native file - `http://:8866/api/file/exif/` -response - -```jsonc -{ - make: "Canon", - model: "Canon EOS 550D", - orientation: "Oben links", - xresolution: "72", - yresolution: "72", -... -} -``` + response + + ```jsonc + { + make: "Canon", + model: "Canon EOS 550D", + orientation: "Oben links", + xresolution: "72", + yresolution: "72", + ... + } + ``` - `http://:8866/api/file/list/` -response - -```jsonc -{ - path: "/store_00020001/DCIM/", - files: [ - { - name: "100CANON", - isFolder: true - } - ], - entries: 1, - return_code: 0 -} -``` + response + + ```jsonc + { + path: "/store_00020001/DCIM/", + files: [ + { + name: "100CANON", + isFolder: true + } + ], + entries: 1, + return_code: 0 + } + ``` ## Mongoose HTTP Server - LICENSE From 08ea9931b9bb7edea0ee0d99f2544ab52c4d3376 Mon Sep 17 00:00:00 2001 From: Thorsten Ludewig Date: Sun, 19 Jun 2022 12:40:19 +0200 Subject: [PATCH 27/97] WEBAPI_SERVER_VERSION added --- gphoto2/gphoto2-webapi.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gphoto2/gphoto2-webapi.h b/gphoto2/gphoto2-webapi.h index 59b7700f..be8429ca 100644 --- a/gphoto2/gphoto2-webapi.h +++ b/gphoto2/gphoto2-webapi.h @@ -36,6 +36,8 @@ #define VERSION "2" #endif +#define WEBAPI_SERVER_VERSION "0.0.1" + void cli_error_print(char *format, ...); void dissolve_filename ( const char *folder, const char *filename, char **newfolder, char **newfilename ); size_t strncpy_lower(char *dst, const char *src, size_t count); From 884225bd8f3f49ae65f91b0101b376cd70da632e Mon Sep 17 00:00:00 2001 From: Thorsten Ludewig Date: Sun, 19 Jun 2022 12:42:13 +0200 Subject: [PATCH 28/97] file/delete/... api added --- README_WEBAPI_SERVER.md | 4 ++++ gphoto2/server.c | 44 ++++++++++++++++++++++++++++++++++++++--- 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/README_WEBAPI_SERVER.md b/README_WEBAPI_SERVER.md index e3157f17..f5285c43 100644 --- a/README_WEBAPI_SERVER.md +++ b/README_WEBAPI_SERVER.md @@ -155,6 +155,10 @@ Nearly all responses are [JSON](https://json.org) formated. } ``` +- `http://:8866/api/file/delete/` + + remove specified file + ## Mongoose HTTP Server - LICENSE https://github.com/cesanta/mongoose diff --git a/gphoto2/server.c b/gphoto2/server.c index 96d7a14f..0ad5238d 100644 --- a/gphoto2/server.c +++ b/gphoto2/server.c @@ -89,7 +89,8 @@ static int server_http_version(struct mg_connection *c) { MG_HTTP_CHUNK_START; - mg_http_printf_chunk(c, "{ \"result\": [{ \"name\": \"mongoose\", \"version\": \"%s\"}", MG_VERSION); + JSON_PRINTF(c, "{ \"result\": [{ \"name\": \"mongoose\", \"version\": \"%s\"}", MG_VERSION); + JSON_PRINTF(c, ",{ \"name\": \"webapi_server\", \"version\": \"%s\"}", WEBAPI_SERVER_VERSION); int n; @@ -253,11 +254,48 @@ fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) strncpy(buffer, hm->uri.ptr, MIN((int)hm->uri.len, 255)); buffer[MIN((int)hm->uri.len, 255)] = 0; char *path = buffer + 13; - puts( path ); get_file_http_common(c, path, GP_FILE_TYPE_NORMAL); } } + else if (mg_http_match_uri(hm, "/api/file/delete/#")) + { + if (hm->uri.len >= 17) + { + char buffer[256]; + char *filename = NULL, *folder = NULL; + + strncpy(buffer, hm->uri.ptr, MIN((int)hm->uri.len, 255)); + buffer[MIN((int)hm->uri.len, 255)] = 0; + char *path = buffer + 16; + + char *lr = strrchr(path, '/'); + if (lr != NULL) + { + *lr = 0; + folder = path; + filename = lr + 1; + } + + MG_HTTP_CHUNK_START; + if (filename != NULL && strlen(filename) > 0) + { + mg_http_printf_chunk(c, "{"); + mg_http_printf_chunk(c, "\"return_code\":%d}\n", delete_file_action(p, folder, filename)); + } + else + { + mg_http_printf_chunk(c, "{ \"return_code\": -1 }\n"); + } + MG_HTTP_CHUNK_END; + + MG_HTTP_CHUNK_START; + mg_http_printf_chunk(c, "{"); + mg_http_printf_chunk(c, "\"return_code\":%d}\n", 0 ); + MG_HTTP_CHUNK_END; + } + } + else if (mg_http_match_uri(hm, "/api/file/list/#")) { if (hm->uri.len >= 16) @@ -341,7 +379,7 @@ int webapi_server(GPParams *params) strcpy(webcfg.server_url, s_http_addr); } - printf("Starting GPhoto2 " VERSION " WebAPI server - %s\n", webcfg.server_url); + printf("Starting GPhoto2 " VERSION " - WebAPI server " WEBAPI_SERVER_VERSION " - %s\n", webcfg.server_url); mg_log_set("2"); mg_mgr_init(&mgr); From 5082419c8ec1cc777c7fdc066b4687c1d99a562c Mon Sep 17 00:00:00 2001 From: Thorsten Ludewig Date: Sun, 19 Jun 2022 12:49:18 +0200 Subject: [PATCH 29/97] clone and build info --- README_WEBAPI_SERVER.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/README_WEBAPI_SERVER.md b/README_WEBAPI_SERVER.md index f5285c43..02cadb62 100644 --- a/README_WEBAPI_SERVER.md +++ b/README_WEBAPI_SERVER.md @@ -4,9 +4,16 @@ I need to control my camera via a small embedded controller so i decided to impl 2022 June, Thorsten Ludewig (t.ludewig@gmail.com) -## clone with submodule (mongoose webserver) +## clone with submodule (mongoose webserver) and build -`git clone --recurse-submodules ` + ```plain + git clone --recurse-submodules + cd gphoto2/gphoto2/mongoose + git checkout tags/7.7 + cd ../.. + ``` + + See README.md for further build info. ## Run server From 69192b6d61ca372052d684bd858afe9d988ec045 Mon Sep 17 00:00:00 2001 From: Thorsten Ludewig Date: Sun, 19 Jun 2022 12:57:21 +0200 Subject: [PATCH 30/97] /api/file/get now returns HTTP 404 if file not exists --- gphoto2/server.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/gphoto2/server.c b/gphoto2/server.c index 0ad5238d..1ee50b75 100644 --- a/gphoto2/server.c +++ b/gphoto2/server.c @@ -254,7 +254,10 @@ fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) strncpy(buffer, hm->uri.ptr, MIN((int)hm->uri.len, 255)); buffer[MIN((int)hm->uri.len, 255)] = 0; char *path = buffer + 13; - get_file_http_common(c, path, GP_FILE_TYPE_NORMAL); + if ( get_file_http_common(c, path, GP_FILE_TYPE_NORMAL) != GP_OK ) + { + mg_http_reply(c, 404, content_type_application_json, "{ \"error\":\"file not found\",\"return_code\":-1}\n"); + } } } From e59aa4ad7c55023bf8847c8a12b742eb523a1cbf Mon Sep 17 00:00:00 2001 From: Thorsten Ludewig Date: Mon, 20 Jun 2022 07:38:27 +0200 Subject: [PATCH 31/97] reformat code --- gphoto2/gphoto2-webapi.c | 1904 +++++++++++++++++++++----------------- 1 file changed, 1042 insertions(+), 862 deletions(-) diff --git a/gphoto2/gphoto2-webapi.c b/gphoto2/gphoto2-webapi.c index b394c813..ebabee21 100644 --- a/gphoto2/gphoto2-webapi.c +++ b/gphoto2/gphoto2-webapi.c @@ -23,7 +23,7 @@ #define _XOPEN_SOURCE 500 #include "config.h" -#if defined (HAVE_SIGNAL_H) +#if defined(HAVE_SIGNAL_H) #include #endif #include "actions.h" @@ -38,7 +38,7 @@ #include "server.h" #ifdef HAVE_CDK -# include "gphoto2-cmd-config.h" +#include "gphoto2-cmd-config.h" #endif #include @@ -57,15 +57,15 @@ #include #ifdef HAVE_RL -# include +#include #endif #ifdef HAVE_PTHREAD -# include +#include #endif #ifndef WIN32 -# include +#include #endif #if defined(_MSC_VER) @@ -78,13 +78,18 @@ #include "mongoose/mongoose.h" #ifndef MAX -# define MAX(a, b) ((a) > (b) ? (a) : (b)) +#define MAX(a, b) ((a) > (b) ? (a) : (b)) #endif #ifndef MIN -# define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define MIN(a, b) ((a) < (b) ? (a) : (b)) #endif -#define CR(result) {int r = (result); if (r < 0) return (r);} +#define CR(result) \ + { \ + int r = (result); \ + if (r < 0) \ + return (r); \ + } #ifdef __GNUC__ #define __unused__ __attribute__((unused)) @@ -122,25 +127,27 @@ size_t strncpy_lower(char *dst, const char *src, size_t count) { unsigned int i; - if ((dst == NULL) || (src == NULL) - || (((unsigned long)count)>= 0x7fff)) { + if ((dst == NULL) || (src == NULL) || (((unsigned long)count) >= 0x7fff)) + { return -1; } - for (i=0; (itm_hour % 12; - if (hour12 == 0) { + if (hour12 == 0) + { hour12 = 12; } @@ -182,31 +191,39 @@ get_path_for_file (const char *folder, const char *name, CameraFileType type, Ca * If the user didn't specify a filename, use the original name * (and prefix). */ - if (!gp_params.filename || !strcmp (gp_params.filename, "")) { - if (file) { - return gp_file_get_name_by_type (file, name, type, path); - } else if ((type == GP_FILE_TYPE_NORMAL) && strchr(name,'.')) { + if (!gp_params.filename || !strcmp(gp_params.filename, "")) + { + if (file) + { + return gp_file_get_name_by_type(file, name, type, path); + } + else if ((type == GP_FILE_TYPE_NORMAL) && strchr(name, '.')) + { /* return the original name */ *path = strdup(name); return (GP_OK); - } else + } + else /* Download required to get the path from CameraFile *file */ return (GP_ERROR_BAD_PARAMETERS); } /* The user did specify a filename. Use it. */ - b[sizeof (b) - 1] = '\0'; - for (i = 0; i < strlen (gp_params.filename); i++) { - if (gp_params.filename[i] == '%') { + b[sizeof(b) - 1] = '\0'; + for (i = 0; i < strlen(gp_params.filename); i++) + { + if (gp_params.filename[i] == '%') + { char padding = '0'; /* default padding character */ - int precision = 0; /* default: no padding */ + int precision = 0; /* default: no padding */ i++; /* determine padding character */ - switch (gp_params.filename[i]) { - /* case ' ': - * spaces are not supported everywhere, so we - * restrict ourselves to padding with zeros. - */ + switch (gp_params.filename[i]) + { + /* case ' ': + * spaces are not supported everywhere, so we + * restrict ourselves to padding with zeros. + */ case '0': padding = gp_params.filename[i]; precision = 1; /* do padding */ @@ -214,80 +231,90 @@ get_path_for_file (const char *folder, const char *name, CameraFileType type, Ca break; } /* determine padding width */ - if (isdigit((int)gp_params.filename[i])) { + if (isdigit((int)gp_params.filename[i])) + { char *cp; long int _prec; _prec = strtol(&gp_params.filename[i], - &cp, 10); + &cp, 10); if (_prec < 1) precision = 1; else if (_prec > 20) precision = 20; else precision = _prec; - if (*cp != 'n') { + if (*cp != 'n') + { /* make sure this is %n */ - gp_context_error (gp_params.context, - _("Zero padding numbers " - "in file names is only " - "possible with %%n.")); + gp_context_error(gp_params.context, + _("Zero padding numbers " + "in file names is only " + "possible with %%n.")); return GP_ERROR_BAD_PARAMETERS; } /* go to first non-digit character */ i += (cp - &gp_params.filename[i]); - } else if (precision && ( gp_params.filename[i] != 'n')) { - gp_context_error (gp_params.context, - _("You cannot use %%n " - "zero padding " - "without a " - "precision value!" - )); + } + else if (precision && (gp_params.filename[i] != 'n')) + { + gp_context_error(gp_params.context, + _("You cannot use %%n " + "zero padding " + "without a " + "precision value!")); return GP_ERROR_BAD_PARAMETERS; } - switch (gp_params.filename[i]) { + switch (gp_params.filename[i]) + { case 'n': /* - * Previously this used an folder index number. + * Previously this used an folder index number. * Now this uses a linear increasing number. */ - if (precision > 1) { + if (precision > 1) + { char padfmt[16]; strcpy(padfmt, "%!.*i"); padfmt[1] = padding; - snprintf (b, sizeof (b), padfmt, - precision, gp_params.filenr); - } else { - snprintf (b, sizeof (b), "%i", - gp_params.filenr); + snprintf(b, sizeof(b), padfmt, + precision, gp_params.filenr); + } + else + { + snprintf(b, sizeof(b), "%i", + gp_params.filenr); } gp_params.filenr++; break; case 'C': /* Get the suffix of the original name */ - s = strrchr (name, '.'); - if (!s) { - free (*path); + s = strrchr(name, '.'); + if (!s) + { + free(*path); *path = NULL; - gp_context_error (gp_params.context, - _("The filename provided " - "by the camera ('%s') " - "does not contain a " - "suffix!"), name); + gp_context_error(gp_params.context, + _("The filename provided " + "by the camera ('%s') " + "does not contain a " + "suffix!"), + name); return (GP_ERROR_BAD_PARAMETERS); } - strncpy (b, s + 1, sizeof (b) - 1); + strncpy(b, s + 1, sizeof(b) - 1); break; case 'f': /* Get the file name without suffix */ - s = strrchr (name, '.'); + s = strrchr(name, '.'); if (!s) - strncpy (b, name, sizeof (b) - 1); - else { - l = MIN ((unsigned long)(sizeof(b) - 1), - (unsigned long)(s - name)); - strncpy (b, name, l); + strncpy(b, name, sizeof(b) - 1); + else + { + l = MIN((unsigned long)(sizeof(b) - 1), + (unsigned long)(s - name)); + strncpy(b, name, l); b[l] = '\0'; } break; @@ -296,10 +323,12 @@ get_path_for_file (const char *folder, const char *name, CameraFileType type, Ca /* Get the folder name */ { const char *f = folder ? folder : ""; - if (f[0] == '/') f++; /* Skip first '/' */ - if (!f[0]) f = "."; /* replace empty folder name by '.' */ + if (f[0] == '/') + f++; /* Skip first '/' */ + if (!f[0]) + f = "."; /* replace empty folder name by '.' */ - strncpy (b, f, sizeof (b) - 1 - strlen(b)); + strncpy(b, f, sizeof(b) - 1 - strlen(b)); b[sizeof(b) - 1] = '\0'; } break; @@ -319,48 +348,55 @@ get_path_for_file (const char *folder, const char *name, CameraFileType type, Ca case 'S': case 'y': case 'Y': - { - char fmt[3] = { '%', '\0', '\0' }; - if (!file) return (GP_ERROR_BAD_PARAMETERS); /* mtime unknown */ + { + char fmt[3] = {'%', '\0', '\0'}; + if (!file) + return (GP_ERROR_BAD_PARAMETERS); /* mtime unknown */ - fmt[1] = gp_params.filename[i]; /* the letter of this 'case' */ - strftime(b, sizeof (b), fmt, tm); - break; - } + fmt[1] = gp_params.filename[i]; /* the letter of this 'case' */ + strftime(b, sizeof(b), fmt, tm); + break; + } case '%': - strcpy (b, "%"); + strcpy(b, "%"); break; case ':': strncpy_lower(b, name, sizeof(b)); - b[sizeof(b)-1] = '\0'; + b[sizeof(b) - 1] = '\0'; break; default: - free (*path); + free(*path); *path = NULL; - gp_context_error (gp_params.context, - _("Invalid format '%s' (error at " - "position %i)."), gp_params.filename, - i + 1); + gp_context_error(gp_params.context, + _("Invalid format '%s' (error at " + "position %i)."), + gp_params.filename, + i + 1); return (GP_ERROR_BAD_PARAMETERS); } - } else { + } + else + { b[0] = gp_params.filename[i]; b[1] = '\0'; } - s = *path ? realloc (*path, strlen (*path) + strlen (b) + 1) : - malloc (strlen (b) + 1); - if (!s) { - free (*path); + s = *path ? realloc(*path, strlen(*path) + strlen(b) + 1) : malloc(strlen(b) + 1); + if (!s) + { + free(*path); *path = NULL; return (GP_ERROR_NO_MEMORY); } - if (*path) { + if (*path) + { *path = s; - strcat (*path, b); - } else { + strcat(*path, b); + } + else + { *path = s; - strcpy (*path, b); + strcpy(*path, b); } } @@ -368,8 +404,9 @@ get_path_for_file (const char *folder, const char *name, CameraFileType type, Ca * If the file is a capture_preview, * apply prefix over the calculated basename */ - if (type == GP_FILE_TYPE_PREVIEW) { - return gp_file_get_name_by_type (file, s, type, path); + if (type == GP_FILE_TYPE_PREVIEW) + { + return gp_file_get_name_by_type(file, s, type, path); } return (GP_OK); @@ -585,167 +622,190 @@ static int x_read(void *priv, unsigned char *data, uint64_t *size) return GP_OK; } -static int x_write(void*priv,unsigned char *data, uint64_t *size) { - struct privstr *ps = priv; - int fd = ps->fd; - uint64_t curwritten = 0, xsize, res; +static int x_write(void *priv, unsigned char *data, uint64_t *size) +{ + struct privstr *ps = priv; + int fd = ps->fd; + uint64_t curwritten = 0, xsize, res; - gp_log (GP_LOG_DEBUG, "x_write","(%p,%p,%u)", priv, data, (unsigned int)*size); + gp_log(GP_LOG_DEBUG, "x_write", "(%p,%p,%u)", priv, data, (unsigned int)*size); xsize = *size; - while (curwritten < xsize) { - res = write (fd, data+curwritten, xsize-curwritten); - if (res == -1) return GP_ERROR_IO_WRITE; - if (!res) break; + while (curwritten < xsize) + { + res = write(fd, data + curwritten, xsize - curwritten); + if (res == -1) + return GP_ERROR_IO_WRITE; + if (!res) + break; curwritten += res; } *size = curwritten; return GP_OK; } -static CameraFileHandler xhandler = { x_size, x_read, x_write }; +static CameraFileHandler xhandler = {x_size, x_read, x_write}; -int -save_file_to_file (struct mg_connection *c, Camera *camera, GPContext *context, Flags flags, - const char *folder, const char *filename, - CameraFileType type, int webapi ) +int save_file_to_file(struct mg_connection *c, Camera *camera, GPContext *context, Flags flags, + const char *folder, const char *filename, + CameraFileType type, int webapi) { - int fd, res; - CameraFile *file; - char tmpname[20], *tmpfilename; + int fd, res; + CameraFile *file; + char tmpname[20], *tmpfilename; struct privstr *ps = NULL; - if (flags & FLAGS_SKIP_EXISTING && !(flags & FLAGS_STDOUT)) { + if (flags & FLAGS_SKIP_EXISTING && !(flags & FLAGS_STDOUT)) + { char *path = NULL; /* Check if the file is present before downloading it. */ - res = get_path_for_file (folder, filename, type, NULL, &path); - if (res == (GP_OK) && gp_system_is_file (path)) { + res = get_path_for_file(folder, filename, type, NULL, &path); + if (res == (GP_OK) && gp_system_is_file(path)) + { /* File name pattern do not require CameraFile and target file - exists: Skip this file. */ - if ((gp_params.flags & FLAGS_QUIET) == 0) { - printf (_("Skip existing file %s\n"), path); - fflush (stdout); + exists: Skip this file. */ + if ((gp_params.flags & FLAGS_QUIET) == 0) + { + printf(_("Skip existing file %s\n"), path); + fflush(stdout); } return (GP_OK); } } - if (flags & FLAGS_NEW) { + if (flags & FLAGS_NEW) + { CameraFileInfo info; - CR (gp_camera_file_get_info (camera, folder, filename, &info, context)); + CR(gp_camera_file_get_info(camera, folder, filename, &info, context)); - switch (type) { + switch (type) + { case GP_FILE_TYPE_PREVIEW: if (info.preview.fields & GP_FILE_INFO_STATUS && - info.preview.status == GP_FILE_STATUS_DOWNLOADED) + info.preview.status == GP_FILE_STATUS_DOWNLOADED) return (GP_OK); break; case GP_FILE_TYPE_NORMAL: case GP_FILE_TYPE_RAW: case GP_FILE_TYPE_EXIF: if (info.file.fields & GP_FILE_INFO_STATUS && - info.file.status == GP_FILE_STATUS_DOWNLOADED) + info.file.status == GP_FILE_STATUS_DOWNLOADED) return (GP_OK); break; case GP_FILE_TYPE_AUDIO: if (info.audio.fields & GP_FILE_INFO_STATUS && - info.audio.status == GP_FILE_STATUS_DOWNLOADED) + info.audio.status == GP_FILE_STATUS_DOWNLOADED) return (GP_OK); break; default: return (GP_ERROR_NOT_SUPPORTED); } } - - strcpy (tmpname, "tmpfileXXXXXX"); + + strcpy(tmpname, "tmpfileXXXXXX"); fd = mkstemp(tmpname); - if (fd == -1) { - if (errno == EACCES) { - gp_context_error (context, _("Permission denied")); - return GP_ERROR; - } - CR (gp_file_new (&file)); - tmpfilename = NULL; - } else { - if (time(NULL) & 1) { /* to test both methods. */ - gp_log (GP_LOG_DEBUG, "save_file_to_file","using fd method"); - res = gp_file_new_from_fd (&file, fd); - if (res < GP_OK) { - close (fd); - unlink (tmpname); + if (fd == -1) + { + if (errno == EACCES) + { + gp_context_error(context, _("Permission denied")); + return GP_ERROR; + } + CR(gp_file_new(&file)); + tmpfilename = NULL; + } + else + { + if (time(NULL) & 1) + { /* to test both methods. */ + gp_log(GP_LOG_DEBUG, "save_file_to_file", "using fd method"); + res = gp_file_new_from_fd(&file, fd); + if (res < GP_OK) + { + close(fd); + unlink(tmpname); return res; } - } else { - gp_log (GP_LOG_DEBUG, "save_file_to_file","using handler method"); - ps = malloc (sizeof(*ps)); - if (!ps) return GP_ERROR_NO_MEMORY; + } + else + { + gp_log(GP_LOG_DEBUG, "save_file_to_file", "using handler method"); + ps = malloc(sizeof(*ps)); + if (!ps) + return GP_ERROR_NO_MEMORY; ps->fd = fd; /* just pass in the file pointer as private */ - res = gp_file_new_from_handler (&file, &xhandler, ps); - if (res < GP_OK) { - close (fd); - unlink (tmpname); + res = gp_file_new_from_handler(&file, &xhandler, ps); + if (res < GP_OK) + { + close(fd); + unlink(tmpname); return res; } } tmpfilename = tmpname; } - res = gp_camera_file_get (camera, folder, filename, type, file, context); + res = gp_camera_file_get(camera, folder, filename, type, file, context); - if (res < GP_OK) { - free (ps); - gp_file_unref (file); - if (tmpfilename) unlink (tmpfilename); + if (res < GP_OK) + { + free(ps); + gp_file_unref(file); + if (tmpfilename) + unlink(tmpfilename); return res; } - if (flags & FLAGS_STDOUT) { - const char *data; - unsigned long int size; + if (flags & FLAGS_STDOUT) + { + const char *data; + unsigned long int size; - CR (gp_file_get_data_and_size (file, &data, &size)); + CR(gp_file_get_data_and_size(file, &data, &size)); if (flags & FLAGS_STDOUT_SIZE) /* this will be difficult in fd mode */ - printf ("%li\n", size); + printf("%li\n", size); - if (1!=fwrite (data, size, 1, stdout)) - fprintf(stderr,"fwrite failed writing to stdout.\n"); + if (1 != fwrite(data, size, 1, stdout)) + fprintf(stderr, "fwrite failed writing to stdout.\n"); - if (ps && ps->fd) - close (ps->fd); + if (ps && ps->fd) + close(ps->fd); - free (ps); - gp_file_unref (file); - unlink (tmpname); + free(ps); + gp_file_unref(file); + unlink(tmpname); return (GP_OK); } - if (webapi) { - const char *data; - unsigned long int size; + if (webapi) + { + const char *data; + unsigned long int size; - CR (gp_file_get_data_and_size (file, &data, &size)); + CR(gp_file_get_data_and_size(file, &data, &size)); - mg_send( c, data, size ); + mg_send(c, data, size); - if (ps && ps->fd) - close (ps->fd); + if (ps && ps->fd) + close(ps->fd); - free (ps); - gp_file_unref (file); - unlink (tmpname); + free(ps); + gp_file_unref(file); + unlink(tmpname); return (GP_OK); } - res = save_camera_file_to_file (folder, filename, type, file, tmpfilename); + res = save_camera_file_to_file(folder, filename, type, file, tmpfilename); - if (ps && ps->fd) close (ps->fd); - free (ps); - gp_file_unref (file); - if ((res!=GP_OK) && tmpfilename) - unlink (tmpfilename); - return (res); + if (ps && ps->fd) + close(ps->fd); + free(ps); + gp_file_unref(file); + if ((res != GP_OK) && tmpfilename) + unlink(tmpfilename); + return (res); } void dissolve_filename( @@ -776,8 +836,7 @@ void dissolve_filename( #endif } -int -get_file_http_common(struct mg_connection *c, const char *path, CameraFileType type) +int get_file_http_common(struct mg_connection *c, const char *path, CameraFileType type) { gp_log(GP_LOG_DEBUG, "gphoto2-webapi", "Getting '%s'...", path); @@ -818,80 +877,85 @@ get_file_http_common(struct mg_connection *c, const char *path, CameraFileType t * thumbnails according to thumbnail argument, and save to files. */ -int -get_file_common (struct mg_connection *c, const char *arg, CameraFileType type ) +int get_file_common(struct mg_connection *c, const char *arg, CameraFileType type) { unsigned int mightberange = 1, i; - gp_log (GP_LOG_DEBUG, "main", "Getting '%s'...", arg); + gp_log(GP_LOG_DEBUG, "main", "Getting '%s'...", arg); gp_params.download_type = type; /* remember for multi download */ /* * If the user specified the file directly (and not a number), * get that file. */ - for (i=0;i '9'))) { + for (i = 0; i < strlen(arg); i++) + { + if ((arg[i] != '-') && ((arg[i] < '0') || (arg[i] > '9'))) + { mightberange = 0; break; } } - if (!mightberange) { + if (!mightberange) + { int ret; char *newfolder, *newfilename; - dissolve_filename (gp_params.folder, arg, &newfolder, &newfilename); - ret = save_file_to_file ( c, gp_params.camera, gp_params.context, gp_params.flags, - newfolder, newfilename, type, FALSE); - free (newfolder); free (newfilename); + dissolve_filename(gp_params.folder, arg, &newfolder, &newfilename); + ret = save_file_to_file(c, gp_params.camera, gp_params.context, gp_params.flags, + newfolder, newfilename, type, FALSE); + free(newfolder); + free(newfilename); return ret; } - switch (type) { - case GP_FILE_TYPE_PREVIEW: - CR (for_each_file_in_range (&gp_params, save_thumbnail_action, arg)); + switch (type) + { + case GP_FILE_TYPE_PREVIEW: + CR(for_each_file_in_range(&gp_params, save_thumbnail_action, arg)); break; - case GP_FILE_TYPE_NORMAL: - CR (for_each_file_in_range (&gp_params, save_file_action, arg)); + case GP_FILE_TYPE_NORMAL: + CR(for_each_file_in_range(&gp_params, save_file_action, arg)); break; - case GP_FILE_TYPE_RAW: - CR (for_each_file_in_range (&gp_params, save_raw_action, arg)); + case GP_FILE_TYPE_RAW: + CR(for_each_file_in_range(&gp_params, save_raw_action, arg)); break; case GP_FILE_TYPE_AUDIO: - CR (for_each_file_in_range (&gp_params, save_audio_action, arg)); + CR(for_each_file_in_range(&gp_params, save_audio_action, arg)); break; case GP_FILE_TYPE_EXIF: - CR (for_each_file_in_range (&gp_params, save_exif_action, arg)); + CR(for_each_file_in_range(&gp_params, save_exif_action, arg)); break; case GP_FILE_TYPE_METADATA: - CR (for_each_file_in_range (&gp_params, save_meta_action, arg)); + CR(for_each_file_in_range(&gp_params, save_meta_action, arg)); break; - default: - return (GP_ERROR_NOT_SUPPORTED); - } + default: + return (GP_ERROR_NOT_SUPPORTED); + } return (GP_OK); } static void -sig_handler_capture_now (int sig_num) +sig_handler_capture_now(int sig_num) { - signal (SIGUSR1, sig_handler_capture_now); + signal(SIGUSR1, sig_handler_capture_now); capture_now = 1; } static void -sig_handler_end_next (int sig_num) +sig_handler_end_next(int sig_num) { - signal (SIGUSR2, sig_handler_end_next); - end_next = 1; + signal(SIGUSR2, sig_handler_end_next); + end_next = 1; } /* temp test function */ -int -trigger_capture (void) { - int result = gp_camera_trigger_capture (gp_params.camera, gp_params.context); - if (result != GP_OK) { +int trigger_capture(void) +{ + int result = gp_camera_trigger_capture(gp_params.camera, gp_params.context); + if (result != GP_OK) + { cli_error_print(_("Could not trigger capture.")); return (result); } @@ -899,16 +963,18 @@ trigger_capture (void) { } static long -timediff_now (struct timeval *target) { +timediff_now(struct timeval *target) +{ struct timeval now; - gettimeofday (&now, NULL); - return (target->tv_sec-now.tv_sec)*1000+ - (target->tv_usec-now.tv_usec)/1000; + gettimeofday(&now, NULL); + return (target->tv_sec - now.tv_sec) * 1000 + + (target->tv_usec - now.tv_usec) / 1000; } static int -save_captured_file (struct mg_connection *c, CameraFilePath *path, int download) { +save_captured_file(struct mg_connection *c, CameraFilePath *path, int download) +{ char *pathsep; static CameraFilePath last; int result; @@ -918,213 +984,242 @@ save_captured_file (struct mg_connection *c, CameraFilePath *path, int download) else pathsep = "/"; - if (gp_params.flags & FLAGS_QUIET) { - if (!(gp_params.flags & (FLAGS_STDOUT|FLAGS_STDOUT_SIZE))) - printf ("%s%s%s\n", path->folder, pathsep, path->name); - } else { - printf (_("New file is in location %s%s%s on the camera\n"), - path->folder, pathsep, path->name); - } - - CameraFileInfo info; - CR (gp_camera_file_get_info (gp_params.camera, path->folder, path->name, &info, gp_params.context)); - - JSON_PRINTF( c, "\"image_info\":{" ); - JSON_PRINTF( c, "\"name\": \"%s\",", path->name ); - JSON_PRINTF( c, "\"folder\": \"%s\",", path->folder ); - JSON_PRINTF( c, "\"path\": \"%s%s%s\",", path->folder, pathsep, path->name ); - JSON_PRINTF( c, "\"mtime\":%ld,", info.file.mtime ); - JSON_PRINTF( c, "\"size\":%ld,", info.file.size ); - JSON_PRINTF( c, "\"height\":%d,", info.file.height ); - JSON_PRINTF( c, "\"width\":%d,", info.file.width ); - JSON_PRINTF( c, "\"type\":\"%s\"\n", info.file.type ); - JSON_PRINTF( c, "}," ); - JSON_PRINTF( c, "\"download\": %s,", (download) ? "true" : "false" ); - - if (download) { - char *cwd; - if( (cwd=getcwd(NULL, 0)) != NULL) { - JSON_PRINTF( c, "\"local_folder\": \"%s\",", cwd ); - free(cwd); - } - - if (strcmp(path->folder, last.folder)) { + if (gp_params.flags & FLAGS_QUIET) + { + if (!(gp_params.flags & (FLAGS_STDOUT | FLAGS_STDOUT_SIZE))) + printf("%s%s%s\n", path->folder, pathsep, path->name); + } + else + { + printf(_("New file is in location %s%s%s on the camera\n"), + path->folder, pathsep, path->name); + } + + CameraFileInfo info; + CR(gp_camera_file_get_info(gp_params.camera, path->folder, path->name, &info, gp_params.context)); + + JSON_PRINTF(c, "\"image_info\":{"); + JSON_PRINTF(c, "\"name\": \"%s\",", path->name); + JSON_PRINTF(c, "\"folder\": \"%s\",", path->folder); + JSON_PRINTF(c, "\"path\": \"%s%s%s\",", path->folder, pathsep, path->name); + JSON_PRINTF(c, "\"mtime\":%ld,", info.file.mtime); + JSON_PRINTF(c, "\"size\":%ld,", info.file.size); + JSON_PRINTF(c, "\"height\":%d,", info.file.height); + JSON_PRINTF(c, "\"width\":%d,", info.file.width); + JSON_PRINTF(c, "\"type\":\"%s\"\n", info.file.type); + JSON_PRINTF(c, "},"); + JSON_PRINTF(c, "\"download\": %s,", (download) ? "true" : "false"); + + if (download) + { + char *cwd; + if ((cwd = getcwd(NULL, 0)) != NULL) + { + JSON_PRINTF(c, "\"local_folder\": \"%s\",", cwd); + free(cwd); + } + + if (strcmp(path->folder, last.folder)) + { memcpy(&last, path, sizeof(last)); result = set_folder_action(&gp_params, path->folder); - if (result != GP_OK) { + if (result != GP_OK) + { cli_error_print(_("Could not set folder.")); return (result); } } - if ( (gp_params.flags & FLAGS_KEEP_RAW) && - (!strstr(path->name,".jpg") && !strstr(path->name,".JPG")) - ) { + if ((gp_params.flags & FLAGS_KEEP_RAW) && + (!strstr(path->name, ".jpg") && !strstr(path->name, ".JPG"))) + { if (!(gp_params.flags & FLAGS_QUIET)) - printf (_("Keeping file %s%s%s on the camera\n"), - path->folder, pathsep, path->name); + printf(_("Keeping file %s%s%s on the camera\n"), + path->folder, pathsep, path->name); return GP_OK; } - result = get_file_common ( c, path->name, GP_FILE_TYPE_NORMAL); + result = get_file_common(c, path->name, GP_FILE_TYPE_NORMAL); - if (result != GP_OK) { - cli_error_print (_("Could not get image.")); - if(result == GP_ERROR_FILE_NOT_FOUND) { + if (result != GP_OK) + { + cli_error_print(_("Could not get image.")); + if (result == GP_ERROR_FILE_NOT_FOUND) + { /* Buggy libcanon.so? * Can happen if this was the first capture after a * CF card format, or during a directory roll-over, * ie: CANON100 -> CANON101 */ - cli_error_print ( _("Buggy libcanon.so?")); + cli_error_print(_("Buggy libcanon.so?")); } return (result); } - JSON_PRINTF( c, "\"keeping_file_on_camera\": %s,", (gp_params.flags & FLAGS_KEEP) ? "true" : "false" ); + JSON_PRINTF(c, "\"keeping_file_on_camera\": %s,", (gp_params.flags & FLAGS_KEEP) ? "true" : "false"); - if (!(gp_params.flags & FLAGS_KEEP)) { + if (!(gp_params.flags & FLAGS_KEEP)) + { if (!(gp_params.flags & FLAGS_QUIET)) - printf (_("Deleting file %s%s%s on the camera\n"), - path->folder, pathsep, path->name); + printf(_("Deleting file %s%s%s on the camera\n"), + path->folder, pathsep, path->name); - result = delete_file_action (&gp_params, path->folder, path->name); - if (result != GP_OK) { - cli_error_print ( _("Could not delete image.")); + result = delete_file_action(&gp_params, path->folder, path->name); + if (result != GP_OK) + { + cli_error_print(_("Could not delete image.")); return (result); } - } else { - if (!(gp_params.flags & FLAGS_QUIET)) - printf (_("Keeping file %s%s%s on the camera\n"), - path->folder, pathsep, path->name); + } + else + { + if (!(gp_params.flags & FLAGS_QUIET)) + printf(_("Keeping file %s%s%s on the camera\n"), + path->folder, pathsep, path->name); } } return GP_OK; } static int -wait_and_handle_event (struct mg_connection *c, long waittime, CameraEventType *type, int download) { - int result; - CameraEventType evtype; - void *data; - CameraFilePath *path; +wait_and_handle_event(struct mg_connection *c, long waittime, CameraEventType *type, int download) +{ + int result; + CameraEventType evtype; + void *data; + CameraFilePath *path; - if (!type) type = &evtype; + if (!type) + type = &evtype; evtype = GP_EVENT_UNKNOWN; data = NULL; result = gp_camera_wait_for_event(gp_params.camera, waittime, type, &data, gp_params.context); - if (result == GP_ERROR_NOT_SUPPORTED) { + if (result == GP_ERROR_NOT_SUPPORTED) + { *type = GP_EVENT_TIMEOUT; - usleep(waittime*1000); + usleep(waittime * 1000); return GP_OK; } if (result != GP_OK) return result; path = data; - switch (*type) { + switch (*type) + { case GP_EVENT_TIMEOUT: break; case GP_EVENT_CAPTURE_COMPLETE: break; case GP_EVENT_FOLDER_ADDED: if (!(gp_params.flags & FLAGS_QUIET)) - printf (_("Event FOLDER_ADDED %s/%s during wait, ignoring.\n"), path->folder, path->name); - free (data); + printf(_("Event FOLDER_ADDED %s/%s during wait, ignoring.\n"), path->folder, path->name); + free(data); break; case GP_EVENT_FILE_CHANGED: if (!(gp_params.flags & FLAGS_QUIET)) - printf (_("Event FILE_CHANGED %s/%s during wait, ignoring.\n"), path->folder, path->name); - free (data); + printf(_("Event FILE_CHANGED %s/%s during wait, ignoring.\n"), path->folder, path->name); + free(data); break; case GP_EVENT_FILE_ADDED: - result = save_captured_file (c, path, download); - free (data); + result = save_captured_file(c, path, download); + free(data); /* result will fall through to final return */ break; case GP_EVENT_UNKNOWN: #if 0 /* too much spam for the common usage */ printf (_("Event UNKNOWN %s during wait, ignoring.\n"), (char*)data); #endif - free (data); + free(data); break; default: if (!(gp_params.flags & FLAGS_QUIET)) - printf (_("Unknown event type %d during bulb wait, ignoring.\n"), *type); + printf(_("Unknown event type %d during bulb wait, ignoring.\n"), *type); break; } return result; } -int -capture_generic (struct mg_connection *c, CameraCaptureType type, const char __unused__ *name, int download) +int capture_generic(struct mg_connection *c, CameraCaptureType type, const char __unused__ *name, int download) { CameraFilePath path; int result, frames = 0; - CameraAbilities a; + CameraAbilities a; CameraEventType evtype; long waittime; struct timeval next_pic_time, expose_end_time; - result = gp_camera_get_abilities (gp_params.camera, &a); - if (result != GP_OK) { + result = gp_camera_get_abilities(gp_params.camera, &a); + if (result != GP_OK) + { cli_error_print(_("Could not get capabilities?")); return (result); } - gettimeofday (&next_pic_time, NULL); + gettimeofday(&next_pic_time, NULL); next_pic_time.tv_sec += glob_interval; - if(glob_interval) { - if (!(gp_params.flags & FLAGS_QUIET)) { + if (glob_interval) + { + if (!(gp_params.flags & FLAGS_QUIET)) + { if (glob_interval != -1) - printf (_("Time-lapse mode enabled (interval: %ds).\n"), - glob_interval); + printf(_("Time-lapse mode enabled (interval: %ds).\n"), + glob_interval); else - printf (_("Standing by waiting for SIGUSR1 to capture.\n")); + printf(_("Standing by waiting for SIGUSR1 to capture.\n")); } } - if(glob_bulblength) { - if (!(gp_params.flags & FLAGS_QUIET)) { - printf (_("Bulb mode enabled (exposure time: %ds).\n"), - glob_bulblength); + if (glob_bulblength) + { + if (!(gp_params.flags & FLAGS_QUIET)) + { + printf(_("Bulb mode enabled (exposure time: %ds).\n"), + glob_bulblength); } } - while(++frames) { - if (!(gp_params.flags & FLAGS_QUIET) && glob_interval) { - if(!glob_frames) - printf (_("Capturing frame #%d...\n"), frames); + while (++frames) + { + if (!(gp_params.flags & FLAGS_QUIET) && glob_interval) + { + if (!glob_frames) + printf(_("Capturing frame #%d...\n"), frames); else - printf (_("Capturing frame #%d/%d...\n"), frames, glob_frames); + printf(_("Capturing frame #%d/%d...\n"), frames, glob_frames); } fflush(stdout); /* Now handle the different capture methods */ - if(glob_bulblength) { + if (glob_bulblength) + { /* Bulb mode is special ... we enable it, wait disable it */ - result = set_config_action (&gp_params, "bulb", "1"); - if (result != GP_OK) { + result = set_config_action(&gp_params, "bulb", "1"); + if (result != GP_OK) + { cli_error_print(_("Could not set bulb capture, result %d."), result); return (result); } - gettimeofday (&expose_end_time, NULL); + gettimeofday(&expose_end_time, NULL); expose_end_time.tv_sec += glob_bulblength; - waittime = timediff_now (&expose_end_time); - while(waittime > 0) { - result = wait_and_handle_event(c,waittime, &evtype, download); + waittime = timediff_now(&expose_end_time); + while (waittime > 0) + { + result = wait_and_handle_event(c, waittime, &evtype, download); if (result != GP_OK) return result; - waittime = timediff_now (&expose_end_time); + waittime = timediff_now(&expose_end_time); } - result = set_config_action (&gp_params, "bulb", "0"); - if (result != GP_OK) { + result = set_config_action(&gp_params, "bulb", "0"); + if (result != GP_OK) + { cli_error_print(_("Could not end capture (bulb mode).")); return (result); } /* The actual download will happen down below in the interval wait * or the loop exit. */ - } else { + } + else + { result = GP_ERROR_NOT_SUPPORTED; #if 0 /* Not a good idea, as we do not know how long to wait after capture ... */ @@ -1135,42 +1230,48 @@ capture_generic (struct mg_connection *c, CameraCaptureType type, const char __u /* The downloads will be handled by wait_event */ } #endif - if (result == GP_ERROR_NOT_SUPPORTED) { - result = gp_camera_capture (gp_params.camera, type, &path, gp_params.context); - if (result != GP_OK) { - printf( "(%s:%d) result=%d\n", __FILE__, __LINE__, result ); + if (result == GP_ERROR_NOT_SUPPORTED) + { + result = gp_camera_capture(gp_params.camera, type, &path, gp_params.context); + if (result != GP_OK) + { + printf("(%s:%d) result=%d\n", __FILE__, __LINE__, result); cli_error_print(_("Could not capture image.")); - } else { + } + else + { /* If my Canon EOS 10D is set to auto-focus and it is unable to * get focus lock - it will return with *UNKNOWN* as the filename. */ - if (glob_interval && strcmp(path.name, "*UNKNOWN*") == 0) { - if (!(gp_params.flags & FLAGS_QUIET)) { - printf (_("Capture failed (auto-focus problem?)...\n")); + if (glob_interval && strcmp(path.name, "*UNKNOWN*") == 0) + { + if (!(gp_params.flags & FLAGS_QUIET)) + { + printf(_("Capture failed (auto-focus problem?)...\n")); sleep(1); continue; } } - result = save_captured_file (c, &path, download); + result = save_captured_file(c, &path, download); if (result != GP_OK) break; } } - if (result != GP_OK) { - printf( "(%s:%d) result=%d\n", __FILE__, __LINE__, result ); + if (result != GP_OK) + { + printf("(%s:%d) result=%d\n", __FILE__, __LINE__, result); cli_error_print(_("Could not capture.")); - if ( (result == GP_ERROR_NOT_SUPPORTED) || - (result == GP_ERROR_NO_MEMORY) || - (result == GP_ERROR_CANCEL) || - (result == GP_ERROR_NO_SPACE) || - (result == GP_ERROR_IO_USB_CLAIM) || - (result == GP_ERROR_IO_LOCK) || - (result == GP_ERROR_CAMERA_BUSY) || - (result == GP_ERROR_OS_FAILURE) || - (result == -2) - ) - - JSON_PRINTF( c, "\"error_message\": \"Could not capture.\"," ); + if ((result == GP_ERROR_NOT_SUPPORTED) || + (result == GP_ERROR_NO_MEMORY) || + (result == GP_ERROR_CANCEL) || + (result == GP_ERROR_NO_SPACE) || + (result == GP_ERROR_IO_USB_CLAIM) || + (result == GP_ERROR_IO_LOCK) || + (result == GP_ERROR_CAMERA_BUSY) || + (result == GP_ERROR_OS_FAILURE) || + (result == -2)) + + JSON_PRINTF(c, "\"error_message\": \"Could not capture.\","); return (result); } @@ -1179,12 +1280,15 @@ capture_generic (struct mg_connection *c, CameraCaptureType type, const char __u /* Break if we've reached the requested number of frames * to capture. */ - if(!glob_interval) break; + if (!glob_interval) + break; - if(glob_frames && frames == glob_frames) break; + if (glob_frames && frames == glob_frames) + break; /* Break if we've been told to end before the next frame */ - if(end_next) break; + if (end_next) + break; /* * Even if the interval is set to -1, it is better to take a @@ -1192,41 +1296,52 @@ capture_generic (struct mg_connection *c, CameraCaptureType type, const char __u * response when a signal is caught. * [alesan] */ - if (glob_interval != -1) { - waittime = timediff_now (&next_pic_time); + if (glob_interval != -1) + { + waittime = timediff_now(&next_pic_time); result = GP_OK; - if (waittime > 0) { + if (waittime > 0) + { if (!(gp_params.flags & FLAGS_QUIET) && glob_interval) - printf (_("Waiting for next capture slot %ld seconds...\n"), waittime/1000); + printf(_("Waiting for next capture slot %ld seconds...\n"), waittime / 1000); /* We're not sure about sleep() semantics when catching a signal */ - do { + do + { /* granularity in which we can receive signals is 200 */ - if (waittime > 200) waittime = 200; - result = wait_and_handle_event (c,waittime, NULL, download); + if (waittime > 200) + waittime = 200; + result = wait_and_handle_event(c, waittime, NULL, download); if (result != GP_OK) break; - if (capture_now && !(gp_params.flags & FLAGS_QUIET) && glob_interval) { - printf (_("Awakened by SIGUSR1...\n")); + if (capture_now && !(gp_params.flags & FLAGS_QUIET) && glob_interval) + { + printf(_("Awakened by SIGUSR1...\n")); break; } - waittime = timediff_now (&next_pic_time); + waittime = timediff_now(&next_pic_time); } while (waittime > 0); - } else { + } + else + { /* drain the queue first though, even though there is no time. */ - while (1) { - result = wait_and_handle_event (c,1, &evtype, download); + while (1) + { + result = wait_and_handle_event(c, 1, &evtype, download); if ((result != GP_OK) || (evtype == GP_EVENT_TIMEOUT)) break; } if (!(gp_params.flags & FLAGS_QUIET) && glob_interval) - printf (_("not sleeping (%ld seconds behind schedule)\n"), -waittime/1000); + printf(_("not sleeping (%ld seconds behind schedule)\n"), -waittime / 1000); } - if (result != GP_OK) break; - if (capture_now && (gp_params.flags & FLAGS_RESET_CAPTURE_INTERVAL)) { - gettimeofday (&next_pic_time, NULL); + if (result != GP_OK) + break; + if (capture_now && (gp_params.flags & FLAGS_RESET_CAPTURE_INTERVAL)) + { + gettimeofday(&next_pic_time, NULL); next_pic_time.tv_sec += glob_interval; } - else if (!capture_now) { + else if (!capture_now) + { /* * In the case of a (huge) time-sync while gphoto is running, * gphoto could percieve an extremely large amount of time and @@ -1234,123 +1349,135 @@ capture_generic (struct mg_connection *c, CameraCaptureType type, const char __u * difference of time with the following loop. * [alesan] */ - do { + do + { next_pic_time.tv_sec += glob_interval; - } while (glob_interval && (timediff_now (&next_pic_time) < 0)); + } while (glob_interval && (timediff_now(&next_pic_time) < 0)); } capture_now = 0; - } else { + } + else + { /* wait indefinitely for SIGUSR1 */ - do { - result = wait_and_handle_event (c,200, &evtype, download); - } while(!capture_now && (result == GP_OK)); + do + { + result = wait_and_handle_event(c, 200, &evtype, download); + } while (!capture_now && (result == GP_OK)); if (result != GP_OK) break; capture_now = 0; if (!(gp_params.flags & FLAGS_QUIET)) - printf (_("Awakened by SIGUSR1...\n")); + printf(_("Awakened by SIGUSR1...\n")); } } /* The final capture will fall out of the loop into this case, * so make sure we wait a bit for the the camera to finish stuff. */ - gettimeofday (&expose_end_time, NULL); + gettimeofday(&expose_end_time, NULL); waittime = 3000; /* tricky, this loop might need to download both JPG and RAW. so wait longer. */ /*if (glob_frames || end_next || !glob_interval || glob_bulblength) waittime = 2000;*/ /* Drain the event queue at the end and download left over added images */ - while (1) { + while (1) + { int realwait = waittime - (-timediff_now(&expose_end_time)); - if (realwait < -3) break; /* wait at most 6 seconds */ + if (realwait < -3) + break; /* wait at most 6 seconds */ - if (realwait < 0) realwait = 0; /* just drain the queue now */ - result = wait_and_handle_event(c,realwait, &evtype, download); - if ((result != GP_OK) || (evtype == GP_EVENT_TIMEOUT)) { + if (realwait < 0) + realwait = 0; /* just drain the queue now */ + result = wait_and_handle_event(c, realwait, &evtype, download); + if ((result != GP_OK) || (evtype == GP_EVENT_TIMEOUT)) + { /*printf("Timeout or error, leaving loop.\n");*/ break; } - if (evtype == GP_EVENT_CAPTURE_COMPLETE) { + if (evtype == GP_EVENT_CAPTURE_COMPLETE) + { /*printf("Capture complete, waiting final 0.1s.\n");*/ waittime = 100; } - if (evtype == GP_EVENT_FILE_ADDED) { /* Restart timer, more image data might come. */ + if (evtype == GP_EVENT_FILE_ADDED) + { /* Restart timer, more image data might come. */ /*printf("File added, waiting more %gs.\n", waittime/1000.0);*/ - gettimeofday (&expose_end_time, NULL); + gettimeofday(&expose_end_time, NULL); } } return GP_OK; } - /* Set/init global variables */ /* ------------------------------------------------------------ */ #ifdef HAVE_PTHREAD typedef struct _ThreadData ThreadData; -struct _ThreadData { +struct _ThreadData +{ Camera *camera; time_t timeout; CameraTimeoutFunc func; }; static void -thread_cleanup_func (void *data) +thread_cleanup_func(void *data) { ThreadData *td = data; - free (td); + free(td); } static void * -thread_func (void *data) +thread_func(void *data) { ThreadData *td = data; time_t t, last; - pthread_cleanup_push (thread_cleanup_func, td); + pthread_cleanup_push(thread_cleanup_func, td); - last = time (NULL); - while (1) { - t = time (NULL); - if (t - last > td->timeout) { - td->func (td->camera, NULL); + last = time(NULL); + while (1) + { + t = time(NULL); + if (t - last > td->timeout) + { + td->func(td->camera, NULL); last = t; } - pthread_testcancel (); + pthread_testcancel(); } - pthread_cleanup_pop (1); + pthread_cleanup_pop(1); } static unsigned int -start_timeout_func (Camera *camera, unsigned int timeout, - CameraTimeoutFunc func, void __unused__ *data) +start_timeout_func(Camera *camera, unsigned int timeout, + CameraTimeoutFunc func, void __unused__ *data) { pthread_t tid; ThreadData *td; - td = malloc (sizeof (ThreadData)); + td = malloc(sizeof(ThreadData)); if (!td) return 0; - memset (td, 0, sizeof (ThreadData)); + memset(td, 0, sizeof(ThreadData)); td->camera = camera; td->timeout = timeout; td->func = func; - pthread_create (&tid, NULL, thread_func, td); + pthread_create(&tid, NULL, thread_func, td); return (tid); } static void -stop_timeout_func (Camera __unused__ *camera, unsigned int id, - void __unused__ *data) +stop_timeout_func(Camera __unused__ *camera, unsigned int id, + void __unused__ *data) { pthread_t tid = id; - pthread_cancel (tid); - pthread_join (tid, NULL); + pthread_cancel(tid); + pthread_join(tid, NULL); } #endif @@ -1358,46 +1485,46 @@ stop_timeout_func (Camera __unused__ *camera, unsigned int id, /* Misc functions */ /* ------------------------------------------------------------------ */ -void -cli_error_print (char *format, ...) +void cli_error_print(char *format, ...) { - va_list pvar; + va_list pvar; - fprintf(stderr, _("ERROR: ")); - va_start(pvar, format); - vfprintf(stderr, format, pvar); - va_end(pvar); - fprintf(stderr, "\n"); + fprintf(stderr, _("ERROR: ")); + va_start(pvar, format); + vfprintf(stderr, format, pvar); + va_end(pvar); + fprintf(stderr, "\n"); } static void -signal_resize (int __unused__ signo) +signal_resize(int __unused__ signo) { const char *columns; - columns = getenv ("COLUMNS"); - if (columns && atoi (columns)) - gp_params.cols = atoi (columns); + columns = getenv("COLUMNS"); + if (columns && atoi(columns)) + gp_params.cols = atoi(columns); } static void -signal_exit (int __unused__ signo) +signal_exit(int __unused__ signo) { /* If we already were told to cancel, abort. */ - if (glob_cancel) { + if (glob_cancel) + { if ((gp_params.flags & FLAGS_QUIET) == 0) - printf (_("\nAborting...\n")); + printf(_("\nAborting...\n")); if (gp_params.camera) - gp_camera_unref (gp_params.camera); + gp_camera_unref(gp_params.camera); if (gp_params.context) - gp_context_unref (gp_params.context); + gp_context_unref(gp_params.context); if ((gp_params.flags & FLAGS_QUIET) == 0) - printf (_("Aborted.\n")); - exit (EXIT_FAILURE); + printf(_("Aborted.\n")); + exit(EXIT_FAILURE); } if ((gp_params.flags & FLAGS_QUIET) == 0) - printf (_("\nCancelling...\n")); + printf(_("\nCancelling...\n")); glob_cancel = 1; glob_interval = 0; @@ -1406,7 +1533,8 @@ signal_exit (int __unused__ signo) /* Main :) */ /* -------------------------------------------------------------------- */ -typedef enum { +typedef enum +{ ARG_ABILITIES, ARG_ABOUT, ARG_SHOW_PREVIEW, @@ -1442,7 +1570,8 @@ typedef enum { ARG_WAIT_EVENT } Arg; -typedef enum { +typedef enum +{ CALLBACK_PARAMS_TYPE_NONE, CALLBACK_PARAMS_TYPE_PREINITIALIZE, CALLBACK_PARAMS_TYPE_INITIALIZE, @@ -1451,9 +1580,11 @@ typedef enum { } CallbackParamsType; typedef struct _CallbackParams CallbackParams; -struct _CallbackParams { +struct _CallbackParams +{ CallbackParamsType type; - union { + union + { /* * CALLBACK_PARAMS_TYPE_RUN, * CALLBACK_PARAMS_TYPE_INITIALIZE, @@ -1462,7 +1593,8 @@ struct _CallbackParams { int r; /* CALLBACK_PARAMS_TYPE_QUERY */ - struct { + struct + { Arg arg; char found; } q; @@ -1471,69 +1603,70 @@ struct _CallbackParams { } p; }; - /*! \brief popt callback with type CALLBACK_PARAMS_TYPE_QUERY */ static void -cb_arg_query (poptContext __unused__ ctx, - enum poptCallbackReason __unused__ reason, - const struct poptOption *opt, const char __unused__ *arg, - CallbackParams *params) +cb_arg_query(poptContext __unused__ ctx, + enum poptCallbackReason __unused__ reason, + const struct poptOption *opt, const char __unused__ *arg, + CallbackParams *params) { /* p.q.arg is an enum, but opt->val is an int */ if (opt->val == (int)(params->p.q.arg)) params->p.q.found = 1; } - /*! \brief popt callback with type CALLBACK_PARAMS_TYPE_PREINITIALIZE */ static void -cb_arg_preinit (poptContext __unused__ ctx, - enum poptCallbackReason __unused__ reason, - const struct poptOption *opt, const char *arg, - CallbackParams *params) +cb_arg_preinit(poptContext __unused__ ctx, + enum poptCallbackReason __unused__ reason, + const struct poptOption *opt, const char *arg, + CallbackParams *params) { int usb_product, usb_vendor; int usb_product_modified, usb_vendor_modified; - switch (opt->val) { + switch (opt->val) + { case ARG_USBID: - gp_log (GP_LOG_DEBUG, "main", "Overriding USB " - "IDs to '%s'...", arg); - if (sscanf (arg, "0x%x:0x%x=0x%x:0x%x", - &usb_vendor_modified, - &usb_product_modified, &usb_vendor, - &usb_product) != 4) { - printf (_("Use the following syntax a:b=c:d " - "to treat any USB device detected " - "as a:b as c:d instead. " - "a b c d should be hexadecimal " - "numbers beginning with '0x'.\n")); + gp_log(GP_LOG_DEBUG, "main", "Overriding USB " + "IDs to '%s'...", + arg); + if (sscanf(arg, "0x%x:0x%x=0x%x:0x%x", + &usb_vendor_modified, + &usb_product_modified, &usb_vendor, + &usb_product) != 4) + { + printf(_("Use the following syntax a:b=c:d " + "to treat any USB device detected " + "as a:b as c:d instead. " + "a b c d should be hexadecimal " + "numbers beginning with '0x'.\n")); params->p.r = GP_ERROR_BAD_PARAMETERS; break; } - params->p.r = override_usbids_action (&gp_params, usb_vendor, - usb_product, usb_vendor_modified, - usb_product_modified); + params->p.r = override_usbids_action(&gp_params, usb_vendor, + usb_product, usb_vendor_modified, + usb_product_modified); break; default: break; } } - /*! \brief popt callback with type CALLBACK_PARAMS_TYPE_INITIALIZE */ static void -cb_arg_init (poptContext __unused__ ctx, - enum poptCallbackReason __unused__ reason, - const struct poptOption *opt, const char *arg, - CallbackParams *params) +cb_arg_init(poptContext __unused__ ctx, + enum poptCallbackReason __unused__ reason, + const struct poptOption *opt, const char *arg, + CallbackParams *params) { - switch (opt->val) { + switch (opt->val) + { case ARG_KEEP_RAW: gp_params.flags |= FLAGS_KEEP_RAW; @@ -1551,18 +1684,20 @@ cb_arg_init (poptContext __unused__ ctx, break; case ARG_MODEL: - gp_log (GP_LOG_DEBUG, "main", "Processing 'model' " - "option ('%s')...", arg); - params->p.r = action_camera_set_model (&gp_params, arg); + gp_log(GP_LOG_DEBUG, "main", "Processing 'model' " + "option ('%s')...", + arg); + params->p.r = action_camera_set_model(&gp_params, arg); break; case ARG_PORT: - gp_log (GP_LOG_DEBUG, "main", "Processing 'port' " - "option ('%s')...", arg); - params->p.r = action_camera_set_port (&gp_params, arg); + gp_log(GP_LOG_DEBUG, "main", "Processing 'port' " + "option ('%s')...", + arg); + params->p.r = action_camera_set_port(&gp_params, arg); break; case ARG_SPEED: - params->p.r = action_camera_set_speed (&gp_params, atoi (arg)); + params->p.r = action_camera_set_speed(&gp_params, atoi(arg)); break; case ARG_QUIET: @@ -1574,8 +1709,8 @@ cb_arg_init (poptContext __unused__ ctx, break; case ARG_VERSION: - params->p.r = print_version_action (&gp_params); - printf( "%-16s%-15s%s\n", "mongoose", MG_VERSION, "webserver" ); + params->p.r = print_version_action(&gp_params); + printf("%-16s%-15s%s\n", "mongoose", MG_VERSION, "webserver"); break; default: break; @@ -1586,155 +1721,169 @@ cb_arg_init (poptContext __unused__ ctx, */ static void -cb_arg_run (poptContext __unused__ ctx, - enum poptCallbackReason __unused__ reason, - const struct poptOption *opt, const char *arg, - CallbackParams *params) +cb_arg_run(poptContext __unused__ ctx, + enum poptCallbackReason __unused__ reason, + const struct poptOption *opt, const char *arg, + CallbackParams *params) { char *newfilename = NULL, *newfolder = NULL; - switch (opt->val) { + switch (opt->val) + { case ARG_ABILITIES: - params->p.r = action_camera_show_abilities (&gp_params); + params->p.r = action_camera_show_abilities(&gp_params); break; case ARG_ABOUT: - params->p.r = action_camera_about (&gp_params); + params->p.r = action_camera_about(&gp_params); break; case ARG_SHOW_PREVIEW: - params->p.r = action_camera_show_preview (&gp_params); + params->p.r = action_camera_show_preview(&gp_params); break; case ARG_CONFIG: #ifdef HAVE_CDK - params->p.r = gp_cmd_config (gp_params.camera, gp_params.context); + params->p.r = gp_cmd_config(gp_params.camera, gp_params.context); #else - gp_context_error (gp_params.context, - _("gphoto2 has been compiled without " - "support for CDK.")); + gp_context_error(gp_params.context, + _("gphoto2 has been compiled without " + "support for CDK.")); params->p.r = GP_ERROR_NOT_SUPPORTED; #endif break; case ARG_LIST_CAMERAS: - params->p.r = list_cameras_action (&gp_params); + params->p.r = list_cameras_action(&gp_params); break; case ARG_LIST_PORTS: - params->p.r = list_ports_action (&gp_params); + params->p.r = list_ports_action(&gp_params); break; - case ARG_RESET: { - GPPort *port; - GPPortInfo info; + case ARG_RESET: + { + GPPort *port; + GPPortInfo info; /* If a camera is already open, close it, as we need a new port */ - if (gp_params.camera) { - gp_camera_exit (gp_params.camera, gp_params.context); + if (gp_params.camera) + { + gp_camera_exit(gp_params.camera, gp_params.context); /* exit, not free. will reopen on next command. */ } - params->p.r = gp_port_new (&port); - if (params->p.r != GP_OK) { - gp_log(GP_LOG_ERROR,"port_reset", "new failed %d", params->p.r); + params->p.r = gp_port_new(&port); + if (params->p.r != GP_OK) + { + gp_log(GP_LOG_ERROR, "port_reset", "new failed %d", params->p.r); break; } - params->p.r = gp_camera_get_port_info (gp_params.camera, &info); - if (params->p.r != GP_OK) { - gp_log(GP_LOG_ERROR,"port_reset", "camera_get_port_info failed"); + params->p.r = gp_camera_get_port_info(gp_params.camera, &info); + if (params->p.r != GP_OK) + { + gp_log(GP_LOG_ERROR, "port_reset", "camera_get_port_info failed"); break; } - params->p.r = gp_port_set_info (port, info); - if (params->p.r != GP_OK) { - gp_log(GP_LOG_ERROR,"port_reset", "port_set_info failed"); + params->p.r = gp_port_set_info(port, info); + if (params->p.r != GP_OK) + { + gp_log(GP_LOG_ERROR, "port_reset", "port_set_info failed"); break; } - params->p.r = gp_port_open (port); - if (params->p.r != GP_OK) { - gp_log(GP_LOG_ERROR,"port_reset", "open failed %d", params->p.r); + params->p.r = gp_port_open(port); + if (params->p.r != GP_OK) + { + gp_log(GP_LOG_ERROR, "port_reset", "open failed %d", params->p.r); break; } - params->p.r = gp_port_reset (port); - gp_port_close (port); - gp_port_free (port); + params->p.r = gp_port_reset(port); + gp_port_close(port); + gp_port_free(port); break; } case ARG_SERVER_URL: - strncpy( webcfg.server_url, arg, WEBCFG_STR_LEN); + strncpy(webcfg.server_url, arg, WEBCFG_STR_LEN); webcfg.server_url[WEBCFG_STR_LEN] = 0; break; case ARG_SERVER: - params->p.r = webapi_server (&gp_params); + params->p.r = webapi_server(&gp_params); break; case ARG_SHOW_INFO: /* Did the user specify a file or a range? */ - if (strchr (arg, '.')) { - dissolve_filename (gp_params.folder, arg, &newfolder, &newfilename); - params->p.r = print_info_action (&gp_params, newfolder, newfilename); - free (newfolder); free (newfilename); + if (strchr(arg, '.')) + { + dissolve_filename(gp_params.folder, arg, &newfolder, &newfilename); + params->p.r = print_info_action(&gp_params, newfolder, newfilename); + free(newfolder); + free(newfilename); break; } - params->p.r = for_each_file_in_range (&gp_params, - print_info_action, arg); + params->p.r = for_each_file_in_range(&gp_params, + print_info_action, arg); break; case ARG_SUMMARY: - params->p.r = action_camera_summary (&gp_params); + params->p.r = action_camera_summary(&gp_params); break; case ARG_GET_CONFIG: - params->p.r = get_config_action (&gp_params, arg); + params->p.r = get_config_action(&gp_params, arg); break; - case ARG_SET_CONFIG: { + case ARG_SET_CONFIG: + { char *name, *value; - if (strchr (arg, '=') == NULL) { + if (strchr(arg, '=') == NULL) + { params->p.r = GP_ERROR_BAD_PARAMETERS; break; } - name = strdup (arg); - value = strchr (name, '='); + name = strdup(arg); + value = strchr(name, '='); *value = '\0'; value++; - params->p.r = set_config_action (&gp_params, name, value); - free (name); + params->p.r = set_config_action(&gp_params, name, value); + free(name); break; } - case ARG_SET_CONFIG_INDEX: { + case ARG_SET_CONFIG_INDEX: + { char *name, *value; - if (strchr (arg, '=') == NULL) { + if (strchr(arg, '=') == NULL) + { params->p.r = GP_ERROR_BAD_PARAMETERS; break; } - name = strdup (arg); - value = strchr (name, '='); + name = strdup(arg); + value = strchr(name, '='); *value = '\0'; value++; - params->p.r = set_config_index_action (&gp_params, name, value); - free (name); + params->p.r = set_config_index_action(&gp_params, name, value); + free(name); break; } - case ARG_SET_CONFIG_VALUE: { + case ARG_SET_CONFIG_VALUE: + { char *name, *value; - if (strchr (arg, '=') == NULL) { + if (strchr(arg, '=') == NULL) + { params->p.r = GP_ERROR_BAD_PARAMETERS; break; } - name = strdup (arg); - value = strchr (name, '='); + name = strdup(arg); + value = strchr(name, '='); *value = '\0'; value++; - params->p.r = set_config_value_action (&gp_params, name, value); - free (name); + params->p.r = set_config_value_action(&gp_params, name, value); + free(name); break; } case ARG_WAIT_EVENT: - params->p.r = action_camera_wait_event (&gp_params, DT_NO_DOWNLOAD, arg); + params->p.r = action_camera_wait_event(&gp_params, DT_NO_DOWNLOAD, arg); break; case ARG_STORAGE_INFO: - params->p.r = print_storage_info (&gp_params); + params->p.r = print_storage_info(&gp_params); break; default: break; }; } - /*! \brief Callback function called while parsing command line options. * * This callback function is called multiple times in multiple @@ -1742,101 +1891,108 @@ cb_arg_run (poptContext __unused__ ctx, */ static void -cb_arg (poptContext __unused__ ctx, - enum poptCallbackReason __unused__ reason, - const struct poptOption *opt, const char *arg, - void *data) +cb_arg(poptContext __unused__ ctx, + enum poptCallbackReason __unused__ reason, + const struct poptOption *opt, const char *arg, + void *data) { - CallbackParams *params = (CallbackParams *) data; + CallbackParams *params = (CallbackParams *)data; /* Check if we are only to query. */ - switch (params->type) { + switch (params->type) + { case CALLBACK_PARAMS_TYPE_NONE: /* do nothing */ break; case CALLBACK_PARAMS_TYPE_QUERY: - cb_arg_query (ctx, reason, opt, arg, params); + cb_arg_query(ctx, reason, opt, arg, params); break; case CALLBACK_PARAMS_TYPE_PREINITIALIZE: - cb_arg_preinit (ctx, reason, opt, arg, params); + cb_arg_preinit(ctx, reason, opt, arg, params); break; case CALLBACK_PARAMS_TYPE_INITIALIZE: /* Check if we are only to initialize. */ - cb_arg_init (ctx, reason, opt, arg, params); + cb_arg_init(ctx, reason, opt, arg, params); break; case CALLBACK_PARAMS_TYPE_RUN: - cb_arg_run (ctx, reason, opt, arg, params); + cb_arg_run(ctx, reason, opt, arg, params); break; } } - static void -report_failure (int result, int argc, char **argv) +report_failure(int result, int argc, char **argv) { if (result >= 0) return; - if (result == GP_ERROR_CANCEL) { - fprintf (stderr, _("Operation cancelled.\n")); + if (result == GP_ERROR_CANCEL) + { + fprintf(stderr, _("Operation cancelled.\n")); return; } - if (result == -2000) { - fprintf (stderr, _("*** Error: No camera found. ***\n\n")); - } else { - fprintf (stderr, _("*** Error (%i: '%s') *** \n\n"), result, - gp_result_as_string (result)); + if (result == -2000) + { + fprintf(stderr, _("*** Error: No camera found. ***\n\n")); } - if (!debug_option_given) { + else + { + fprintf(stderr, _("*** Error (%i: '%s') *** \n\n"), result, + gp_result_as_string(result)); + } + if (!debug_option_given) + { int n; - printf (_("For debugging messages, please use " - "the --debug option.\n" - "Debugging messages may help finding a " - "solution to your problem.\n" - "If you intend to send any error or " - "debug messages to the gphoto\n" - "developer mailing list " - ", please run\n" - "gphoto2 as follows:\n\n")); + printf(_("For debugging messages, please use " + "the --debug option.\n" + "Debugging messages may help finding a " + "solution to your problem.\n" + "If you intend to send any error or " + "debug messages to the gphoto\n" + "developer mailing list " + ", please run\n" + "gphoto2 as follows:\n\n")); /* * Print the exact command line to assist bugreporters */ - printf (" env LANG=C gphoto2 --debug --debug-logfile=my-logfile.txt"); - for (n = 1; n < argc; n++) { + printf(" env LANG=C gphoto2 --debug --debug-logfile=my-logfile.txt"); + for (n = 1; n < argc; n++) + { if (strchr(argv[n], ' ') == NULL) - printf(" %s",argv[n]); + printf(" %s", argv[n]); else - printf(" \"%s\"",argv[n]); + printf(" \"%s\"", argv[n]); } - printf ("\n\n"); - printf (_("Please make sure there is sufficient quoting " - "around the arguments.\n\n")); + printf("\n\n"); + printf(_("Please make sure there is sufficient quoting " + "around the arguments.\n\n")); } } -#define CR_MAIN(result) \ - do { \ - int r = (result); \ - \ - if (r < 0) { \ - report_failure (r, argc, argv); \ - \ - /* Run stop hook */ \ - gp_params_run_hook(&gp_params, "stop", NULL); \ - \ - gp_params_exit (&gp_params); \ - poptFreeContext(ctx); \ - return (EXIT_FAILURE); \ - } \ +#define CR_MAIN(result) \ + do \ + { \ + int r = (result); \ + \ + if (r < 0) \ + { \ + report_failure(r, argc, argv); \ + \ + /* Run stop hook */ \ + gp_params_run_hook(&gp_params, "stop", NULL); \ + \ + gp_params_exit(&gp_params); \ + poptFreeContext(ctx); \ + return (EXIT_FAILURE); \ + } \ } while (0) -#define GPHOTO2_POPT_CALLBACK \ +#define GPHOTO2_POPT_CALLBACK \ {NULL, '\0', POPT_ARG_CALLBACK, \ - (void *) &cb_arg, 0, (char *) &cb_params, NULL}, + (void *)&cb_arg, 0, (char *)&cb_params, NULL}, -int -main (int argc, char **argv, char **envp) +int main(int argc, char **argv, char **envp) { CallbackParams cb_params; poptContext ctx; @@ -1845,117 +2001,105 @@ main (int argc, char **argv, char **envp) char *debug_logfile_name = NULL, *debug_loglevel = NULL; const struct poptOption generalOptions[] = { - GPHOTO2_POPT_CALLBACK - {"help", '?', POPT_ARG_NONE, (void *) &help_option_given, ARG_HELP, - N_("Print complete help message on program usage"), NULL}, - {"usage", '\0', POPT_ARG_NONE, (void *) &usage_option_given, ARG_USAGE, - N_("Print short message on program usage"), NULL}, - {"debug", '\0', POPT_ARG_NONE, (void *) &debug_option_given, ARG_DEBUG, - N_("Turn on debugging"), NULL}, - {"debug-loglevel", '\0', POPT_ARG_STRING, (void *) &debug_loglevel, ARG_DEBUG_LOGLEVEL, - N_("Set debug level [error|debug|data|all]"), NULL}, - {"debug-logfile", '\0', POPT_ARG_STRING, (void *) &debug_logfile_name, ARG_DEBUG_LOGFILE, - N_("Name of file to write debug info to"), N_("FILENAME")}, - {"quiet", 'q', POPT_ARG_NONE, NULL, ARG_QUIET, - N_("Quiet output (default=verbose)"), NULL}, - POPT_TABLEEND - }; + GPHOTO2_POPT_CALLBACK{"help", '?', POPT_ARG_NONE, (void *)&help_option_given, ARG_HELP, + N_("Print complete help message on program usage"), NULL}, + {"usage", '\0', POPT_ARG_NONE, (void *)&usage_option_given, ARG_USAGE, + N_("Print short message on program usage"), NULL}, + {"debug", '\0', POPT_ARG_NONE, (void *)&debug_option_given, ARG_DEBUG, + N_("Turn on debugging"), NULL}, + {"debug-loglevel", '\0', POPT_ARG_STRING, (void *)&debug_loglevel, ARG_DEBUG_LOGLEVEL, + N_("Set debug level [error|debug|data|all]"), NULL}, + {"debug-logfile", '\0', POPT_ARG_STRING, (void *)&debug_logfile_name, ARG_DEBUG_LOGFILE, + N_("Name of file to write debug info to"), N_("FILENAME")}, + {"quiet", 'q', POPT_ARG_NONE, NULL, ARG_QUIET, + N_("Quiet output (default=verbose)"), NULL}, + POPT_TABLEEND}; const struct poptOption cameraOptions[] = { - GPHOTO2_POPT_CALLBACK - {"port", '\0', POPT_ARG_STRING, NULL, ARG_PORT, - N_("Specify device port"), N_("FILENAME")}, - {"speed", '\0', POPT_ARG_INT, NULL, ARG_SPEED, - N_("Specify serial transfer speed"), N_("SPEED")}, - {"camera", '\0', POPT_ARG_STRING, NULL, ARG_MODEL, - N_("Specify camera model"), N_("MODEL")}, - {"usbid", '\0', POPT_ARG_STRING, NULL, ARG_USBID, - N_("(expert only) Override USB IDs"), N_("USBIDs")}, - POPT_TABLEEND - }; + GPHOTO2_POPT_CALLBACK{"port", '\0', POPT_ARG_STRING, NULL, ARG_PORT, + N_("Specify device port"), N_("FILENAME")}, + {"speed", '\0', POPT_ARG_INT, NULL, ARG_SPEED, + N_("Specify serial transfer speed"), N_("SPEED")}, + {"camera", '\0', POPT_ARG_STRING, NULL, ARG_MODEL, + N_("Specify camera model"), N_("MODEL")}, + {"usbid", '\0', POPT_ARG_STRING, NULL, ARG_USBID, + N_("(expert only) Override USB IDs"), N_("USBIDs")}, + POPT_TABLEEND}; const struct poptOption infoOptions[] = { - GPHOTO2_POPT_CALLBACK - {"version", 'v', POPT_ARG_NONE, NULL, ARG_VERSION, - N_("Display version and exit"), NULL}, - {"list-cameras", '\0', POPT_ARG_NONE, NULL, ARG_LIST_CAMERAS, - N_("List supported camera models"), NULL}, - {"list-ports", '\0', POPT_ARG_NONE, NULL, ARG_LIST_PORTS, - N_("List supported port devices"), NULL}, - {"abilities", 'a', POPT_ARG_NONE, NULL, ARG_ABILITIES, - N_("Display the camera/driver abilities in the libgphoto2 database"), NULL}, - POPT_TABLEEND - }; + GPHOTO2_POPT_CALLBACK{"version", 'v', POPT_ARG_NONE, NULL, ARG_VERSION, + N_("Display version and exit"), NULL}, + {"list-cameras", '\0', POPT_ARG_NONE, NULL, ARG_LIST_CAMERAS, + N_("List supported camera models"), NULL}, + {"list-ports", '\0', POPT_ARG_NONE, NULL, ARG_LIST_PORTS, + N_("List supported port devices"), NULL}, + {"abilities", 'a', POPT_ARG_NONE, NULL, ARG_ABILITIES, + N_("Display the camera/driver abilities in the libgphoto2 database"), NULL}, + POPT_TABLEEND}; const struct poptOption configOptions[] = { - GPHOTO2_POPT_CALLBACK + GPHOTO2_POPT_CALLBACK #ifdef HAVE_CDK - {"config", '\0', POPT_ARG_NONE, NULL, ARG_CONFIG, - N_("Configure"), NULL}, + {"config", '\0', POPT_ARG_NONE, NULL, ARG_CONFIG, + N_("Configure"), NULL}, #endif - {"get-config", '\0', POPT_ARG_STRING, NULL, ARG_GET_CONFIG, - N_("Get configuration value"), NULL}, - {"set-config", '\0', POPT_ARG_STRING, NULL, ARG_SET_CONFIG, - N_("Set configuration value or index in choices"), NULL}, - {"set-config-index", '\0', POPT_ARG_STRING, NULL, ARG_SET_CONFIG_INDEX, - N_("Set configuration value index in choices"), NULL}, - {"set-config-value", '\0', POPT_ARG_STRING, NULL, ARG_SET_CONFIG_VALUE, - N_("Set configuration value"), NULL}, - {"reset", '\0', POPT_ARG_NONE, NULL, ARG_RESET, - N_("Reset device port"), NULL}, - POPT_TABLEEND - }; + {"get-config", '\0', POPT_ARG_STRING, NULL, ARG_GET_CONFIG, + N_("Get configuration value"), NULL}, + {"set-config", '\0', POPT_ARG_STRING, NULL, ARG_SET_CONFIG, + N_("Set configuration value or index in choices"), NULL}, + {"set-config-index", '\0', POPT_ARG_STRING, NULL, ARG_SET_CONFIG_INDEX, + N_("Set configuration value index in choices"), NULL}, + {"set-config-value", '\0', POPT_ARG_STRING, NULL, ARG_SET_CONFIG_VALUE, + N_("Set configuration value"), NULL}, + {"reset", '\0', POPT_ARG_NONE, NULL, ARG_RESET, + N_("Reset device port"), NULL}, + POPT_TABLEEND}; + const struct poptOption captureOptions[] = { - GPHOTO2_POPT_CALLBACK - {"keep", '\0', POPT_ARG_NONE, NULL, ARG_KEEP, - N_("Keep images on camera after capturing"), NULL}, - {"keep-raw", '\0', POPT_ARG_NONE, NULL, ARG_KEEP_RAW, - N_("Keep RAW images on camera after capturing"), NULL}, - {"no-keep", '\0', POPT_ARG_NONE, NULL, ARG_NO_KEEP, - N_("Remove images from camera after capturing"), NULL}, - {"wait-event", '\0', POPT_ARG_STRING|POPT_ARGFLAG_OPTIONAL, NULL, ARG_WAIT_EVENT, - N_("Wait for event(s) from camera"), N_("EVENT")}, - {"show-preview", '\0', POPT_ARG_NONE, NULL, - ARG_SHOW_PREVIEW, - N_("Show a quick preview as Ascii Art"), NULL}, - {"reset-interval", '\0', POPT_ARG_NONE, NULL, ARG_RESET_INTERVAL, - N_("Reset capture interval on signal (default=no)"), NULL}, - POPT_TABLEEND - }; + GPHOTO2_POPT_CALLBACK{"keep", '\0', POPT_ARG_NONE, NULL, ARG_KEEP, + N_("Keep images on camera after capturing"), NULL}, + {"keep-raw", '\0', POPT_ARG_NONE, NULL, ARG_KEEP_RAW, + N_("Keep RAW images on camera after capturing"), NULL}, + {"no-keep", '\0', POPT_ARG_NONE, NULL, ARG_NO_KEEP, + N_("Remove images from camera after capturing"), NULL}, + {"wait-event", '\0', POPT_ARG_STRING | POPT_ARGFLAG_OPTIONAL, NULL, ARG_WAIT_EVENT, + N_("Wait for event(s) from camera"), N_("EVENT")}, + {"show-preview", '\0', POPT_ARG_NONE, NULL, + ARG_SHOW_PREVIEW, + N_("Show a quick preview as Ascii Art"), NULL}, + {"reset-interval", '\0', POPT_ARG_NONE, NULL, ARG_RESET_INTERVAL, + N_("Reset capture interval on signal (default=no)"), NULL}, + POPT_TABLEEND}; const struct poptOption miscOptions[] = { - GPHOTO2_POPT_CALLBACK - {"show-info", '\0', POPT_ARG_STRING, NULL, ARG_SHOW_INFO, - N_("Show image information, like width, height, and capture time"), NULL}, - {"summary", '\0', POPT_ARG_NONE, NULL, ARG_SUMMARY, - N_("Show camera summary"), NULL}, - {"about", '\0', POPT_ARG_NONE, NULL, ARG_ABOUT, - N_("About the camera driver manual"), NULL}, - {"storage-info", '\0', POPT_ARG_NONE, NULL, ARG_STORAGE_INFO, - N_("Show storage information"), NULL}, - {"server", '\0', POPT_ARG_NONE, NULL, ARG_SERVER, - N_("gPhoto webapi server"), NULL}, - {"server-url", '\0', POPT_ARG_STRING, NULL, ARG_SERVER_URL, - N_("Server URL - e.g http://0.0.0.0:8866"), NULL}, - POPT_TABLEEND - }; + GPHOTO2_POPT_CALLBACK{"show-info", '\0', POPT_ARG_STRING, NULL, ARG_SHOW_INFO, + N_("Show image information, like width, height, and capture time"), NULL}, + {"summary", '\0', POPT_ARG_NONE, NULL, ARG_SUMMARY, + N_("Show camera summary"), NULL}, + {"about", '\0', POPT_ARG_NONE, NULL, ARG_ABOUT, + N_("About the camera driver manual"), NULL}, + {"storage-info", '\0', POPT_ARG_NONE, NULL, ARG_STORAGE_INFO, + N_("Show storage information"), NULL}, + {"server", '\0', POPT_ARG_NONE, NULL, ARG_SERVER, + N_("gPhoto webapi server"), NULL}, + {"server-url", '\0', POPT_ARG_STRING, NULL, ARG_SERVER_URL, + N_("Server URL - e.g http://0.0.0.0:8866"), NULL}, + POPT_TABLEEND}; const struct poptOption options[] = { - GPHOTO2_POPT_CALLBACK - {NULL, '\0', POPT_ARG_INCLUDE_TABLE, (void *) &generalOptions, 0, - N_("Common options"), NULL}, - {NULL, '\0', POPT_ARG_INCLUDE_TABLE, (void *) &miscOptions, 0, - N_("Miscellaneous options (unsorted)"), NULL}, - {NULL, '\0', POPT_ARG_INCLUDE_TABLE, (void *) &infoOptions, 0, - N_("Get information on software and host system (not from the camera)"), NULL}, - {NULL, '\0', POPT_ARG_INCLUDE_TABLE, (void *) &cameraOptions, 0, - N_("Specify the camera to use"), NULL}, - {NULL, '\0', POPT_ARG_INCLUDE_TABLE, (void *) &configOptions, 0, - N_("Camera and software configuration"), NULL}, - {NULL, '\0', POPT_ARG_INCLUDE_TABLE, (void *) &captureOptions, 0, - N_("Capture an image from or on the camera"), NULL}, - POPT_TABLEEND - }; + GPHOTO2_POPT_CALLBACK{NULL, '\0', POPT_ARG_INCLUDE_TABLE, (void *)&generalOptions, 0, + N_("Common options"), NULL}, + {NULL, '\0', POPT_ARG_INCLUDE_TABLE, (void *)&miscOptions, 0, + N_("Miscellaneous options (unsorted)"), NULL}, + {NULL, '\0', POPT_ARG_INCLUDE_TABLE, (void *)&infoOptions, 0, + N_("Get information on software and host system (not from the camera)"), NULL}, + {NULL, '\0', POPT_ARG_INCLUDE_TABLE, (void *)&cameraOptions, 0, + N_("Specify the camera to use"), NULL}, + {NULL, '\0', POPT_ARG_INCLUDE_TABLE, (void *)&configOptions, 0, + N_("Camera and software configuration"), NULL}, + {NULL, '\0', POPT_ARG_INCLUDE_TABLE, (void *)&captureOptions, 0, + N_("Capture an image from or on the camera"), NULL}, + POPT_TABLEEND}; CameraAbilities a; GPPortInfo info; @@ -1964,11 +2108,11 @@ main (int argc, char **argv, char **envp) cb_params.type = CALLBACK_PARAMS_TYPE_NONE; /* For translation */ - setlocale (LC_ALL, ""); - bindtextdomain (PACKAGE, LOCALEDIR); - textdomain (PACKAGE); + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); - webapi_server_initialize(); + webapi_server_initialize(); /* These are for people signaling us */ capture_now = 0; @@ -1979,20 +2123,21 @@ main (int argc, char **argv, char **envp) /* Create/Initialize the global variables before we first use * them. And the signal handlers and popt callback functions * do use them. */ - gp_params_init (&gp_params, envp); + gp_params_init(&gp_params, envp); /* Figure out the width of the terminal and watch out for changes */ - signal_resize (0); + signal_resize(0); #ifdef SIGWINCH - signal (SIGWINCH, signal_resize); + signal(SIGWINCH, signal_resize); #endif /* Prepare processing options. */ - ctx = poptGetContext (PACKAGE, argc, (const char **) argv, options, 0); - if (argc <= 1) { - poptPrintUsage (ctx, stdout, 0); - gp_params_exit (&gp_params); - poptFreeContext(ctx); + ctx = poptGetContext(PACKAGE, argc, (const char **)argv, options, 0); + if (argc <= 1) + { + poptPrintUsage(ctx, stdout, 0); + gp_params_exit(&gp_params); + poptFreeContext(ctx); return (0); } @@ -2000,262 +2145,297 @@ main (int argc, char **argv, char **envp) * Do we need debugging output? While we are at it, scan the * options for bad ones. */ - poptResetContext (ctx); - while ((result = poptGetNextOpt (ctx)) >= 0); - if (result == POPT_ERROR_BADOPT) { - poptPrintUsage (ctx, stderr, 0); - gp_params_exit (&gp_params); - poptFreeContext(ctx); + poptResetContext(ctx); + while ((result = poptGetNextOpt(ctx)) >= 0) + ; + if (result == POPT_ERROR_BADOPT) + { + poptPrintUsage(ctx, stderr, 0); + gp_params_exit(&gp_params); + poptFreeContext(ctx); return (EXIT_FAILURE); } - if (help_option_given) { + + if (help_option_given) + { poptPrintHelp(ctx, stdout, 0); fprintf(stdout, "\n%s", - _("EVENT can be either COUNT, SECONDS, MILLISECONDS, or MATCHSTRING.\n")); - gp_params_exit (&gp_params); - poptFreeContext(ctx); + _("EVENT can be either COUNT, SECONDS, MILLISECONDS, or MATCHSTRING.\n")); + gp_params_exit(&gp_params); + poptFreeContext(ctx); return 0; } - if (usage_option_given) { + + if (usage_option_given) + { poptPrintUsage(ctx, stdout, 0); - gp_params_exit (&gp_params); - poptFreeContext(ctx); + gp_params_exit(&gp_params); + poptFreeContext(ctx); return 0; } - if (debug_option_given) { - CR_MAIN (debug_action (&gp_params, debug_loglevel, debug_logfile_name)); - } - gp_log (GP_LOG_DEBUG, "main", "invoked with following arguments:"); - for (i=1;i= GP_OK) && (poptGetNextOpt (ctx) >= 0)); + poptResetContext(ctx); + while ((cb_params.p.r >= GP_OK) && (poptGetNextOpt(ctx) >= 0)) + ; - CR_MAIN (cb_params.p.r); + CR_MAIN(cb_params.p.r); cb_params.type = CALLBACK_PARAMS_TYPE_INITIALIZE; - poptResetContext (ctx); - while ((cb_params.p.r >= GP_OK) && (poptGetNextOpt (ctx) >= 0)); + poptResetContext(ctx); + while ((cb_params.p.r >= GP_OK) && (poptGetNextOpt(ctx) >= 0)) + ; /* Load default values for --filename and --hook-script if not * explicitely specified */ - if (!gp_params.filename) { + if (!gp_params.filename) + { char buf[256]; - if (gp_setting_get("gphoto2","filename",buf)>=0) { - set_filename_action(&gp_params,buf); - } + if (gp_setting_get("gphoto2", "filename", buf) >= 0) + { + set_filename_action(&gp_params, buf); + } } - if (!gp_params.hook_script) { + if (!gp_params.hook_script) + { char buf[PATH_MAX]; - if (gp_setting_get("gphoto2","hook-script",buf)>=0) { + if (gp_setting_get("gphoto2", "hook-script", buf) >= 0) + { gp_params.hook_script = strdup(buf); /* Run init hook */ - if (0!=gp_params_run_hook(&gp_params, "init", NULL)) { + if (0 != gp_params_run_hook(&gp_params, "init", NULL)) + { fprintf(stdout, - "Hook script \"%s\" init failed. Aborting.\n", - gp_params.hook_script); + "Hook script \"%s\" init failed. Aborting.\n", + gp_params.hook_script); exit(3); } - } + } } - CR_MAIN (cb_params.p.r); - -#define CHECK_OPT(o) \ - if (!cb_params.p.q.found) { \ - cb_params.p.q.arg = (o); \ - poptResetContext (ctx); \ - while (poptGetNextOpt (ctx) >= 0); \ + CR_MAIN(cb_params.p.r); + +#define CHECK_OPT(o) \ + if (!cb_params.p.q.found) \ + { \ + cb_params.p.q.arg = (o); \ + poptResetContext(ctx); \ + while (poptGetNextOpt(ctx) >= 0) \ + ; \ } /* If we need a camera, make sure we've got one. */ - CR_MAIN (gp_camera_get_abilities (gp_params.camera, &a)); - CR_MAIN (gp_camera_get_port_info (gp_params.camera, &info)); + CR_MAIN(gp_camera_get_abilities(gp_params.camera, &a)); + CR_MAIN(gp_camera_get_port_info(gp_params.camera, &info)); /* Determine which command is given on command line */ cb_params.type = CALLBACK_PARAMS_TYPE_QUERY; cb_params.p.q.found = 0; - CHECK_OPT (ARG_ABILITIES); - CHECK_OPT (ARG_SHOW_PREVIEW); - CHECK_OPT (ARG_CONFIG); - CHECK_OPT (ARG_GET_CONFIG); - CHECK_OPT (ARG_RESET); - CHECK_OPT (ARG_SET_CONFIG); - CHECK_OPT (ARG_SET_CONFIG_INDEX); - CHECK_OPT (ARG_SET_CONFIG_VALUE); - CHECK_OPT (ARG_SERVER); - CHECK_OPT (ARG_SERVER_URL); - CHECK_OPT (ARG_SHOW_INFO); - CHECK_OPT (ARG_STORAGE_INFO); - CHECK_OPT (ARG_SUMMARY); - CHECK_OPT (ARG_WAIT_EVENT); - gp_port_info_get_type (info, &type); + CHECK_OPT(ARG_ABILITIES); + CHECK_OPT(ARG_SHOW_PREVIEW); + CHECK_OPT(ARG_CONFIG); + CHECK_OPT(ARG_GET_CONFIG); + CHECK_OPT(ARG_RESET); + CHECK_OPT(ARG_SET_CONFIG); + CHECK_OPT(ARG_SET_CONFIG_INDEX); + CHECK_OPT(ARG_SET_CONFIG_VALUE); + CHECK_OPT(ARG_SERVER); + CHECK_OPT(ARG_SERVER_URL); + CHECK_OPT(ARG_SHOW_INFO); + CHECK_OPT(ARG_STORAGE_INFO); + CHECK_OPT(ARG_SUMMARY); + CHECK_OPT(ARG_WAIT_EVENT); + + gp_port_info_get_type(info, &type); if (cb_params.p.q.found && - (!strcmp (a.model, "") || (type == GP_PORT_NONE))) { + (!strcmp(a.model, "") || (type == GP_PORT_NONE))) + { int count; const char *model = NULL, *path = NULL; CameraList *list; char buf[1024]; int use_auto = 1; - gp_log (GP_LOG_DEBUG, "main", "The user has not specified " - "both a model and a port. Try to figure them out."); - + gp_log(GP_LOG_DEBUG, "main", "The user has not specified " + "both a model and a port. Try to figure them out."); _get_portinfo_list(&gp_params); - CR_MAIN (gp_list_new (&list)); /* no freeing below */ - CR_MAIN (gp_abilities_list_detect (gp_params_abilities_list(&gp_params), - gp_params.portinfo_list, - list, gp_params.context)); - CR_MAIN (count = gp_list_count (list)); - if (count == 1) { - /* Exactly one camera detected */ - CR_MAIN (gp_list_get_name (list, 0, &model)); - CR_MAIN (gp_list_get_value (list, 0, &path)); - if (a.model[0] && strcmp(a.model,model)) { + CR_MAIN(gp_list_new(&list)); /* no freeing below */ + CR_MAIN(gp_abilities_list_detect(gp_params_abilities_list(&gp_params), + gp_params.portinfo_list, + list, gp_params.context)); + CR_MAIN(count = gp_list_count(list)); + if (count == 1) + { + /* Exactly one camera detected */ + CR_MAIN(gp_list_get_name(list, 0, &model)); + CR_MAIN(gp_list_get_value(list, 0, &path)); + if (a.model[0] && strcmp(a.model, model)) + { CameraAbilities alt; int m; - CR_MAIN (m = gp_abilities_list_lookup_model ( - gp_params_abilities_list(&gp_params), - model)); - CR_MAIN (gp_abilities_list_get_abilities ( - gp_params_abilities_list(&gp_params), - m, &alt)); - - if ((a.port == GP_PORT_USB) && (alt.port == GP_PORT_USB)) { - if ( (a.usb_vendor == alt.usb_vendor) && - (a.usb_product == alt.usb_product) - ) + CR_MAIN(m = gp_abilities_list_lookup_model( + gp_params_abilities_list(&gp_params), + model)); + CR_MAIN(gp_abilities_list_get_abilities( + gp_params_abilities_list(&gp_params), + m, &alt)); + + if ((a.port == GP_PORT_USB) && (alt.port == GP_PORT_USB)) + { + if ((a.usb_vendor == alt.usb_vendor) && + (a.usb_product == alt.usb_product)) use_auto = 0; } } - if (use_auto) { - CR_MAIN (action_camera_set_model (&gp_params, model)); + if (use_auto) + { + CR_MAIN(action_camera_set_model(&gp_params, model)); } - if (type == GP_PORT_USB) { + if (type == GP_PORT_USB) + { char *xpath; - gp_port_info_get_path (info, &xpath); - if (strcmp(xpath, "usb:") && strcmp(xpath, path)) { - fprintf (stderr, _("Port %s not found\n"), xpath ); - CR_MAIN (GP_ERROR_UNKNOWN_PORT); + gp_port_info_get_path(info, &xpath); + if (strcmp(xpath, "usb:") && strcmp(xpath, path)) + { + fprintf(stderr, _("Port %s not found\n"), xpath); + CR_MAIN(GP_ERROR_UNKNOWN_PORT); } } - CR_MAIN (action_camera_set_port (&gp_params, path)); - - } else if (!count) { + CR_MAIN(action_camera_set_port(&gp_params, path)); + } + else if (!count) + { int ret; /* * No camera detected. Have a look at the settings. * Ignore errors here, it might be a serial one. */ - if (gp_setting_get ("gphoto2", "model", buf) >= 0) - action_camera_set_model (&gp_params, buf); - if (gp_setting_get ("gphoto2", "port", buf) >= 0) - action_camera_set_port (&gp_params, buf); - ret = gp_camera_init (gp_params.camera, gp_params.context); - if (ret != GP_OK) { + if (gp_setting_get("gphoto2", "model", buf) >= 0) + action_camera_set_model(&gp_params, buf); + if (gp_setting_get("gphoto2", "port", buf) >= 0) + action_camera_set_port(&gp_params, buf); + ret = gp_camera_init(gp_params.camera, gp_params.context); + if (ret != GP_OK) + { if (ret == GP_ERROR_BAD_PARAMETERS) ret = -2000; - CR_MAIN (ret); + CR_MAIN(ret); } - } else { + } + else + { /* If --port override, search the model with the same port. */ - if (type != GP_PORT_NONE) { + if (type != GP_PORT_NONE) + { int i; char *xpath, *xname; - gp_port_info_get_path (info, &xpath); - gp_port_info_get_name (info, &xname); - gp_log (GP_LOG_DEBUG, "gphoto2", "Looking for port ...\n"); - gp_log (GP_LOG_DEBUG, "gphoto2", "info.type = %d\n", type); - gp_log (GP_LOG_DEBUG, "gphoto2", "info.name = %s\n", xname); - gp_log (GP_LOG_DEBUG, "gphoto2", "info.path = %s\n", xpath); + gp_port_info_get_path(info, &xpath); + gp_port_info_get_name(info, &xname); + gp_log(GP_LOG_DEBUG, "gphoto2", "Looking for port ...\n"); + gp_log(GP_LOG_DEBUG, "gphoto2", "info.type = %d\n", type); + gp_log(GP_LOG_DEBUG, "gphoto2", "info.name = %s\n", xname); + gp_log(GP_LOG_DEBUG, "gphoto2", "info.path = %s\n", xpath); - for (i=0;i= GP_OK) && (poptGetNextOpt (ctx) >= 0)); + while ((cb_params.p.r >= GP_OK) && (poptGetNextOpt(ctx) >= 0)) + ; - CR_MAIN (cb_params.p.r); + CR_MAIN(cb_params.p.r); /* Run stop hook */ gp_params_run_hook(&gp_params, "stop", NULL); @@ -2263,7 +2443,7 @@ main (int argc, char **argv, char **envp) /* FIXME: Env var checks (e.g. for Windows, OS/2) should happen before * we load the camlibs */ - gp_params_exit (&gp_params); - poptFreeContext(ctx); - return EXIT_SUCCESS; + gp_params_exit(&gp_params); + poptFreeContext(ctx); + return EXIT_SUCCESS; } From 984eaf3403a0f5088a9df9d3bfdc316cdcf0b61e Mon Sep 17 00:00:00 2001 From: Thorsten Ludewig Date: Mon, 20 Jun 2022 07:39:05 +0200 Subject: [PATCH 32/97] basic auth implemented --- gphoto2/server.c | 51 ++++++++++++++++++++++++++++++++++-------------- gphoto2/server.h | 7 ++++--- 2 files changed, 40 insertions(+), 18 deletions(-) diff --git a/gphoto2/server.c b/gphoto2/server.c index 1ee50b75..3872fbe2 100644 --- a/gphoto2/server.c +++ b/gphoto2/server.c @@ -81,6 +81,7 @@ static const char *http_chunked_header = "HTTP/1.1 200 OK\r\n" "Transfer-Encoding: chunked\r\n\r\n"; static const char *content_type_application_json = "Content-Type: application/json\r\n"; +static const char *content_type_text_html = "Content-Type: text/html\r\n"; #define MG_HTTP_CHUNK_START mg_printf(c, http_chunked_header) #define MG_HTTP_CHUNK_END mg_http_printf_chunk(c, "") @@ -134,7 +135,7 @@ static int list_files(struct mg_connection *c, const char *path) JSON_PRINTF(c, "\"path\":\"%s\",\"files\":[", path); - int sizeD = gp_list_count(list); + int sizeD = gp_list_count(list); for (int i = 0; i < sizeD; i++) { CL(gp_list_get_name(list, i, &name), list); @@ -143,7 +144,7 @@ static int list_files(struct mg_connection *c, const char *path) CL(gp_camera_folder_list_files(p->camera, path, list, p->context), list); - int sizeF = gp_list_count(list); + int sizeF = gp_list_count(list); for (int i = 0; i < sizeF; i++) { CL(gp_list_get_name(list, i, &name), list); @@ -151,7 +152,7 @@ static int list_files(struct mg_connection *c, const char *path) } JSON_PRINTF(c, "],"); - JSON_PRINTF(c, "\"entries\":%d,", sizeD + sizeF) ; + JSON_PRINTF(c, "\"entries\":%d,", sizeD + sizeF); gp_list_free(list); return GP_OK; @@ -167,9 +168,28 @@ fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) if (mg_http_match_uri(hm, "/")) { server_http_version(c); + return; } - else if (mg_http_match_uri(hm, "/api/version")) + if (webcfg.auth_enabled) + { + char remote_user[WEBCFG_STR_LEN + 1], password[WEBCFG_STR_LEN + 1]; + remote_user[0] = 0; + password[0] = 0; + + mg_http_creds(hm, remote_user, sizeof(remote_user), password, sizeof(password)); + + if (mg_http_match_uri(hm, "/api/#")) + { + if (remote_user[0] == 0 || strncmp(remote_user, webcfg.auth_user, WEBCFG_STR_LEN) != 0 || password[0] == 0 || strncmp(password, webcfg.auth_password, WEBCFG_STR_LEN) != 0) + { + mg_printf(c, "HTTP/1.1 401 Unauthorized\r\nWWW-Authenticate: Basic realm=\"gphoto2-webapi\"\r\nContent-Length: 0\r\n\r\n"); + return; + } + } + } + + if (mg_http_match_uri(hm, "/api/version")) { server_http_version(c); } @@ -254,7 +274,7 @@ fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) strncpy(buffer, hm->uri.ptr, MIN((int)hm->uri.len, 255)); buffer[MIN((int)hm->uri.len, 255)] = 0; char *path = buffer + 13; - if ( get_file_http_common(c, path, GP_FILE_TYPE_NORMAL) != GP_OK ) + if (get_file_http_common(c, path, GP_FILE_TYPE_NORMAL) != GP_OK) { mg_http_reply(c, 404, content_type_application_json, "{ \"error\":\"file not found\",\"return_code\":-1}\n"); } @@ -294,7 +314,7 @@ fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) MG_HTTP_CHUNK_START; mg_http_printf_chunk(c, "{"); - mg_http_printf_chunk(c, "\"return_code\":%d}\n", 0 ); + mg_http_printf_chunk(c, "\"return_code\":%d}\n", 0); MG_HTTP_CHUNK_END; } } @@ -307,11 +327,11 @@ fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) strncpy(buffer, hm->uri.ptr, MIN((int)hm->uri.len, 255)); buffer[MIN((int)hm->uri.len, 255)] = 0; char *path = buffer + 14; - char *lastChar = path + strlen(path)-1; + char *lastChar = path + strlen(path) - 1; - if( *lastChar != '/' ) + if (*lastChar != '/') { - strcat( lastChar, "/" ); + strcat(lastChar, "/"); } MG_HTTP_CHUNK_START; @@ -341,24 +361,24 @@ fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) newfilename = lr + 1; } - MG_HTTP_CHUNK_START; if (newfilename != NULL && strlen(newfilename) > 0) { + MG_HTTP_CHUNK_START; mg_http_printf_chunk(c, "{"); mg_http_printf_chunk(c, "\"return_code\":%d}\n", print_exif_action(c, p, newfolder, newfilename)); + MG_HTTP_CHUNK_END; } else { - mg_http_printf_chunk(c, "{ \"return_code\": -1 }\n"); + mg_http_reply(c, 404, content_type_application_json, "{ \"error\":\"file not found\",\"return_code\":-1}\n"); } - MG_HTTP_CHUNK_END; } } #endif else { - mg_http_reply(c, 404, "", "Page not found.\n"); + mg_http_reply(c, 404, content_type_text_html, "404

Error: 404

Page not found."); } } } @@ -366,8 +386,9 @@ fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) void webapi_server_initialize(void) { webcfg.server_url[0] = 0; - webcfg.api_user[0] = 0; - webcfg.api_password[0] = 0; + webcfg.auth_enabled = FALSE; + webcfg.auth_user[0] = 0; + webcfg.auth_password[0] = 0; webcfg.server_done = FALSE; } diff --git a/gphoto2/server.h b/gphoto2/server.h index e3e90a5d..7163c1a7 100644 --- a/gphoto2/server.h +++ b/gphoto2/server.h @@ -8,9 +8,10 @@ typedef struct _webapi_server_config { char server_url[WEBCFG_STR_LEN+1]; - char api_user[WEBCFG_STR_LEN+1]; - char api_password[WEBCFG_STR_LEN+1]; - int server_done; + char auth_enabled; + char auth_user[WEBCFG_STR_LEN+1]; + char auth_password[WEBCFG_STR_LEN+1]; + char server_done; } WebAPIServerConfig; From f8df22170fe90742313ec31ccb460707c9128d6a Mon Sep 17 00:00:00 2001 From: Thorsten Ludewig Date: Mon, 20 Jun 2022 13:23:21 +0200 Subject: [PATCH 33/97] capture image result now supports multiple files --- README_WEBAPI_SERVER.md | 78 ++++++++++++++++++++++++++++------------ gphoto2/gphoto2-webapi.c | 50 ++++++++------------------ gphoto2/server.c | 8 ++--- 3 files changed, 75 insertions(+), 61 deletions(-) diff --git a/README_WEBAPI_SERVER.md b/README_WEBAPI_SERVER.md index 02cadb62..faba57e5 100644 --- a/README_WEBAPI_SERVER.md +++ b/README_WEBAPI_SERVER.md @@ -66,16 +66,32 @@ Nearly all responses are [JSON](https://json.org) formated. ```jsonc { - image_info: { - name: "IMG_0264.JPG", - folder: "/store_00020001/DCIM/100CANON", - mtime: 1655542624, - size: 4838064, - height: 3456, - width: 5184, - type: "image/jpeg" - }, - download: false, + images: [ + { + info: { + name: "IMG_0322.CR2", + folder: "/store_00020001/DCIM/100CANON", + path: "/store_00020001/DCIM/100CANON/IMG_0322.CR2", + mtime: 1655724028, + size: 21937775, + type: "image/x-canon-cr2" + }, + download: false + }, + { + info: { + name: "IMG_0322.JPG", + folder: "/store_00020001/DCIM/100CANON", + path: "/store_00020001/DCIM/100CANON/IMG_0322.JPG", + mtime: 1655724026, + size: 4038986, + height: 3456, + width: 5184, + type: "image/jpeg" + }, + download: false + } + ], return_code: 0 } ``` @@ -86,18 +102,36 @@ Nearly all responses are [JSON](https://json.org) formated. ```jsonc { - image_info: { - name: "IMG_0265.JPG", - folder: "/store_00020001/DCIM/100CANON", - mtime: 1655542624, - size: 4838064, - height: 3456, - width: 5184, - type: "image/jpeg" - }, - download: true, - local_folder: "/home/user/Projects/gphoto2", - keeping_file_on_camera: true, + images: [ + { + info: { + name: "IMG_0321.CR2", + folder: "/store_00020001/DCIM/100CANON", + path: "/store_00020001/DCIM/100CANON/IMG_0321.CR2", + mtime: 1655723756, + size: 21888193, + type: "image/x-canon-cr2" + }, + download: true, + local_folder: "/home/th/Projects/gphoto2", + keeping_file_on_camera: true + }, + { + info: { + name: "IMG_0321.JPG", + folder: "/store_00020001/DCIM/100CANON", + path: "/store_00020001/DCIM/100CANON/IMG_0321.JPG", + mtime: 1655723756, + size: 4113431, + height: 3456, + width: 5184, + type: "image/jpeg" + }, + download: true, + local_folder: "/home/th/Projects/gphoto2", + keeping_file_on_camera: true + } + ], return_code: 0 } ``` diff --git a/gphoto2/gphoto2-webapi.c b/gphoto2/gphoto2-webapi.c index ebabee21..4b614edb 100644 --- a/gphoto2/gphoto2-webapi.c +++ b/gphoto2/gphoto2-webapi.c @@ -478,7 +478,7 @@ int save_camera_file_to_file( return GP_ERROR; s[strlen(s) - 1] = 0; } - printf(_("Saving file as %s\n"), s); + // printf(_("Saving file as %s\n"), s); fflush(stdout); } path = s; @@ -973,7 +973,7 @@ timediff_now(struct timeval *target) } static int -save_captured_file(struct mg_connection *c, CameraFilePath *path, int download) +save_captured_file(struct mg_connection *c, CameraFilePath *path, int download, int filenumber ) { char *pathsep; static CameraFilePath last; @@ -984,21 +984,10 @@ save_captured_file(struct mg_connection *c, CameraFilePath *path, int download) else pathsep = "/"; - if (gp_params.flags & FLAGS_QUIET) - { - if (!(gp_params.flags & (FLAGS_STDOUT | FLAGS_STDOUT_SIZE))) - printf("%s%s%s\n", path->folder, pathsep, path->name); - } - else - { - printf(_("New file is in location %s%s%s on the camera\n"), - path->folder, pathsep, path->name); - } - CameraFileInfo info; CR(gp_camera_file_get_info(gp_params.camera, path->folder, path->name, &info, gp_params.context)); - JSON_PRINTF(c, "\"image_info\":{"); + JSON_PRINTF(c, "%s{\"image_info\":{", (filenumber==0) ? "" : "," ); JSON_PRINTF(c, "\"name\": \"%s\",", path->name); JSON_PRINTF(c, "\"folder\": \"%s\",", path->folder); JSON_PRINTF(c, "\"path\": \"%s%s%s\",", path->folder, pathsep, path->name); @@ -1008,14 +997,14 @@ save_captured_file(struct mg_connection *c, CameraFilePath *path, int download) JSON_PRINTF(c, "\"width\":%d,", info.file.width); JSON_PRINTF(c, "\"type\":\"%s\"\n", info.file.type); JSON_PRINTF(c, "},"); - JSON_PRINTF(c, "\"download\": %s,", (download) ? "true" : "false"); + JSON_PRINTF(c, "\"download\": %s", (download) ? "true" : "false"); if (download) { char *cwd; if ((cwd = getcwd(NULL, 0)) != NULL) { - JSON_PRINTF(c, "\"local_folder\": \"%s\",", cwd); + JSON_PRINTF(c, ",\"local_folder\": \"%s\"", cwd); free(cwd); } @@ -1030,14 +1019,6 @@ save_captured_file(struct mg_connection *c, CameraFilePath *path, int download) return (result); } } - if ((gp_params.flags & FLAGS_KEEP_RAW) && - (!strstr(path->name, ".jpg") && !strstr(path->name, ".JPG"))) - { - if (!(gp_params.flags & FLAGS_QUIET)) - printf(_("Keeping file %s%s%s on the camera\n"), - path->folder, pathsep, path->name); - return GP_OK; - } result = get_file_common(c, path->name, GP_FILE_TYPE_NORMAL); @@ -1056,7 +1037,7 @@ save_captured_file(struct mg_connection *c, CameraFilePath *path, int download) return (result); } - JSON_PRINTF(c, "\"keeping_file_on_camera\": %s,", (gp_params.flags & FLAGS_KEEP) ? "true" : "false"); + JSON_PRINTF(c, ",\"keeping_file_on_camera\": %s", (gp_params.flags & FLAGS_KEEP) ? "true" : "false"); if (!(gp_params.flags & FLAGS_KEEP)) { @@ -1071,13 +1052,9 @@ save_captured_file(struct mg_connection *c, CameraFilePath *path, int download) return (result); } } - else - { - if (!(gp_params.flags & FLAGS_QUIET)) - printf(_("Keeping file %s%s%s on the camera\n"), - path->folder, pathsep, path->name); - } } + + JSON_PRINTF( c, "}" ); return GP_OK; } @@ -1115,14 +1092,16 @@ wait_and_handle_event(struct mg_connection *c, long waittime, CameraEventType *t free(data); break; case GP_EVENT_FILE_CHANGED: - if (!(gp_params.flags & FLAGS_QUIET)) - printf(_("Event FILE_CHANGED %s/%s during wait, ignoring.\n"), path->folder, path->name); + //if (!(gp_params.flags & FLAGS_QUIET)) + // printf(_("Event FILE_CHANGED %s/%s during wait, ignoring.\n"), path->folder, path->name); free(data); break; case GP_EVENT_FILE_ADDED: - result = save_captured_file(c, path, download); + { + result = save_captured_file(c, path, download, 1 ); free(data); /* result will fall through to final return */ + } break; case GP_EVENT_UNKNOWN: #if 0 /* too much spam for the common usage */ @@ -1146,6 +1125,7 @@ int capture_generic(struct mg_connection *c, CameraCaptureType type, const char CameraEventType evtype; long waittime; struct timeval next_pic_time, expose_end_time; + int fileNumber = 0; result = gp_camera_get_abilities(gp_params.camera, &a); if (result != GP_OK) @@ -1252,7 +1232,7 @@ int capture_generic(struct mg_connection *c, CameraCaptureType type, const char continue; } } - result = save_captured_file(c, &path, download); + result = save_captured_file(c, &path, download, fileNumber++ ); if (result != GP_OK) break; } diff --git a/gphoto2/server.c b/gphoto2/server.c index 3872fbe2..6f47829f 100644 --- a/gphoto2/server.c +++ b/gphoto2/server.c @@ -253,16 +253,16 @@ fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) else if (mg_http_match_uri(hm, "/api/capture-image")) { MG_HTTP_CHUNK_START; - mg_http_printf_chunk(c, "{"); - mg_http_printf_chunk(c, "\"return_code\":%d}\n", capture_generic(c, GP_CAPTURE_IMAGE, NULL, 0)); + mg_http_printf_chunk(c, "{ \"files\":["); + mg_http_printf_chunk(c, "],\"return_code\":%d}\n", capture_generic(c, GP_CAPTURE_IMAGE, NULL, 0)); MG_HTTP_CHUNK_END; } else if (mg_http_match_uri(hm, "/api/capture-image-download")) { MG_HTTP_CHUNK_START; - mg_http_printf_chunk(c, "{"); - mg_http_printf_chunk(c, "\"return_code\":%d}\n", capture_generic(c, GP_CAPTURE_IMAGE, NULL, 1)); + mg_http_printf_chunk(c, "{ \"files\":["); + mg_http_printf_chunk(c, "],\"return_code\":%d}\n", capture_generic(c, GP_CAPTURE_IMAGE, NULL, 1)); MG_HTTP_CHUNK_END; } From 6714b6c4cb5b7b96ba44eb5d8182facc15444ad4 Mon Sep 17 00:00:00 2001 From: Thorsten Ludewig Date: Mon, 20 Jun 2022 13:24:46 +0200 Subject: [PATCH 34/97] 'files' attr renamed to 'images' --- gphoto2/server.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gphoto2/server.c b/gphoto2/server.c index 6f47829f..f2e851a0 100644 --- a/gphoto2/server.c +++ b/gphoto2/server.c @@ -253,7 +253,7 @@ fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) else if (mg_http_match_uri(hm, "/api/capture-image")) { MG_HTTP_CHUNK_START; - mg_http_printf_chunk(c, "{ \"files\":["); + mg_http_printf_chunk(c, "{ \"images\":["); mg_http_printf_chunk(c, "],\"return_code\":%d}\n", capture_generic(c, GP_CAPTURE_IMAGE, NULL, 0)); MG_HTTP_CHUNK_END; } @@ -261,7 +261,7 @@ fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) else if (mg_http_match_uri(hm, "/api/capture-image-download")) { MG_HTTP_CHUNK_START; - mg_http_printf_chunk(c, "{ \"files\":["); + mg_http_printf_chunk(c, "{ \"images\":["); mg_http_printf_chunk(c, "],\"return_code\":%d}\n", capture_generic(c, GP_CAPTURE_IMAGE, NULL, 1)); MG_HTTP_CHUNK_END; } From 237b1cf85b3ea026e0eb14e77ba0589c3fc0a3fb Mon Sep 17 00:00:00 2001 From: Thorsten Ludewig Date: Mon, 20 Jun 2022 13:25:43 +0200 Subject: [PATCH 35/97] 'image_info' attr renamed to 'info' --- gphoto2/gphoto2-webapi.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gphoto2/gphoto2-webapi.c b/gphoto2/gphoto2-webapi.c index 4b614edb..95946b4c 100644 --- a/gphoto2/gphoto2-webapi.c +++ b/gphoto2/gphoto2-webapi.c @@ -987,14 +987,14 @@ save_captured_file(struct mg_connection *c, CameraFilePath *path, int download, CameraFileInfo info; CR(gp_camera_file_get_info(gp_params.camera, path->folder, path->name, &info, gp_params.context)); - JSON_PRINTF(c, "%s{\"image_info\":{", (filenumber==0) ? "" : "," ); + JSON_PRINTF(c, "%s{\"info\":{", (filenumber==0) ? "" : "," ); JSON_PRINTF(c, "\"name\": \"%s\",", path->name); JSON_PRINTF(c, "\"folder\": \"%s\",", path->folder); JSON_PRINTF(c, "\"path\": \"%s%s%s\",", path->folder, pathsep, path->name); JSON_PRINTF(c, "\"mtime\":%ld,", info.file.mtime); JSON_PRINTF(c, "\"size\":%ld,", info.file.size); - JSON_PRINTF(c, "\"height\":%d,", info.file.height); - JSON_PRINTF(c, "\"width\":%d,", info.file.width); + if ( info.file.height > 0 ) JSON_PRINTF(c, "\"height\":%d,", info.file.height); + if ( info.file.width > 0 ) JSON_PRINTF(c, "\"width\":%d,", info.file.width); JSON_PRINTF(c, "\"type\":\"%s\"\n", info.file.type); JSON_PRINTF(c, "},"); JSON_PRINTF(c, "\"download\": %s", (download) ? "true" : "false"); From c1add9a5c5a4af68ab1303f5ec98d8f07d4c4d3d Mon Sep 17 00:00:00 2001 From: Thorsten Ludewig Date: Mon, 20 Jun 2022 13:40:41 +0200 Subject: [PATCH 36/97] auth (enabled,user,password) parameters added --- gphoto2/gphoto2-webapi.c | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/gphoto2/gphoto2-webapi.c b/gphoto2/gphoto2-webapi.c index 95946b4c..5cae85ac 100644 --- a/gphoto2/gphoto2-webapi.c +++ b/gphoto2/gphoto2-webapi.c @@ -1540,6 +1540,9 @@ typedef enum ARG_RESET_INTERVAL, ARG_SERVER, ARG_SERVER_URL, + ARG_AUTH_ENABLED, + ARG_AUTH_USER, + ARG_AUTH_PASSWORD, ARG_SHOW_INFO, ARG_SPEED, ARG_STORAGE_INFO, @@ -1776,13 +1779,30 @@ cb_arg_run(poptContext __unused__ ctx, gp_port_free(port); break; } + case ARG_SERVER_URL: strncpy(webcfg.server_url, arg, WEBCFG_STR_LEN); webcfg.server_url[WEBCFG_STR_LEN] = 0; break; + case ARG_SERVER: params->p.r = webapi_server(&gp_params); break; + + case ARG_AUTH_ENABLED: + webcfg.auth_enabled = TRUE; + break; + + case ARG_AUTH_USER: + strncpy(webcfg.auth_user, arg, WEBCFG_STR_LEN); + webcfg.auth_user[WEBCFG_STR_LEN] = 0; + break; + + case ARG_AUTH_PASSWORD: + strncpy(webcfg.auth_password, arg, WEBCFG_STR_LEN); + webcfg.auth_password[WEBCFG_STR_LEN] = 0; + break; + case ARG_SHOW_INFO: /* Did the user specify a file or a range? */ if (strchr(arg, '.')) @@ -2064,6 +2084,12 @@ int main(int argc, char **argv, char **envp) N_("gPhoto webapi server"), NULL}, {"server-url", '\0', POPT_ARG_STRING, NULL, ARG_SERVER_URL, N_("Server URL - e.g http://0.0.0.0:8866"), NULL}, + {"auth-enabled", '\0', POPT_ARG_NONE, NULL, ARG_AUTH_ENABLED, + N_("api authentication enabled"), NULL}, + {"auth-user", '\0', POPT_ARG_STRING, NULL, ARG_AUTH_USER, + N_("api user"), NULL}, + {"auth-password", '\0', POPT_ARG_STRING, NULL, ARG_AUTH_PASSWORD, + N_("api password"), NULL}, POPT_TABLEEND}; const struct poptOption options[] = { @@ -2235,6 +2261,9 @@ int main(int argc, char **argv, char **envp) CHECK_OPT(ARG_SET_CONFIG_VALUE); CHECK_OPT(ARG_SERVER); CHECK_OPT(ARG_SERVER_URL); + CHECK_OPT(ARG_AUTH_ENABLED); + CHECK_OPT(ARG_AUTH_USER); + CHECK_OPT(ARG_AUTH_PASSWORD); CHECK_OPT(ARG_SHOW_INFO); CHECK_OPT(ARG_STORAGE_INFO); CHECK_OPT(ARG_SUMMARY); From 4906da7958fbe6909c220ac4a74d0b75ba6d6da5 Mon Sep 17 00:00:00 2001 From: Thorsten Ludewig Date: Mon, 20 Jun 2022 13:41:40 +0200 Subject: [PATCH 37/97] WEBAPI_SERVER_VERSION 0.0.2 --- gphoto2/gphoto2-webapi.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gphoto2/gphoto2-webapi.h b/gphoto2/gphoto2-webapi.h index be8429ca..f42bee1a 100644 --- a/gphoto2/gphoto2-webapi.h +++ b/gphoto2/gphoto2-webapi.h @@ -36,7 +36,7 @@ #define VERSION "2" #endif -#define WEBAPI_SERVER_VERSION "0.0.1" +#define WEBAPI_SERVER_VERSION "0.0.2" void cli_error_print(char *format, ...); void dissolve_filename ( const char *folder, const char *filename, char **newfolder, char **newfilename ); From f94d084ebdcbcdd16356a015e86dbb6a2d477468 Mon Sep 17 00:00:00 2001 From: Thorsten Ludewig Date: Mon, 20 Jun 2022 17:50:09 +0200 Subject: [PATCH 38/97] api get single config property --- gphoto2/actions.c | 10 +++++++++- gphoto2/actions.h | 3 ++- gphoto2/gphoto2-webapi.c | 13 ------------- gphoto2/server.c | 14 ++++++++++++++ 4 files changed, 25 insertions(+), 15 deletions(-) diff --git a/gphoto2/actions.c b/gphoto2/actions.c index 260450f8..ce1af2c7 100644 --- a/gphoto2/actions.c +++ b/gphoto2/actions.c @@ -1977,8 +1977,14 @@ print_widget (struct mg_connection *c, GPParams *p, const char *name, CameraWidg } +#ifndef WEBAPI int -get_config_action (GPParams *p, const char *name) { +get_config_action (GPParams *p, const char *name) +#else +int +get_config_action (struct mg_connection *c, GPParams *p, const char *name) +#endif +{ CameraWidget *rootconfig,*child; int ret; @@ -1988,6 +1994,8 @@ get_config_action (GPParams *p, const char *name) { #ifndef WEBAPI ret = print_widget (p, name, child); +#else + ret = print_widget (c, p, name, child); #endif gp_widget_free (rootconfig); diff --git a/gphoto2/actions.h b/gphoto2/actions.h index 9b01561c..e37c8559 100644 --- a/gphoto2/actions.h +++ b/gphoto2/actions.h @@ -98,7 +98,6 @@ int print_version_action (GPParams *); int override_usbids_action (GPParams *, int usb_vendor, int usb_product, int usb_vendor_modified, int usb_product_modified); int debug_action (GPParams *, const char *debug_loglevel, const char *debug_logfile_name); -int get_config_action (GPParams *, const char *name); int set_config_action (GPParams *, const char *name, const char *value); int set_config_index_action (GPParams *, const char *name, const char *value); int set_config_value_action (GPParams *, const char *name, const char *value); @@ -111,11 +110,13 @@ int auto_detect_action (struct mg_connection *, GPParams *); int list_config_action (struct mg_connection *, GPParams *); int list_all_config_action (struct mg_connection *, GPParams *); int print_exif_action (struct mg_connection *, GPParams *, const char *folder, const char *filename); +int get_config_action (struct mg_connection *, GPParams *, const char *name); #else int auto_detect_action (GPParams *); int list_config_action (GPParams *); int list_all_config_action (GPParams *); int print_exif_action (GPParams *, const char *folder, const char *filename); +int get_config_action (GPParams *, const char *name); #endif #endif /* !defined(GPHOTO2_ACTIONS_H) */ diff --git a/gphoto2/gphoto2-webapi.c b/gphoto2/gphoto2-webapi.c index 5cae85ac..34069883 100644 --- a/gphoto2/gphoto2-webapi.c +++ b/gphoto2/gphoto2-webapi.c @@ -1522,7 +1522,6 @@ typedef enum ARG_DEBUG, ARG_DEBUG_LOGLEVEL, ARG_DEBUG_LOGFILE, - ARG_GET_CONFIG, ARG_SET_CONFIG, ARG_SET_CONFIG_INDEX, ARG_SET_CONFIG_VALUE, @@ -1819,9 +1818,6 @@ cb_arg_run(poptContext __unused__ ctx, case ARG_SUMMARY: params->p.r = action_camera_summary(&gp_params); break; - case ARG_GET_CONFIG: - params->p.r = get_config_action(&gp_params, arg); - break; case ARG_SET_CONFIG: { char *name, *value; @@ -2043,14 +2039,6 @@ int main(int argc, char **argv, char **envp) {"config", '\0', POPT_ARG_NONE, NULL, ARG_CONFIG, N_("Configure"), NULL}, #endif - {"get-config", '\0', POPT_ARG_STRING, NULL, ARG_GET_CONFIG, - N_("Get configuration value"), NULL}, - {"set-config", '\0', POPT_ARG_STRING, NULL, ARG_SET_CONFIG, - N_("Set configuration value or index in choices"), NULL}, - {"set-config-index", '\0', POPT_ARG_STRING, NULL, ARG_SET_CONFIG_INDEX, - N_("Set configuration value index in choices"), NULL}, - {"set-config-value", '\0', POPT_ARG_STRING, NULL, ARG_SET_CONFIG_VALUE, - N_("Set configuration value"), NULL}, {"reset", '\0', POPT_ARG_NONE, NULL, ARG_RESET, N_("Reset device port"), NULL}, POPT_TABLEEND}; @@ -2254,7 +2242,6 @@ int main(int argc, char **argv, char **envp) CHECK_OPT(ARG_ABILITIES); CHECK_OPT(ARG_SHOW_PREVIEW); CHECK_OPT(ARG_CONFIG); - CHECK_OPT(ARG_GET_CONFIG); CHECK_OPT(ARG_RESET); CHECK_OPT(ARG_SET_CONFIG); CHECK_OPT(ARG_SET_CONFIG_INDEX); diff --git a/gphoto2/server.c b/gphoto2/server.c index f2e851a0..7185c69d 100644 --- a/gphoto2/server.c +++ b/gphoto2/server.c @@ -250,6 +250,20 @@ fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) mg_http_reply(c, 200, content_type_application_json, "{\"return_code\":%d}\n", ret); } + else if (mg_http_match_uri(hm, "/api/config/get/#")) + { + char buffer[256]; + strncpy(buffer, hm->uri.ptr, MIN((int)hm->uri.len, 255)); + buffer[MIN((int)hm->uri.len, 255)] = 0; + char *name = buffer + 15; + puts( name ); + + MG_HTTP_CHUNK_START; + mg_http_printf_chunk(c, "{ \"path\": \"%s\"", name ); + mg_http_printf_chunk(c, ",\"return_code\":%d}", get_config_action( c, p, name ) ); + MG_HTTP_CHUNK_END; + } + else if (mg_http_match_uri(hm, "/api/capture-image")) { MG_HTTP_CHUNK_START; From d87e6ebd0c23f95e7436f770d1fd68bb3dfec77e Mon Sep 17 00:00:00 2001 From: Thorsten Ludewig Date: Mon, 20 Jun 2022 17:50:55 +0200 Subject: [PATCH 39/97] get single config property --- README_WEBAPI_SERVER.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README_WEBAPI_SERVER.md b/README_WEBAPI_SERVER.md index faba57e5..8113c1a5 100644 --- a/README_WEBAPI_SERVER.md +++ b/README_WEBAPI_SERVER.md @@ -157,6 +157,10 @@ Nearly all responses are [JSON](https://json.org) formated. set the `config name` to the specified `index value` +- `http://:8866/api/config/get/` + + get single config property + ### file - `http://:8866/api/file/get/` From b8ec855a0fa7c8606e2eb536e8581c796383a814 Mon Sep 17 00:00:00 2001 From: Thorsten Ludewig Date: Mon, 20 Jun 2022 17:55:41 +0200 Subject: [PATCH 40/97] sample request and response for config/get added --- README_WEBAPI_SERVER.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/README_WEBAPI_SERVER.md b/README_WEBAPI_SERVER.md index 8113c1a5..5e3efd2a 100644 --- a/README_WEBAPI_SERVER.md +++ b/README_WEBAPI_SERVER.md @@ -161,6 +161,34 @@ Nearly all responses are [JSON](https://json.org) formated. get single config property + sample request + + `http://localhost:8866/api/config/get/main/imgsettings/colorspace` + + response + + ```jsonc + { + path: "/main/imgsettings/colorspace", + label: "Farbraum", + readonly: false, + type: "MENU", + current: "sRGB", + choice: [ + { + index: 0, + value: "sRGB" + }, + { + index: 1, + value: "AdobeRGB" + } + ], + return_code: 0 + } + ``` + + ### file - `http://:8866/api/file/get/` From 5fc97833e6c4eb06c6da058f2afaa023053d736d Mon Sep 17 00:00:00 2001 From: Thorsten Ludewig Date: Mon, 20 Jun 2022 17:58:11 +0200 Subject: [PATCH 41/97] useless 'puts' removed --- gphoto2/server.c | 1 - 1 file changed, 1 deletion(-) diff --git a/gphoto2/server.c b/gphoto2/server.c index 7185c69d..85260665 100644 --- a/gphoto2/server.c +++ b/gphoto2/server.c @@ -256,7 +256,6 @@ fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) strncpy(buffer, hm->uri.ptr, MIN((int)hm->uri.len, 255)); buffer[MIN((int)hm->uri.len, 255)] = 0; char *name = buffer + 15; - puts( name ); MG_HTTP_CHUNK_START; mg_http_printf_chunk(c, "{ \"path\": \"%s\"", name ); From ca5d070ced50a851fb87e08e76af0d85eaeb92e8 Mon Sep 17 00:00:00 2001 From: Thorsten Ludewig Date: Mon, 20 Jun 2022 19:25:43 +0200 Subject: [PATCH 42/97] remove mongoose submodule --- .gitmodules | 3 --- gphoto2/mongoose | 1 - 2 files changed, 4 deletions(-) delete mode 160000 gphoto2/mongoose diff --git a/.gitmodules b/.gitmodules index d1064c64..e69de29b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +0,0 @@ -[submodule "gphoto2/mongoose"] - path = gphoto2/mongoose - url = https://github.com/cesanta/mongoose.git diff --git a/gphoto2/mongoose b/gphoto2/mongoose deleted file mode 160000 index d5993ba2..00000000 --- a/gphoto2/mongoose +++ /dev/null @@ -1 +0,0 @@ -Subproject commit d5993ba27ece4b406c230eca63b76dd5d2c28a2d From b61d2a0dcab82be9dfa722a91fede37e8a2a8ffd Mon Sep 17 00:00:00 2001 From: Thorsten Ludewig Date: Mon, 20 Jun 2022 19:26:13 +0200 Subject: [PATCH 43/97] remove mongoose submodule --- .gitmodules | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .gitmodules diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index e69de29b..00000000 From a4bb6b9afa6162d5c5e34b8eed8ad4e3a99851a8 Mon Sep 17 00:00:00 2001 From: Thorsten Ludewig Date: Mon, 20 Jun 2022 19:27:31 +0200 Subject: [PATCH 44/97] mongoose version 7.7 source added --- gphoto2/mongoose/LICENSE | 16 + gphoto2/mongoose/README.md | 83 + gphoto2/mongoose/mongoose.c | 5898 +++++++++++++++++++++++++++++++++++ gphoto2/mongoose/mongoose.h | 1299 ++++++++ 4 files changed, 7296 insertions(+) create mode 100644 gphoto2/mongoose/LICENSE create mode 100644 gphoto2/mongoose/README.md create mode 100644 gphoto2/mongoose/mongoose.c create mode 100644 gphoto2/mongoose/mongoose.h diff --git a/gphoto2/mongoose/LICENSE b/gphoto2/mongoose/LICENSE new file mode 100644 index 00000000..8fdd2a93 --- /dev/null +++ b/gphoto2/mongoose/LICENSE @@ -0,0 +1,16 @@ +Copyright (c) 2004-2013 Sergey Lyubka +Copyright (c) 2013-2021 Cesanta Software Limited +All rights reserved + +This software is dual-licensed: you can redistribute it and/or modify +it under the terms of the GNU General Public License version 2 as +published by the Free Software Foundation. For the terms of this +license, see . + +You are free to use this software under the terms of the GNU General +Public License, but WITHOUT ANY WARRANTY; without even the implied +warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +See the GNU General Public License for more details. + +Alternatively, you can license this software under a commercial +license, as set out in . diff --git a/gphoto2/mongoose/README.md b/gphoto2/mongoose/README.md new file mode 100644 index 00000000..cca8d321 --- /dev/null +++ b/gphoto2/mongoose/README.md @@ -0,0 +1,83 @@ +# Mongoose - Embedded Web Server / Embedded Networking Library + +[![License: GPLv2/Commercial](https://img.shields.io/badge/License-GPLv2%20or%20Commercial-green.svg)](https://opensource.org/licenses/gpl-2.0.php) +[![Build Status]( https://github.com/cesanta/mongoose/workflows/build/badge.svg)](https://github.com/cesanta/mongoose/actions) +[![Code Coverage](https://codecov.io/gh/cesanta/mongoose/branch/master/graph/badge.svg)](https://codecov.io/gh/cesanta/mongoose) +[![Fuzzing Status](https://oss-fuzz-build-logs.storage.googleapis.com/badges/mongoose.svg)](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:mongoose) + +Mongoose is a networking library for C/C++. It implements event-driven +non-blocking APIs for TCP, UDP, HTTP, WebSocket, MQTT. It is designed for +connecting devices and bringing them online. On the market since 2004, used by +vast number of open source and commercial products - it even runs on the +International Space Station! Mongoose makes embedded network programming fast, +robust, and easy. Features include: + +- Cross-platform: works on Linux/UNIX, MacOS, Windows, Android, FreeRTOS, etc. +- Supported embedded architectures: ESP32, NRF52, STM32, NXP, and more +- Built-in protocols: plain TCP/UDP, HTTP, MQTT, Websocket +- SSL/TLS support: mbedTLS, OpenSSL or custom (via API) +- Asynchronous DNS resolver +- Tiny static and run-time footprint +- Source code is both ISO C and ISO C++ compliant +- Works with any network stack with socket API, like LwIP or FreeRTOS-Plus-TCP +- Very easy to integrate: just copy `mongoose.c` and `mongoose.h` files to your build tree +- Detailed [documentation](https://mongoose.ws/documentation/) and + [tutorials](https://mongoose.ws/tutorials/) + + +# Commercial use +- Mongoose is used by hundreds of businesses, from Fortune500 giants like + Siemens, Schneider Electric, Broadcom, Bosch, Google, Samsung, Qualcomm, Caterpillar to the small businesses +- Used to solve a wide range of business needs, like implementing Web UI + interface on devices, RESTful API services, telemetry data exchange, remote + control for a product, remote software updates, remote monitoring, and others +- Deployed to hundreds of millions devices in production environment worldwide +- See [Case Studies](https://mongoose.ws/case-studies/) from our respected + customers like [Schneider Electric](https://mongoose.ws/case-studies/schneider-electric/) (industrial automation), [Broadcom](https://mongoose.ws/case-studies/broadcom/) (semiconductors), [Pilz](https://mongoose.ws/case-studies/pilz/) (industrial automation), and others +- See [Testimonials](https://mongoose.ws/testimonials/) from engineers that integrated Mongoose in their commercial products +- We provide [Evaluation and Commercial licensing](https://mongoose.ws/licensing/), [support](https://mongoose.ws/support/), consultancy and integration + assistance - don't hesitate to [contact us](https://mongoose.ws/contact/) + + +# Security + +We take security seriously: +1. Mongoose repository runs a + [continuous integration test powered by GitHub](https://github.com/cesanta/mongoose/actions), + which runs through hundreds of unit tests on every commit to the repository. + Our [unit tests](https://github.com/cesanta/mongoose/tree/master/test) + are built with modern address sanitizer technologies, which help to find + security vulnerabilities early +2. Mongoose repository is integrated into Google's + [oss-fuzz continuous fuzzer](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:mongoose) + which scans for potential vulnerabilities continuously +3. We receive periodic vulnerability reports from the independent security + groups like + [Cisco Talos](https://www.cisco.com/c/en/us/products/security/talos.html), + [Microsoft Security Response Center](https://www.microsoft.com/en-us/msrc), + [MITRE Corporation](https://www.mitre.org/), + [Compass Security](https://www.compass-security.com/en/) and others. + In case of the vulnerability found, we act according to the industry best + practice: hold on to the publication, fix the software and notify all our + customers that have an appropriate subscription +4. Some of our customers (for example NASA) + have specific security requirements and run independent security audits, + of which we get notified and in case of any issue, act similar to (3). + +# Supplement software + +This software is often used together with Mongoose: +- [mjson](https://github.com/cesanta/mjson) - a JSON parser, emitter and + JSON-RPC engine. Used to implement + RESTful APIs that use JSON, or implement data exchange (e.g. over MQTT + or Websocket) that use JSON for data encapsulation +- [elk](https://github.com/cesanta/elk) - a tiny JavaScript engine. + Used to implement scripting support + + +# Contributions + +Contributions are welcome! Please follow the guidelines below: + +- Sign [Cesanta CLA](https://cesanta.com/cla.html) and send GitHub pull request +- Make sure that PRs have only one commit, and deal with one issue only diff --git a/gphoto2/mongoose/mongoose.c b/gphoto2/mongoose/mongoose.c new file mode 100644 index 00000000..ff996417 --- /dev/null +++ b/gphoto2/mongoose/mongoose.c @@ -0,0 +1,5898 @@ +// Copyright (c) 2004-2013 Sergey Lyubka +// Copyright (c) 2013-2022 Cesanta Software Limited +// All rights reserved +// +// This software is dual-licensed: you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2 as +// published by the Free Software Foundation. For the terms of this +// license, see http://www.gnu.org/licenses/ +// +// You are free to use this software under the terms of the GNU General +// Public License, but WITHOUT ANY WARRANTY; without even the implied +// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU General Public License for more details. +// +// Alternatively, you can license this software under a commercial +// license, as set out in https://www.mongoose.ws/licensing/ +// +// SPDX-License-Identifier: GPL-2.0 or commercial + +#include "mongoose.h" + +#ifdef MG_ENABLE_LINES +#line 1 "src/base64.c" +#endif + +#include + +static int mg_b64idx(int c) { + if (c < 26) { + return c + 'A'; + } else if (c < 52) { + return c - 26 + 'a'; + } else if (c < 62) { + return c - 52 + '0'; + } else { + return c == 62 ? '+' : '/'; + } +} + +static int mg_b64rev(int c) { + if (c >= 'A' && c <= 'Z') { + return c - 'A'; + } else if (c >= 'a' && c <= 'z') { + return c + 26 - 'a'; + } else if (c >= '0' && c <= '9') { + return c + 52 - '0'; + } else if (c == '+') { + return 62; + } else if (c == '/') { + return 63; + } else if (c == '=') { + return 64; + } else { + return -1; + } +} + +int mg_base64_update(unsigned char ch, char *to, int n) { + int rem = (n & 3) % 3; + if (rem == 0) { + to[n] = (char) mg_b64idx(ch >> 2); + to[++n] = (char) ((ch & 3) << 4); + } else if (rem == 1) { + to[n] = (char) mg_b64idx(to[n] | (ch >> 4)); + to[++n] = (char) ((ch & 15) << 2); + } else { + to[n] = (char) mg_b64idx(to[n] | (ch >> 6)); + to[++n] = (char) mg_b64idx(ch & 63); + n++; + } + return n; +} + +int mg_base64_final(char *to, int n) { + int saved = n; + // printf("---[%.*s]\n", n, to); + if (n & 3) n = mg_base64_update(0, to, n); + if ((saved & 3) == 2) n--; + // printf(" %d[%.*s]\n", n, n, to); + while (n & 3) to[n++] = '='; + to[n] = '\0'; + return n; +} + +int mg_base64_encode(const unsigned char *p, int n, char *to) { + int i, len = 0; + for (i = 0; i < n; i++) len = mg_base64_update(p[i], to, len); + len = mg_base64_final(to, len); + return len; +} + +int mg_base64_decode(const char *src, int n, char *dst) { + const char *end = src + n; + int len = 0; + while (src + 3 < end) { + int a = mg_b64rev(src[0]), b = mg_b64rev(src[1]), c = mg_b64rev(src[2]), + d = mg_b64rev(src[3]); + if (a == 64 || a < 0 || b == 64 || b < 0 || c < 0 || d < 0) return 0; + dst[len++] = (char) ((a << 2) | (b >> 4)); + if (src[2] != '=') { + dst[len++] = (char) ((b << 4) | (c >> 2)); + if (src[3] != '=') dst[len++] = (char) ((c << 6) | d); + } + src += 4; + } + dst[len] = '\0'; + return len; +} + +#ifdef MG_ENABLE_LINES +#line 1 "src/dns.c" +#endif + + + + + + + +struct dns_data { + struct dns_data *next; + struct mg_connection *c; + uint64_t expire; + uint16_t txnid; +}; + +static void mg_sendnsreq(struct mg_connection *, struct mg_str *, int, + struct mg_dns *, bool); + +static void mg_dns_free(struct mg_connection *c, struct dns_data *d) { + LIST_DELETE(struct dns_data, + (struct dns_data **) &c->mgr->active_dns_requests, d); + free(d); +} + +void mg_resolve_cancel(struct mg_connection *c) { + struct dns_data *tmp, *d = (struct dns_data *) c->mgr->active_dns_requests; + for (; d != NULL; d = tmp) { + tmp = d->next; + if (d->c == c) mg_dns_free(c, d); + } +} + +static size_t mg_dns_parse_name_depth(const uint8_t *s, size_t len, size_t ofs, + char *to, size_t tolen, size_t j, + int depth) { + size_t i = 0; + if (tolen > 0 && depth == 0) to[0] = '\0'; + if (depth > 5) return 0; + // MG_INFO(("ofs %lx %x %x", (unsigned long) ofs, s[ofs], s[ofs + 1])); + while (ofs + i + 1 < len) { + size_t n = s[ofs + i]; + if (n == 0) { + i++; + break; + } + if (n & 0xc0) { + size_t ptr = (((n & 0x3f) << 8) | s[ofs + i + 1]); // 12 is hdr len + // MG_INFO(("PTR %lx", (unsigned long) ptr)); + if (ptr + 1 < len && (s[ptr] & 0xc0) == 0 && + mg_dns_parse_name_depth(s, len, ptr, to, tolen, j, depth + 1) == 0) + return 0; + i += 2; + break; + } + if (ofs + i + n + 1 >= len) return 0; + if (j > 0) { + if (j < tolen) to[j] = '.'; + j++; + } + if (j + n < tolen) memcpy(&to[j], &s[ofs + i + 1], n); + j += n; + i += n + 1; + if (j < tolen) to[j] = '\0'; // Zero-terminate this chunk + // MG_INFO(("--> [%s]", to)); + } + if (tolen > 0) to[tolen - 1] = '\0'; // Make sure make sure it is nul-term + return i; +} + +static size_t mg_dns_parse_name(const uint8_t *s, size_t n, size_t ofs, + char *dst, size_t dstlen) { + return mg_dns_parse_name_depth(s, n, ofs, dst, dstlen, 0, 0); +} + +size_t mg_dns_parse_rr(const uint8_t *buf, size_t len, size_t ofs, + bool is_question, struct mg_dns_rr *rr) { + const uint8_t *s = buf + ofs, *e = &buf[len]; + + memset(rr, 0, sizeof(*rr)); + if (len < sizeof(struct mg_dns_header)) return 0; // Too small + if (len > 512) return 0; // Too large, we don't expect that + if (s >= e) return 0; // Overflow + + if ((rr->nlen = (uint16_t) mg_dns_parse_name(buf, len, ofs, NULL, 0)) == 0) + return 0; + s += rr->nlen + 4; + if (s > e) return 0; + rr->atype = (uint16_t) (((uint16_t) s[-4] << 8) | s[-3]); + rr->aclass = (uint16_t) (((uint16_t) s[-2] << 8) | s[-1]); + if (is_question) return (size_t) (rr->nlen + 4); + + s += 6; + if (s > e) return 0; + rr->alen = (uint16_t) (((uint16_t) s[-2] << 8) | s[-1]); + if (s + rr->alen > e) return 0; + return (size_t) (rr->nlen + rr->alen + 10); +} + +bool mg_dns_parse(const uint8_t *buf, size_t len, struct mg_dns_message *dm) { + const struct mg_dns_header *h = (struct mg_dns_header *) buf; + struct mg_dns_rr rr; + size_t i, n, ofs = sizeof(*h); + memset(dm, 0, sizeof(*dm)); + + if (len < sizeof(*h)) return 0; // Too small, headers dont fit + if (mg_ntohs(h->num_questions) > 1) return 0; // Sanity + if (mg_ntohs(h->num_answers) > 10) return 0; // Sanity + dm->txnid = mg_ntohs(h->txnid); + + for (i = 0; i < mg_ntohs(h->num_questions); i++) { + if ((n = mg_dns_parse_rr(buf, len, ofs, true, &rr)) == 0) return false; + // MG_INFO(("Q %lu %lu %hu/%hu", ofs, n, rr.atype, rr.aclass)); + ofs += n; + } + for (i = 0; i < mg_ntohs(h->num_answers); i++) { + if ((n = mg_dns_parse_rr(buf, len, ofs, false, &rr)) == 0) return false; + // MG_INFO(("A -- %lu %lu %hu/%hu %s", ofs, n, rr.atype, rr.aclass, + // dm->name)); + mg_dns_parse_name(buf, len, ofs, dm->name, sizeof(dm->name)); + ofs += n; + + if (rr.alen == 4 && rr.atype == 1 && rr.aclass == 1) { + dm->addr.is_ip6 = false; + memcpy(&dm->addr.ip, &buf[ofs - 4], 4); + dm->resolved = true; + break; // Return success + } else if (rr.alen == 16 && rr.atype == 28 && rr.aclass == 1) { + dm->addr.is_ip6 = true; + memcpy(&dm->addr.ip6, &buf[ofs - 16], 16); + dm->resolved = true; + break; // Return success + } + } + return true; +} + +static void dns_cb(struct mg_connection *c, int ev, void *ev_data, + void *fn_data) { + struct dns_data *d, *tmp; + if (ev == MG_EV_POLL) { + uint64_t now = *(uint64_t *) ev_data; + for (d = (struct dns_data *) c->mgr->active_dns_requests; d != NULL; + d = tmp) { + tmp = d->next; + // MG_DEBUG ("%lu %lu dns poll", d->expire, now)); + if (now > d->expire) mg_error(d->c, "DNS timeout"); + } + } else if (ev == MG_EV_READ) { + struct mg_dns_message dm; + int resolved = 0; + if (mg_dns_parse(c->recv.buf, c->recv.len, &dm) == false) { + MG_ERROR(("Unexpected DNS response:")); + mg_hexdump(c->recv.buf, c->recv.len); + } else { + MG_VERBOSE(("%s %d", dm.name, dm.resolved)); + for (d = (struct dns_data *) c->mgr->active_dns_requests; d != NULL; + d = tmp) { + tmp = d->next; + // MG_INFO(("d %p %hu %hu", d, d->txnid, dm.txnid)); + if (dm.txnid != d->txnid) continue; + if (d->c->is_resolving) { + if (dm.resolved) { + char buf[100]; + dm.addr.port = d->c->rem.port; // Save port + d->c->rem = dm.addr; // Copy resolved address + MG_DEBUG(("%lu %s is %s", d->c->id, dm.name, + mg_ntoa(&d->c->rem, buf, sizeof(buf)))); + mg_connect_resolved(d->c); +#if MG_ENABLE_IPV6 + } else if (dm.addr.is_ip6 == false && dm.name[0] != '\0' && + c->mgr->use_dns6 == false) { + struct mg_str x = mg_str(dm.name); + mg_sendnsreq(d->c, &x, c->mgr->dnstimeout, &c->mgr->dns6, true); +#endif + } else { + mg_error(d->c, "%s DNS lookup failed", dm.name); + } + } else { + MG_ERROR(("%lu already resolved", d->c->id)); + } + mg_dns_free(c, d); + resolved = 1; + } + } + if (!resolved) MG_ERROR(("stray DNS reply")); + c->recv.len = 0; + } else if (ev == MG_EV_CLOSE) { + for (d = (struct dns_data *) c->mgr->active_dns_requests; d != NULL; + d = tmp) { + tmp = d->next; + mg_error(d->c, "DNS error"); + mg_dns_free(c, d); + } + } + (void) fn_data; +} + +static bool mg_dns_send(struct mg_connection *c, const struct mg_str *name, + uint16_t txnid, bool ipv6) { + struct { + struct mg_dns_header header; + uint8_t data[256]; + } pkt; + size_t i, n; + memset(&pkt, 0, sizeof(pkt)); + pkt.header.txnid = mg_htons(txnid); + pkt.header.flags = mg_htons(0x100); + pkt.header.num_questions = mg_htons(1); + for (i = n = 0; i < sizeof(pkt.data) - 5; i++) { + if (name->ptr[i] == '.' || i >= name->len) { + pkt.data[n] = (uint8_t) (i - n); + memcpy(&pkt.data[n + 1], name->ptr + n, i - n); + n = i + 1; + } + if (i >= name->len) break; + } + memcpy(&pkt.data[n], "\x00\x00\x01\x00\x01", 5); // A query + n += 5; + if (ipv6) pkt.data[n - 3] = 0x1c; // AAAA query + // memcpy(&pkt.data[n], "\xc0\x0c\x00\x1c\x00\x01", 6); // AAAA query + // n += 6; + return mg_send(c, &pkt, sizeof(pkt.header) + n); +} + +static void mg_sendnsreq(struct mg_connection *c, struct mg_str *name, int ms, + struct mg_dns *dnsc, bool ipv6) { + struct dns_data *d = NULL; + if (dnsc->url == NULL) { + mg_error(c, "DNS server URL is NULL. Call mg_mgr_init()"); + } else if (dnsc->c == NULL) { + dnsc->c = mg_connect(c->mgr, dnsc->url, NULL, NULL); + if (dnsc->c != NULL) { + dnsc->c->pfn = dns_cb; + // dnsc->c->is_hexdumping = 1; + } + } + if (dnsc->c == NULL) { + mg_error(c, "resolver"); + } else if ((d = (struct dns_data *) calloc(1, sizeof(*d))) == NULL) { + mg_error(c, "resolve OOM"); + } else { + struct dns_data *reqs = (struct dns_data *) c->mgr->active_dns_requests; + char buf[100]; + d->txnid = reqs ? (uint16_t) (reqs->txnid + 1) : 1; + d->next = (struct dns_data *) c->mgr->active_dns_requests; + c->mgr->active_dns_requests = d; + d->expire = mg_millis() + (uint64_t) ms; + d->c = c; + c->is_resolving = 1; + MG_VERBOSE(("%lu resolving %.*s @ %s, txnid %hu", c->id, (int) name->len, + name->ptr, mg_ntoa(&dnsc->c->rem, buf, sizeof(buf)), d->txnid)); + if (!mg_dns_send(dnsc->c, name, d->txnid, ipv6)) { + mg_error(dnsc->c, "DNS send"); + } + } +} + +void mg_resolve(struct mg_connection *c, const char *url) { + struct mg_str host = mg_url_host(url); + c->rem.port = mg_htons(mg_url_port(url)); + if (mg_aton(host, &c->rem)) { + // host is an IP address, do not fire name resolution + mg_connect_resolved(c); + } else { + // host is not an IP, send DNS resolution request + struct mg_dns *dns = c->mgr->use_dns6 ? &c->mgr->dns6 : &c->mgr->dns4; + mg_sendnsreq(c, &host, c->mgr->dnstimeout, dns, c->mgr->use_dns6); + } +} + +#ifdef MG_ENABLE_LINES +#line 1 "src/event.c" +#endif + + + + + +void mg_call(struct mg_connection *c, int ev, void *ev_data) { + // Run user-defined handler first, in order to give it an ability + // to intercept processing (e.g. clean input buffer) before the + // protocol handler kicks in + if (c->fn != NULL) c->fn(c, ev, ev_data, c->fn_data); + if (c->pfn != NULL) c->pfn(c, ev, ev_data, c->pfn_data); +} + +void mg_error(struct mg_connection *c, const char *fmt, ...) { + char mem[256], *buf = mem; + va_list ap; + va_start(ap, fmt); + mg_vasprintf(&buf, sizeof(mem), fmt, ap); + va_end(ap); + MG_ERROR(("%lu %p %s", c->id, c->fd, buf)); + c->is_closing = 1; // Set is_closing before sending MG_EV_CALL + mg_call(c, MG_EV_ERROR, buf); // Let user handler to override it + if (buf != mem) free(buf); +} + +#ifdef MG_ENABLE_LINES +#line 1 "src/fs.c" +#endif + + + +struct mg_fd *mg_fs_open(struct mg_fs *fs, const char *path, int flags) { + struct mg_fd *fd = (struct mg_fd *) calloc(1, sizeof(*fd)); + if (fd != NULL) { + fd->fd = fs->op(path, flags); + fd->fs = fs; + if (fd->fd == NULL) { + free(fd); + fd = NULL; + } + } + return fd; +} + +void mg_fs_close(struct mg_fd *fd) { + if (fd != NULL) { + fd->fs->cl(fd->fd); + free(fd); + } +} + +char *mg_file_read(struct mg_fs *fs, const char *path, size_t *sizep) { + struct mg_fd *fd; + char *data = NULL; + size_t size = 0; + fs->st(path, &size, NULL); + if ((fd = mg_fs_open(fs, path, MG_FS_READ)) != NULL) { + data = (char *) calloc(1, size + 1); + if (data != NULL) { + if (fs->rd(fd->fd, data, size) != size) { + free(data); + data = NULL; + } else { + data[size] = '\0'; + if (sizep != NULL) *sizep = size; + } + } + mg_fs_close(fd); + } + return data; +} + +bool mg_file_write(struct mg_fs *fs, const char *path, const void *buf, + size_t len) { + bool result = false; + struct mg_fd *fd; + char tmp[MG_PATH_MAX]; + mg_snprintf(tmp, sizeof(tmp), "%s..%d", path, rand()); + if ((fd = mg_fs_open(fs, tmp, MG_FS_WRITE)) != NULL) { + result = fs->wr(fd->fd, buf, len) == len; + mg_fs_close(fd); + if (result) { + fs->rm(path); + fs->mv(tmp, path); + } else { + fs->rm(tmp); + } + } + return result; +} + +bool mg_file_printf(struct mg_fs *fs, const char *path, const char *fmt, ...) { + char tmp[256], *buf = tmp; + bool result; + size_t len; + va_list ap; + va_start(ap, fmt); + len = mg_vasprintf(&buf, sizeof(tmp), fmt, ap); + va_end(ap); + result = mg_file_write(fs, path, buf, len > 0 ? (size_t) len : 0); + if (buf != tmp) free(buf); + return result; +} + +#ifdef MG_ENABLE_LINES +#line 1 "src/fs_fat.c" +#endif + + +#if MG_ENABLE_FATFS +#include + +static int mg_days_from_epoch(int y, int m, int d) { + y -= m <= 2; + int era = y / 400; + int yoe = y - era * 400; + int doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1; + int doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; + return era * 146097 + doe - 719468; +} + +static time_t mg_timegm(const struct tm *t) { + int year = t->tm_year + 1900; + int month = t->tm_mon; // 0-11 + if (month > 11) { + year += month / 12; + month %= 12; + } else if (month < 0) { + int years_diff = (11 - month) / 12; + year -= years_diff; + month += 12 * years_diff; + } + int x = mg_days_from_epoch(year, month + 1, t->tm_mday); + return 60 * (60 * (24L * x + t->tm_hour) + t->tm_min) + t->tm_sec; +} + +static time_t ff_time_to_epoch(uint16_t fdate, uint16_t ftime) { + struct tm tm; + memset(&tm, 0, sizeof(struct tm)); + tm.tm_sec = (ftime << 1) & 0x3e; + tm.tm_min = ((ftime >> 5) & 0x3f); + tm.tm_hour = ((ftime >> 11) & 0x1f); + tm.tm_mday = (fdate & 0x1f); + tm.tm_mon = ((fdate >> 5) & 0x0f) - 1; + tm.tm_year = ((fdate >> 9) & 0x7f) + 80; + return mg_timegm(&tm); +} + +static int ff_stat(const char *path, size_t *size, time_t *mtime) { + FILINFO fi; + if (path[0] == '\0') { + if (size) *size = 0; + if (mtime) *mtime = 0; + return MG_FS_DIR; + } else if (f_stat(path, &fi) == 0) { + if (size) *size = (size_t) fi.fsize; + if (mtime) *mtime = ff_time_to_epoch(fi.fdate, fi.ftime); + return MG_FS_READ | MG_FS_WRITE | ((fi.fattrib & AM_DIR) ? MG_FS_DIR : 0); + } else { + return 0; + } +} + +static void ff_list(const char *dir, void (*fn)(const char *, void *), + void *userdata) { + DIR d; + FILINFO fi; + if (f_opendir(&d, dir) == FR_OK) { + while (f_readdir(&d, &fi) == FR_OK && fi.fname[0] != '\0') { + if (!strcmp(fi.fname, ".") || !strcmp(fi.fname, "..")) continue; + fn(fi.fname, userdata); + } + f_closedir(&d); + } +} + +static void *ff_open(const char *path, int flags) { + FIL f; + unsigned char mode = FA_READ; + if (flags & MG_FS_WRITE) mode |= FA_WRITE | FA_OPEN_ALWAYS | FA_OPEN_APPEND; + if (f_open(&f, path, mode) == 0) { + FIL *fp = calloc(1, sizeof(*fp)); + *fp = f; + return fp; + } else { + return NULL; + } +} + +static void ff_close(void *fp) { + if (fp != NULL) { + f_close((FIL *) fp); + free(fp); + } +} + +static size_t ff_read(void *fp, void *buf, size_t len) { + unsigned n = 0, misalign = ((size_t) buf) & 3; + if (misalign) { + char aligned[4]; + f_read((FIL *) fp, aligned, len > misalign ? misalign : len, &n); + memcpy(buf, aligned, n); + } else { + f_read((FIL *) fp, buf, len, &n); + } + return n; +} + +static size_t ff_write(void *fp, const void *buf, size_t len) { + unsigned n = 0; + return f_write((FIL *) fp, (char *) buf, len, &n) == FR_OK ? n : 0; +} + +static size_t ff_seek(void *fp, size_t offset) { + f_lseek((FIL *) fp, offset); + return offset; +} + +static bool ff_rename(const char *from, const char *to) { + return f_rename(from, to) == FR_OK; +} + +static bool ff_remove(const char *path) { + return f_unlink(path) == FR_OK; +} + +static bool ff_mkdir(const char *path) { + return f_mkdir(path) == FR_OK; +} + +struct mg_fs mg_fs_fat = {ff_stat, ff_list, ff_open, ff_close, ff_read, + ff_write, ff_seek, ff_rename, ff_remove, ff_mkdir}; +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/fs_packed.c" +#endif + + + +struct packed_file { + const char *data; + size_t size; + size_t pos; +}; + +const char *mg_unpack(const char *path, size_t *size, time_t *mtime); +const char *mg_unlist(size_t no); + +#if MG_ENABLE_PACKED_FS +#else +const char *mg_unpack(const char *path, size_t *size, time_t *mtime) { + (void) path, (void) size, (void) mtime; + return NULL; +} +const char *mg_unlist(size_t no) { + (void) no; + return NULL; +} +#endif + +static int is_dir_prefix(const char *prefix, size_t n, const char *path) { + // MG_INFO(("[%.*s] [%s] %c", (int) n, prefix, path, path[n])); + return n < strlen(path) && strncmp(prefix, path, n) == 0 && + (n == 0 || path[n] == '/' || path[n - 1] == '/'); +} + +static int packed_stat(const char *path, size_t *size, time_t *mtime) { + const char *p; + size_t i, n = strlen(path); + if (mg_unpack(path, size, mtime)) return MG_FS_READ; // Regular file + // Scan all files. If `path` is a dir prefix for any of them, it's a dir + for (i = 0; (p = mg_unlist(i)) != NULL; i++) { + if (is_dir_prefix(path, n, p)) return MG_FS_DIR; + } + return 0; +} + +static void packed_list(const char *dir, void (*fn)(const char *, void *), + void *userdata) { + char buf[256], tmp[sizeof(buf)]; + const char *path, *begin, *end; + size_t i, n = strlen(dir); + tmp[0] = '\0'; // Previously listed entry + for (i = 0; (path = mg_unlist(i)) != NULL; i++) { + if (!is_dir_prefix(dir, n, path)) continue; + begin = &path[n + 1]; + end = strchr(begin, '/'); + if (end == NULL) end = begin + strlen(begin); + mg_snprintf(buf, sizeof(buf), "%.*s", (int) (end - begin), begin); + buf[sizeof(buf) - 1] = '\0'; + // If this entry has been already listed, skip + // NOTE: we're assuming that file list is sorted alphabetically + if (strcmp(buf, tmp) == 0) continue; + fn(buf, userdata); // Not yet listed, call user function + strcpy(tmp, buf); // And save this entry as listed + } +} + +static void *packed_open(const char *path, int flags) { + size_t size = 0; + const char *data = mg_unpack(path, &size, NULL); + struct packed_file *fp = NULL; + if (data == NULL) return NULL; + if (flags & MG_FS_WRITE) return NULL; + fp = (struct packed_file *) calloc(1, sizeof(*fp)); + fp->size = size; + fp->data = data; + return (void *) fp; +} + +static void packed_close(void *fp) { + if (fp != NULL) free(fp); +} + +static size_t packed_read(void *fd, void *buf, size_t len) { + struct packed_file *fp = (struct packed_file *) fd; + if (fp->pos + len > fp->size) len = fp->size - fp->pos; + memcpy(buf, &fp->data[fp->pos], len); + fp->pos += len; + return len; +} + +static size_t packed_write(void *fd, const void *buf, size_t len) { + (void) fd, (void) buf, (void) len; + return 0; +} + +static size_t packed_seek(void *fd, size_t offset) { + struct packed_file *fp = (struct packed_file *) fd; + fp->pos = offset; + if (fp->pos > fp->size) fp->pos = fp->size; + return fp->pos; +} + +static bool packed_rename(const char *from, const char *to) { + (void) from, (void) to; + return false; +} + +static bool packed_remove(const char *path) { + (void) path; + return false; +} + +static bool packed_mkdir(const char *path) { + (void) path; + return false; +} + +struct mg_fs mg_fs_packed = { + packed_stat, packed_list, packed_open, packed_close, packed_read, + packed_write, packed_seek, packed_rename, packed_remove, packed_mkdir}; + +#ifdef MG_ENABLE_LINES +#line 1 "src/fs_posix.c" +#endif + + +#if MG_ENABLE_FILE + +#ifndef MG_STAT_STRUCT +#define MG_STAT_STRUCT stat +#endif + +#ifndef MG_STAT_FUNC +#define MG_STAT_FUNC stat +#endif + +static int p_stat(const char *path, size_t *size, time_t *mtime) { +#if !defined(S_ISDIR) + MG_ERROR(("stat() API is not supported. %p %p %p", path, size, mtime)); + return 0; +#else +#if MG_ARCH == MG_ARCH_WIN32 + struct _stati64 st; + wchar_t tmp[PATH_MAX]; + MultiByteToWideChar(CP_UTF8, 0, path, -1, tmp, sizeof(tmp) / sizeof(tmp[0])); + if (_wstati64(tmp, &st) != 0) return 0; +#else + struct MG_STAT_STRUCT st; + if (MG_STAT_FUNC(path, &st) != 0) return 0; +#endif + if (size) *size = (size_t) st.st_size; + if (mtime) *mtime = st.st_mtime; + return MG_FS_READ | MG_FS_WRITE | (S_ISDIR(st.st_mode) ? MG_FS_DIR : 0); +#endif +} + +#if MG_ARCH == MG_ARCH_WIN32 +struct dirent { + char d_name[MAX_PATH]; +}; + +typedef struct win32_dir { + HANDLE handle; + WIN32_FIND_DATAW info; + struct dirent result; +} DIR; + +int gettimeofday(struct timeval *tv, void *tz) { + FILETIME ft; + unsigned __int64 tmpres = 0; + + if (tv != NULL) { + GetSystemTimeAsFileTime(&ft); + tmpres |= ft.dwHighDateTime; + tmpres <<= 32; + tmpres |= ft.dwLowDateTime; + tmpres /= 10; // convert into microseconds + tmpres -= (int64_t) 11644473600000000; + tv->tv_sec = (long) (tmpres / 1000000UL); + tv->tv_usec = (long) (tmpres % 1000000UL); + } + (void) tz; + return 0; +} + +static int to_wchar(const char *path, wchar_t *wbuf, size_t wbuf_len) { + int ret; + char buf[MAX_PATH * 2], buf2[MAX_PATH * 2], *p; + strncpy(buf, path, sizeof(buf)); + buf[sizeof(buf) - 1] = '\0'; + // Trim trailing slashes. Leave backslash for paths like "X:\" + p = buf + strlen(buf) - 1; + while (p > buf && p[-1] != ':' && (p[0] == '\\' || p[0] == '/')) *p-- = '\0'; + memset(wbuf, 0, wbuf_len * sizeof(wchar_t)); + ret = MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, (int) wbuf_len); + // Convert back to Unicode. If doubly-converted string does not match the + // original, something is fishy, reject. + WideCharToMultiByte(CP_UTF8, 0, wbuf, (int) wbuf_len, buf2, sizeof(buf2), + NULL, NULL); + if (strcmp(buf, buf2) != 0) { + wbuf[0] = L'\0'; + ret = 0; + } + return ret; +} + +DIR *opendir(const char *name) { + DIR *d = NULL; + wchar_t wpath[MAX_PATH]; + DWORD attrs; + + if (name == NULL) { + SetLastError(ERROR_BAD_ARGUMENTS); + } else if ((d = (DIR *) calloc(1, sizeof(*d))) == NULL) { + SetLastError(ERROR_NOT_ENOUGH_MEMORY); + } else { + to_wchar(name, wpath, sizeof(wpath) / sizeof(wpath[0])); + attrs = GetFileAttributesW(wpath); + if (attrs != 0Xffffffff && (attrs & FILE_ATTRIBUTE_DIRECTORY)) { + (void) wcscat(wpath, L"\\*"); + d->handle = FindFirstFileW(wpath, &d->info); + d->result.d_name[0] = '\0'; + } else { + free(d); + d = NULL; + } + } + return d; +} + +int closedir(DIR *d) { + int result = 0; + if (d != NULL) { + if (d->handle != INVALID_HANDLE_VALUE) + result = FindClose(d->handle) ? 0 : -1; + free(d); + } else { + result = -1; + SetLastError(ERROR_BAD_ARGUMENTS); + } + return result; +} + +struct dirent *readdir(DIR *d) { + struct dirent *result = NULL; + if (d != NULL) { + memset(&d->result, 0, sizeof(d->result)); + if (d->handle != INVALID_HANDLE_VALUE) { + result = &d->result; + WideCharToMultiByte(CP_UTF8, 0, d->info.cFileName, -1, result->d_name, + sizeof(result->d_name), NULL, NULL); + if (!FindNextFileW(d->handle, &d->info)) { + FindClose(d->handle); + d->handle = INVALID_HANDLE_VALUE; + } + } else { + SetLastError(ERROR_FILE_NOT_FOUND); + } + } else { + SetLastError(ERROR_BAD_ARGUMENTS); + } + return result; +} +#endif + +static void p_list(const char *dir, void (*fn)(const char *, void *), + void *userdata) { +#if MG_ENABLE_DIRLIST + struct dirent *dp; + DIR *dirp; + if ((dirp = (opendir(dir))) == NULL) return; + while ((dp = readdir(dirp)) != NULL) { + if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) continue; + fn(dp->d_name, userdata); + } + closedir(dirp); +#else + (void) dir, (void) fn, (void) userdata; +#endif +} + +static void *p_open(const char *path, int flags) { + const char *mode = flags == MG_FS_READ ? "rb" : "a+b"; +#if MG_ARCH == MG_ARCH_WIN32 + wchar_t b1[PATH_MAX], b2[10]; + MultiByteToWideChar(CP_UTF8, 0, path, -1, b1, sizeof(b1) / sizeof(b1[0])); + MultiByteToWideChar(CP_UTF8, 0, mode, -1, b2, sizeof(b2) / sizeof(b2[0])); + return (void *) _wfopen(b1, b2); +#else + return (void *) fopen(path, mode); +#endif +} + +static void p_close(void *fp) { + fclose((FILE *) fp); +} + +static size_t p_read(void *fp, void *buf, size_t len) { + return fread(buf, 1, len, (FILE *) fp); +} + +static size_t p_write(void *fp, const void *buf, size_t len) { + return fwrite(buf, 1, len, (FILE *) fp); +} + +static size_t p_seek(void *fp, size_t offset) { +#if (defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64) || \ + (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200112L) || \ + (defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 600) + if (fseeko((FILE *) fp, (off_t) offset, SEEK_SET) != 0) (void) 0; +#else + if (fseek((FILE *) fp, (long) offset, SEEK_SET) != 0) (void) 0; +#endif + return (size_t) ftell((FILE *) fp); +} + +static bool p_rename(const char *from, const char *to) { + return rename(from, to) == 0; +} + +static bool p_remove(const char *path) { + return remove(path) == 0; +} + +static bool p_mkdir(const char *path) { + return mkdir(path, 0775) == 0; +} + +#else + +static int p_stat(const char *path, size_t *size, time_t *mtime) { + (void) path, (void) size, (void) mtime; + return 0; +} +static void p_list(const char *path, void (*fn)(const char *, void *), + void *userdata) { + (void) path, (void) fn, (void) userdata; +} +static void *p_open(const char *path, int flags) { + (void) path, (void) flags; + return NULL; +} +static void p_close(void *fp) { + (void) fp; +} +static size_t p_read(void *fd, void *buf, size_t len) { + (void) fd, (void) buf, (void) len; + return 0; +} +static size_t p_write(void *fd, const void *buf, size_t len) { + (void) fd, (void) buf, (void) len; + return 0; +} +static size_t p_seek(void *fd, size_t offset) { + (void) fd, (void) offset; + return (size_t) ~0; +} +static bool p_rename(const char *from, const char *to) { + (void) from, (void) to; + return false; +} +static bool p_remove(const char *path) { + (void) path; + return false; +} +static bool p_mkdir(const char *path) { + (void) path; + return false; +} +#endif + +struct mg_fs mg_fs_posix = {p_stat, p_list, p_open, p_close, p_read, + p_write, p_seek, p_rename, p_remove, p_mkdir}; + +#ifdef MG_ENABLE_LINES +#line 1 "src/http.c" +#endif + + + + + + + + + + +// Multipart POST example: +// --xyz +// Content-Disposition: form-data; name="val" +// +// abcdef +// --xyz +// Content-Disposition: form-data; name="foo"; filename="a.txt" +// Content-Type: text/plain +// +// hello world +// +// --xyz-- +size_t mg_http_next_multipart(struct mg_str body, size_t ofs, + struct mg_http_part *part) { + struct mg_str cd = mg_str_n("Content-Disposition", 19); + const char *s = body.ptr; + size_t b = ofs, h1, h2, b1, b2, max = body.len; + + // Init part params + if (part != NULL) part->name = part->filename = part->body = mg_str_n(0, 0); + + // Skip boundary + while (b + 2 < max && s[b] != '\r' && s[b + 1] != '\n') b++; + if (b <= ofs || b + 2 >= max) return 0; + // MG_INFO(("B: %zu %zu [%.*s]", ofs, b - ofs, (int) (b - ofs), s)); + + // Skip headers + h1 = h2 = b + 2; + for (;;) { + while (h2 + 2 < max && s[h2] != '\r' && s[h2 + 1] != '\n') h2++; + if (h2 == h1) break; + if (h2 + 2 >= max) return 0; + // MG_INFO(("Header: [%.*s]", (int) (h2 - h1), &s[h1])); + if (part != NULL && h1 + cd.len + 2 < h2 && s[h1 + cd.len] == ':' && + mg_ncasecmp(&s[h1], cd.ptr, cd.len) == 0) { + struct mg_str v = mg_str_n(&s[h1 + cd.len + 2], h2 - (h1 + cd.len + 2)); + part->name = mg_http_get_header_var(v, mg_str_n("name", 4)); + part->filename = mg_http_get_header_var(v, mg_str_n("filename", 8)); + } + h1 = h2 = h2 + 2; + } + b1 = b2 = h2 + 2; + while (b2 + 2 + (b - ofs) + 2 < max && !(s[b2] == '\r' && s[b2 + 1] == '\n' && + memcmp(&s[b2 + 2], s, b - ofs) == 0)) + b2++; + + if (b2 + 2 >= max) return 0; + if (part != NULL) part->body = mg_str_n(&s[b1], b2 - b1); + // MG_INFO(("Body: [%.*s]", (int) (b2 - b1), &s[b1])); + return b2 + 2; +} + +void mg_http_bauth(struct mg_connection *c, const char *user, + const char *pass) { + struct mg_str u = mg_str(user), p = mg_str(pass); + size_t need = c->send.len + 36 + (u.len + p.len) * 2; + if (c->send.size < need) mg_iobuf_resize(&c->send, need); + if (c->send.size >= need) { + int i, n = 0; + char *buf = (char *) &c->send.buf[c->send.len + 21]; + memcpy(&buf[-21], "Authorization: Basic ", 21); // DON'T use mg_send! + for (i = 0; i < (int) u.len; i++) { + n = mg_base64_update(((unsigned char *) u.ptr)[i], buf, n); + } + if (p.len > 0) { + n = mg_base64_update(':', buf, n); + for (i = 0; i < (int) p.len; i++) { + n = mg_base64_update(((unsigned char *) p.ptr)[i], buf, n); + } + } + n = mg_base64_final(buf, n); + c->send.len += 21 + (size_t) n + 2; + memcpy(&c->send.buf[c->send.len - 2], "\r\n", 2); + } else { + MG_ERROR(("%lu %s cannot resize iobuf %d->%d ", c->id, c->label, + (int) c->send.size, (int) need)); + } +} + +int mg_http_get_var(const struct mg_str *buf, const char *name, char *dst, + size_t dst_len) { + const char *p, *e, *s; + size_t name_len; + int len; + + if (dst == NULL || dst_len == 0) { + len = -2; // Bad destination + } else if (buf->ptr == NULL || name == NULL || buf->len == 0) { + len = -1; // Bad source + dst[0] = '\0'; + } else { + name_len = strlen(name); + e = buf->ptr + buf->len; + len = -4; // Name does not exist + dst[0] = '\0'; + for (p = buf->ptr; p + name_len < e; p++) { + if ((p == buf->ptr || p[-1] == '&') && p[name_len] == '=' && + !mg_ncasecmp(name, p, name_len)) { + p += name_len + 1; + s = (const char *) memchr(p, '&', (size_t) (e - p)); + if (s == NULL) s = e; + len = mg_url_decode(p, (size_t) (s - p), dst, dst_len, 1); + if (len < 0) len = -3; // Failed to decode + break; + } + } + } + return len; +} + +static bool isx(int c) { + return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || + (c >= 'A' && c <= 'F'); +} + +int mg_url_decode(const char *src, size_t src_len, char *dst, size_t dst_len, + int is_form_url_encoded) { + size_t i, j; + for (i = j = 0; i < src_len && j + 1 < dst_len; i++, j++) { + if (src[i] == '%') { + // Use `i + 2 < src_len`, not `i < src_len - 2`, note small src_len + if (i + 2 < src_len && isx(src[i + 1]) && isx(src[i + 2])) { + mg_unhex(src + i + 1, 2, (uint8_t *) &dst[j]); + i += 2; + } else { + return -1; + } + } else if (is_form_url_encoded && src[i] == '+') { + dst[j] = ' '; + } else { + dst[j] = src[i]; + } + } + if (j < dst_len) dst[j] = '\0'; // Null-terminate the destination + return i >= src_len && j < dst_len ? (int) j : -1; +} + +static bool isok(uint8_t c) { + return c == '\n' || c == '\r' || c >= ' '; +} + +int mg_http_get_request_len(const unsigned char *buf, size_t buf_len) { + size_t i; + for (i = 0; i < buf_len; i++) { + if (!isok(buf[i])) return -1; + if ((i > 0 && buf[i] == '\n' && buf[i - 1] == '\n') || + (i > 3 && buf[i] == '\n' && buf[i - 1] == '\r' && buf[i - 2] == '\n')) + return (int) i + 1; + } + return 0; +} + +static const char *skip(const char *s, const char *e, const char *d, + struct mg_str *v) { + v->ptr = s; + while (s < e && *s != '\n' && strchr(d, *s) == NULL) s++; + v->len = (size_t) (s - v->ptr); + while (s < e && strchr(d, *s) != NULL) s++; + return s; +} + +struct mg_str *mg_http_get_header(struct mg_http_message *h, const char *name) { + size_t i, n = strlen(name), max = sizeof(h->headers) / sizeof(h->headers[0]); + for (i = 0; i < max && h->headers[i].name.len > 0; i++) { + struct mg_str *k = &h->headers[i].name, *v = &h->headers[i].value; + if (n == k->len && mg_ncasecmp(k->ptr, name, n) == 0) return v; + } + return NULL; +} + +static void mg_http_parse_headers(const char *s, const char *end, + struct mg_http_header *h, int max_headers) { + int i; + for (i = 0; i < max_headers; i++) { + struct mg_str k, v, tmp; + const char *he = skip(s, end, "\n", &tmp); + s = skip(s, he, ": \r\n", &k); + s = skip(s, he, "\r\n", &v); + if (k.len == tmp.len) continue; + while (v.len > 0 && v.ptr[v.len - 1] == ' ') v.len--; // Trim spaces + if (k.len == 0) break; + // MG_INFO(("--HH [%.*s] [%.*s] [%.*s]", (int) tmp.len - 1, tmp.ptr, + //(int) k.len, k.ptr, (int) v.len, v.ptr)); + h[i].name = k; + h[i].value = v; + } +} + +int mg_http_parse(const char *s, size_t len, struct mg_http_message *hm) { + int is_response, req_len = mg_http_get_request_len((unsigned char *) s, len); + const char *end = s + req_len, *qs; + struct mg_str *cl; + + memset(hm, 0, sizeof(*hm)); + if (req_len <= 0) return req_len; + + hm->message.ptr = hm->head.ptr = s; + hm->body.ptr = end; + hm->head.len = (size_t) req_len; + hm->chunk.ptr = end; + hm->message.len = hm->body.len = (size_t) ~0; // Set body length to infinite + + // Parse request line + s = skip(s, end, " ", &hm->method); + s = skip(s, end, " ", &hm->uri); + s = skip(s, end, "\r\n", &hm->proto); + + // Sanity check. Allow protocol/reason to be empty + if (hm->method.len == 0 || hm->uri.len == 0) return -1; + + // If URI contains '?' character, setup query string + if ((qs = (const char *) memchr(hm->uri.ptr, '?', hm->uri.len)) != NULL) { + hm->query.ptr = qs + 1; + hm->query.len = (size_t) (&hm->uri.ptr[hm->uri.len] - (qs + 1)); + hm->uri.len = (size_t) (qs - hm->uri.ptr); + } + + mg_http_parse_headers(s, end, hm->headers, + sizeof(hm->headers) / sizeof(hm->headers[0])); + if ((cl = mg_http_get_header(hm, "Content-Length")) != NULL) { + hm->body.len = (size_t) mg_to64(*cl); + hm->message.len = (size_t) req_len + hm->body.len; + } + + // mg_http_parse() is used to parse both HTTP requests and HTTP + // responses. If HTTP response does not have Content-Length set, then + // body is read until socket is closed, i.e. body.len is infinite (~0). + // + // For HTTP requests though, according to + // http://tools.ietf.org/html/rfc7231#section-8.1.3, + // only POST and PUT methods have defined body semantics. + // Therefore, if Content-Length is not specified and methods are + // not one of PUT or POST, set body length to 0. + // + // So, if it is HTTP request, and Content-Length is not set, + // and method is not (PUT or POST) then reset body length to zero. + is_response = mg_ncasecmp(hm->method.ptr, "HTTP/", 5) == 0; + if (hm->body.len == (size_t) ~0 && !is_response && + mg_vcasecmp(&hm->method, "PUT") != 0 && + mg_vcasecmp(&hm->method, "POST") != 0) { + hm->body.len = 0; + hm->message.len = (size_t) req_len; + } + + // The 204 (No content) responses also have 0 body length + if (hm->body.len == (size_t) ~0 && is_response && + mg_vcasecmp(&hm->uri, "204") == 0) { + hm->body.len = 0; + hm->message.len = (size_t) req_len; + } + + return req_len; +} + +static void mg_http_vprintf_chunk(struct mg_connection *c, const char *fmt, + va_list ap) { + char mem[256], *buf = mem; + size_t len = mg_vasprintf(&buf, sizeof(mem), fmt, ap); + mg_printf(c, "%lx\r\n", (unsigned long) len); + mg_send(c, buf, len > 0 ? (size_t) len : 0); + mg_send(c, "\r\n", 2); + if (buf != mem) free(buf); +} + +void mg_http_printf_chunk(struct mg_connection *c, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + mg_http_vprintf_chunk(c, fmt, ap); + va_end(ap); +} + +void mg_http_write_chunk(struct mg_connection *c, const char *buf, size_t len) { + mg_printf(c, "%lx\r\n", (unsigned long) len); + mg_send(c, buf, len); + mg_send(c, "\r\n", 2); +} + +// clang-format off +static const char *mg_http_status_code_str(int status_code) { + switch (status_code) { + case 100: return "Continue"; + case 201: return "Created"; + case 202: return "Accepted"; + case 204: return "No Content"; + case 206: return "Partial Content"; + case 301: return "Moved Permanently"; + case 302: return "Found"; + case 304: return "Not Modified"; + case 400: return "Bad Request"; + case 401: return "Unauthorized"; + case 403: return "Forbidden"; + case 404: return "Not Found"; + case 418: return "I'm a teapot"; + case 500: return "Internal Server Error"; + case 501: return "Not Implemented"; + default: return "OK"; + } +} +// clang-format on + +void mg_http_reply(struct mg_connection *c, int code, const char *headers, + const char *fmt, ...) { + char mem[256], *buf = mem; + va_list ap; + size_t len; + va_start(ap, fmt); + len = mg_vasprintf(&buf, sizeof(mem), fmt, ap); + va_end(ap); + mg_printf(c, "HTTP/1.1 %d %s\r\n%sContent-Length: %d\r\n\r\n", code, + mg_http_status_code_str(code), headers == NULL ? "" : headers, len); + mg_send(c, buf, len > 0 ? len : 0); + if (buf != mem) free(buf); +} + +static void http_cb(struct mg_connection *, int, void *, void *); +static void restore_http_cb(struct mg_connection *c) { + mg_fs_close((struct mg_fd *) c->pfn_data); + c->pfn_data = NULL; + c->pfn = http_cb; +} + +char *mg_http_etag(char *buf, size_t len, size_t size, time_t mtime); +char *mg_http_etag(char *buf, size_t len, size_t size, time_t mtime) { + mg_snprintf(buf, len, "\"%lld.%lld\"", (int64_t) mtime, (int64_t) size); + return buf; +} + +static void static_cb(struct mg_connection *c, int ev, void *ev_data, + void *fn_data) { + if (ev == MG_EV_WRITE || ev == MG_EV_POLL) { + struct mg_fd *fd = (struct mg_fd *) fn_data; + // Read to send IO buffer directly, avoid extra on-stack buffer + size_t n, max = MG_IO_SIZE, space, *cl = (size_t *) c->label; + if (c->send.size < max) mg_iobuf_resize(&c->send, max); + if (c->send.len >= c->send.size) return; // Rate limit + if ((space = c->send.size - c->send.len) > *cl) space = *cl; + n = fd->fs->rd(fd->fd, c->send.buf + c->send.len, space); + c->send.len += n; + *cl -= n; + if (n == 0) restore_http_cb(c); + } else if (ev == MG_EV_CLOSE) { + restore_http_cb(c); + } + (void) ev_data; +} + +static struct mg_str guess_content_type(struct mg_str path, const char *extra) { + struct mg_str k, v, s = mg_str(extra); + size_t i = 0; + + // clang-format off + struct mg_str tab[] = { + MG_C_STR("html"), MG_C_STR("text/html; charset=utf-8"), + MG_C_STR("htm"), MG_C_STR("text/html; charset=utf-8"), + MG_C_STR("css"), MG_C_STR("text/css; charset=utf-8"), + MG_C_STR("js"), MG_C_STR("text/javascript; charset=utf-8"), + MG_C_STR("gif"), MG_C_STR("image/gif"), + MG_C_STR("png"), MG_C_STR("image/png"), + MG_C_STR("jpg"), MG_C_STR("image/jpeg"), + MG_C_STR("jpeg"), MG_C_STR("image/jpeg"), + MG_C_STR("woff"), MG_C_STR("font/woff"), + MG_C_STR("ttf"), MG_C_STR("font/ttf"), + MG_C_STR("svg"), MG_C_STR("image/svg+xml"), + MG_C_STR("txt"), MG_C_STR("text/plain; charset=utf-8"), + MG_C_STR("avi"), MG_C_STR("video/x-msvideo"), + MG_C_STR("csv"), MG_C_STR("text/csv"), + MG_C_STR("doc"), MG_C_STR("application/msword"), + MG_C_STR("exe"), MG_C_STR("application/octet-stream"), + MG_C_STR("gz"), MG_C_STR("application/gzip"), + MG_C_STR("ico"), MG_C_STR("image/x-icon"), + MG_C_STR("json"), MG_C_STR("application/json"), + MG_C_STR("mov"), MG_C_STR("video/quicktime"), + MG_C_STR("mp3"), MG_C_STR("audio/mpeg"), + MG_C_STR("mp4"), MG_C_STR("video/mp4"), + MG_C_STR("mpeg"), MG_C_STR("video/mpeg"), + MG_C_STR("pdf"), MG_C_STR("application/pdf"), + MG_C_STR("shtml"), MG_C_STR("text/html; charset=utf-8"), + MG_C_STR("tgz"), MG_C_STR("application/tar-gz"), + MG_C_STR("wav"), MG_C_STR("audio/wav"), + MG_C_STR("webp"), MG_C_STR("image/webp"), + MG_C_STR("zip"), MG_C_STR("application/zip"), + MG_C_STR("3gp"), MG_C_STR("video/3gpp"), + {0, 0}, + }; + // clang-format on + + // Shrink path to its extension only + while (i < path.len && path.ptr[path.len - i - 1] != '.') i++; + path.ptr += path.len - i; + path.len = i; + + // Process user-provided mime type overrides, if any + while (mg_commalist(&s, &k, &v)) { + if (mg_strcmp(path, k) == 0) return v; + } + + // Process built-in mime types + for (i = 0; tab[i].ptr != NULL; i += 2) { + if (mg_strcmp(path, tab[i]) == 0) return tab[i + 1]; + } + + return mg_str("text/plain; charset=utf-8"); +} + +static int getrange(struct mg_str *s, int64_t *a, int64_t *b) { + size_t i, numparsed = 0; + // MG_INFO(("%.*s", (int) s->len, s->ptr)); + for (i = 0; i + 6 < s->len; i++) { + if (memcmp(&s->ptr[i], "bytes=", 6) == 0) { + struct mg_str p = mg_str_n(s->ptr + i + 6, s->len - i - 6); + if (p.len > 0 && p.ptr[0] >= '0' && p.ptr[0] <= '9') numparsed++; + *a = mg_to64(p); + // MG_INFO(("PPP [%.*s] %d", (int) p.len, p.ptr, numparsed)); + while (p.len && p.ptr[0] >= '0' && p.ptr[0] <= '9') p.ptr++, p.len--; + if (p.len && p.ptr[0] == '-') p.ptr++, p.len--; + *b = mg_to64(p); + if (p.len > 0 && p.ptr[0] >= '0' && p.ptr[0] <= '9') numparsed++; + // MG_INFO(("PPP [%.*s] %d", (int) p.len, p.ptr, numparsed)); + break; + } + } + return (int) numparsed; +} + +void mg_http_serve_file(struct mg_connection *c, struct mg_http_message *hm, + const char *path, + const struct mg_http_serve_opts *opts) { + char etag[64]; + struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs; + struct mg_fd *fd = mg_fs_open(fs, path, MG_FS_READ); + size_t size = 0; + time_t mtime = 0; + struct mg_str *inm = NULL; + + if (fd == NULL || fs->st(path, &size, &mtime) == 0) { + MG_DEBUG(("404 [%s] %p", path, (void *) fd)); + mg_http_reply(c, 404, "", "%s", "Not found\n"); + mg_fs_close(fd); + // NOTE: mg_http_etag() call should go first! + } else if (mg_http_etag(etag, sizeof(etag), size, mtime) != NULL && + (inm = mg_http_get_header(hm, "If-None-Match")) != NULL && + mg_vcasecmp(inm, etag) == 0) { + mg_fs_close(fd); + mg_printf(c, "HTTP/1.1 304 Not Modified\r\nContent-Length: 0\r\n\r\n"); + } else { + int n, status = 200; + char range[100] = ""; + int64_t r1 = 0, r2 = 0, cl = (int64_t) size; + struct mg_str mime = guess_content_type(mg_str(path), opts->mime_types); + + // Handle Range header + struct mg_str *rh = mg_http_get_header(hm, "Range"); + if (rh != NULL && (n = getrange(rh, &r1, &r2)) > 0 && r1 >= 0 && r2 >= 0) { + // If range is specified like "400-", set second limit to content len + if (n == 1) r2 = cl - 1; + if (r1 > r2 || r2 >= cl) { + status = 416; + cl = 0; + mg_snprintf(range, sizeof(range), "Content-Range: bytes */%lld\r\n", + (int64_t) size); + } else { + status = 206; + cl = r2 - r1 + 1; + mg_snprintf(range, sizeof(range), + "Content-Range: bytes %lld-%lld/%lld\r\n", r1, r1 + cl - 1, + (int64_t) size); + fs->sk(fd->fd, (size_t) r1); + } + } + mg_printf(c, + "HTTP/1.1 %d %s\r\n" + "Content-Type: %.*s\r\n" + "Etag: %s\r\n" + "Content-Length: %llu\r\n" + "%s%s\r\n", + status, mg_http_status_code_str(status), (int) mime.len, mime.ptr, + etag, cl, range, opts->extra_headers ? opts->extra_headers : ""); + if (mg_vcasecmp(&hm->method, "HEAD") == 0) { + c->is_draining = 1; + mg_fs_close(fd); + } else { + c->pfn = static_cb; + c->pfn_data = fd; + *(size_t *) c->label = (size_t) cl; // Track to-be-sent content length + } + } +} + +struct printdirentrydata { + struct mg_connection *c; + struct mg_http_message *hm; + const struct mg_http_serve_opts *opts; + const char *dir; +}; + +static void printdirentry(const char *name, void *userdata) { + struct printdirentrydata *d = (struct printdirentrydata *) userdata; + struct mg_fs *fs = d->opts->fs == NULL ? &mg_fs_posix : d->opts->fs; + size_t size = 0; + time_t t = 0; + char path[MG_PATH_MAX], sz[40], mod[40]; + int flags, n = 0; + + // MG_DEBUG(("[%s] [%s]", d->dir, name)); + if (mg_snprintf(path, sizeof(path), "%s%c%s", d->dir, '/', name) > + sizeof(path)) { + MG_ERROR(("%s truncated", name)); + } else if ((flags = fs->st(path, &size, &t)) == 0) { + MG_ERROR(("%lu stat(%s): %d", d->c->id, path, errno)); + } else { + const char *slash = flags & MG_FS_DIR ? "/" : ""; + if (flags & MG_FS_DIR) { + mg_snprintf(sz, sizeof(sz), "%s", "[DIR]"); + } else { + mg_snprintf(sz, sizeof(sz), "%lld", (uint64_t) size); + } + mg_snprintf(mod, sizeof(mod), "%ld", (unsigned long) t); + n = (int) mg_url_encode(name, strlen(name), path, sizeof(path)); + mg_printf(d->c, + " %s%s" + "%s%s\n", + n, path, slash, name, slash, (unsigned long) t, mod, + flags & MG_FS_DIR ? (int64_t) -1 : (int64_t) size, sz); + } +} + +static void listdir(struct mg_connection *c, struct mg_http_message *hm, + const struct mg_http_serve_opts *opts, char *dir) { + const char *sort_js_code = + ""; + struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs; + struct printdirentrydata d = {c, hm, opts, dir}; + char tmp[10], buf[MG_PATH_MAX]; + size_t off, n; + int len = mg_url_decode(hm->uri.ptr, hm->uri.len, buf, sizeof(buf), 0); + struct mg_str uri = len > 0 ? mg_str_n(buf, (size_t) len) : hm->uri; + + mg_printf(c, + "HTTP/1.1 200 OK\r\n" + "Content-Type: text/html; charset=utf-8\r\n" + "%s" + "Content-Length: \r\n\r\n", + opts->extra_headers == NULL ? "" : opts->extra_headers); + off = c->send.len; // Start of body + mg_printf(c, + "Index of %.*s%s%s" + "" + "

Index of %.*s

" + "" + "" + "" + "" + "\n", + (int) uri.len, uri.ptr, sort_js_code, sort_js_code2, (int) uri.len, + uri.ptr); + mg_printf(c, "%s", + " " + "\n"); + + fs->ls(dir, printdirentry, &d); + mg_printf(c, + "" + "
Name" + "ModifiedSize

..[DIR]

Mongoose v.%s
\n", + MG_VERSION); + n = mg_snprintf(tmp, sizeof(tmp), "%lu", (unsigned long) (c->send.len - off)); + if (n > sizeof(tmp)) n = 0; + memcpy(c->send.buf + off - 10, tmp, n); // Set content length +} + +static void remove_double_dots(char *s) { + char *p = s; + while (*s != '\0') { + *p++ = *s++; + if (s[-1] == '/' || s[-1] == '\\') { + while (s[0] != '\0') { + if (s[0] == '/' || s[0] == '\\') { + s++; + } else if (s[0] == '.' && s[1] == '.' && + (s[2] == '/' || s[2] == '\\')) { + s += 2; + } else { + break; + } + } + } + } + *p = '\0'; +} + +// Resolve requested file into `path` and return its fs->st() result +static int uri_to_path2(struct mg_connection *c, struct mg_http_message *hm, + struct mg_fs *fs, struct mg_str url, struct mg_str dir, + char *path, size_t path_size) { + int flags, tmp; + // Append URI to the root_dir, and sanitize it + size_t n = mg_snprintf(path, path_size, "%.*s", (int) dir.len, dir.ptr); + if (n > path_size) n = path_size; + path[path_size - 1] = '\0'; + if (n + 2 < path_size) path[n++] = '/', path[n] = '\0'; + mg_url_decode(hm->uri.ptr + url.len, hm->uri.len - url.len, path + n, + path_size - n, 0); + path[path_size - 1] = '\0'; // Double-check + remove_double_dots(path); + n = strlen(path); + MG_VERBOSE(("%lu %.*s -> %s", c->id, (int) hm->uri.len, hm->uri.ptr, path)); + while (n > 0 && path[n - 1] == '/') path[--n] = 0; // Trim trailing slashes + flags = mg_vcmp(&hm->uri, "/") == 0 ? MG_FS_DIR : fs->st(path, NULL, NULL); + if (flags == 0) { + mg_http_reply(c, 404, "", "Not found\n"); // Does not exist, doh + } else if ((flags & MG_FS_DIR) && hm->uri.len > 0 && + hm->uri.ptr[hm->uri.len - 1] != '/') { + mg_printf(c, + "HTTP/1.1 301 Moved\r\n" + "Location: %.*s/\r\n" + "Content-Length: 0\r\n" + "\r\n", + (int) hm->uri.len, hm->uri.ptr); + flags = 0; + } else if (flags & MG_FS_DIR) { + if (((mg_snprintf(path + n, path_size - n, "/" MG_HTTP_INDEX) > 0 && + (tmp = fs->st(path, NULL, NULL)) != 0) || + (mg_snprintf(path + n, path_size - n, "/index.shtml") > 0 && + (tmp = fs->st(path, NULL, NULL)) != 0))) { + flags = tmp; + } else { + path[n] = '\0'; // Remove appended index file name + } + } + return flags; +} + +static int uri_to_path(struct mg_connection *c, struct mg_http_message *hm, + const struct mg_http_serve_opts *opts, char *path, + size_t path_size) { + struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs; + struct mg_str k, v, s = mg_str(opts->root_dir), u = {0, 0}, p = {0, 0}; + while (mg_commalist(&s, &k, &v)) { + if (v.len == 0) v = k, k = mg_str("/"); + if (hm->uri.len < k.len) continue; + if (mg_strcmp(k, mg_str_n(hm->uri.ptr, k.len)) != 0) continue; + u = k, p = v; + } + return uri_to_path2(c, hm, fs, u, p, path, path_size); +} + +void mg_http_serve_dir(struct mg_connection *c, struct mg_http_message *hm, + const struct mg_http_serve_opts *opts) { + char path[MG_PATH_MAX] = ""; + const char *sp = opts->ssi_pattern; + int flags = uri_to_path(c, hm, opts, path, sizeof(path)); + if (flags == 0) return; + if (flags & MG_FS_DIR) { + listdir(c, hm, opts, path); + } else if (sp != NULL && mg_globmatch(sp, strlen(sp), path, strlen(path))) { + mg_http_serve_ssi(c, opts->root_dir, path); + } else { + mg_http_serve_file(c, hm, path, opts); + } +} + +static bool mg_is_url_safe(int c) { + return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || c == '.' || c == '_' || c == '-' || c == '~'; +} + +size_t mg_url_encode(const char *s, size_t sl, char *buf, size_t len) { + size_t i, n = 0; + for (i = 0; i < sl; i++) { + int c = *(unsigned char *) &s[i]; + if (n + 4 >= len) return 0; + if (mg_is_url_safe(c)) { + buf[n++] = s[i]; + } else { + buf[n++] = '%'; + mg_hex(&s[i], 1, &buf[n]); + n += 2; + } + } + return n; +} + +void mg_http_creds(struct mg_http_message *hm, char *user, size_t userlen, + char *pass, size_t passlen) { + struct mg_str *v = mg_http_get_header(hm, "Authorization"); + user[0] = pass[0] = '\0'; + if (v != NULL && v->len > 6 && memcmp(v->ptr, "Basic ", 6) == 0) { + char buf[256]; + int n = mg_base64_decode(v->ptr + 6, (int) v->len - 6, buf); + const char *p = (const char *) memchr(buf, ':', n > 0 ? (size_t) n : 0); + if (p != NULL) { + mg_snprintf(user, userlen, "%.*s", (int) (p - buf), buf); + mg_snprintf(pass, passlen, "%.*s", n - (int) (p - buf) - 1, p + 1); + } + } else if (v != NULL && v->len > 7 && memcmp(v->ptr, "Bearer ", 7) == 0) { + mg_snprintf(pass, passlen, "%.*s", (int) v->len - 7, v->ptr + 7); + } else if ((v = mg_http_get_header(hm, "Cookie")) != NULL) { + struct mg_str t = mg_http_get_header_var(*v, mg_str_n("access_token", 12)); + if (t.len > 0) mg_snprintf(pass, passlen, "%.*s", (int) t.len, t.ptr); + } else { + mg_http_get_var(&hm->query, "access_token", pass, passlen); + } +} + +static struct mg_str stripquotes(struct mg_str s) { + return s.len > 1 && s.ptr[0] == '"' && s.ptr[s.len - 1] == '"' + ? mg_str_n(s.ptr + 1, s.len - 2) + : s; +} + +struct mg_str mg_http_get_header_var(struct mg_str s, struct mg_str v) { + size_t i; + for (i = 0; v.len > 0 && i + v.len + 2 < s.len; i++) { + if (s.ptr[i + v.len] == '=' && memcmp(&s.ptr[i], v.ptr, v.len) == 0) { + const char *p = &s.ptr[i + v.len + 1], *b = p, *x = &s.ptr[s.len]; + int q = p < x && *p == '"' ? 1 : 0; + while (p < x && + (q ? p == b || *p != '"' : *p != ';' && *p != ' ' && *p != ',')) + p++; + // MG_INFO(("[%.*s] [%.*s] [%.*s]", (int) s.len, s.ptr, (int) v.len, + // v.ptr, (int) (p - b), b)); + return stripquotes(mg_str_n(b, (size_t) (p - b + q))); + } + } + return mg_str_n(NULL, 0); +} + +bool mg_http_match_uri(const struct mg_http_message *hm, const char *glob) { + return mg_match(hm->uri, mg_str(glob), NULL); +} + +static size_t get_chunk_length(const char *buf, size_t len, size_t *ll) { + size_t i = 0, n; + while (i < len && buf[i] != '\r' && i != '\n') i++; + n = mg_unhexn((char *) buf, i); + while (i < len && (buf[i] == '\r' || i == '\n')) i++; + // MG_INFO(("len %zu i %zu n %zu ", len, i, n)); + if (ll != NULL) *ll = i + 1; + if (i < len && i + n + 2 < len) return i + n + 3; + return 0; +} + +// Walk through all chunks in the chunked body. For each chunk, fire +// an MG_EV_HTTP_CHUNK event. +static void walkchunks(struct mg_connection *c, struct mg_http_message *hm, + size_t reqlen) { + size_t off = 0, bl, ll; + while (off + reqlen < c->recv.len) { + char *buf = (char *) &c->recv.buf[reqlen]; + size_t memo = c->recv.len; + size_t cl = get_chunk_length(&buf[off], memo - reqlen - off, &ll); + // MG_INFO(("len %zu off %zu cl %zu ll %zu", len, off, cl, ll)); + if (cl == 0) break; + hm->chunk = mg_str_n(&buf[off + ll], cl < ll + 2 ? 0 : cl - ll - 2); + mg_call(c, MG_EV_HTTP_CHUNK, hm); + // Increase offset only if user has not deleted this chunk + if (memo == c->recv.len) off += cl; + if (cl <= 5) { + // Zero chunk - last one. Prepare body - cut off chunk lengths + off = bl = 0; + while (off + reqlen < c->recv.len) { + char *buf2 = (char *) &c->recv.buf[reqlen]; + size_t memo2 = c->recv.len; + size_t cl2 = get_chunk_length(&buf2[off], memo2 - reqlen - off, &ll); + size_t n = cl2 < ll + 2 ? 0 : cl2 - ll - 2; + memmove(buf2 + bl, buf2 + off + ll, n); + bl += n; + off += cl2; + if (cl2 <= 5) break; + } + // MG_INFO(("BL->%d del %d off %d", (int) bl, (int) del, (int) off)); + c->recv.len -= off - bl; + // Set message length to indicate we've received + // everything, to fire MG_EV_HTTP_MSG + hm->message.len = bl + reqlen; + hm->body.len = bl; + break; + } + } +} + +static bool mg_is_chunked(struct mg_http_message *hm) { + const char *needle = "chunked"; + struct mg_str *te = mg_http_get_header(hm, "Transfer-Encoding"); + return te != NULL && mg_vcasecmp(te, needle) == 0; +} + +void mg_http_delete_chunk(struct mg_connection *c, struct mg_http_message *hm) { + struct mg_str ch = hm->chunk; + const char *end = (char *) &c->recv.buf[c->recv.len], *ce; + bool chunked = mg_is_chunked(hm); + if (chunked) { + ch.len += 4, ch.ptr -= 2; // \r\n before and after the chunk + while (ch.ptr > hm->body.ptr && *ch.ptr != '\n') ch.ptr--, ch.len++; + } + ce = &ch.ptr[ch.len]; + if (ce < end) memmove((void *) ch.ptr, ce, (size_t) (end - ce)); + c->recv.len -= ch.len; + if (c->pfn_data != NULL) c->pfn_data = (char *) c->pfn_data - ch.len; +} + +int mg_http_upload(struct mg_connection *c, struct mg_http_message *hm, + struct mg_fs *fs, const char *dir) { + char offset[40] = "", name[200] = "", path[256]; + int res = 0; + mg_http_get_var(&hm->query, "offset", offset, sizeof(offset)); + mg_http_get_var(&hm->query, "name", name, sizeof(name)); + if (name[0] == '\0') { + mg_http_reply(c, 400, "", "%s", "name required"); + res = -1; + } else if (hm->body.len > 0) { + struct mg_fd *fd; + long oft = strtol(offset, NULL, 0); + mg_snprintf(path, sizeof(path), "%s%c%s", dir, MG_DIRSEP, name); + remove_double_dots(path); + MG_DEBUG(("%d bytes @ %ld [%s]", (int) hm->body.len, oft, path)); + if (oft == 0) fs->rm(path); + if ((fd = mg_fs_open(fs, path, MG_FS_WRITE)) == NULL) { + mg_http_reply(c, 400, "", "open(%s): %d", path, errno); + res = -2; + } else { + res = (int) fs->wr(fd->fd, hm->body.ptr, hm->body.len); + mg_fs_close(fd); + mg_http_reply(c, 200, "", "%d", res); + } + } + return res; +} + +int mg_http_status(const struct mg_http_message *hm) { + return atoi(hm->uri.ptr); +} + +static void http_cb(struct mg_connection *c, int ev, void *evd, void *fnd) { + if (ev == MG_EV_READ || ev == MG_EV_CLOSE) { + struct mg_http_message hm; + while (c->recv.buf != NULL && c->recv.len > 0) { + int n = mg_http_parse((char *) c->recv.buf, c->recv.len, &hm); + bool is_chunked = n > 0 && mg_is_chunked(&hm); + if (ev == MG_EV_CLOSE) { + hm.message.len = c->recv.len; + hm.body.len = hm.message.len - (size_t) (hm.body.ptr - hm.message.ptr); + } else if (is_chunked && n > 0) { + walkchunks(c, &hm, (size_t) n); + } + // MG_INFO(("---->%d %d\n%.*s", n, is_chunked, (int) c->recv.len, + // c->recv.buf)); + if (n < 0 && ev == MG_EV_READ) { + mg_error(c, "HTTP parse:\n%.*s", (int) c->recv.len, c->recv.buf); + break; + } else if (n > 0 && (size_t) c->recv.len >= hm.message.len) { + mg_call(c, MG_EV_HTTP_MSG, &hm); + mg_iobuf_del(&c->recv, 0, hm.message.len); + } else { + if (n > 0 && !is_chunked) { + hm.chunk = + mg_str_n((char *) &c->recv.buf[n], c->recv.len - (size_t) n); + // Store remaining body length in c->pfn_data + if (c->pfn_data == NULL) + c->pfn_data = (void *) (hm.message.len - (size_t) n); + mg_call(c, MG_EV_HTTP_CHUNK, &hm); + if (c->pfn_data == NULL) { + hm.chunk.len = 0; // Last chunk! + mg_call(c, MG_EV_HTTP_CHUNK, &hm); // Lest user know + memmove(c->recv.buf, c->recv.buf + n, c->recv.len - (size_t) n); + c->recv.len -= (size_t) n; + } + } + break; + } + } + } + (void) fnd; + (void) evd; +} + +struct mg_connection *mg_http_connect(struct mg_mgr *mgr, const char *url, + mg_event_handler_t fn, void *fn_data) { + struct mg_connection *c = mg_connect(mgr, url, fn, fn_data); + if (c != NULL) c->pfn = http_cb; + return c; +} + +struct mg_connection *mg_http_listen(struct mg_mgr *mgr, const char *url, + mg_event_handler_t fn, void *fn_data) { + struct mg_connection *c = mg_listen(mgr, url, fn, fn_data); + if (c != NULL) c->pfn = http_cb; + return c; +} + +#ifdef MG_ENABLE_LINES +#line 1 "src/iobuf.c" +#endif + + + +#include + +// Not using memset for zeroing memory, cause it can be dropped by compiler +// See https://github.com/cesanta/mongoose/pull/1265 +static void zeromem(volatile unsigned char *buf, size_t len) { + if (buf != NULL) { + while (len--) *buf++ = 0; + } +} + +int mg_iobuf_resize(struct mg_iobuf *io, size_t new_size) { + int ok = 1; + if (new_size == 0) { + zeromem(io->buf, io->size); + free(io->buf); + io->buf = NULL; + io->len = io->size = 0; + } else if (new_size != io->size) { + // NOTE(lsm): do not use realloc here. Use calloc/free only, to ease the + // porting to some obscure platforms like FreeRTOS + void *p = calloc(1, new_size); + if (p != NULL) { + size_t len = new_size < io->len ? new_size : io->len; + if (len > 0) memmove(p, io->buf, len); + zeromem(io->buf, io->size); + free(io->buf); + io->buf = (unsigned char *) p; + io->size = new_size; + } else { + ok = 0; + MG_ERROR(("%lld->%lld", (uint64_t) io->size, (uint64_t) new_size)); + } + } + return ok; +} + +int mg_iobuf_init(struct mg_iobuf *io, size_t size) { + io->buf = NULL; + io->size = io->len = 0; + return mg_iobuf_resize(io, size); +} + +size_t mg_iobuf_add(struct mg_iobuf *io, size_t ofs, const void *buf, + size_t len, size_t chunk_size) { + size_t new_size = io->len + len; + if (new_size > io->size) { + new_size += chunk_size; // Make sure that io->size + new_size -= new_size % chunk_size; // is aligned by chunk_size boundary + mg_iobuf_resize(io, new_size); // Attempt to realloc + if (new_size != io->size) len = 0; // Realloc failure, append nothing + } + if (ofs < io->len) memmove(io->buf + ofs + len, io->buf + ofs, io->len - ofs); + if (buf != NULL) memmove(io->buf + ofs, buf, len); + if (ofs > io->len) io->len += ofs - io->len; + io->len += len; + return len; +} + +size_t mg_iobuf_del(struct mg_iobuf *io, size_t ofs, size_t len) { + if (ofs > io->len) ofs = io->len; + if (ofs + len > io->len) len = io->len - ofs; + if (io->buf) memmove(io->buf + ofs, io->buf + ofs + len, io->len - ofs - len); + if (io->buf) zeromem(io->buf + io->len - len, len); + io->len -= len; + return len; +} + +void mg_iobuf_free(struct mg_iobuf *io) { + mg_iobuf_resize(io, 0); +} + +#ifdef MG_ENABLE_LINES +#line 1 "src/log.c" +#endif + + + +static const char *s_spec = "2"; + +static void logc(unsigned char c) { + MG_PUTCHAR(c); +} + +static void logs(const char *buf, size_t len) { + size_t i; + for (i = 0; i < len; i++) logc(((unsigned char *) buf)[i]); +} + +void mg_log_set(const char *spec) { + MG_DEBUG(("Setting log level to %s", spec)); + s_spec = spec; +} + +bool mg_log_prefix(int level, const char *file, int line, const char *fname) { + // static unsigned long seq; + int max = MG_LL_INFO; + struct mg_str k, v, s = mg_str(s_spec); + const char *p = strrchr(file, '/'); + + if (p == NULL) p = strrchr(file, '\\'); + p = p == NULL ? file : p + 1; + + while (mg_commalist(&s, &k, &v)) { + if (v.len == 0) max = atoi(k.ptr); + if (v.len > 0 && strncmp(p, k.ptr, k.len) == 0) max = atoi(v.ptr); + } + + if (level <= max) { + char buf[41]; + size_t n = mg_snprintf(buf, sizeof(buf), "%llx %d %s:%d:%s", mg_millis(), + level, p, line, fname); + if (n > sizeof(buf) - 2) n = sizeof(buf) - 2; + while (n < sizeof(buf)) buf[n++] = ' '; + logs(buf, n - 1); + return true; + } else { + return false; + } +} + +void mg_log(const char *fmt, ...) { + char mem[256], *buf = mem; + va_list ap; + size_t len; + va_start(ap, fmt); + len = mg_vasprintf(&buf, sizeof(mem), fmt, ap); + va_end(ap); + logs(buf, len); + logc((unsigned char) '\n'); + if (buf != mem) free(buf); +} + +static unsigned char nibble(unsigned c) { + return (unsigned char) (c < 10 ? c + '0' : c + 'W'); +} + +#define ISPRINT(x) ((x) >= ' ' && (x) <= '~') +void mg_hexdump(const void *buf, size_t len) { + const unsigned char *p = (const unsigned char *) buf; + unsigned char ascii[16], alen = 0; + size_t i; + for (i = 0; i < len; i++) { + if ((i % 16) == 0) { + // Print buffered ascii chars + if (i > 0) logs(" ", 2), logs((char *) ascii, 16), logc('\n'), alen = 0; + // Print hex address, then \t + logc(nibble((i >> 12) & 15)), logc(nibble((i >> 8) & 15)), + logc(nibble((i >> 4) & 15)), logc('0'), logs(" ", 3); + } + logc(nibble(p[i] >> 4)), logc(nibble(p[i] & 15)); // Two nibbles, e.g. c5 + logc(' '); // Space after hex number + ascii[alen++] = ISPRINT(p[i]) ? p[i] : '.'; // Add to the ascii buf + } + while (alen < 16) logs(" ", 3), ascii[alen++] = ' '; + logs(" ", 2), logs((char *) ascii, 16), logc('\n'); +} + +#ifdef MG_ENABLE_LINES +#line 1 "src/md5.c" +#endif +#include + + +#if defined(MG_ENABLE_MD5) && MG_ENABLE_MD5 +#if !defined(BYTE_ORDER) && defined(__BYTE_ORDER) +#define BYTE_ORDER __BYTE_ORDER +#ifndef LITTLE_ENDIAN +#define LITTLE_ENDIAN __LITTLE_ENDIAN +#endif /* LITTLE_ENDIAN */ +#ifndef BIG_ENDIAN +#define BIG_ENDIAN __LITTLE_ENDIAN +#endif /* BIG_ENDIAN */ +#endif /* BYTE_ORDER */ + +static void mg_byte_reverse(unsigned char *buf, unsigned longs) { +/* Forrest: MD5 expect LITTLE_ENDIAN, swap if BIG_ENDIAN */ +#if BYTE_ORDER == BIG_ENDIAN + do { + uint32_t t = (uint32_t)((unsigned) buf[3] << 8 | buf[2]) << 16 | + ((unsigned) buf[1] << 8 | buf[0]); + *(uint32_t *) buf = t; + buf += 4; + } while (--longs); +#else + (void) buf; + (void) longs; +#endif +} + +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) + +#define MD5STEP(f, w, x, y, z, data, s) \ + (w += f(x, y, z) + data, w = w << s | w >> (32 - s), w += x) + +/* + * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious + * initialization constants. + */ +void mg_md5_init(mg_md5_ctx *ctx) { + ctx->buf[0] = 0x67452301; + ctx->buf[1] = 0xefcdab89; + ctx->buf[2] = 0x98badcfe; + ctx->buf[3] = 0x10325476; + + ctx->bits[0] = 0; + ctx->bits[1] = 0; +} + +static void mg_md5_transform(uint32_t buf[4], uint32_t const in[16]) { + uint32_t a, b, c, d; + + a = buf[0]; + b = buf[1]; + c = buf[2]; + d = buf[3]; + + MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7); + MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12); + MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17); + MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22); + MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7); + MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12); + MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17); + MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22); + MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7); + MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12); + MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17); + MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22); + MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7); + MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12); + MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17); + MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22); + + MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5); + MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9); + MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14); + MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20); + MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5); + MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9); + MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14); + MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20); + MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5); + MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9); + MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14); + MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20); + MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5); + MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9); + MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14); + MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); + + MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4); + MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11); + MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16); + MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23); + MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4); + MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11); + MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16); + MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23); + MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4); + MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11); + MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16); + MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23); + MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4); + MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11); + MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); + MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23); + + MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6); + MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10); + MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15); + MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21); + MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6); + MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10); + MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15); + MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21); + MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6); + MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); + MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15); + MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21); + MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6); + MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10); + MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15); + MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21); + + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; +} + +void mg_md5_update(mg_md5_ctx *ctx, const unsigned char *buf, size_t len) { + uint32_t t; + + t = ctx->bits[0]; + if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t) ctx->bits[1]++; + ctx->bits[1] += (uint32_t) len >> 29; + + t = (t >> 3) & 0x3f; + + if (t) { + unsigned char *p = (unsigned char *) ctx->in + t; + + t = 64 - t; + if (len < t) { + memcpy(p, buf, len); + return; + } + memcpy(p, buf, t); + mg_byte_reverse(ctx->in, 16); + mg_md5_transform(ctx->buf, (uint32_t *) ctx->in); + buf += t; + len -= t; + } + + while (len >= 64) { + memcpy(ctx->in, buf, 64); + mg_byte_reverse(ctx->in, 16); + mg_md5_transform(ctx->buf, (uint32_t *) ctx->in); + buf += 64; + len -= 64; + } + + memcpy(ctx->in, buf, len); +} + +void mg_md5_final(mg_md5_ctx *ctx, unsigned char digest[16]) { + unsigned count; + unsigned char *p; + uint32_t *a; + + count = (ctx->bits[0] >> 3) & 0x3F; + + p = ctx->in + count; + *p++ = 0x80; + count = 64 - 1 - count; + if (count < 8) { + memset(p, 0, count); + mg_byte_reverse(ctx->in, 16); + mg_md5_transform(ctx->buf, (uint32_t *) ctx->in); + memset(ctx->in, 0, 56); + } else { + memset(p, 0, count - 8); + } + mg_byte_reverse(ctx->in, 14); + + a = (uint32_t *) ctx->in; + a[14] = ctx->bits[0]; + a[15] = ctx->bits[1]; + + mg_md5_transform(ctx->buf, (uint32_t *) ctx->in); + mg_byte_reverse((unsigned char *) ctx->buf, 4); + memcpy(digest, ctx->buf, 16); + memset((char *) ctx, 0, sizeof(*ctx)); +} +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/mip.c" +#endif + + + +#if MG_ENABLE_MIP +#include +#define MIP_ETHEMERAL_PORT 49152 +#define _packed __attribute__((packed)) +#define U16(ptr) ((((uint16_t) (ptr)[0]) << 8) | (ptr)[1]) +#define NET16(x) __builtin_bswap16(x) +#define NET32(x) __builtin_bswap32(x) +#define PDIFF(a, b) ((size_t) (((char *) (b)) - ((char *) (a)))) + +#ifndef MIP_ARP_ENTRIES +#define MIP_ARP_ENTRIES 5 // Number of ARP cache entries. Maximum 21 +#endif +#define MIP_ARP_CS (2 + 12 * MIP_ARP_ENTRIES) // ARP cache size + +struct str { + uint8_t *buf; + size_t len; +}; + +// Receive queue - single producer, single consumer queue. Interrupt-based +// drivers copy received frames to the queue in interrupt context. mip_poll() +// function runs in event loop context, reads from the queue +struct queue { + uint8_t *buf; + size_t len; + volatile _Atomic size_t tail, head; +}; + +// Network interface +struct mip_if { + uint8_t mac[6]; // MAC address. Must be set to a valid MAC + uint32_t ip, mask, gw; // IP address, mask, default gateway. Can be 0 + struct str rx; // Output (TX) buffer + struct str tx; // Input (RX) buffer + bool use_dhcp; // Enable DCHP + struct mip_driver *driver; // Low level driver + struct mg_mgr *mgr; // Mongoose event manager + + // Internal state, user can use it but should not change it + uint64_t curtime; // Last poll timestamp in millis + uint64_t timer; // Timer + uint8_t arp_cache[MIP_ARP_CS]; // Each entry is 12 bytes + uint16_t eport; // Next ephemeral port + int state; // Current state +#define MIP_STATE_DOWN 0 // Interface is down +#define MIP_STATE_UP 1 // Interface is up +#define MIP_STATE_READY 2 // Interface is up and has IP + struct queue queue; // Receive queue +}; + +struct lcp { + uint8_t addr, ctrl, proto[2], code, id, len[2]; +} _packed; + +struct eth { + uint8_t dst[6]; // Destination MAC address + uint8_t src[6]; // Source MAC address + uint16_t type; // Ethernet type +} _packed; + +struct ip { + uint8_t ver; // Version + uint8_t tos; // Unused + uint16_t len; // Length + uint16_t id; // Unused + uint16_t frag; // Fragmentation + uint8_t ttl; // Time to live + uint8_t proto; // Upper level protocol + uint16_t csum; // Checksum + uint32_t src; // Source IP + uint32_t dst; // Destination IP +} _packed; + +struct ip6 { + uint8_t ver; // Version + uint8_t opts[3]; // Options + uint16_t len; // Length + uint8_t proto; // Upper level protocol + uint8_t ttl; // Time to live + uint8_t src[16]; // Source IP + uint8_t dst[16]; // Destination IP +} _packed; + +struct icmp { + uint8_t type; + uint8_t code; + uint16_t csum; +} _packed; + +struct arp { + uint16_t fmt; // Format of hardware address + uint16_t pro; // Format of protocol address + uint8_t hlen; // Length of hardware address + uint8_t plen; // Length of protocol address + uint16_t op; // Operation + uint8_t sha[6]; // Sender hardware address + uint32_t spa; // Sender protocol address + uint8_t tha[6]; // Target hardware address + uint32_t tpa; // Target protocol address +} _packed; + +struct tcp { + uint16_t sport; // Source port + uint16_t dport; // Destination port + uint32_t seq; // Sequence number + uint32_t ack; // Acknowledgement number + uint8_t off; // Data offset + uint8_t flags; // TCP flags +#define TH_FIN 0x01 +#define TH_SYN 0x02 +#define TH_RST 0x04 +#define TH_PUSH 0x08 +#define TH_ACK 0x10 +#define TH_URG 0x20 +#define TH_ECE 0x40 +#define TH_CWR 0x80 + uint16_t win; // Window + uint16_t csum; // Checksum + uint16_t urp; // Urgent pointer +} _packed; + +struct udp { + uint16_t sport; // Source port + uint16_t dport; // Destination port + uint16_t len; // UDP length + uint16_t csum; // UDP checksum +} _packed; + +struct dhcp { + uint8_t op, htype, hlen, hops; + uint32_t xid; + uint16_t secs, flags; + uint32_t ciaddr, yiaddr, siaddr, giaddr; + uint8_t hwaddr[208]; + uint32_t magic; + uint8_t options[32]; +} _packed; + +struct pkt { + struct str raw; // Raw packet data + struct str pay; // Payload data + struct eth *eth; + struct llc *llc; + struct arp *arp; + struct ip *ip; + struct ip6 *ip6; + struct icmp *icmp; + struct tcp *tcp; + struct udp *udp; + struct dhcp *dhcp; +}; + +static void q_copyin(struct queue *q, const uint8_t *buf, size_t len, + size_t head) { + size_t i = 0, left = q->len - head; + for (; i < len && i < left; i++) q->buf[head + i] = buf[i]; + for (; i < len; i++) q->buf[i - left] = buf[i]; +} + +static void q_copyout(struct queue *q, uint8_t *buf, size_t len, size_t tail) { + size_t i = 0, left = q->len - tail; + for (; i < len && i < left; i++) buf[i] = q->buf[tail + i]; + for (; i < len; i++) buf[i] = q->buf[i - left]; +} + +static bool q_write(struct queue *q, const void *buf, size_t len) { + bool success = false; + size_t left = q->len - q->head + q->tail; + if (len + sizeof(size_t) <= left) { + q_copyin(q, (uint8_t *) &len, sizeof(len), q->head); + q_copyin(q, (uint8_t *) buf, len, (q->head + sizeof(size_t)) % q->len); + q->head = (q->head + sizeof(len) + len) % q->len; + success = true; + } + return success; +} + +static size_t q_avail(struct queue *q) { + size_t n = 0; + if (q->tail != q->head) q_copyout(q, (uint8_t *) &n, sizeof(n), q->tail); + return n; +} + +static size_t q_read(struct queue *q, void *buf) { + size_t n = q_avail(q); + if (n > 0) { + q_copyout(q, (uint8_t *) buf, n, (q->tail + sizeof(n)) % q->len); + q->tail = (q->tail + sizeof(n) + n) % q->len; + } + return n; +} + +static struct str mkstr(void *buf, size_t len) { + struct str str = {(uint8_t *) buf, len}; + return str; +} + +static void mkpay(struct pkt *pkt, void *p) { + pkt->pay = mkstr(p, (size_t) (&pkt->raw.buf[pkt->raw.len] - (uint8_t *) p)); +} + +static uint32_t csumup(uint32_t sum, const void *buf, size_t len) { + const uint8_t *p = (const uint8_t *) buf; + for (size_t i = 0; i < len; i++) sum += i & 1 ? p[i] : (uint32_t) (p[i] << 8); + return sum; +} + +static uint16_t csumfin(uint32_t sum) { + while (sum >> 16) sum = (sum & 0xffff) + (sum >> 16); + return NET16(~sum & 0xffff); +} + +static uint16_t ipcsum(const void *buf, size_t len) { + uint32_t sum = csumup(0, buf, len); + return csumfin(sum); +} + +// ARP cache is organised as a doubly linked list. A successful cache lookup +// moves an entry to the head of the list. New entries are added by replacing +// the last entry in the list with a new IP/MAC. +// ARP cache format: | prev | next | Entry0 | Entry1 | .... | EntryN | +// ARP entry format: | prev | next | IP (4bytes) | MAC (6bytes) | +// prev and next are 1-byte offsets in the cache, so cache size is max 256 bytes +// ARP entry size is 12 bytes +static void arp_cache_init(uint8_t *p, int n, int size) { + for (int i = 0; i < n; i++) p[2 + i * size] = (uint8_t) (2 + (i - 1) * size); + for (int i = 0; i < n; i++) p[3 + i * size] = (uint8_t) (2 + (i + 1) * size); + p[0] = p[2] = (uint8_t) (2 + (n - 1) * size); + p[1] = p[3 + (n - 1) * size] = 2; +} + +static uint8_t *arp_cache_find(struct mip_if *ifp, uint32_t ip) { + uint8_t *p = ifp->arp_cache; + if (ip == 0) return NULL; + if (p[0] == 0 || p[1] == 0) arp_cache_init(p, MIP_ARP_ENTRIES, 12); + for (uint8_t i = 0, j = p[1]; i < MIP_ARP_ENTRIES; i++, j = p[j + 1]) { + if (memcmp(p + j + 2, &ip, sizeof(ip)) == 0) { + p[1] = j, p[0] = p[j]; // Found entry! Point list head to us + // MG_DEBUG(("ARP find: %#lx @ %x:%x:%x:%x:%x:%x\n", (long) ip, p[j + 6], + // p[j + 7], p[j + 8], p[j + 9], p[j + 10], p[j + 11])); + return p + j + 6; // And return MAC address + } + } + return NULL; +} + +static void arp_cache_add(struct mip_if *ifp, uint32_t ip, uint8_t mac[6]) { + uint8_t *p = ifp->arp_cache; + if (ip == 0 || ip == ~0U) return; // Bad IP + if (arp_cache_find(ifp, ip) != NULL) return; // Already exists, do nothing + memcpy(p + p[0] + 2, &ip, sizeof(ip)); // Replace last entry: IP address + memcpy(p + p[0] + 6, mac, 6); // And MAC address + p[1] = p[0], p[0] = p[p[1]]; // Point list head to us + // MG_DEBUG(("ARP cache: added %#lx @ %x:%x:%x:%x:%x:%x\n", (long) ip, mac[0], + // mac[1], mac[2], mac[3], mac[4], mac[5])); +} + +static void arp_ask(struct mip_if *ifp, uint32_t ip) { + struct eth *eth = (struct eth *) ifp->tx.buf; + struct arp *arp = (struct arp *) (eth + 1); + memset(eth->dst, 255, sizeof(eth->dst)); + memcpy(eth->src, ifp->mac, sizeof(eth->src)); + eth->type = NET16(0x806); + memset(arp, 0, sizeof(*arp)); + arp->fmt = NET16(1), arp->pro = NET16(0x800), arp->hlen = 6, arp->plen = 4; + arp->op = NET16(1), arp->tpa = ip, arp->spa = ifp->ip; + memcpy(arp->sha, ifp->mac, sizeof(arp->sha)); + ifp->driver->tx(eth, PDIFF(eth, arp + 1), ifp->driver->data); +} + +static void onstatechange(struct mip_if *ifp) { + if (ifp->state == MIP_STATE_READY) { + char buf[40]; + struct mg_addr addr = {.ip = ifp->ip}; + MG_INFO(("READY, IP: %s", mg_ntoa(&addr, buf, sizeof(buf)))); + arp_ask(ifp, ifp->gw); + } else if (ifp->state == MIP_STATE_UP) { + MG_ERROR(("Network up")); + } else if (ifp->state == MIP_STATE_DOWN) { + MG_ERROR(("Network down")); + } +} + +static struct ip *tx_ip(struct mip_if *ifp, uint8_t proto, uint32_t ip_src, + uint32_t ip_dst, size_t plen) { + struct eth *eth = (struct eth *) ifp->tx.buf; + struct ip *ip = (struct ip *) (eth + 1); + uint8_t *mac = arp_cache_find(ifp, ip_dst); // Dst IP in ARP cache ? + if (!mac) mac = arp_cache_find(ifp, ifp->gw); // No, use gateway + if (mac) memcpy(eth->dst, mac, sizeof(eth->dst)); // Found? Use it + if (!mac) memset(eth->dst, 255, sizeof(eth->dst)); // No? Use broadcast + memcpy(eth->src, ifp->mac, sizeof(eth->src)); // TODO(cpq): ARP lookup + eth->type = NET16(0x800); + memset(ip, 0, sizeof(*ip)); + ip->ver = 0x45; // Version 4, header length 5 words + ip->frag = 0x40; // Don't fragment + ip->len = NET16((uint16_t) (sizeof(*ip) + plen)); + ip->ttl = 64; + ip->proto = proto; + ip->src = ip_src; + ip->dst = ip_dst; + ip->csum = ipcsum(ip, sizeof(*ip)); + return ip; +} + +void tx_udp(struct mip_if *ifp, uint32_t ip_src, uint16_t sport, + uint32_t ip_dst, uint16_t dport, const void *buf, size_t len) { + struct ip *ip = tx_ip(ifp, 17, ip_src, ip_dst, len + sizeof(struct udp)); + struct udp *udp = (struct udp *) (ip + 1); + udp->sport = sport; + udp->dport = dport; + udp->len = NET16((uint16_t) (sizeof(*udp) + len)); + udp->csum = 0; + uint32_t cs = csumup(0, udp, sizeof(*udp)); + cs = csumup(cs, buf, len); + cs = csumup(cs, &ip->src, sizeof(ip->src)); + cs = csumup(cs, &ip->dst, sizeof(ip->dst)); + cs += ip->proto + sizeof(*udp) + len; + udp->csum = csumfin(cs); + memmove(udp + 1, buf, len); + // MG_DEBUG(("UDP LEN %d %d\n", (int) len, (int) ifp->frame_len)); + ifp->driver->tx(ifp->tx.buf, + sizeof(struct eth) + sizeof(*ip) + sizeof(*udp) + len, + ifp->driver->data); +} + +static void tx_dhcp(struct mip_if *ifp, uint32_t src, uint32_t dst, + uint8_t *opts, size_t optslen) { + struct dhcp dhcp = {.op = 1, + .htype = 1, + .hlen = 6, + .ciaddr = src, + .magic = NET32(0x63825363)}; + memcpy(&dhcp.hwaddr, ifp->mac, sizeof(ifp->mac)); + memcpy(&dhcp.xid, ifp->mac + 2, sizeof(dhcp.xid)); + memcpy(&dhcp.options, opts, optslen); + tx_udp(ifp, src, NET16(68), dst, NET16(67), &dhcp, sizeof(dhcp)); +} + +static void tx_dhcp_request(struct mip_if *ifp, uint32_t src, uint32_t dst) { + uint8_t opts[] = { + 53, 1, 3, // Type: DHCP request + 55, 2, 1, 3, // GW and mask + 12, 3, 'm', 'i', 'p', // Host name: "mip" + 54, 4, 0, 0, 0, 0, // DHCP server ID + 50, 4, 0, 0, 0, 0, // Requested IP + 255 // End of options + }; + memcpy(opts + 14, &dst, sizeof(dst)); + memcpy(opts + 20, &src, sizeof(src)); + tx_dhcp(ifp, src, dst, opts, sizeof(opts)); +} + +static void tx_dhcp_discover(struct mip_if *ifp) { + uint8_t opts[] = { + 53, 1, 1, // Type: DHCP discover + 55, 2, 1, 3, // Parameters: ip, mask + 255 // End of options + }; + tx_dhcp(ifp, 0, 0xffffffff, opts, sizeof(opts)); +} + +static void rx_arp(struct mip_if *ifp, struct pkt *pkt) { + // MG_DEBUG(("ARP op %d %#x %#x\n", NET16(arp->op), arp->spa, arp->tpa)); + if (pkt->arp->op == NET16(1) && pkt->arp->tpa == ifp->ip) { + // ARP request. Make a response, then send + struct eth *eth = (struct eth *) ifp->tx.buf; + struct arp *arp = (struct arp *) (eth + 1); + memcpy(eth->dst, pkt->eth->src, sizeof(eth->dst)); + memcpy(eth->src, ifp->mac, sizeof(eth->src)); + eth->type = NET16(0x806); + *arp = *pkt->arp; + arp->op = NET16(2); + memcpy(arp->tha, pkt->arp->sha, sizeof(pkt->arp->tha)); + memcpy(arp->sha, ifp->mac, sizeof(pkt->arp->sha)); + arp->tpa = pkt->arp->spa; + arp->spa = ifp->ip; + MG_DEBUG(("ARP response: we're %#lx", (long) ifp->ip)); + ifp->driver->tx(ifp->tx.buf, PDIFF(eth, arp + 1), ifp->driver->data); + } else if (pkt->arp->op == NET16(2)) { + if (memcmp(pkt->arp->tha, ifp->mac, sizeof(pkt->arp->tha)) != 0) return; + // MG_INFO(("ARP RESPONSE")); + arp_cache_add(ifp, pkt->arp->spa, pkt->arp->sha); + } +} + +static void rx_icmp(struct mip_if *ifp, struct pkt *pkt) { + // MG_DEBUG(("ICMP %d\n", (int) len)); + if (pkt->icmp->type == 8 && pkt->ip->dst == ifp->ip) { + struct ip *ip = tx_ip(ifp, 1, ifp->ip, pkt->ip->src, + sizeof(struct icmp) + pkt->pay.len); + struct icmp *icmp = (struct icmp *) (ip + 1); + memset(icmp, 0, sizeof(*icmp)); // Important - set csum to 0 + memcpy(icmp + 1, pkt->pay.buf, pkt->pay.len); + icmp->csum = ipcsum(icmp, sizeof(*icmp) + pkt->pay.len); + ifp->driver->tx(ifp->tx.buf, PDIFF(ifp->tx.buf, icmp + 1) + pkt->pay.len, + ifp->driver->data); + } +} + +static void rx_dhcp(struct mip_if *ifp, struct pkt *pkt) { + uint32_t ip = 0, gw = 0, mask = 0; + uint8_t *p = pkt->dhcp->options, *end = &pkt->raw.buf[pkt->raw.len]; + if (end < (uint8_t *) (pkt->dhcp + 1)) return; + // MG_DEBUG(("DHCP %u\n", (unsigned) pkt->raw.len)); + while (p < end && p[0] != 255) { + if (p[0] == 1 && p[1] == sizeof(ifp->mask)) { + memcpy(&mask, p + 2, sizeof(mask)); + // MG_DEBUG(("MASK %x\n", mask)); + } else if (p[0] == 3 && p[1] == sizeof(ifp->gw)) { + memcpy(&gw, p + 2, sizeof(gw)); + ip = pkt->dhcp->yiaddr; + // MG_DEBUG(("IP %x GW %x\n", ip, gw)); + } + p += p[1] + 2; + } + if (ip && mask && gw && ifp->ip == 0) { + // MG_DEBUG(("DHCP offer ip %#08lx mask %#08lx gw %#08lx\n", + // (long) ip, (long) mask, (long) gw)); + arp_cache_add(ifp, pkt->dhcp->siaddr, ((struct eth *) pkt->raw.buf)->src); + ifp->ip = ip, ifp->gw = gw, ifp->mask = mask; + ifp->state = MIP_STATE_READY; + onstatechange(ifp); + tx_dhcp_request(ifp, ip, pkt->dhcp->siaddr); + } +} + +struct mg_connection *getpeer(struct mg_mgr *mgr, struct pkt *pkt, bool lsn) { + struct mg_connection *c = NULL; + for (c = mgr->conns; c != NULL; c = c->next) { + if (c->is_udp && pkt->udp && c->loc.port == pkt->udp->dport) break; + if (!c->is_udp && pkt->tcp && c->loc.port == pkt->tcp->dport && + lsn == c->is_listening && (lsn || c->rem.port == pkt->tcp->sport)) + break; + } + return c; +} + +static void rx_udp(struct mip_if *ifp, struct pkt *pkt) { + struct mg_connection *c = getpeer(ifp->mgr, pkt, true); + if (c == NULL) { + // No UDP listener on this port. Should send ICMP, but keep silent. + } else if (c != NULL) { + c->rem.port = pkt->udp->sport; + c->rem.ip = pkt->ip->src; + if (c->recv.len >= MG_MAX_RECV_BUF_SIZE) { + mg_error(c, "max_recv_buf_size reached"); + } else if (c->recv.size - c->recv.len < pkt->pay.len && + !mg_iobuf_resize(&c->recv, c->recv.len + pkt->pay.len)) { + mg_error(c, "oom"); + } else { + memcpy(&c->recv.buf[c->recv.len], pkt->pay.buf, pkt->pay.len); + c->recv.len += pkt->pay.len; + struct mg_str evd = mg_str_n((char *) pkt->pay.buf, pkt->pay.len); + mg_call(c, MG_EV_READ, &evd); + } + } +} + +struct tcpstate { + uint32_t seq, ack; + time_t expire; +}; + +static size_t tx_tcp(struct mip_if *ifp, uint32_t dst_ip, uint8_t flags, + uint16_t sport, uint16_t dport, uint32_t seq, uint32_t ack, + const void *buf, size_t len) { + struct ip *ip = tx_ip(ifp, 6, ifp->ip, dst_ip, sizeof(struct tcp) + len); + struct tcp *tcp = (struct tcp *) (ip + 1); + memset(tcp, 0, sizeof(*tcp)); + memmove(tcp + 1, buf, len); + tcp->sport = sport; + tcp->dport = dport; + tcp->seq = seq; + tcp->ack = ack; + tcp->flags = flags; + tcp->win = mg_htons(8192); + tcp->off = (uint8_t) (sizeof(*tcp) / 4 << 4); + uint32_t cs = 0; + uint16_t n = (uint16_t) (sizeof(*tcp) + len); + uint8_t pseudo[] = {0, ip->proto, (uint8_t) (n >> 8), (uint8_t) (n & 255)}; + cs = csumup(cs, tcp, n); + cs = csumup(cs, &ip->src, sizeof(ip->src)); + cs = csumup(cs, &ip->dst, sizeof(ip->dst)); + cs = csumup(cs, pseudo, sizeof(pseudo)); + tcp->csum = csumfin(cs); + return ifp->driver->tx(ifp->tx.buf, PDIFF(ifp->tx.buf, tcp + 1) + len, + ifp->driver->data); +} + +static size_t tx_tcp_pkt(struct mip_if *ifp, struct pkt *pkt, uint8_t flags, + uint32_t seq, const void *buf, size_t len) { + uint32_t delta = (pkt->tcp->flags & (TH_SYN | TH_FIN)) ? 1 : 0; + return tx_tcp(ifp, pkt->ip->src, flags, pkt->tcp->dport, pkt->tcp->sport, seq, + mg_htonl(mg_ntohl(pkt->tcp->seq) + delta), buf, len); +} + +static struct mg_connection *accept_conn(struct mg_connection *lsn, + struct pkt *pkt) { + struct mg_connection *c = mg_alloc_conn(lsn->mgr); + struct tcpstate *s = (struct tcpstate *) (c + 1); + s->seq = mg_ntohl(pkt->tcp->ack), s->ack = mg_ntohl(pkt->tcp->seq); + c->rem.ip = pkt->ip->src; + c->rem.port = pkt->tcp->sport; + MG_DEBUG(("%lu accepted %lx:%hx", c->id, c->rem.ip, c->rem.port)); + LIST_ADD_HEAD(struct mg_connection, &lsn->mgr->conns, c); + c->fd = (void *) (size_t) mg_ntohl(pkt->tcp->ack); + c->is_accepted = 1; + c->is_hexdumping = lsn->is_hexdumping; + c->pfn = lsn->pfn; + c->loc = lsn->loc; + c->pfn_data = lsn->pfn_data; + c->fn = lsn->fn; + c->fn_data = lsn->fn_data; + mg_call(c, MG_EV_OPEN, NULL); + mg_call(c, MG_EV_ACCEPT, NULL); + return c; +} + +static void read_conn(struct mg_connection *c, struct pkt *pkt) { + struct tcpstate *s = (struct tcpstate *) (c + 1); + if (pkt->tcp->flags & TH_FIN) { + s->ack = mg_htonl(pkt->tcp->seq) + 1, s->seq = mg_htonl(pkt->tcp->ack); + c->is_closing = 1; + } else if (pkt->pay.len == 0) { + } else if (c->recv.size - c->recv.len < pkt->pay.len && + !mg_iobuf_resize(&c->recv, c->recv.len + pkt->pay.len)) { + mg_error(c, "oom"); + } else if (mg_ntohl(pkt->tcp->seq) != s->ack) { + mg_error(c, "oob: %x %x", mg_ntohl(pkt->tcp->seq), s->ack); + } else { + s->ack = mg_htonl(pkt->tcp->seq) + pkt->pay.len; + memcpy(&c->recv.buf[c->recv.len], pkt->pay.buf, pkt->pay.len); + c->recv.len += pkt->pay.len; + struct mg_str evd = mg_str_n((char *) pkt->pay.buf, pkt->pay.len); + mg_call(c, MG_EV_READ, &evd); +#if 0 + // Send ACK immediately + tx_tcp(ifp, c->rem.ip, TH_ACK, c->loc.port, c->rem.port, mg_htonl(s->seq), + mg_htonl(s->ack), NULL, 0); +#endif + } +} + +static void rx_tcp(struct mip_if *ifp, struct pkt *pkt) { + struct mg_connection *c = getpeer(ifp->mgr, pkt, false); +#if 0 + MG_INFO(("%lu %hhu %d", c ? c->id : 0, pkt->tcp->flags, (int) pkt->pay.len)); +#endif + if (c != NULL) { +#if 0 + MG_DEBUG(("%lu %d %lx:%hx -> %lx:%hx", c->id, (int) pkt->raw.len, + pkt->ip->src, pkt->tcp->sport, pkt->ip->dst, pkt->tcp->dport)); + hexdump(pkt->pay.buf, pkt->pay.len); +#endif + read_conn(c, pkt); + } else if ((c = getpeer(ifp->mgr, pkt, true)) == NULL) { + tx_tcp_pkt(ifp, pkt, TH_RST | TH_ACK, pkt->tcp->ack, NULL, 0); + } else if (pkt->tcp->flags & TH_SYN) { + // Use peer's source port as ISN, in order to recognise the handshake + uint32_t isn = mg_htonl((uint32_t) mg_ntohs(pkt->tcp->sport)); + tx_tcp_pkt(ifp, pkt, TH_SYN | TH_ACK, isn, NULL, 0); + } else if (pkt->tcp->flags & TH_FIN) { + tx_tcp_pkt(ifp, pkt, TH_FIN | TH_ACK, pkt->tcp->ack, NULL, 0); + } else if (mg_htonl(pkt->tcp->ack) == mg_htons(pkt->tcp->sport) + 1U) { + accept_conn(c, pkt); + } else { + // MG_DEBUG(("dropped silently..")); + } +} + +static void rx_ip(struct mip_if *ifp, struct pkt *pkt) { + // MG_DEBUG(("IP %d", (int) pkt->pay.len)); + if (pkt->ip->proto == 1) { + pkt->icmp = (struct icmp *) (pkt->ip + 1); + if (pkt->pay.len < sizeof(*pkt->icmp)) return; + mkpay(pkt, pkt->icmp + 1); + rx_icmp(ifp, pkt); + } else if (pkt->ip->proto == 17) { + pkt->udp = (struct udp *) (pkt->ip + 1); + if (pkt->pay.len < sizeof(*pkt->udp)) return; + // MG_DEBUG((" UDP %u %u -> %u\n", len, NET16(udp->sport), + // NET16(udp->dport))); + mkpay(pkt, pkt->udp + 1); + if (pkt->udp->dport == NET16(68)) { + pkt->dhcp = (struct dhcp *) (pkt->udp + 1); + mkpay(pkt, pkt->dhcp + 1); + rx_dhcp(ifp, pkt); + } else { + rx_udp(ifp, pkt); + } + } else if (pkt->ip->proto == 6) { + pkt->tcp = (struct tcp *) (pkt->ip + 1); + if (pkt->pay.len < sizeof(*pkt->tcp)) return; + mkpay(pkt, pkt->tcp + 1); + uint16_t iplen = mg_ntohs(pkt->ip->len); + uint16_t off = (uint16_t) (sizeof(*pkt->ip) + ((pkt->tcp->off >> 4) * 4U)); + if (iplen >= off) pkt->pay.len = (size_t) (iplen - off); + rx_tcp(ifp, pkt); + } +} + +static void rx_ip6(struct mip_if *ifp, struct pkt *pkt) { + // MG_DEBUG(("IP %d\n", (int) len)); + if (pkt->ip6->proto == 1 || pkt->ip6->proto == 58) { + pkt->icmp = (struct icmp *) (pkt->ip6 + 1); + if (pkt->pay.len < sizeof(*pkt->icmp)) return; + mkpay(pkt, pkt->icmp + 1); + rx_icmp(ifp, pkt); + } else if (pkt->ip->proto == 17) { + pkt->udp = (struct udp *) (pkt->ip6 + 1); + if (pkt->pay.len < sizeof(*pkt->udp)) return; + // MG_DEBUG((" UDP %u %u -> %u\n", len, NET16(udp->sport), + // NET16(udp->dport))); + mkpay(pkt, pkt->udp + 1); + } +} + +static void mip_rx(struct mip_if *ifp, void *buf, size_t len) { + const uint8_t broadcast[] = {255, 255, 255, 255, 255, 255}; + struct pkt pkt = {.raw = {.buf = (uint8_t *) buf, .len = len}}; + pkt.eth = (struct eth *) buf; + if (pkt.raw.len < sizeof(*pkt.eth)) return; // Truncated - runt? + if (memcmp(pkt.eth->dst, ifp->mac, sizeof(pkt.eth->dst)) != 0 && + memcmp(pkt.eth->dst, broadcast, sizeof(pkt.eth->dst)) != 0) { + // Not for us. Drop silently + } else if (pkt.eth->type == NET16(0x806)) { + pkt.arp = (struct arp *) (pkt.eth + 1); + if (sizeof(*pkt.eth) + sizeof(*pkt.arp) > pkt.raw.len) return; // Truncated + rx_arp(ifp, &pkt); + } else if (pkt.eth->type == NET16(0x86dd)) { + pkt.ip6 = (struct ip6 *) (pkt.eth + 1); + if (pkt.raw.len < sizeof(*pkt.eth) + sizeof(*pkt.ip6)) return; // Truncated + if ((pkt.ip6->ver >> 4) != 0x6) return; // Not IP + mkpay(&pkt, pkt.ip6 + 1); + rx_ip6(ifp, &pkt); + } else if (pkt.eth->type == NET16(0x800)) { + pkt.ip = (struct ip *) (pkt.eth + 1); + if (pkt.raw.len < sizeof(*pkt.eth) + sizeof(*pkt.ip)) return; // Truncated + if ((pkt.ip->ver >> 4) != 4) return; // Not IP + mkpay(&pkt, pkt.ip + 1); + rx_ip(ifp, &pkt); + } else { + MG_DEBUG((" Unknown eth type %x\n", NET16(pkt.eth->type))); + } +} + +static void mip_poll(struct mip_if *ifp, uint64_t uptime_ms) { + ifp->curtime = uptime_ms; + + if (ifp->ip == 0 && uptime_ms > ifp->timer) { + tx_dhcp_discover(ifp); // If IP not configured, send DHCP + ifp->timer = uptime_ms + 1000; // with some interval + } else if (ifp->use_dhcp == false && uptime_ms > ifp->timer && + arp_cache_find(ifp, ifp->gw) == NULL) { + arp_ask(ifp, ifp->gw); // If GW's MAC address in not in ARP cache + ifp->timer = uptime_ms + 1000; // send ARP who-has request + } + + // Handle physical interface up/down status + if (ifp->driver->status) { + bool up = ifp->driver->status(ifp->driver->data); + bool current = ifp->state != MIP_STATE_DOWN; + if (up != current) { + ifp->state = up == false ? MIP_STATE_DOWN + : ifp->use_dhcp ? MIP_STATE_UP + : MIP_STATE_READY; + if (!up && ifp->use_dhcp) ifp->ip = 0; + onstatechange(ifp); + } + } + + // Read data from the network + for (;;) { + size_t len = ifp->queue.len > 0 ? q_read(&ifp->queue, ifp->rx.buf) + : ifp->driver->rx(ifp->rx.buf, ifp->rx.len, + ifp->driver->data); + if (len == 0) break; + mip_rx(ifp, ifp->rx.buf, len); + } +} + +// This function executes in interrupt context, thus it should copy data +// somewhere fast. Note that newlib's malloc is not thread safe, thus use +// our lock-free queue with preallocated buffer to copy data and return asap +static void on_rx(void *buf, size_t len, void *userdata) { + struct mip_if *ifp = (struct mip_if *) userdata; + if (!q_write(&ifp->queue, buf, len)) MG_ERROR(("dropped %d", (int) len)); +} + +void mip_init(struct mg_mgr *mgr, struct mip_ipcfg *ipcfg, + struct mip_driver *driver) { + size_t maxpktsize = 1500, qlen = driver->rxcb ? 1024 * 16 : 0; + struct mip_if *ifp = + (struct mip_if *) calloc(1, sizeof(*ifp) + 2 * maxpktsize + qlen); + memcpy(ifp->mac, ipcfg->mac, sizeof(ifp->mac)); + ifp->use_dhcp = ipcfg->ip == 0; + ifp->ip = ipcfg->ip, ifp->mask = ipcfg->mask, ifp->gw = ipcfg->gw; + ifp->rx.buf = (uint8_t *) (ifp + 1), ifp->rx.len = maxpktsize; + ifp->tx.buf = ifp->rx.buf + maxpktsize, ifp->tx.len = maxpktsize; + ifp->driver = driver; + ifp->mgr = mgr; + ifp->queue.buf = ifp->tx.buf + maxpktsize; + ifp->queue.len = qlen; + if (driver->init) driver->init(driver->data); + if (driver->rxcb) driver->rxcb(on_rx, ifp); + mgr->priv = ifp; + mgr->extraconnsize = sizeof(struct tcpstate); +} + +void mg_connect_resolved(struct mg_connection *c) { + struct mip_if *ifp = (struct mip_if *) c->mgr->priv; + if (ifp->eport < MIP_ETHEMERAL_PORT) ifp->eport = MIP_ETHEMERAL_PORT; + if (c->is_udp) { + c->loc.ip = ifp->ip; + c->loc.port = mg_htons(ifp->eport++); + MG_DEBUG(("%lu %08lx.%hu->%08lx.%hu", c->id, mg_ntohl(c->loc.ip), + mg_ntohs(c->loc.port), mg_ntohl(c->rem.ip), + mg_ntohs(c->rem.port))); + mg_call(c, MG_EV_RESOLVE, NULL); + mg_call(c, MG_EV_CONNECT, NULL); + } else { + mg_error(c, "Not implemented"); + } + c->is_resolving = 0; +} + +bool mg_open_listener(struct mg_connection *c, const char *url) { + c->loc.port = mg_htons(mg_url_port(url)); + return true; +} + +static void write_conn(struct mg_connection *c) { + struct mip_if *ifp = (struct mip_if *) c->mgr->priv; + struct tcpstate *s = (struct tcpstate *) (c + 1); + size_t sent, n = c->send.len, hdrlen = 14 + 24 /*max IP*/ + 60 /*max TCP*/; + if (n + hdrlen > ifp->tx.len) n = ifp->tx.len - hdrlen; + sent = tx_tcp(ifp, c->rem.ip, TH_PUSH | TH_ACK, c->loc.port, c->rem.port, + mg_htonl(s->seq), mg_htonl(s->ack), c->send.buf, n); + if (sent > 0) { + mg_iobuf_del(&c->send, 0, n); + s->seq += n; + mg_call(c, MG_EV_WRITE, &n); + } +} + +static void fin_conn(struct mg_connection *c) { + struct mip_if *ifp = (struct mip_if *) c->mgr->priv; + struct tcpstate *s = (struct tcpstate *) (c + 1); + tx_tcp(ifp, c->rem.ip, TH_FIN | TH_ACK, c->loc.port, c->rem.port, + mg_htonl(s->seq), mg_htonl(s->ack), NULL, 0); +} + +void mg_mgr_poll(struct mg_mgr *mgr, int ms) { + struct mg_connection *c, *tmp; + uint64_t now = mg_millis(); + mip_poll((struct mip_if *) mgr->priv, now); + mg_timer_poll(&mgr->timers, now); + for (c = mgr->conns; c != NULL; c = tmp) { + tmp = c->next; + if (c->send.len > 0) write_conn(c); + if (c->is_draining && c->send.len == 0) c->is_closing = 1; + if (c->is_closing) { + if (c->is_udp == false && c->is_listening == false) fin_conn(c); + mg_close_conn(c); + } + } + (void) ms; +} + +bool mg_send(struct mg_connection *c, const void *buf, size_t len) { + struct mip_if *ifp = (struct mip_if *) c->mgr->priv; + bool res = false; + if (ifp->ip == 0) { + mg_error(c, "net down"); + } else if (c->is_udp) { + tx_udp(ifp, ifp->ip, c->loc.port, c->rem.ip, c->rem.port, buf, len); + res = true; + } else { + // tx_tdp(ifp, ifp->ip, c->loc.port, c->rem.ip, c->rem.port, buf, len); + return mg_iobuf_add(&c->send, c->send.len, buf, len, MG_IO_SIZE); + } + return res; +} + +int mg_mkpipe(struct mg_mgr *mgr, mg_event_handler_t fn, void *fn_data) { + (void) mgr, (void) fn, (void) fn_data; + return -1; +} +#endif // MG_ENABLE_MIP + +#ifdef MG_ENABLE_LINES +#line 1 "src/mqtt.c" +#endif + + + + + + + + +#define MQTT_CLEAN_SESSION 0x02 +#define MQTT_HAS_WILL 0x04 +#define MQTT_WILL_RETAIN 0x20 +#define MQTT_HAS_PASSWORD 0x40 +#define MQTT_HAS_USER_NAME 0x80 + +enum { MQTT_OK, MQTT_INCOMPLETE, MQTT_MALFORMED }; + +void mg_mqtt_send_header(struct mg_connection *c, uint8_t cmd, uint8_t flags, + uint32_t len) { + uint8_t buf[1 + sizeof(len)], *vlen = &buf[1]; + buf[0] = (uint8_t) ((cmd << 4) | flags); + do { + *vlen = len % 0x80; + len /= 0x80; + if (len > 0) *vlen |= 0x80; + vlen++; + } while (len > 0 && vlen < &buf[sizeof(buf)]); + mg_send(c, buf, (size_t) (vlen - buf)); +} + +static void mg_send_u16(struct mg_connection *c, uint16_t value) { + mg_send(c, &value, sizeof(value)); +} + +void mg_mqtt_login(struct mg_connection *c, const struct mg_mqtt_opts *opts) { + char rnd[9], client_id[16]; + struct mg_str cid = opts->client_id; + uint32_t total_len = 7 + 1 + 2 + 2; + uint8_t connflag = (uint8_t) ((opts->will_qos & 3) << 3); + + if (cid.len == 0) { + mg_random(rnd, sizeof(rnd)); + mg_base64_encode((unsigned char *) rnd, sizeof(rnd), client_id); + client_id[sizeof(client_id) - 1] = '\0'; + cid = mg_str(client_id); + } + + if (opts->user.len > 0) { + total_len += 2 + (uint32_t) opts->user.len; + connflag |= MQTT_HAS_USER_NAME; + } + if (opts->pass.len > 0) { + total_len += 2 + (uint32_t) opts->pass.len; + connflag |= MQTT_HAS_PASSWORD; + } + if (opts->will_topic.len > 0 && opts->will_message.len > 0) { + total_len += + 4 + (uint32_t) opts->will_topic.len + (uint32_t) opts->will_message.len; + connflag |= MQTT_HAS_WILL; + } + if (opts->clean || cid.len == 0) connflag |= MQTT_CLEAN_SESSION; + if (opts->will_retain) connflag |= MQTT_WILL_RETAIN; + total_len += (uint32_t) cid.len; + + mg_mqtt_send_header(c, MQTT_CMD_CONNECT, 0, total_len); + mg_send(c, "\00\04MQTT\04", 7); + mg_send(c, &connflag, sizeof(connflag)); + // keepalive == 0 means "do not disconnect us!" + mg_send_u16(c, mg_htons((uint16_t) opts->keepalive)); + mg_send_u16(c, mg_htons((uint16_t) cid.len)); + mg_send(c, cid.ptr, cid.len); + if (connflag & MQTT_HAS_WILL) { + mg_send_u16(c, mg_htons((uint16_t) opts->will_topic.len)); + mg_send(c, opts->will_topic.ptr, opts->will_topic.len); + mg_send_u16(c, mg_htons((uint16_t) opts->will_message.len)); + mg_send(c, opts->will_message.ptr, opts->will_message.len); + } + if (opts->user.len > 0) { + mg_send_u16(c, mg_htons((uint16_t) opts->user.len)); + mg_send(c, opts->user.ptr, opts->user.len); + } + if (opts->pass.len > 0) { + mg_send_u16(c, mg_htons((uint16_t) opts->pass.len)); + mg_send(c, opts->pass.ptr, opts->pass.len); + } +} + +void mg_mqtt_pub(struct mg_connection *c, struct mg_str topic, + struct mg_str data, int qos, bool retain) { + uint8_t flags = (uint8_t) (((qos & 3) << 1) | (retain ? 1 : 0)); + uint32_t total_len = 2 + (uint32_t) topic.len + (uint32_t) data.len; + MG_DEBUG(("%lu [%.*s] -> [%.*s]", c->id, (int) topic.len, (char *) topic.ptr, + (int) data.len, (char *) data.ptr)); + if (qos > 0) total_len += 2; + mg_mqtt_send_header(c, MQTT_CMD_PUBLISH, flags, total_len); + mg_send_u16(c, mg_htons((uint16_t) topic.len)); + mg_send(c, topic.ptr, topic.len); + if (qos > 0) { + if (++c->mgr->mqtt_id == 0) ++c->mgr->mqtt_id; + mg_send_u16(c, mg_htons(c->mgr->mqtt_id)); + } + mg_send(c, data.ptr, data.len); +} + +void mg_mqtt_sub(struct mg_connection *c, struct mg_str topic, int qos) { + uint8_t qos_ = qos & 3; + uint32_t total_len = 2 + (uint32_t) topic.len + 2 + 1; + mg_mqtt_send_header(c, MQTT_CMD_SUBSCRIBE, 2, total_len); + if (++c->mgr->mqtt_id == 0) ++c->mgr->mqtt_id; + mg_send_u16(c, mg_htons(c->mgr->mqtt_id)); + mg_send_u16(c, mg_htons((uint16_t) topic.len)); + mg_send(c, topic.ptr, topic.len); + mg_send(c, &qos_, sizeof(qos_)); +} + +int mg_mqtt_parse(const uint8_t *buf, size_t len, struct mg_mqtt_message *m) { + uint8_t lc = 0, *p, *end; + uint32_t n = 0, len_len = 0; + + memset(m, 0, sizeof(*m)); + m->dgram.ptr = (char *) buf; + if (len < 2) return MQTT_INCOMPLETE; + m->cmd = (uint8_t) (buf[0] >> 4); + m->qos = (buf[0] >> 1) & 3; + + n = len_len = 0; + p = (uint8_t *) buf + 1; + while ((size_t) (p - buf) < len) { + lc = *((uint8_t *) p++); + n += (uint32_t) ((lc & 0x7f) << 7 * len_len); + len_len++; + if (!(lc & 0x80)) break; + if (len_len >= 4) return MQTT_MALFORMED; + } + end = p + n; + if ((lc & 0x80) || (end > buf + len)) return MQTT_INCOMPLETE; + m->dgram.len = (size_t) (end - buf); + + switch (m->cmd) { + case MQTT_CMD_CONNACK: + if (end - p < 2) return MQTT_MALFORMED; + m->ack = p[1]; + break; + case MQTT_CMD_PUBACK: + case MQTT_CMD_PUBREC: + case MQTT_CMD_PUBREL: + case MQTT_CMD_PUBCOMP: + case MQTT_CMD_SUBACK: + if (p + 2 > end) return MQTT_MALFORMED; + m->id = (uint16_t) ((((uint16_t) p[0]) << 8) | p[1]); + break; + case MQTT_CMD_SUBSCRIBE: { + if (p + 2 > end) return MQTT_MALFORMED; + m->id = (uint16_t) ((((uint16_t) p[0]) << 8) | p[1]); + p += 2; + break; + } + case MQTT_CMD_PUBLISH: { + if (p + 2 > end) return MQTT_MALFORMED; + m->topic.len = (uint16_t) ((((uint16_t) p[0]) << 8) | p[1]); + m->topic.ptr = (char *) p + 2; + p += 2 + m->topic.len; + if (p > end) return MQTT_MALFORMED; + if (m->qos > 0) { + if (p + 2 > end) return MQTT_MALFORMED; + m->id = (uint16_t) ((((uint16_t) p[0]) << 8) | p[1]); + p += 2; + } + if (p > end) return MQTT_MALFORMED; + m->data.ptr = (char *) p; + m->data.len = (size_t) (end - p); + break; + } + default: + break; + } + return MQTT_OK; +} + +static size_t mg_mqtt_next_topic(struct mg_mqtt_message *msg, + struct mg_str *topic, uint8_t *qos, + size_t pos) { + unsigned char *buf = (unsigned char *) msg->dgram.ptr + pos; + size_t new_pos; + if (pos >= msg->dgram.len) return 0; + + topic->len = (size_t) (((unsigned) buf[0]) << 8 | buf[1]); + topic->ptr = (char *) buf + 2; + new_pos = pos + 2 + topic->len + (qos == NULL ? 0 : 1); + if ((size_t) new_pos > msg->dgram.len) return 0; + if (qos != NULL) *qos = buf[2 + topic->len]; + return new_pos; +} + +size_t mg_mqtt_next_sub(struct mg_mqtt_message *msg, struct mg_str *topic, + uint8_t *qos, size_t pos) { + uint8_t tmp; + return mg_mqtt_next_topic(msg, topic, qos == NULL ? &tmp : qos, pos); +} + +size_t mg_mqtt_next_unsub(struct mg_mqtt_message *msg, struct mg_str *topic, + size_t pos) { + return mg_mqtt_next_topic(msg, topic, NULL, pos); +} + +static void mqtt_cb(struct mg_connection *c, int ev, void *ev_data, + void *fn_data) { + if (ev == MG_EV_READ) { + for (;;) { + struct mg_mqtt_message mm; + int rc = mg_mqtt_parse(c->recv.buf, c->recv.len, &mm); + if (rc == MQTT_MALFORMED) { + MG_ERROR(("%lu MQTT malformed message", c->id)); + c->is_closing = 1; + break; + } else if (rc == MQTT_OK) { + MG_VERBOSE(("%p MQTT CMD %d len %d [%.*s]", c->fd, mm.cmd, + (int) mm.dgram.len, (int) mm.data.len, mm.data.ptr)); + switch (mm.cmd) { + case MQTT_CMD_CONNACK: + mg_call(c, MG_EV_MQTT_OPEN, &mm.ack); + if (mm.ack == 0) { + MG_DEBUG(("%lu Connected", c->id)); + } else { + MG_ERROR(("%lu MQTT auth failed, code %d", c->id, mm.ack)); + c->is_closing = 1; + } + break; + case MQTT_CMD_PUBLISH: { + MG_DEBUG(("%lu [%.*s] -> [%.*s]", c->id, (int) mm.topic.len, + mm.topic.ptr, (int) mm.data.len, mm.data.ptr)); + mg_call(c, MG_EV_MQTT_MSG, &mm); + break; + } + } + mg_call(c, MG_EV_MQTT_CMD, &mm); + mg_iobuf_del(&c->recv, 0, mm.dgram.len); + } else { + break; + } + } + } + (void) ev_data; + (void) fn_data; +} + +void mg_mqtt_ping(struct mg_connection *nc) { + mg_mqtt_send_header(nc, MQTT_CMD_PINGREQ, 0, 0); +} + +void mg_mqtt_pong(struct mg_connection *nc) { + mg_mqtt_send_header(nc, MQTT_CMD_PINGRESP, 0, 0); +} + +void mg_mqtt_disconnect(struct mg_connection *nc) { + mg_mqtt_send_header(nc, MQTT_CMD_DISCONNECT, 0, 0); +} + +struct mg_connection *mg_mqtt_connect(struct mg_mgr *mgr, const char *url, + const struct mg_mqtt_opts *opts, + mg_event_handler_t fn, void *fn_data) { + struct mg_connection *c = mg_connect(mgr, url, fn, fn_data); + if (c != NULL) { + struct mg_mqtt_opts empty; + memset(&empty, 0, sizeof(empty)); + mg_mqtt_login(c, opts == NULL ? &empty : opts); + c->pfn = mqtt_cb; + } + return c; +} + +struct mg_connection *mg_mqtt_listen(struct mg_mgr *mgr, const char *url, + mg_event_handler_t fn, void *fn_data) { + struct mg_connection *c = mg_listen(mgr, url, fn, fn_data); + if (c != NULL) c->pfn = mqtt_cb, c->pfn_data = mgr; + return c; +} + +#ifdef MG_ENABLE_LINES +#line 1 "src/net.c" +#endif + + + + + + + +size_t mg_vprintf(struct mg_connection *c, const char *fmt, va_list ap) { + char mem[256], *buf = mem; + size_t len = mg_vasprintf(&buf, sizeof(mem), fmt, ap); + len = mg_send(c, buf, len); + if (buf != mem) free(buf); + return len; +} + +size_t mg_printf(struct mg_connection *c, const char *fmt, ...) { + size_t len = 0; + va_list ap; + va_start(ap, fmt); + len = mg_vprintf(c, fmt, ap); + va_end(ap); + return len; +} + +char *mg_straddr(struct mg_addr *a, char *buf, size_t len) { + char tmp[30]; + const char *fmt = a->is_ip6 ? "[%s]:%d" : "%s:%d"; + mg_ntoa(a, tmp, sizeof(tmp)); + mg_snprintf(buf, len, fmt, tmp, (int) mg_ntohs(a->port)); + return buf; +} + +char *mg_ntoa(const struct mg_addr *addr, char *buf, size_t len) { + if (addr->is_ip6) { + uint16_t *p = (uint16_t *) addr->ip6; + mg_snprintf(buf, len, "%x:%x:%x:%x:%x:%x:%x:%x", mg_htons(p[0]), + mg_htons(p[1]), mg_htons(p[2]), mg_htons(p[3]), mg_htons(p[4]), + mg_htons(p[5]), mg_htons(p[6]), mg_htons(p[7])); + } else { + uint8_t p[4]; + memcpy(p, &addr->ip, sizeof(p)); + mg_snprintf(buf, len, "%d.%d.%d.%d", (int) p[0], (int) p[1], (int) p[2], + (int) p[3]); + } + return buf; +} + +static bool mg_atonl(struct mg_str str, struct mg_addr *addr) { + if (mg_vcasecmp(&str, "localhost") != 0) return false; + addr->ip = mg_htonl(0x7f000001); + addr->is_ip6 = false; + return true; +} + +static bool mg_atone(struct mg_str str, struct mg_addr *addr) { + if (str.len > 0) return false; + addr->ip = 0; + addr->is_ip6 = false; + return true; +} + +static bool mg_aton4(struct mg_str str, struct mg_addr *addr) { + uint8_t data[4] = {0, 0, 0, 0}; + size_t i, num_dots = 0; + for (i = 0; i < str.len; i++) { + if (str.ptr[i] >= '0' && str.ptr[i] <= '9') { + int octet = data[num_dots] * 10 + (str.ptr[i] - '0'); + if (octet > 255) return false; + data[num_dots] = (uint8_t) octet; + } else if (str.ptr[i] == '.') { + if (num_dots >= 3 || i == 0 || str.ptr[i - 1] == '.') return false; + num_dots++; + } else { + return false; + } + } + if (num_dots != 3 || str.ptr[i - 1] == '.') return false; + memcpy(&addr->ip, data, sizeof(data)); + addr->is_ip6 = false; + return true; +} + +static bool mg_v4mapped(struct mg_str str, struct mg_addr *addr) { + int i; + if (str.len < 14) return false; + if (str.ptr[0] != ':' || str.ptr[1] != ':' || str.ptr[6] != ':') return false; + for (i = 2; i < 6; i++) { + if (str.ptr[i] != 'f' && str.ptr[i] != 'F') return false; + } + if (!mg_aton4(mg_str_n(&str.ptr[7], str.len - 7), addr)) return false; + memset(addr->ip6, 0, sizeof(addr->ip6)); + addr->ip6[10] = addr->ip6[11] = 255; + memcpy(&addr->ip6[12], &addr->ip, 4); + addr->is_ip6 = true; + return true; +} + +static bool mg_aton6(struct mg_str str, struct mg_addr *addr) { + size_t i, j = 0, n = 0, dc = 42; + if (str.len > 2 && str.ptr[0] == '[') str.ptr++, str.len -= 2; + if (mg_v4mapped(str, addr)) return true; + for (i = 0; i < str.len; i++) { + if ((str.ptr[i] >= '0' && str.ptr[i] <= '9') || + (str.ptr[i] >= 'a' && str.ptr[i] <= 'f') || + (str.ptr[i] >= 'A' && str.ptr[i] <= 'F')) { + unsigned long val; + if (i > j + 3) return false; + // MG_DEBUG(("%zu %zu [%.*s]", i, j, (int) (i - j + 1), &str.ptr[j])); + val = mg_unhexn(&str.ptr[j], i - j + 1); + addr->ip6[n] = (uint8_t) ((val >> 8) & 255); + addr->ip6[n + 1] = (uint8_t) (val & 255); + } else if (str.ptr[i] == ':') { + j = i + 1; + if (i > 0 && str.ptr[i - 1] == ':') { + dc = n; // Double colon + if (i > 1 && str.ptr[i - 2] == ':') return false; + } else if (i > 0) { + n += 2; + } + if (n > 14) return false; + addr->ip6[n] = addr->ip6[n + 1] = 0; // For trailing :: + } else { + return false; + } + } + if (n < 14 && dc == 42) return false; + if (n < 14) { + memmove(&addr->ip6[dc + (14 - n)], &addr->ip6[dc], n - dc + 2); + memset(&addr->ip6[dc], 0, 14 - n); + } + addr->is_ip6 = true; + return true; +} + +bool mg_aton(struct mg_str str, struct mg_addr *addr) { + // MG_INFO(("[%.*s]", (int) str.len, str.ptr)); + return mg_atone(str, addr) || mg_atonl(str, addr) || mg_aton4(str, addr) || + mg_aton6(str, addr); +} + +struct mg_connection *mg_alloc_conn(struct mg_mgr *mgr) { + struct mg_connection *c = + (struct mg_connection *) calloc(1, sizeof(*c) + mgr->extraconnsize); + if (c != NULL) { + c->mgr = mgr; + c->id = ++mgr->nextid; + } + return c; +} + +void mg_close_conn(struct mg_connection *c) { + mg_resolve_cancel(c); // Close any pending DNS query + LIST_DELETE(struct mg_connection, &c->mgr->conns, c); + if (c == c->mgr->dns4.c) c->mgr->dns4.c = NULL; + if (c == c->mgr->dns6.c) c->mgr->dns6.c = NULL; + // Order of operations is important. `MG_EV_CLOSE` event must be fired + // before we deallocate received data, see #1331 + mg_call(c, MG_EV_CLOSE, NULL); + MG_DEBUG(("%lu closed", c->id)); + + mg_tls_free(c); + mg_iobuf_free(&c->recv); + mg_iobuf_free(&c->send); + memset(c, 0, sizeof(*c)); + free(c); +} + +struct mg_connection *mg_connect(struct mg_mgr *mgr, const char *url, + mg_event_handler_t fn, void *fn_data) { + struct mg_connection *c = NULL; + if (url == NULL || url[0] == '\0') { + MG_ERROR(("null url")); + } else if ((c = mg_alloc_conn(mgr)) == NULL) { + MG_ERROR(("OOM")); + } else { + LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c); + c->is_udp = (strncmp(url, "udp:", 4) == 0); + c->fn = fn; + c->is_client = true; + c->fd = (void *) (size_t) -1; // Set to invalid socket + c->fn_data = fn_data; + MG_DEBUG(("%lu -1 %s", c->id, url)); + mg_call(c, MG_EV_OPEN, NULL); + mg_resolve(c, url); + } + return c; +} + +struct mg_connection *mg_listen(struct mg_mgr *mgr, const char *url, + mg_event_handler_t fn, void *fn_data) { + struct mg_connection *c = NULL; + if ((c = mg_alloc_conn(mgr)) == NULL) { + MG_ERROR(("OOM %s", url)); + } else if (!mg_open_listener(c, url)) { + MG_ERROR(("Failed: %s, errno %d", url, errno)); + free(c); + } else { + c->is_listening = 1; + c->is_udp = strncmp(url, "udp:", 4) == 0; + LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c); + c->fn = fn; + c->fn_data = fn_data; + mg_call(c, MG_EV_OPEN, NULL); + MG_DEBUG(("%lu %p %s", c->id, c->fd, url)); + } + return c; +} + +struct mg_connection *mg_wrapfd(struct mg_mgr *mgr, int fd, + mg_event_handler_t fn, void *fn_data) { + struct mg_connection *c = mg_alloc_conn(mgr); + if (c != NULL) { + c->fd = (void *) (size_t) fd; + c->fn = fn; + c->fn_data = fn_data; + mg_call(c, MG_EV_OPEN, NULL); + LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c); + } + return c; +} + +struct mg_timer *mg_timer_add(struct mg_mgr *mgr, uint64_t milliseconds, + unsigned flags, void (*fn)(void *), void *arg) { + struct mg_timer *t = (struct mg_timer *) calloc(1, sizeof(*t)); + mg_timer_init(&mgr->timers, t, milliseconds, flags, fn, arg); + return t; +} + +void mg_mgr_free(struct mg_mgr *mgr) { + struct mg_connection *c; + struct mg_timer *tmp, *t = mgr->timers; + while (t != NULL) tmp = t->next, free(t), t = tmp; + mgr->timers = NULL; // Important. Next call to poll won't touch timers + for (c = mgr->conns; c != NULL; c = c->next) c->is_closing = 1; + mg_mgr_poll(mgr, 0); +#if MG_ARCH == MG_ARCH_FREERTOS_TCP + FreeRTOS_DeleteSocketSet(mgr->ss); +#endif + MG_DEBUG(("All connections closed")); +} + +void mg_mgr_init(struct mg_mgr *mgr) { + memset(mgr, 0, sizeof(*mgr)); +#if MG_ARCH == MG_ARCH_WIN32 && MG_ENABLE_WINSOCK + // clang-format off + { WSADATA data; WSAStartup(MAKEWORD(2, 2), &data); } + // clang-format on +#elif MG_ARCH == MG_ARCH_FREERTOS_TCP + mgr->ss = FreeRTOS_CreateSocketSet(); +#elif defined(__unix) || defined(__unix__) || defined(__APPLE__) + // Ignore SIGPIPE signal, so if client cancels the request, it + // won't kill the whole process. + signal(SIGPIPE, SIG_IGN); +#endif + mgr->dnstimeout = 3000; + mgr->dns4.url = "udp://8.8.8.8:53"; + mgr->dns6.url = "udp://[2001:4860:4860::8888]:53"; +} + +#ifdef MG_ENABLE_LINES +#line 1 "src/sha1.c" +#endif +/* Copyright(c) By Steve Reid */ +/* 100% Public Domain */ + +#include + +/* + * clang with std=-c99 uses __LITTLE_ENDIAN, by default + * while for ex, RTOS gcc - LITTLE_ENDIAN, by default + * it depends on __USE_BSD, but let's have everything + */ +#if !defined(BYTE_ORDER) && defined(__BYTE_ORDER) +#define BYTE_ORDER __BYTE_ORDER +#ifndef LITTLE_ENDIAN +#define LITTLE_ENDIAN __LITTLE_ENDIAN +#endif /* LITTLE_ENDIAN */ +#ifndef BIG_ENDIAN +#define BIG_ENDIAN __LITTLE_ENDIAN +#endif /* BIG_ENDIAN */ +#endif /* BYTE_ORDER */ + +union char64long16 { + unsigned char c[64]; + uint32_t l[16]; +}; + +#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) + +static uint32_t blk0(union char64long16 *block, int i) { +/* Forrest: SHA expect BIG_ENDIAN, swap if LITTLE_ENDIAN */ +#if BYTE_ORDER == LITTLE_ENDIAN + block->l[i] = + (rol(block->l[i], 24) & 0xFF00FF00) | (rol(block->l[i], 8) & 0x00FF00FF); +#endif + return block->l[i]; +} + +/* Avoid redefine warning (ARM /usr/include/sys/ucontext.h define R0~R4) */ +#undef blk +#undef R0 +#undef R1 +#undef R2 +#undef R3 +#undef R4 + +#define blk(i) \ + (block->l[i & 15] = rol(block->l[(i + 13) & 15] ^ block->l[(i + 8) & 15] ^ \ + block->l[(i + 2) & 15] ^ block->l[i & 15], \ + 1)) +#define R0(v, w, x, y, z, i) \ + z += ((w & (x ^ y)) ^ y) + blk0(block, i) + 0x5A827999 + rol(v, 5); \ + w = rol(w, 30); +#define R1(v, w, x, y, z, i) \ + z += ((w & (x ^ y)) ^ y) + blk(i) + 0x5A827999 + rol(v, 5); \ + w = rol(w, 30); +#define R2(v, w, x, y, z, i) \ + z += (w ^ x ^ y) + blk(i) + 0x6ED9EBA1 + rol(v, 5); \ + w = rol(w, 30); +#define R3(v, w, x, y, z, i) \ + z += (((w | x) & y) | (w & x)) + blk(i) + 0x8F1BBCDC + rol(v, 5); \ + w = rol(w, 30); +#define R4(v, w, x, y, z, i) \ + z += (w ^ x ^ y) + blk(i) + 0xCA62C1D6 + rol(v, 5); \ + w = rol(w, 30); + +static void mg_sha1_transform(uint32_t state[5], + const unsigned char buffer[64]) { + uint32_t a, b, c, d, e; + union char64long16 block[1]; + + memcpy(block, buffer, 64); + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + e = state[4]; + R0(a, b, c, d, e, 0); + R0(e, a, b, c, d, 1); + R0(d, e, a, b, c, 2); + R0(c, d, e, a, b, 3); + R0(b, c, d, e, a, 4); + R0(a, b, c, d, e, 5); + R0(e, a, b, c, d, 6); + R0(d, e, a, b, c, 7); + R0(c, d, e, a, b, 8); + R0(b, c, d, e, a, 9); + R0(a, b, c, d, e, 10); + R0(e, a, b, c, d, 11); + R0(d, e, a, b, c, 12); + R0(c, d, e, a, b, 13); + R0(b, c, d, e, a, 14); + R0(a, b, c, d, e, 15); + R1(e, a, b, c, d, 16); + R1(d, e, a, b, c, 17); + R1(c, d, e, a, b, 18); + R1(b, c, d, e, a, 19); + R2(a, b, c, d, e, 20); + R2(e, a, b, c, d, 21); + R2(d, e, a, b, c, 22); + R2(c, d, e, a, b, 23); + R2(b, c, d, e, a, 24); + R2(a, b, c, d, e, 25); + R2(e, a, b, c, d, 26); + R2(d, e, a, b, c, 27); + R2(c, d, e, a, b, 28); + R2(b, c, d, e, a, 29); + R2(a, b, c, d, e, 30); + R2(e, a, b, c, d, 31); + R2(d, e, a, b, c, 32); + R2(c, d, e, a, b, 33); + R2(b, c, d, e, a, 34); + R2(a, b, c, d, e, 35); + R2(e, a, b, c, d, 36); + R2(d, e, a, b, c, 37); + R2(c, d, e, a, b, 38); + R2(b, c, d, e, a, 39); + R3(a, b, c, d, e, 40); + R3(e, a, b, c, d, 41); + R3(d, e, a, b, c, 42); + R3(c, d, e, a, b, 43); + R3(b, c, d, e, a, 44); + R3(a, b, c, d, e, 45); + R3(e, a, b, c, d, 46); + R3(d, e, a, b, c, 47); + R3(c, d, e, a, b, 48); + R3(b, c, d, e, a, 49); + R3(a, b, c, d, e, 50); + R3(e, a, b, c, d, 51); + R3(d, e, a, b, c, 52); + R3(c, d, e, a, b, 53); + R3(b, c, d, e, a, 54); + R3(a, b, c, d, e, 55); + R3(e, a, b, c, d, 56); + R3(d, e, a, b, c, 57); + R3(c, d, e, a, b, 58); + R3(b, c, d, e, a, 59); + R4(a, b, c, d, e, 60); + R4(e, a, b, c, d, 61); + R4(d, e, a, b, c, 62); + R4(c, d, e, a, b, 63); + R4(b, c, d, e, a, 64); + R4(a, b, c, d, e, 65); + R4(e, a, b, c, d, 66); + R4(d, e, a, b, c, 67); + R4(c, d, e, a, b, 68); + R4(b, c, d, e, a, 69); + R4(a, b, c, d, e, 70); + R4(e, a, b, c, d, 71); + R4(d, e, a, b, c, 72); + R4(c, d, e, a, b, 73); + R4(b, c, d, e, a, 74); + R4(a, b, c, d, e, 75); + R4(e, a, b, c, d, 76); + R4(d, e, a, b, c, 77); + R4(c, d, e, a, b, 78); + R4(b, c, d, e, a, 79); + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; + /* Erase working structures. The order of operations is important, + * used to ensure that compiler doesn't optimize those out. */ + memset(block, 0, sizeof(block)); + a = b = c = d = e = 0; + (void) a; + (void) b; + (void) c; + (void) d; + (void) e; +} + +void mg_sha1_init(mg_sha1_ctx *context) { + context->state[0] = 0x67452301; + context->state[1] = 0xEFCDAB89; + context->state[2] = 0x98BADCFE; + context->state[3] = 0x10325476; + context->state[4] = 0xC3D2E1F0; + context->count[0] = context->count[1] = 0; +} + +void mg_sha1_update(mg_sha1_ctx *context, const unsigned char *data, + size_t len) { + size_t i, j; + + j = context->count[0]; + if ((context->count[0] += (uint32_t) len << 3) < j) context->count[1]++; + context->count[1] += (uint32_t) (len >> 29); + j = (j >> 3) & 63; + if ((j + len) > 63) { + memcpy(&context->buffer[j], data, (i = 64 - j)); + mg_sha1_transform(context->state, context->buffer); + for (; i + 63 < len; i += 64) { + mg_sha1_transform(context->state, &data[i]); + } + j = 0; + } else + i = 0; + memcpy(&context->buffer[j], &data[i], len - i); +} + +void mg_sha1_final(unsigned char digest[20], mg_sha1_ctx *context) { + unsigned i; + unsigned char finalcount[8], c; + + for (i = 0; i < 8; i++) { + finalcount[i] = (unsigned char) ((context->count[(i >= 4 ? 0 : 1)] >> + ((3 - (i & 3)) * 8)) & + 255); + } + c = 0200; + mg_sha1_update(context, &c, 1); + while ((context->count[0] & 504) != 448) { + c = 0000; + mg_sha1_update(context, &c, 1); + } + mg_sha1_update(context, finalcount, 8); + for (i = 0; i < 20; i++) { + digest[i] = + (unsigned char) ((context->state[i >> 2] >> ((3 - (i & 3)) * 8)) & 255); + } + memset(context, '\0', sizeof(*context)); + memset(&finalcount, '\0', sizeof(finalcount)); +} + +#ifdef MG_ENABLE_LINES +#line 1 "src/sntp.c" +#endif + + + + + + +#define SNTP_INTERVAL_SEC 3600 +#define SNTP_TIME_OFFSET 2208988800UL + +int64_t mg_sntp_parse(const unsigned char *buf, size_t len) { + int64_t res = -1; + int mode = len > 0 ? buf[0] & 7 : 0; + if (len < 48) { + MG_ERROR(("%s", "corrupt packet")); + } else if ((buf[0] & 0x38) >> 3 != 4) { + MG_ERROR(("%s", "wrong version")); + } else if (mode != 4 && mode != 5) { + MG_ERROR(("%s", "not a server reply")); + } else if (buf[1] == 0) { + MG_ERROR(("%s", "server sent a kiss of death")); + } else { + uint32_t *data = (uint32_t *) &buf[40]; + unsigned long seconds = mg_ntohl(data[0]) - SNTP_TIME_OFFSET; + unsigned long useconds = mg_ntohl(data[1]); + // MG_DEBUG(("%lu %lu %lu", time(0), seconds, useconds)); + res = ((int64_t) seconds) * 1000 + (int64_t) ((useconds / 1000) % 1000); + } + return res; +} + +static void sntp_cb(struct mg_connection *c, int ev, void *evd, void *fnd) { + if (ev == MG_EV_READ) { + int64_t milliseconds = mg_sntp_parse(c->recv.buf, c->recv.len); + if (milliseconds > 0) { + mg_call(c, MG_EV_SNTP_TIME, (uint64_t *) &milliseconds); + MG_VERBOSE(("%u.%u", (unsigned) (milliseconds / 1000), + (unsigned) (milliseconds % 1000))); + } + mg_iobuf_del(&c->recv, 0, c->recv.len); // Free receive buffer + } else if (ev == MG_EV_CONNECT) { + mg_sntp_request(c); + } else if (ev == MG_EV_CLOSE) { + } + (void) fnd; + (void) evd; +} + +void mg_sntp_request(struct mg_connection *c) { + if (c->is_resolving) { + MG_ERROR(("%lu wait until resolved", c->id)); + } else { + uint8_t buf[48] = {0}; + buf[0] = (0 << 6) | (4 << 3) | 3; + mg_send(c, buf, sizeof(buf)); + } +} + +struct mg_connection *mg_sntp_connect(struct mg_mgr *mgr, const char *url, + mg_event_handler_t fn, void *fnd) { + struct mg_connection *c = NULL; + if (url == NULL) url = "udp://time.google.com:123"; + if ((c = mg_connect(mgr, url, fn, fnd)) != NULL) c->pfn = sntp_cb; + return c; +} + +#ifdef MG_ENABLE_LINES +#line 1 "src/sock.c" +#endif + + + + + + + + + + +#if MG_ENABLE_SOCKET +#if MG_ARCH == MG_ARCH_WIN32 && MG_ENABLE_WINSOCK +#define MG_SOCK_ERRNO WSAGetLastError() +#ifndef SO_EXCLUSIVEADDRUSE +#define SO_EXCLUSIVEADDRUSE ((int) (~SO_REUSEADDR)) +#pragma comment(lib, "ws2_32.lib") +#endif +#elif MG_ARCH == MG_ARCH_FREERTOS_TCP +#define MG_SOCK_ERRNO errno +typedef Socket_t SOCKET; +#define INVALID_SOCKET FREERTOS_INVALID_SOCKET +#elif MG_ARCH == MG_ARCH_TIRTOS +#define MG_SOCK_ERRNO errno +#define closesocket(x) close(x) +#else +#define MG_SOCK_ERRNO errno +#ifndef closesocket +#define closesocket(x) close(x) +#endif +#define INVALID_SOCKET (-1) +typedef int SOCKET; +#endif + +#define FD(c_) ((SOCKET) (size_t) (c_)->fd) +#define S2PTR(s_) ((void *) (size_t) (s_)) + +#ifndef MSG_NONBLOCKING +#define MSG_NONBLOCKING 0 +#endif + +#ifndef AF_INET6 +#define AF_INET6 10 +#endif + +union usa { + struct sockaddr sa; + struct sockaddr_in sin; +#if MG_ENABLE_IPV6 + struct sockaddr_in6 sin6; +#endif +}; + +static socklen_t tousa(struct mg_addr *a, union usa *usa) { + socklen_t len = sizeof(usa->sin); + memset(usa, 0, sizeof(*usa)); + usa->sin.sin_family = AF_INET; + usa->sin.sin_port = a->port; + *(uint32_t *) &usa->sin.sin_addr = a->ip; +#if MG_ENABLE_IPV6 + if (a->is_ip6) { + usa->sin.sin_family = AF_INET6; + usa->sin6.sin6_port = a->port; + memcpy(&usa->sin6.sin6_addr, a->ip6, sizeof(a->ip6)); + len = sizeof(usa->sin6); + } +#endif + return len; +} + +static void tomgaddr(union usa *usa, struct mg_addr *a, bool is_ip6) { + a->is_ip6 = is_ip6; + a->port = usa->sin.sin_port; + memcpy(&a->ip, &usa->sin.sin_addr, sizeof(a->ip)); +#if MG_ENABLE_IPV6 + if (is_ip6) { + memcpy(a->ip6, &usa->sin6.sin6_addr, sizeof(a->ip6)); + a->port = usa->sin6.sin6_port; + } +#endif +} + +bool mg_sock_would_block(void); +bool mg_sock_would_block(void) { + int err = MG_SOCK_ERRNO; + return err == EINPROGRESS || err == EWOULDBLOCK +#ifndef WINCE + || err == EAGAIN || err == EINTR +#endif +#if MG_ARCH == MG_ARCH_WIN32 && MG_ENABLE_WINSOCK + || err == WSAEINTR || err == WSAEWOULDBLOCK +#endif + ; +} + +bool mg_sock_conn_reset(void); +bool mg_sock_conn_reset(void) { + int err = MG_SOCK_ERRNO; +#if MG_ARCH == MG_ARCH_WIN32 && MG_ENABLE_WINSOCK + return err == WSAECONNRESET; +#else + return err == EPIPE || err == ECONNRESET; +#endif +} + +static void setlocaddr(SOCKET fd, struct mg_addr *addr) { + union usa usa; + socklen_t n = sizeof(usa); + if (getsockname(fd, &usa.sa, &n) == 0) { + tomgaddr(&usa, addr, n != sizeof(usa.sin)); + } +} + +static void iolog(struct mg_connection *c, char *buf, long n, bool r) { + if (n == 0) { + // Do nothing + } else if (n < 0) { + c->is_closing = 1; // Termination. Don't call mg_error(): #1529 + } else if (n > 0) { + if (c->is_hexdumping) { + union usa usa; + char t1[50] = "", t2[50] = ""; + socklen_t slen = sizeof(usa.sin); + struct mg_addr a; + memset(&usa, 0, sizeof(usa)); + memset(&a, 0, sizeof(a)); + if (getsockname(FD(c), &usa.sa, &slen) < 0) (void) 0; // Ignore result + tomgaddr(&usa, &a, c->rem.is_ip6); + MG_INFO(("\n-- %lu %s %s %s %s %ld", c->id, + mg_straddr(&a, t1, sizeof(t1)), r ? "<-" : "->", + mg_straddr(&c->rem, t2, sizeof(t2)), c->label, n)); + + mg_hexdump(buf, (size_t) n); + } + if (r) { + struct mg_str evd = mg_str_n(buf, (size_t) n); + c->recv.len += (size_t) n; + mg_call(c, MG_EV_READ, &evd); + } else { + mg_iobuf_del(&c->send, 0, (size_t) n); + // if (c->send.len == 0) mg_iobuf_resize(&c->send, 0); + mg_call(c, MG_EV_WRITE, &n); + } + } +} + +static long mg_sock_send(struct mg_connection *c, const void *buf, size_t len) { + long n; + if (c->is_udp) { + union usa usa; + socklen_t slen = tousa(&c->rem, &usa); + n = sendto(FD(c), (char *) buf, len, 0, &usa.sa, slen); + if (n > 0) setlocaddr(FD(c), &c->loc); + } else { + n = send(FD(c), (char *) buf, len, MSG_NONBLOCKING); +#if MG_ARCH == MG_ARCH_RTX + if (n == BSD_EWOULDBLOCK) return 0; +#endif + } + return n == 0 ? -1 : n < 0 && mg_sock_would_block() ? 0 : n; +} + +bool mg_send(struct mg_connection *c, const void *buf, size_t len) { + if (c->is_udp) { + long n = mg_sock_send(c, buf, len); + MG_DEBUG(("%lu %p %d:%d %ld err %d (%s)", c->id, c->fd, (int) c->send.len, + (int) c->recv.len, n, MG_SOCK_ERRNO, strerror(errno))); + iolog(c, (char *) buf, n, false); + return n > 0; + } else { + return mg_iobuf_add(&c->send, c->send.len, buf, len, MG_IO_SIZE); + } +} + +static void mg_set_non_blocking_mode(SOCKET fd) { +#if defined(MG_CUSTOM_NONBLOCK) + MG_CUSTOM_NONBLOCK(fd); +#elif MG_ARCH == MG_ARCH_WIN32 && MG_ENABLE_WINSOCK + unsigned long on = 1; + ioctlsocket(fd, FIONBIO, &on); +#elif MG_ARCH == MG_ARCH_RTX + unsigned long on = 1; + ioctlsocket(fd, FIONBIO, &on); +#elif MG_ARCH == MG_ARCH_FREERTOS_TCP + const BaseType_t off = 0; + if (setsockopt(fd, 0, FREERTOS_SO_RCVTIMEO, &off, sizeof(off)) != 0) (void) 0; + if (setsockopt(fd, 0, FREERTOS_SO_SNDTIMEO, &off, sizeof(off)) != 0) (void) 0; +#elif MG_ARCH == MG_ARCH_FREERTOS_LWIP || MG_ARCH == MG_ARCH_RTX_LWIP + lwip_fcntl(fd, F_SETFL, O_NONBLOCK); +#elif MG_ARCH == MG_ARCH_AZURERTOS + fcntl(fd, F_SETFL, O_NONBLOCK); +#elif MG_ARCH == MG_ARCH_TIRTOS + int val = 0; + setsockopt(fd, 0, SO_BLOCKING, &val, sizeof(val)); + int status = 0; + int res = SockStatus(fd, FDSTATUS_SEND, &status); + if (res == 0 && status > 0) { + val = status / 2; + int val_size = sizeof(val); + res = SockSet(fd, SOL_SOCKET, SO_SNDLOWAT, &val, val_size); + } +#else + fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK); // Non-blocking mode + fcntl(fd, F_SETFD, FD_CLOEXEC); // Set close-on-exec +#endif +} + +bool mg_open_listener(struct mg_connection *c, const char *url) { + SOCKET fd = INVALID_SOCKET; + bool success = false; + c->loc.port = mg_htons(mg_url_port(url)); + if (!mg_aton(mg_url_host(url), &c->loc)) { + MG_ERROR(("invalid listening URL: %s", url)); + } else { + union usa usa; + socklen_t slen = tousa(&c->loc, &usa); + int on = 1, af = c->loc.is_ip6 ? AF_INET6 : AF_INET; + int type = strncmp(url, "udp:", 4) == 0 ? SOCK_DGRAM : SOCK_STREAM; + int proto = type == SOCK_DGRAM ? IPPROTO_UDP : IPPROTO_TCP; + (void) on; + + if ((fd = socket(af, type, proto)) == INVALID_SOCKET) { + MG_ERROR(("socket: %d", MG_SOCK_ERRNO)); +#if ((MG_ARCH == MG_ARCH_WIN32) || (MG_ARCH == MG_ARCH_UNIX) || \ + (defined(LWIP_SOCKET) && SO_REUSE == 1)) + } else if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &on, + sizeof(on)) != 0) { + // 1. SO_RESUSEADDR is not enabled on Windows because the semantics of + // SO_REUSEADDR on UNIX and Windows is different. On Windows, + // SO_REUSEADDR allows to bind a socket to a port without error even + // if the port is already open by another program. This is not the + // behavior SO_REUSEADDR was designed for, and leads to hard-to-track + // failure scenarios. Therefore, SO_REUSEADDR was disabled on Windows + // unless SO_EXCLUSIVEADDRUSE is supported and set on a socket. + // 2. In case of LWIP, SO_REUSEADDR should be explicitly enabled, by + // defining + // SO_REUSE (in lwipopts.h), otherwise the code below will compile + // but won't work! (setsockopt will return EINVAL) + MG_ERROR(("reuseaddr: %d", MG_SOCK_ERRNO)); +#endif +#if MG_ARCH == MG_ARCH_WIN32 && !defined(SO_EXCLUSIVEADDRUSE) && !defined(WINCE) + } else if (setsockopt(fd, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (char *) &on, + sizeof(on)) != 0) { + // "Using SO_REUSEADDR and SO_EXCLUSIVEADDRUSE" + MG_ERROR(("exclusiveaddruse: %d", MG_SOCK_ERRNO)); +#endif + } else if (bind(fd, &usa.sa, slen) != 0) { + MG_ERROR(("bind: %d", MG_SOCK_ERRNO)); + } else if ((type == SOCK_STREAM && + listen(fd, MG_SOCK_LISTEN_BACKLOG_SIZE) != 0)) { + // NOTE(lsm): FreeRTOS uses backlog value as a connection limit + // In case port was set to 0, get the real port number + MG_ERROR(("listen: %d", MG_SOCK_ERRNO)); + } else { + setlocaddr(fd, &c->loc); + mg_set_non_blocking_mode(fd); + c->fd = S2PTR(fd); + success = true; + } + } + if (success == false && fd != INVALID_SOCKET) closesocket(fd); + return success; +} + +static long mg_sock_recv(struct mg_connection *c, void *buf, size_t len) { + long n = 0; + if (c->is_udp) { + union usa usa; + socklen_t slen = tousa(&c->rem, &usa); + n = recvfrom(FD(c), (char *) buf, len, 0, &usa.sa, &slen); + if (n > 0) tomgaddr(&usa, &c->rem, slen != sizeof(usa.sin)); + } else { + n = recv(FD(c), (char *) buf, len, MSG_NONBLOCKING); + } + return n == 0 ? -1 : n < 0 && mg_sock_would_block() ? 0 : n; +} + +// NOTE(lsm): do only one iteration of reads, cause some systems +// (e.g. FreeRTOS stack) return 0 instead of -1/EWOULDBLOCK when no data +static long read_conn(struct mg_connection *c) { + long n = -1; + if (c->recv.len >= MG_MAX_RECV_BUF_SIZE) { + mg_error(c, "max_recv_buf_size reached"); + } else if (c->recv.size - c->recv.len < MG_IO_SIZE && + !mg_iobuf_resize(&c->recv, c->recv.size + MG_IO_SIZE)) { + mg_error(c, "oom"); + } else { + char *buf = (char *) &c->recv.buf[c->recv.len]; + size_t len = c->recv.size - c->recv.len; + n = c->is_tls ? mg_tls_recv(c, buf, len) : mg_sock_recv(c, buf, len); + MG_DEBUG(("%lu %p %d:%d %ld err %d (%s)", c->id, c->fd, (int) c->send.len, + (int) c->recv.len, n, MG_SOCK_ERRNO, strerror(errno))); + iolog(c, buf, n, true); + } + return n; +} + +static void write_conn(struct mg_connection *c) { + char *buf = (char *) c->send.buf; + size_t len = c->send.len; + long n = c->is_tls ? mg_tls_send(c, buf, len) : mg_sock_send(c, buf, len); + MG_DEBUG(("%lu %p %d:%d %ld err %d (%s)", c->id, c->fd, (int) c->send.len, + (int) c->recv.len, n, MG_SOCK_ERRNO, strerror(errno))); + iolog(c, buf, n, false); +} + +static void close_conn(struct mg_connection *c) { + if (FD(c) != INVALID_SOCKET) { + closesocket(FD(c)); +#if MG_ARCH == MG_ARCH_FREERTOS_TCP + FreeRTOS_FD_CLR(c->fd, c->mgr->ss, eSELECT_ALL); +#endif + c->fd = NULL; + } + mg_close_conn(c); +} + +static void setsockopts(struct mg_connection *c) { +#if MG_ARCH == MG_ARCH_FREERTOS_TCP || MG_ARCH == MG_ARCH_AZURERTOS || \ + MG_ARCH == MG_ARCH_TIRTOS + (void) c; +#else + int on = 1; +#if !defined(SOL_TCP) +#define SOL_TCP IPPROTO_TCP +#endif + if (setsockopt(FD(c), SOL_TCP, TCP_NODELAY, (char *) &on, sizeof(on)) != 0) + (void) 0; + if (setsockopt(FD(c), SOL_SOCKET, SO_KEEPALIVE, (char *) &on, sizeof(on)) != + 0) + (void) 0; +#endif +} + +void mg_connect_resolved(struct mg_connection *c) { + // char buf[40]; + int type = c->is_udp ? SOCK_DGRAM : SOCK_STREAM; + int rc, af = c->rem.is_ip6 ? AF_INET6 : AF_INET; + // mg_straddr(&c->rem, buf, sizeof(buf)); + c->fd = S2PTR(socket(af, type, 0)); + c->is_resolving = 0; + if (FD(c) == INVALID_SOCKET) { + mg_error(c, "socket(): %d", MG_SOCK_ERRNO); + } else if (c->is_udp) { + mg_call(c, MG_EV_RESOLVE, NULL); + mg_call(c, MG_EV_CONNECT, NULL); + } else { + union usa usa; + socklen_t slen = tousa(&c->rem, &usa); + mg_set_non_blocking_mode(FD(c)); + setsockopts(c); + mg_call(c, MG_EV_RESOLVE, NULL); + if ((rc = connect(FD(c), &usa.sa, slen)) == 0) { + mg_call(c, MG_EV_CONNECT, NULL); + } else if (mg_sock_would_block()) { + MG_DEBUG(("%lu %p connect in progress...", c->id, c->fd)); + c->is_connecting = 1; + } else { + mg_error(c, "connect: %d", MG_SOCK_ERRNO); + } + } +} + +static SOCKET raccept(SOCKET sock, union usa *usa, socklen_t len) { + SOCKET s = INVALID_SOCKET; + do { + memset(usa, 0, sizeof(*usa)); + s = accept(sock, &usa->sa, &len); + } while (s == INVALID_SOCKET && errno == EINTR); + return s; +} + +static void accept_conn(struct mg_mgr *mgr, struct mg_connection *lsn) { + struct mg_connection *c = NULL; + union usa usa; + socklen_t sa_len = sizeof(usa); + SOCKET fd = raccept(FD(lsn), &usa, sa_len); + if (fd == INVALID_SOCKET) { +#if MG_ARCH == MG_ARCH_AZURERTOS + // AzureRTOS, in non-block socket mode can mark listening socket readable + // even it is not. See comment for 'select' func implementation in + // nx_bsd.c That's not an error, just should try later + if (MG_SOCK_ERRNO != EAGAIN) +#endif + MG_ERROR(("%lu accept failed, errno %d", lsn->id, MG_SOCK_ERRNO)); +#if (MG_ARCH != MG_ARCH_WIN32) && (MG_ARCH != MG_ARCH_FREERTOS_TCP) && \ + (MG_ARCH != MG_ARCH_TIRTOS) + } else if ((long) fd >= FD_SETSIZE) { + MG_ERROR(("%ld > %ld", (long) fd, (long) FD_SETSIZE)); + closesocket(fd); +#endif + } else if ((c = mg_alloc_conn(mgr)) == NULL) { + MG_ERROR(("%lu OOM", lsn->id)); + closesocket(fd); + } else { + char buf[40]; + tomgaddr(&usa, &c->rem, sa_len != sizeof(usa.sin)); + mg_straddr(&c->rem, buf, sizeof(buf)); + MG_DEBUG(("%lu accepted %s", c->id, buf)); + LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c); + c->fd = S2PTR(fd); + mg_set_non_blocking_mode(FD(c)); + setsockopts(c); + c->is_accepted = 1; + c->is_hexdumping = lsn->is_hexdumping; + c->loc = lsn->loc; + c->pfn = lsn->pfn; + c->pfn_data = lsn->pfn_data; + c->fn = lsn->fn; + c->fn_data = lsn->fn_data; + mg_call(c, MG_EV_OPEN, NULL); + mg_call(c, MG_EV_ACCEPT, NULL); + } +} + +static bool mg_socketpair(SOCKET sp[2], union usa usa[2]) { + SOCKET sock; + socklen_t len = sizeof(usa[0].sin); + bool success = false; + + sock = sp[0] = sp[1] = INVALID_SOCKET; + (void) memset(&usa[0], 0, sizeof(usa[0])); + usa[0].sin.sin_family = AF_INET; + *(uint32_t *) &usa->sin.sin_addr = mg_htonl(0x7f000001U); // 127.0.0.1 + usa[1] = usa[0]; + + if ((sock = socket(AF_INET, SOCK_STREAM, 0)) != INVALID_SOCKET && + bind(sock, &usa[0].sa, len) == 0 && + listen(sock, MG_SOCK_LISTEN_BACKLOG_SIZE) == 0 && + getsockname(sock, &usa[0].sa, &len) == 0 && + (sp[0] = socket(AF_INET, SOCK_STREAM, 0)) != INVALID_SOCKET && + connect(sp[0], &usa[0].sa, len) == 0 && + (sp[1] = raccept(sock, &usa[1], len)) != INVALID_SOCKET) { + mg_set_non_blocking_mode(sp[1]); + success = true; + } else { + if (sp[0] != INVALID_SOCKET) closesocket(sp[0]); + if (sp[1] != INVALID_SOCKET) closesocket(sp[1]); + sp[0] = sp[1] = INVALID_SOCKET; + } + if (sock != INVALID_SOCKET) closesocket(sock); + return success; +} + +int mg_mkpipe(struct mg_mgr *mgr, mg_event_handler_t fn, void *fn_data) { + union usa usa[2]; + SOCKET sp[2] = {INVALID_SOCKET, INVALID_SOCKET}; + struct mg_connection *c = NULL; + if (!mg_socketpair(sp, usa)) { + MG_ERROR(("Cannot create socket pair")); + } else if ((c = mg_wrapfd(mgr, (int) sp[1], fn, fn_data)) == NULL) { + closesocket(sp[0]); + closesocket(sp[1]); + sp[0] = sp[1] = INVALID_SOCKET; + } else { + tomgaddr(&usa[0], &c->rem, false); + MG_DEBUG(("%lu %p pipe %lu", c->id, c->fd, (unsigned long) sp[0])); + } + return (int) sp[0]; +} + +static void mg_iotest(struct mg_mgr *mgr, int ms) { +#if MG_ARCH == MG_ARCH_FREERTOS_TCP + struct mg_connection *c; + for (c = mgr->conns; c != NULL; c = c->next) { + if (c->is_closing || c->is_resolving || FD(c) == INVALID_SOCKET) continue; + FreeRTOS_FD_SET(c->fd, mgr->ss, eSELECT_READ | eSELECT_EXCEPT); + if (c->is_connecting || (c->send.len > 0 && c->is_tls_hs == 0)) + FreeRTOS_FD_SET(c->fd, mgr->ss, eSELECT_WRITE); + } + FreeRTOS_select(mgr->ss, pdMS_TO_TICKS(ms)); + for (c = mgr->conns; c != NULL; c = c->next) { + EventBits_t bits = FreeRTOS_FD_ISSET(c->fd, mgr->ss); + c->is_readable = bits & (eSELECT_READ | eSELECT_EXCEPT) ? 1 : 0; + c->is_writable = bits & eSELECT_WRITE ? 1 : 0; + FreeRTOS_FD_CLR(c->fd, mgr->ss, + eSELECT_READ | eSELECT_EXCEPT | eSELECT_WRITE); + } +#else + struct timeval tv = {ms / 1000, (ms % 1000) * 1000}, tv_zero = {0, 0}; + struct mg_connection *c; + fd_set rset, wset; + SOCKET maxfd = 0; + int rc; + + FD_ZERO(&rset); + FD_ZERO(&wset); + + for (c = mgr->conns; c != NULL; c = c->next) { + if (c->is_closing || c->is_resolving || FD(c) == INVALID_SOCKET) continue; + FD_SET(FD(c), &rset); + if (FD(c) > maxfd) maxfd = FD(c); + if (c->is_connecting || (c->send.len > 0 && c->is_tls_hs == 0)) + FD_SET(FD(c), &wset); + if (mg_tls_pending(c) > 0) tv = tv_zero; + } + + if ((rc = select((int) maxfd + 1, &rset, &wset, NULL, &tv)) < 0) { +#if MG_ARCH == MG_ARCH_WIN32 + if (maxfd == 0) Sleep(ms); // On Windows, select fails if no sockets +#else + MG_ERROR(("select: %d %d", rc, MG_SOCK_ERRNO)); +#endif + FD_ZERO(&rset); + FD_ZERO(&wset); + } + + for (c = mgr->conns; c != NULL; c = c->next) { + c->is_readable = FD(c) != INVALID_SOCKET && FD_ISSET(FD(c), &rset); + c->is_writable = FD(c) != INVALID_SOCKET && FD_ISSET(FD(c), &wset); + if (mg_tls_pending(c) > 0) c->is_readable = 1; + } +#endif +} + +static void connect_conn(struct mg_connection *c) { + int rc = 0; +#if (MG_ARCH != MG_ARCH_FREERTOS_TCP) && (MG_ARCH != MG_ARCH_RTX) + socklen_t len = sizeof(rc); + if (getsockopt(FD(c), SOL_SOCKET, SO_ERROR, (char *) &rc, &len)) rc = 1; +#endif + if (rc == EAGAIN || rc == EWOULDBLOCK) rc = 0; + c->is_connecting = 0; + if (rc) { + char buf[50]; + mg_error(c, "error connecting to %s", + mg_straddr(&c->rem, buf, sizeof(buf))); + } else { + if (c->is_tls_hs) mg_tls_handshake(c); + mg_call(c, MG_EV_CONNECT, NULL); + } +} + +void mg_mgr_poll(struct mg_mgr *mgr, int ms) { + struct mg_connection *c, *tmp; + uint64_t now; + + mg_iotest(mgr, ms); + now = mg_millis(); + mg_timer_poll(&mgr->timers, now); + + for (c = mgr->conns; c != NULL; c = tmp) { + tmp = c->next; + mg_call(c, MG_EV_POLL, &now); + MG_VERBOSE(("%lu %c%c %c%c%c%c%c", c->id, c->is_readable ? 'r' : '-', + c->is_writable ? 'w' : '-', c->is_tls ? 'T' : 't', + c->is_connecting ? 'C' : 'c', c->is_tls_hs ? 'H' : 'h', + c->is_resolving ? 'R' : 'r', c->is_closing ? 'C' : 'c')); + if (c->is_resolving || c->is_closing) { + // Do nothing + } else if (c->is_listening && c->is_udp == 0) { + if (c->is_readable) accept_conn(mgr, c); + } else if (c->is_connecting) { + if (c->is_readable || c->is_writable) connect_conn(c); + } else if (c->is_tls_hs) { + if ((c->is_readable || c->is_writable)) mg_tls_handshake(c); + } else { + if (c->is_readable) read_conn(c); + if (c->is_writable) write_conn(c); + } + + if (c->is_draining && c->send.len == 0) c->is_closing = 1; + if (c->is_closing) close_conn(c); + } +} +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/ssi.c" +#endif + + + + +#ifndef MG_MAX_SSI_DEPTH +#define MG_MAX_SSI_DEPTH 5 +#endif + +#ifndef MG_SSI_BUFSIZ +#define MG_SSI_BUFSIZ 1024 +#endif + +#if MG_ENABLE_SSI +static char *mg_ssi(const char *path, const char *root, int depth) { + struct mg_iobuf b = {NULL, 0, 0}; + FILE *fp = fopen(path, "rb"); + if (fp != NULL) { + char buf[MG_SSI_BUFSIZ] = "", arg[sizeof(buf)] = ""; + int ch, intag = 0; + size_t len = 0, align = MG_IO_SIZE; + while ((ch = fgetc(fp)) != EOF) { + if (intag && ch == '>' && buf[len - 1] == '-' && buf[len - 2] == '-') { + buf[len++] = (char) (ch & 0xff); + if (sscanf(buf, "