Browse Source

json parser: fix mapsets in tokenizing (#1252)

* json parser: fix mapsets in tokenizing

This PR fixes the broken tokenizing of `map@mapset`.

Wrong "import_descr":

```
GRASS :~ > r.info globcover@globcover --json
{
  "module": "r.info",
  "id": "r.info_1804289383",
  "inputs":[
     {"import_descr": {"source":"globcover", "type":"raster"},
      "param": "map", "value": "globcover"}
   ]}
```

Fixed with this PR:

```
GRASS :~ > r.info globcover:@globcover --json
{
  "module": "r.info",
  "id": "r.info_1804289383",
  "inputs":[
     {"param": "map", "value": "globcover:"}
   ]}
```

And this remains functional:

```
GRASS :~ > r.slope.aspect elevation="elevation@https://storage.googleapis.com/graas-geodata/elev_ned_30m.tif" slope="slope+GTiff" aspect="aspect+GTiff" --json
{
  "module": "r.slope.aspect",
  "id": "r.slope.aspect_1804289383",
  "inputs":[
     {"import_descr": {"source":"https://storage.googleapis.com/graas-geodata/elev_ned_30m.tif", "type":"raster"},
      "param": "elevation", "value": "elevation"},
     {"param": "format", "value": "degrees"},
     {"param": "precision", "value": "FCELL"},
     {"param": "zscale", "value": "1.0"},
     {"param": "min_slope", "value": "0.0"}
   ],
  "outputs":[
     {"export": {"format":"GTiff", "type":"raster"},
      "param": "slope", "value": "slope"},
     {"export": {"format":"GTiff", "type":"raster"},
      "param": "aspect", "value": "aspect"}
   ]
}
```

It completes the earlier fix in #175.

Co-authored-by: @anikaweinmann

- URL parsing: fatal error checking added
- Check URL to start with http://, https:// or ftp://
- catch multiple `@` chars in user input
- URL detection: only investigate token[1] if there is more then one token
- initialize `urlfound` variable

Co-authored-by: Markus Metz <33666869+metzm@users.noreply.github.com>
Markus Neteler 4 years ago
parent
commit
a501b22b98
1 changed files with 130 additions and 70 deletions
  1. 130 70
      lib/gis/parser_json.c

+ 130 - 70
lib/gis/parser_json.c

@@ -1,19 +1,21 @@
 /*!
-  \file lib/gis/parser_json.c
-  
-  \brief GIS Library - converts the command line arguments into actinia JSON process
-                       chain building blocks
-  
-  (C) 2018 by the GRASS Development Team
-  
-  This program is free software under the GNU General Public License
-  (>=v2). Read the file COPYING that comes with GRASS for details.
-  
-  \author Soeren Gebbert
-*/
+   \file lib/gis/parser_json.c
+
+   \brief GIS Library - converts the command line arguments into actinia JSON process
+   chain building blocks
+
+   (C) 2018-2021 by the GRASS Development Team
+
+   This program is free software under the GNU General Public License
+   (>=v2). Read the file COPYING that comes with GRASS for details.
+
+   \author Soeren Gebbert
+ */
 
 #include <stdio.h>
 #include <stdlib.h>
+#include <string.h>
+#include <grass/glocale.h>
 #include <grass/gis.h>
 
 #include "parser_local_proto.h"
@@ -23,12 +25,12 @@ void check_create_export_opts(struct Option *, char *, FILE *);
 char *check_mapset_in_layer_name(char *, int);
 
 /*!
-  \brief This function generates actinia JSON process chain building blocks
-  from the command line arguments that can be used in the actinia processing API.
+   \brief This function generates actinia JSON process chain building blocks
+   from the command line arguments that can be used in the actinia processing API.
 
-  The following commands will create according JSON output:
+   The following commands will create according JSON output:
 
-    r.slope.aspect elevation="elevation+https://storage.googleapis.com/graas-geodata/elev_ned_30m.tif" slope="slope+GTiff" aspect="aspect+GTiff" --json
+   r.slope.aspect elevation="elevation@https://storage.googleapis.com/graas-geodata/elev_ned_30m.tif" slope="slope+GTiff" aspect="aspect+GTiff" --json
 
     {
       "module": "r.slope.aspect",
@@ -49,7 +51,7 @@ char *check_mapset_in_layer_name(char *, int);
        ]
     }
 
-    v.out.ascii input="hospitals@PERMANENT" output="myfile+TXT" --json
+   v.out.ascii input="hospitals@PERMANENT" output="myfile+TXT" --json
 
     {
       "module": "v.out.ascii",
@@ -68,7 +70,7 @@ char *check_mapset_in_layer_name(char *, int);
        ]
     }
 
-    v.info map="hospitals@PERMANENT" -c --json
+   v.info map="hospitals@PERMANENT" -c --json
 
     {
       "module": "v.info",
@@ -81,7 +83,7 @@ char *check_mapset_in_layer_name(char *, int);
     }
 
 
-  A process chain has the following form
+   A process chain has the following form
 
 {
     'list': [{
@@ -184,7 +186,8 @@ char *check_mapset_in_layer_name(char *, int);
 char *G__json(void)
 {
     FILE *fp = stdout;
-    /*FILE *fp = NULL;*/
+
+    /*FILE *fp = NULL; */
     char *type;
     char *file_name = NULL;
     int c;
@@ -195,23 +198,23 @@ char *G__json(void)
     int i = 0;
 
     char age[KEYLENGTH];
-    char element[KEYLENGTH]; /*cell, file, grid3, vector */
+    char element[KEYLENGTH];    /*cell, file, grid3, vector */
     char desc[KEYLENGTH];
 
     file_name = G_tempfile();
 
     /* fprintf(stderr, "Filename: %s\n", file_name); */
     fp = fopen(file_name, "w+");
-    if (fp == NULL)
-    {
+    if (fp == NULL) {
         fprintf(stderr, "Unable to open temporary file <%s>\n", file_name);
         exit(EXIT_FAILURE);
     }
 
     if (st->n_flags) {
         struct Flag *flag;
+
         for (flag = &st->first_flag; flag; flag = flag->next_flag) {
-            if(flag->answer)
+            if (flag->answer)
                 num_flags += 1;;
         }
     }
@@ -219,20 +222,22 @@ char *G__json(void)
     /* Count input and output options */
     if (st->n_opts) {
         struct Option *opt;
+
         for (opt = &st->first_option; opt; opt = opt->next_opt) {
             if (opt->answer) {
                 if (opt->gisprompt) {
                     G__split_gisprompt(opt->gisprompt, age, element, desc);
                     /* fprintf(stderr, "age: %s element: %s desc: %s\n", age, element, desc); */
                     if (G_strncasecmp("new", age, 3) == 0) {
-                        /*fprintf(fp, "new: %s\n", opt->gisprompt);*/
+                        /*fprintf(fp, "new: %s\n", opt->gisprompt); */
                         num_outputs += 1;
                     }
                     else {
-                        /*fprintf(fp, "%s\n", opt->gisprompt);*/
+                        /*fprintf(fp, "%s\n", opt->gisprompt); */
                         num_inputs += 1;
                     }
-                } else {
+                }
+                else {
                     num_inputs += 1;
                 }
             }
@@ -245,20 +250,22 @@ char *G__json(void)
 
     if (st->n_flags && num_flags > 0) {
         struct Flag *flag;
+
         fprintf(fp, ",\n");
         fprintf(fp, "  \"flags\":\"");
 
         for (flag = &st->first_flag; flag; flag = flag->next_flag) {
-            if(flag->answer)
+            if (flag->answer)
                 fprintf(fp, "%c", flag->key);
         }
         fprintf(fp, "\"");
     }
 
     /* Print the input options
-    */
+     */
     if (st->n_opts && num_inputs > 0) {
         struct Option *opt;
+
         i = 0;
         fprintf(fp, ",\n");
         fprintf(fp, "  \"inputs\":[\n");
@@ -269,26 +276,36 @@ char *G__json(void)
                     if (opt->answer) {
                         check_create_import_opts(opt, element, fp);
                         i++;
-                        if (i < num_inputs) {fprintf(fp, ",\n");}
-                        else {fprintf(fp, "\n");}
+                        if (i < num_inputs) {
+                            fprintf(fp, ",\n");
+                        }
+                        else {
+                            fprintf(fp, "\n");
+                        }
                     }
                 }
-            } else if (opt->answer) {
+            }
+            else if (opt->answer) {
                 /* Check for input options */
                 fprintf(fp, "     {\"param\": \"%s\", ", opt->key);
                 fprintf(fp, "\"value\": \"%s\"}", opt->answer);
                 i++;
-                if (i < num_inputs) {fprintf(fp, ",\n");}
-                else {fprintf(fp, "\n");}
+                if (i < num_inputs) {
+                    fprintf(fp, ",\n");
+                }
+                else {
+                    fprintf(fp, "\n");
+                }
             }
         }
         fprintf(fp, "   ]");
     }
 
     /* Print the output options
-    */
+     */
     if (st->n_opts && num_outputs > 0) {
         struct Option *opt;
+
         i = 0;
         fprintf(fp, ",\n");
         fprintf(fp, "  \"outputs\":[\n");
@@ -299,8 +316,12 @@ char *G__json(void)
                     if (opt->answer) {
                         check_create_export_opts(opt, element, fp);
                         i++;
-                        if (i < num_outputs) {fprintf(fp, ",\n");}
-                        else {fprintf(fp, "\n");}
+                        if (i < num_outputs) {
+                            fprintf(fp, ",\n");
+                        }
+                        else {
+                            fprintf(fp, "\n");
+                        }
                     }
                 }
             }
@@ -313,16 +334,14 @@ char *G__json(void)
 
     /* Print the file content to stdout */
     fp = fopen(file_name, "r");
-    if (fp == NULL)
-    {
+    if (fp == NULL) {
         fprintf(stderr, "Unable to open temporary file <%s>\n", file_name);
         exit(EXIT_FAILURE);
     }
 
     c = fgetc(fp);
-    while (c != EOF)
-    {
-        fprintf (stdout, "%c", c);
+    while (c != EOF) {
+        fprintf(stdout, "%c", c);
         c = fgetc(fp);
     }
     fclose(fp);
@@ -334,42 +353,74 @@ char *G__json(void)
    dependent on the element type (cell, vector, grid3, file)
 
    {'import_descr': {'source': 'https://storage.googleapis.com/graas-geodata/elev_ned_30m.tif',
-                     'type': 'raster'},
-    'param': 'map',
-    'value': 'elevation'}
+   'type': 'raster'},
+   'param': 'map',
+   'value': 'elevation'}
  */
-void check_create_import_opts(struct Option *opt, char *element, FILE *fp)
+void check_create_import_opts(struct Option *opt, char *element, FILE * fp)
 {
-    int i = 0;
+    int i = 0, urlfound = 0;
     int has_import = 0;
     char **tokens;
 
+    G_debug(2, "tokenize opt string: <%s> with '@'", opt->answer);
     tokens = G_tokenize(opt->answer, "@");
     while (tokens[i]) {
         G_chop(tokens[i]);
         i++;
     }
+    if (i > 2)
+        G_fatal_error(_("Input string not understood: <%s>. Multiple '@' chars?"),
+                      opt->answer);
+
+    if (i > 1) {
+        /* check if tokens[1] starts with an URL or name@mapset */
+        G_debug(2, "tokens[1]: <%s>", tokens[1]);
+        if (strncmp(tokens[1], "http://", 7) == 0 ||
+            strncmp(tokens[1], "https://", 8) == 0 ||
+            strncmp(tokens[1], "ftp://", 6) == 0) {
+            urlfound = 1;
+            G_debug(2, "URL found");
+        }
+        else {
+            urlfound = 0;
+            G_debug(2, "name@mapset found");
+        }
+    }
 
     fprintf(fp, "     {");
 
-    if (i > 1) {
+    if (i > 1 && urlfound == 1) {
         if (G_strncasecmp("cell", element, 4) == 0) {
-            fprintf(fp, "\"import_descr\": {\"source\":\"%s\", \"type\":\"raster\"},\n      ", tokens[1]);
+            fprintf(fp,
+                    "\"import_descr\": {\"source\":\"%s\", \"type\":\"raster\"},\n      ",
+                    tokens[1]);
             has_import = 1;
         }
         else if (G_strncasecmp("file", element, 4) == 0) {
-            fprintf(fp, "\"import_descr\": {\"source\":\"%s\", \"type\":\"file\"},\n      ", tokens[1]);
+            fprintf(fp,
+                    "\"import_descr\": {\"source\":\"%s\", \"type\":\"file\"},\n      ",
+                    tokens[1]);
             has_import = 1;
         }
         else if (G_strncasecmp("vector", element, 4) == 0) {
-            fprintf(fp, "\"import_descr\": {\"source\":\"%s\", \"type\":\"vector\"},\n      ", tokens[1]);
+            fprintf(fp,
+                    "\"import_descr\": {\"source\":\"%s\", \"type\":\"vector\"},\n      ",
+                    tokens[1]);
             has_import = 1;
         }
     }
 
     fprintf(fp, "\"param\": \"%s\", ", opt->key);
     /* In case of import the mapset must be removed always */
-    fprintf(fp, "\"value\": \"%s\"", check_mapset_in_layer_name(tokens[0], has_import));
+    if (urlfound == 1) {
+        fprintf(fp, "\"value\": \"%s\"",
+                check_mapset_in_layer_name(tokens[0], has_import));
+    }
+    else {
+        fprintf(fp, "\"value\": \"%s\"",
+                check_mapset_in_layer_name(opt->answer, has_import));
+    };
     fprintf(fp, "}");
 
     G_free_tokens(tokens);
@@ -378,16 +429,16 @@ void check_create_import_opts(struct Option *opt, char *element, FILE *fp)
 /* \brief Check the provided answer and generate the export statement
    dependent on the element type (cell, vector, grid3, file)
 
-    "outputs": [
-        {"export": {"type": "file", "format": "TXT"},
-         "param": "output",
-         "value": "$file::out1"},
-        {'export': {'format': 'GTiff', 'type': 'raster'},
-         'param': 'map',
-         'value': 'LT52170762005240COA00_dos1.1'}
-    ]
+   "outputs": [
+   {"export": {"type": "file", "format": "TXT"},
+   "param": "output",
+   "value": "$file::out1"},
+   {'export': {'format': 'GTiff', 'type': 'raster'},
+   'param': 'map',
+   'value': 'LT52170762005240COA00_dos1.1'}
+   ]
  */
-void check_create_export_opts(struct Option *opt, char *element, FILE *fp)
+void check_create_export_opts(struct Option *opt, char *element, FILE * fp)
 {
     int i = 0;
     int has_file_export = 0;
@@ -403,22 +454,31 @@ void check_create_export_opts(struct Option *opt, char *element, FILE *fp)
 
     if (i > 1) {
         if (G_strncasecmp("cell", element, 4) == 0) {
-            fprintf(fp, "\"export\": {\"format\":\"%s\", \"type\":\"raster\"},\n      ", tokens[1]);
+            fprintf(fp,
+                    "\"export\": {\"format\":\"%s\", \"type\":\"raster\"},\n      ",
+                    tokens[1]);
         }
         else if (G_strncasecmp("file", element, 4) == 0) {
-            fprintf(fp, "\"export\": {\"format\":\"%s\", \"type\":\"file\"},\n      ", tokens[1]);
+            fprintf(fp,
+                    "\"export\": {\"format\":\"%s\", \"type\":\"file\"},\n      ",
+                    tokens[1]);
             has_file_export = 1;
         }
         else if (G_strncasecmp("vector", element, 4) == 0) {
-            fprintf(fp, "\"export\": {\"format\":\"%s\", \"type\":\"vector\"},\n      ", tokens[1]);
+            fprintf(fp,
+                    "\"export\": {\"format\":\"%s\", \"type\":\"vector\"},\n      ",
+                    tokens[1]);
         }
     }
 
     fprintf(fp, "\"param\": \"%s\", ", opt->key);
     if (has_file_export == 1) {
-        fprintf(fp, "\"value\": \"$file::%s\"", check_mapset_in_layer_name(tokens[0], 1));
-    } else {
-        fprintf(fp, "\"value\": \"%s\"", check_mapset_in_layer_name(tokens[0], 1));
+        fprintf(fp, "\"value\": \"$file::%s\"",
+                check_mapset_in_layer_name(tokens[0], 1));
+    }
+    else {
+        fprintf(fp, "\"value\": \"%s\"",
+                check_mapset_in_layer_name(tokens[0], 1));
     }
     fprintf(fp, "}");
 
@@ -426,11 +486,11 @@ void check_create_export_opts(struct Option *opt, char *element, FILE *fp)
 }
 
 /*
-  \brief Check if the current mapset is present in the layer name and remove it
+   \brief Check if the current mapset is present in the layer name and remove it
 
-  The flag always_remove tells this function to always remove all mapset names.
+   The flag always_remove tells this function to always remove all mapset names.
 
-  \return pointer to the layer name without the current mapset
+   \return pointer to the layer name without the current mapset
 */
 char *check_mapset_in_layer_name(char *layer_name, int always_remove)
 {