diff options
Diffstat (limited to 'src')
49 files changed, 3454 insertions, 1 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index 05c0b742..8e398106 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -22,6 +22,7 @@ SUBDIRS = \    curl \    $(PQ_DIR) \    $(SQ_DIR) \ +  templating \    mhd \    bank-lib \    exchangedb \ diff --git a/src/include/Makefile.am b/src/include/Makefile.am index 5cb1698c..a535986c 100644 --- a/src/include/Makefile.am +++ b/src/include/Makefile.am @@ -26,6 +26,7 @@ talerinclude_HEADERS = \    taler_pq_lib.h \    taler_signatures.h \    taler_sq_lib.h \ +  taler_templating_lib.h \    taler_twister_testing_lib.h  EXTRA_DIST = \ diff --git a/src/include/taler_templating_lib.h b/src/include/taler_templating_lib.h new file mode 100644 index 00000000..bad200f5 --- /dev/null +++ b/src/include/taler_templating_lib.h @@ -0,0 +1,65 @@ +/* +  This file is part of TALER +  Copyright (C) 2020, 2022 Taler Systems SA + +  TALER is free software; you can redistribute it and/or modify it under the +  terms of the GNU General Public License as published by the Free Software +  Foundation; either version 3, or (at your option) any later version. + +  TALER 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 General Public License for more details. + +  You should have received a copy of the GNU General Public License along with +  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file taler_templating_lib.h + * @brief logic to load and complete HTML templates + * @author Christian Grothoff + */ +#ifndef TALER_TEMPLATING_LIB_H +#define TALER_TEMPLATING_LIB_H + +#include <microhttpd.h> + + +/** + * Load a @a template and substitute using @a root, returning + * the result to the @a connection with the given + * @a http_status code. + * + * @param connection the connection we act upon + * @param http_status code to use on success + * @param template basename of the template to load + * @param instance_id instance ID, used to compute static files URL + * @param taler_uri value for "Taler:" header to set, or NULL + * @param root JSON object to pass as the root context + * @return #GNUNET_OK on success (reply queued), #GNUNET_NO if an error was queued, + *         #GNUNET_SYSERR on failure (to queue an error) + */ +enum GNUNET_GenericReturnValue +TALER_TEMPLATING_reply (struct MHD_Connection *connection, +                        unsigned int http_status, +                        const char *template, +                        const char *instance_id, +                        const char *taler_uri, +                        json_t *root); + +/** + * Preload templates. + * + * @param subsystem name of the subsystem, "merchant" or "exchange" + * @return #GNUNET_OK on success + */ +enum GNUNET_GenericReturnValue +TALER_TEMPLATING_init (const char *subsystem); + + +/** + * Nicely shut down templating subsystem. + */ +void +TALER_TEMPLATING_done (void); + +#endif diff --git a/src/kyclogic/taler-exchange-kyc-tester.c b/src/kyclogic/taler-exchange-kyc-tester.c index 9133b68c..9d75b3ec 100644 --- a/src/kyclogic/taler-exchange-kyc-tester.c +++ b/src/kyclogic/taler-exchange-kyc-tester.c @@ -494,6 +494,7 @@ clean_kwh (struct TEKT_RequestContext *rc)   * @param provider_section   * @param provider_legitimization_id legi to look up   * @param[out] h_payto where to write the result + * @param[out] legi_row where to write the row ID for the legitimization ID   * @return database transaction status   */  static enum GNUNET_DB_QueryStatus @@ -501,13 +502,15 @@ kyc_provider_account_lookup (    void *cls,    const char *provider_section,    const char *provider_legitimization_id, -  struct TALER_PaytoHashP *h_payto) +  struct TALER_PaytoHashP *h_payto, +  uint64_t *legi_row)  {    GNUNET_log (GNUNET_ERROR_TYPE_INFO,                "Simulated account lookup using `%s/%s'\n",                provider_section,                provider_legitimization_id);    *h_payto = cmd_line_h_payto; +  *legi_row = kyc_row_id;    return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;  } diff --git a/src/templating/.gitignore b/src/templating/.gitignore new file mode 100644 index 00000000..b2bf6ef9 --- /dev/null +++ b/src/templating/.gitignore @@ -0,0 +1 @@ +test_mustach_jansson diff --git a/src/templating/AUTHORS b/src/templating/AUTHORS new file mode 100644 index 00000000..2fcc6043 --- /dev/null +++ b/src/templating/AUTHORS @@ -0,0 +1,23 @@ +Main author: + José Bollo <jobol@nonadev.net> + +Contributors: + Abhishek Mishra + Atlas + Harold L Marzan + Lailton Fernando Mariano + Sami Kerola + Sijmen J. Mulder + Tomasz Sieprawski + +Packagers: + pkgsrc: Sijmen J. Mulder + alpine linux: Lucas Ramage + +Thanks to issue submitters: + Dante Torres + @fabbe + Johann Oskarsson + Mark Bucciarelli + Paul Wisehart + Thierry Fournier diff --git a/src/templating/LICENSE-2.0.txt b/src/templating/LICENSE-2.0.txt new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/src/templating/LICENSE-2.0.txt @@ -0,0 +1,202 @@ + +                                 Apache License +                           Version 2.0, January 2004 +                        http://www.apache.org/licenses/ + +   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +   1. Definitions. + +      "License" shall mean the terms and conditions for use, reproduction, +      and distribution as defined by Sections 1 through 9 of this document. + +      "Licensor" shall mean the copyright owner or entity authorized by +      the copyright owner that is granting the License. + +      "Legal Entity" shall mean the union of the acting entity and all +      other entities that control, are controlled by, or are under common +      control with that entity. For the purposes of this definition, +      "control" means (i) the power, direct or indirect, to cause the +      direction or management of such entity, whether by contract or +      otherwise, or (ii) ownership of fifty percent (50%) or more of the +      outstanding shares, or (iii) beneficial ownership of such entity. + +      "You" (or "Your") shall mean an individual or Legal Entity +      exercising permissions granted by this License. + +      "Source" form shall mean the preferred form for making modifications, +      including but not limited to software source code, documentation +      source, and configuration files. + +      "Object" form shall mean any form resulting from mechanical +      transformation or translation of a Source form, including but +      not limited to compiled object code, generated documentation, +      and conversions to other media types. + +      "Work" shall mean the work of authorship, whether in Source or +      Object form, made available under the License, as indicated by a +      copyright notice that is included in or attached to the work +      (an example is provided in the Appendix below). + +      "Derivative Works" shall mean any work, whether in Source or Object +      form, that is based on (or derived from) the Work and for which the +      editorial revisions, annotations, elaborations, or other modifications +      represent, as a whole, an original work of authorship. For the purposes +      of this License, Derivative Works shall not include works that remain +      separable from, or merely link (or bind by name) to the interfaces of, +      the Work and Derivative Works thereof. + +      "Contribution" shall mean any work of authorship, including +      the original version of the Work and any modifications or additions +      to that Work or Derivative Works thereof, that is intentionally +      submitted to Licensor for inclusion in the Work by the copyright owner +      or by an individual or Legal Entity authorized to submit on behalf of +      the copyright owner. For the purposes of this definition, "submitted" +      means any form of electronic, verbal, or written communication sent +      to the Licensor or its representatives, including but not limited to +      communication on electronic mailing lists, source code control systems, +      and issue tracking systems that are managed by, or on behalf of, the +      Licensor for the purpose of discussing and improving the Work, but +      excluding communication that is conspicuously marked or otherwise +      designated in writing by the copyright owner as "Not a Contribution." + +      "Contributor" shall mean Licensor and any individual or Legal Entity +      on behalf of whom a Contribution has been received by Licensor and +      subsequently incorporated within the Work. + +   2. Grant of Copyright License. Subject to the terms and conditions of +      this License, each Contributor hereby grants to You a perpetual, +      worldwide, non-exclusive, no-charge, royalty-free, irrevocable +      copyright license to reproduce, prepare Derivative Works of, +      publicly display, publicly perform, sublicense, and distribute the +      Work and such Derivative Works in Source or Object form. + +   3. Grant of Patent License. Subject to the terms and conditions of +      this License, each Contributor hereby grants to You a perpetual, +      worldwide, non-exclusive, no-charge, royalty-free, irrevocable +      (except as stated in this section) patent license to make, have made, +      use, offer to sell, sell, import, and otherwise transfer the Work, +      where such license applies only to those patent claims licensable +      by such Contributor that are necessarily infringed by their +      Contribution(s) alone or by combination of their Contribution(s) +      with the Work to which such Contribution(s) was submitted. If You +      institute patent litigation against any entity (including a +      cross-claim or counterclaim in a lawsuit) alleging that the Work +      or a Contribution incorporated within the Work constitutes direct +      or contributory patent infringement, then any patent licenses +      granted to You under this License for that Work shall terminate +      as of the date such litigation is filed. + +   4. Redistribution. You may reproduce and distribute copies of the +      Work or Derivative Works thereof in any medium, with or without +      modifications, and in Source or Object form, provided that You +      meet the following conditions: + +      (a) You must give any other recipients of the Work or +          Derivative Works a copy of this License; and + +      (b) You must cause any modified files to carry prominent notices +          stating that You changed the files; and + +      (c) You must retain, in the Source form of any Derivative Works +          that You distribute, all copyright, patent, trademark, and +          attribution notices from the Source form of the Work, +          excluding those notices that do not pertain to any part of +          the Derivative Works; and + +      (d) If the Work includes a "NOTICE" text file as part of its +          distribution, then any Derivative Works that You distribute must +          include a readable copy of the attribution notices contained +          within such NOTICE file, excluding those notices that do not +          pertain to any part of the Derivative Works, in at least one +          of the following places: within a NOTICE text file distributed +          as part of the Derivative Works; within the Source form or +          documentation, if provided along with the Derivative Works; or, +          within a display generated by the Derivative Works, if and +          wherever such third-party notices normally appear. The contents +          of the NOTICE file are for informational purposes only and +          do not modify the License. You may add Your own attribution +          notices within Derivative Works that You distribute, alongside +          or as an addendum to the NOTICE text from the Work, provided +          that such additional attribution notices cannot be construed +          as modifying the License. + +      You may add Your own copyright statement to Your modifications and +      may provide additional or different license terms and conditions +      for use, reproduction, or distribution of Your modifications, or +      for any such Derivative Works as a whole, provided Your use, +      reproduction, and distribution of the Work otherwise complies with +      the conditions stated in this License. + +   5. Submission of Contributions. Unless You explicitly state otherwise, +      any Contribution intentionally submitted for inclusion in the Work +      by You to the Licensor shall be under the terms and conditions of +      this License, without any additional terms or conditions. +      Notwithstanding the above, nothing herein shall supersede or modify +      the terms of any separate license agreement you may have executed +      with Licensor regarding such Contributions. + +   6. Trademarks. This License does not grant permission to use the trade +      names, trademarks, service marks, or product names of the Licensor, +      except as required for reasonable and customary use in describing the +      origin of the Work and reproducing the content of the NOTICE file. + +   7. Disclaimer of Warranty. Unless required by applicable law or +      agreed to in writing, Licensor provides the Work (and each +      Contributor provides its Contributions) on an "AS IS" BASIS, +      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +      implied, including, without limitation, any warranties or conditions +      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A +      PARTICULAR PURPOSE. You are solely responsible for determining the +      appropriateness of using or redistributing the Work and assume any +      risks associated with Your exercise of permissions under this License. + +   8. Limitation of Liability. In no event and under no legal theory, +      whether in tort (including negligence), contract, or otherwise, +      unless required by applicable law (such as deliberate and grossly +      negligent acts) or agreed to in writing, shall any Contributor be +      liable to You for damages, including any direct, indirect, special, +      incidental, or consequential damages of any character arising as a +      result of this License or out of the use or inability to use the +      Work (including but not limited to damages for loss of goodwill, +      work stoppage, computer failure or malfunction, or any and all +      other commercial damages or losses), even if such Contributor +      has been advised of the possibility of such damages. + +   9. Accepting Warranty or Additional Liability. While redistributing +      the Work or Derivative Works thereof, You may choose to offer, +      and charge a fee for, acceptance of support, warranty, indemnity, +      or other liability obligations and/or rights consistent with this +      License. However, in accepting such obligations, You may act only +      on Your own behalf and on Your sole responsibility, not on behalf +      of any other Contributor, and only if You agree to indemnify, +      defend, and hold each Contributor harmless for any liability +      incurred by, or claims asserted against, such Contributor by reason +      of your accepting any such warranty or additional liability. + +   END OF TERMS AND CONDITIONS + +   APPENDIX: How to apply the Apache License to your work. + +      To apply the Apache License to your work, attach the following +      boilerplate notice, with the fields enclosed by brackets "[]" +      replaced with your own identifying information. (Don't include +      the brackets!)  The text should be enclosed in the appropriate +      comment syntax for the file format. We also recommend that a +      file or class name and description of purpose be included on the +      same "printed page" as the copyright notice for easier +      identification within third-party archives. + +   Copyright [yyyy] [name of copyright owner] + +   Licensed under the Apache License, Version 2.0 (the "License"); +   you may not use this file except in compliance with the License. +   You may obtain a copy of the License at + +       http://www.apache.org/licenses/LICENSE-2.0 + +   Unless required by applicable law or agreed to in writing, software +   distributed under the License is distributed on an "AS IS" BASIS, +   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +   See the License for the specific language governing permissions and +   limitations under the License. diff --git a/src/templating/Makefile.am b/src/templating/Makefile.am new file mode 100644 index 00000000..e0aef033 --- /dev/null +++ b/src/templating/Makefile.am @@ -0,0 +1,50 @@ +# This Makefile.am is in the public domain +AM_CPPFLAGS = -I$(top_srcdir)/src/include $(LIBGCRYPT_CFLAGS) + +if USE_COVERAGE +  AM_CFLAGS = --coverage -O0 +  XLIB = -lgcov +endif + + +lib_LTLIBRARIES = \ +  libtalertemplating.la + +noinst_LTLIBRARIES = \ +  libmustach.la + +libtalertemplating_la_SOURCES = \ +  mustach.c mustach.h \ +  mustach-jansson.c mustach-jansson.h \ +  templating_api.c +libtalertemplating_la_LIBADD = \ +  -ltalermhd \ +  -ltalerutil \ +  -lgnunetutil \ +  $(XLIB) +libtalertemplating_la_LDFLAGS = \ +  -version-info 0:0:0 \ +  -no-undefined + + +libmustach_la_SOURCES = \ +  mustach.c mustach.h \ +  mustach-jansson.c mustach-jansson.h + +test_mustach_jansson_SOURCES = \ +  test_mustach_jansson.c +test_mustach_jansson_LDADD = \ +  -lgnunetutil \ +  -lmustach.la \ +  $(XLIB) + +check_PROGRAMS = \ +  test_mustach_jansson + +check_SCRIPTS = \ +  run-original-tests.sh + +TESTS = $(check_SCRIPTS) $(check_PROGRAMS) + +EXTRA_DIST = \ +  $(check_SCRIPTS) diff --git a/src/templating/ORIGIN b/src/templating/ORIGIN new file mode 100644 index 00000000..fafb0ae7 --- /dev/null +++ b/src/templating/ORIGIN @@ -0,0 +1,9 @@ +Cloned originally from https://gitlab.com/jobol/mustach/ + +Changes: +======== + +Renamed original Makefile to Makefile.orig and wrote Makefile.am for us. + +Added run-original-tests.sh shell script as a wrapper around Makefile.org +to us the original build process for the test suite. diff --git a/src/templating/README.md b/src/templating/README.md new file mode 100644 index 00000000..a6df19f6 --- /dev/null +++ b/src/templating/README.md @@ -0,0 +1,214 @@ +# Introduction to Mustach 0.99 + +`mustach` is a C implementation of the [mustache](http://mustache.github.io "main site for mustache") +template specification. + +The main site for `mustach` is on [gitlab](https://gitlab.com/jobol/mustach). + +The best way to use mustach is to copy the files **mustach.h** and **mustach.c** +directly into your project and use it. + +Alternatively, make and meson files are provided for building `mustach` and  +`libmustach.so` shared library. + +## Distributions offering mustach package + +### Alpine Linux + +```sh +apk add mustach +apk add mustach-lib +apk add mustach-dev +``` + +### NetBSD + +```sh +cd devel/mustach +make +``` + +See http://pkgsrc.se/devel/mustach + +## Using Mustach from sources + +The file **mustach.h** is the main documentation. Look at it. + +The current source files are: + +- **mustach.c** core implementation of mustache in C +- **mustach.h** header file for core definitions +- **mustach-json-c.c** tiny json wrapper of mustach using [json-c](https://github.com/json-c/json-c) +- **mustach-json-c.h** header file for using the tiny JSON wrapper +- **mustach-tool.c** simple tool for applying template files to a JSON file + +The file **mustach-json-c.c** is the main example of use of **mustach** core +and it is also a practical implementation that can be used. It uses the library +json-c. (NOTE for Mac OS: available through homebrew). + +HELP REQUESTED TO GIVE EXAMPLE BASED ON OTHER LIBRARIES (ex: janson, ...). + +The tool **mustach** is build using `make`,  its usage is: + +    mustach json template [template]... + +It then outputs the result of applying the templates files to the JSON file. + +### Portability + +Some system does not provide *open_memstream*. In that case, tell your +preferred compiler to declare the preprocessor symbol **NO_OPEN_MEMSTREAM**. +Example: + +	gcc -DNO_OPEN_MEMSTREAM + +### Integration + +The file **mustach.h** is the main documentation. Look at it. + +The file **mustach-json-c.c** provides a good example of integration. + +If you intend to use basic HTML/XML escaping and standard C FILE, the callbacks +of the interface **mustach_itf** that you have to implement are: +`enter`, `next`, `leave`, `get`. + +If you intend to use specific escaping and/or specific output, the callbacks +of the interface **mustach_itf** that you have to implement are: +`enter`, `next`, `leave`, `get` and `emit`. + +### Extensions + +By default, the current implementation provides the following extensions: + +#### Explicit Substitution + +This is a core extension implemented in file **mustach.c**. + +In somecases the name of the key used for substitution begins with a +character reserved for mustach: one of `#`, `^`, `/`, `&`, `{`, `>` and `=`. +This extension introduces the special character `:` to explicitly +tell mustach to just substitute the value. So `:` becomes a new special +character. + +#### Value Testing and Comparing + +This are a tool extension implemented in file **mustach-json-c.c**. + +These extensions allows you to test the value of the selected key. +They allow to write `key=value` (matching test) or `key=!value` +(not matching test) in any query. + +The specific comparison extension also allows to compare if greater, +lesser, etc.. than a value. It allows to write `key>value`. + +It the comparator sign appears in the first column it is ignored +as if it was escaped. + +#### Access to current value + +The value of the current field can be accessed using single dot like +in `{{#key}}{{.}}{{/key}}` that applied to `{"key":3.14}` produces `3.14` +and `{{#array}} {{.}}{{/array}}` applied to `{"array":[1,2]}` produces +` 1 2`. + +#### Iteration on objects + +Using the pattern `{{#X.*}}...{{/X.*}}` it is possible to iterate on +fields of `X`. Example: `{{s.*}} {{*}}:{{.}}{{/s.*}}` applied on +`{"s":{"a":1,"b":true}}` produces ` a:1 b:true`. Here the single star +`{{*}}` is replaced by the iterated key and the single dot `{{.}}` is +replaced by its value. + +### Removing Extensions + +When compiling mustach.c or mustach-json-c.c, +extensions can be removed by defining macros +using option -D. + +The possible macros are of 3 categories, the global, +the mustach core specific and the mustach-json-c example +of implementation specific. + +#### Global macros + +- `NO_EXTENSION_FOR_MUSTACH` + +  This macro disables any current or future +  extensions for the core or the example. + +#### Macros for the core mustach engine (mustach.c) + +- `NO_COLON_EXTENSION_FOR_MUSTACH` + +  This macro remove the ability to use colon (:) +  as explicit command for variable substitution. +  This extension allows to have name starting +  with one of the mustach character `:#^/&{=>` + +- `NO_ALLOW_EMPTY_TAG` + +  Generate the error MUSTACH_ERROR_EMPTY_TAG automatically +  when an empty tag is encountered. + +#### Macros for the implementation example (mustach-json-c.c) + +- `NO_EQUAL_VALUE_EXTENSION_FOR_MUSTACH` + +  This macro allows the program to check whether +  the actual value is equal to an expected value. +  This is useful in `{{#key=val}}` or `{{^key=val}}` +  with the corresponding `{{/key=val}}`. +  It can also be used in `{{key=val}}` but this +  doesn't seem to be useful. + +- `NO_COMPARE_VALUE_EXTENSION_FOR_MUSTACH` + +  This macro allows the program to compare the actual +  value with an expected value. The comparison operators +  are `=`, `>`, `<`, `>=`, `<=`. The meaning of the +  comparison numeric or alphabetic depends on the type +  of the inspected value. Also the result of the comparison +  can be inverted if the value starts with `!`. +  Example of use: `{{key>=val}}`, or `{{#key>=val}}` and +  `{{^key>=val}}` with their matching `{{/key>=val}}`. + +- `NO_USE_VALUE_ESCAPE_FIRST_EXTENSION_FOR_MUSTACH` + +  This macro fordids automatic escaping of coparison +  sign appearing at first column. + +- `NO_JSON_POINTER_EXTENSION_FOR_MUSTACH` + +  This macro removes the possible use of JSON pointers. +  JSON pointers are defined in IETF RFC 6901. +  If not set, any key starting with "/" is a JSON pointer. +  This implies to use the colon to introduce keys. +  So `NO_COLON_EXTENSION_FOR_MUSTACH` implies +  `NO_JSON_POINTER_EXTENSION_FOR_MUSTACH`. +  A special escaping is used for `=`, `<`, `>` signs when +  values comparisons are enabled: `~=` gives `=` in the key. + +- `NO_OBJECT_ITERATION_FOR_MUSTACH` + +  Disable the object iteration extension. That extension allows +  to iterate over the keys of an object. The iteration on object +  is selected by using the selector `{{#key.*}}`. In the context +  of iterating over object keys, the single key `{{*}}` returns the +  key and `{{.}}` returns the value. + +- `NO_SINGLE_DOT_EXTENSION_FOR_MUSTACH` + +  Disable access to current object value using single dot +  like in `{{.}}`. + +- `NO_INCLUDE_PARTIAL_FALLBACK` + +  Disable include of file by partial pattern like `{{> name}}`. +  By default if a such pattern is found, **mustach** search +  for `name` in the current json context. This what is done +  historically and when `NO_INCLUDE_PARTIAL_FALLBACK` is defined. +  When `NO_INCLUDE_PARTIAL_FALLBACK` is defined, if the value is +  found in the json context, the files `name` and `name.mustache` +  are searched in that order and the first file found is used +  as partial content. The macro `INCLUDE_PARTIAL_EXTENSION` can +  be use for changing the extension added. diff --git a/src/templating/meson.build b/src/templating/meson.build new file mode 100644 index 00000000..c7ecc8df --- /dev/null +++ b/src/templating/meson.build @@ -0,0 +1,12 @@ +project('mustach', 'c', +    version: '1.0.0' +) + +mustach_inc = include_directories('.') +mustach_lib = shared_library('mustach', +    'mustach.c', +    include_directories: mustach_inc +) + +mustach_dep = declare_dependency(link_with: mustach_lib, +    include_directories: mustach_inc) diff --git a/src/templating/mustach-jansson.c b/src/templating/mustach-jansson.c new file mode 100644 index 00000000..2aed5829 --- /dev/null +++ b/src/templating/mustach-jansson.c @@ -0,0 +1,417 @@ +/* + Copyright (C) 2020 Taler Systems SA + + Original license: + Author: José Bollo <jobol@nonadev.net> + Author: José Bollo <jose.bollo@iot.bzh> + + https://gitlab.com/jobol/mustach + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + +     http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include "platform.h" +#include "mustach-jansson.h" + +struct Context +{ +  /** +   * Context object. +   */ +  json_t *cont; + +  /** +   * Current object. +   */ +  json_t *obj; + +  /** +   * Opaque object iterator. +   */ +  void *iter; + +  /** +   * Current index when iterating over an array. +   */ +  unsigned int index; + +  /** +   * Count when iterating over an array. +   */ +  unsigned int count; + +  bool is_objiter; +}; + +enum Bang +{ +  BANG_NONE, +  BANG_I18N, +  BANG_STRINGIFY, +  BANG_AMOUNT_CURRENCY, +  BANG_AMOUNT_DECIMAL, +}; + +struct JanssonClosure +{ +  json_t *root; +  mustach_jansson_write_cb writecb; +  int depth; + +  /** +   * Did the last find(..) call result in an iterable? +   */ +  struct Context stack[MUSTACH_MAX_DEPTH]; + +  /** +   * The last object we found should be iterated over. +   */ +  bool found_iter; + +  /** +   * Last bang we found. +   */ +  enum Bang found_bang; +   +  /** +   * Language for i18n lookups. +   */ +  const char *lang; +}; + + +static json_t * +walk (json_t *obj, const char *path) +{ +  char *saveptr = NULL; +  char *sp = GNUNET_strdup (path); +  char *p = sp; +  while (true) +  { +    char *tok = strtok_r (p, ".", &saveptr); +    if (tok == NULL) +      break; +    obj = json_object_get (obj, tok); +    if (obj == NULL) +      break; +    p = NULL; +  } +  GNUNET_free (sp); +  return obj; +} + + +static json_t * +find (struct JanssonClosure *e, const char *name) +{ +  json_t *obj = NULL; +  char *path = GNUNET_strdup (name); +  char *bang; + +  bang = strchr (path, '!'); + +  e->found_bang = BANG_NONE; + +  if (NULL != bang) +  { +    *bang = 0; +    bang++; + +    if (0 == strcmp (bang, "i18n")) +      e->found_bang = BANG_I18N; +    else if (0 == strcmp(bang, "stringify")) +      e->found_bang = BANG_STRINGIFY; +    else if (0 == strcmp(bang, "amount_decimal")) +      e->found_bang = BANG_AMOUNT_CURRENCY; +    else if (0 == strcmp(bang, "amount_currency")) +      e->found_bang = BANG_AMOUNT_DECIMAL; +  } + +  if (BANG_I18N == e->found_bang && NULL != e->lang) +  { +    char *aug_path; +    GNUNET_asprintf (&aug_path, "%s_i18n.%s", path, e->lang); +    obj = walk (e->stack[e->depth].obj, aug_path); +    GNUNET_free (aug_path); +  } + +  if (NULL == obj) +  { +    obj = walk (e->stack[e->depth].obj, path); +  } + +  GNUNET_free (path); + +  return obj; +} + + +static int +start(void *closure) +{ +  struct JanssonClosure *e = closure; +  e->depth = 0; +  e->stack[0].cont = NULL; +  e->stack[0].obj = e->root; +  e->stack[0].index = 0; +  e->stack[0].count = 1; +  e->lang = json_string_value (json_object_get (e->root, "$language")); +  return MUSTACH_OK; +} + + +static int +emituw (void *closure, const char *buffer, size_t size, int escape, FILE *file) +{ +  struct JanssonClosure *e = closure; +  if (!escape) +    e->writecb (file, buffer, size); +  else +    do +    { +      switch (*buffer) +      { +        case '<': +          e->writecb (file, "<", 4); +          break; +        case '>': +          e->writecb (file, ">", 4); +          break; +        case '&': +          e->writecb (file, "&", 5); +          break; +        default: +          e->writecb (file, buffer, 1); +          break; +      } +      buffer++; +    } +    while(--size); +  return MUSTACH_OK; +} + + +static int +enter(void *closure, const char *name) +{ +  struct JanssonClosure *e = closure; +  json_t *o = find(e, name); +  if (++e->depth >= MUSTACH_MAX_DEPTH) +    return MUSTACH_ERROR_TOO_DEEP; + +  if (json_is_object (o)) +  { +    if (e->found_iter) +    { +      void *iter = json_object_iter (o); +      if (NULL == iter) +      { +        e->depth--; +        return 0; +      } +      e->stack[e->depth].is_objiter = 1; +      e->stack[e->depth].iter = iter; +      e->stack[e->depth].obj = json_object_iter_value (iter); +      e->stack[e->depth].cont = o; +    } +    else +    { +      e->stack[e->depth].is_objiter = 0; +      e->stack[e->depth].obj = o; +      e->stack[e->depth].cont = o; +    } +    return 1; +  } + +  if (json_is_array (o)) +  { +    unsigned int size = json_array_size (o); +    if (size == 0) +    { +      e->depth--; +      return 0; +    } +    e->stack[e->depth].count = size; +    e->stack[e->depth].cont = o; +    e->stack[e->depth].obj = json_array_get (o, 0); +    e->stack[e->depth].index = 0; +    e->stack[e->depth].is_objiter = 0; +    return 1; +  } + +  e->depth--; +  return 0; +} + + +static int +next (void *closure) +{ +  struct JanssonClosure *e = closure; +  struct Context *ctx; +  if (e->depth <= 0) +    return MUSTACH_ERROR_CLOSING; +  ctx = &e->stack[e->depth]; +  if (ctx->is_objiter) +  { +    ctx->iter = json_object_iter_next (ctx->obj, ctx->iter); +    if (NULL == ctx->iter) +      return 0; +    ctx->obj = json_object_iter_value (ctx->iter); +    return 1; +  } +  ctx->index++; +  if (ctx->index >= ctx->count) +    return 0; +  ctx->obj = json_array_get (ctx->cont, ctx->index); +  return 1; +} + +static int +leave (void *closure) +{ +  struct JanssonClosure *e = closure; +  if (e->depth <= 0) +    return MUSTACH_ERROR_CLOSING; +  e->depth--; +  return 0; +} + +static void +freecb (void *v) +{ +  free (v); +} + +static int +get (void *closure, const char *name, struct mustach_sbuf *sbuf) +{ +  struct JanssonClosure *e = closure; +  json_t *obj; + +  if ( (0 == strcmp (name, "*") ) && +       (e->stack[e->depth].is_objiter ) ) +  { +    sbuf->value = json_object_iter_key (e->stack[e->depth].iter); +    return MUSTACH_OK; +  } +  obj = find (e, name); +  if (NULL != obj) +  { +    switch (e->found_bang) +    { +      case BANG_I18N: +      case BANG_NONE: +        { +          const char *s = json_string_value (obj); +          if (NULL != s) +          { +            sbuf->value = s; +            return MUSTACH_OK; +          } +        } +        break; +      case BANG_STRINGIFY: +        sbuf->value = json_dumps (obj, JSON_INDENT (2)); +        sbuf->freecb = freecb; +        return MUSTACH_OK; +      case BANG_AMOUNT_DECIMAL: +        { +          char *s; +          char *c; +          if (!json_is_string (obj)) +            break; +          s = strdup (json_string_value (obj)); +          c = strchr (s, ':'); +          if (NULL != c) +            *c = 0; +          sbuf->value = s; +          sbuf->freecb = freecb; +          return MUSTACH_OK; +        } +        break; +      case BANG_AMOUNT_CURRENCY: +        { +          const char *s; +          if (!json_is_string (obj)) +            break; +          s = json_string_value (obj); +          s = strchr (s, ':'); +          if (NULL == s) +            break; +          sbuf->value = s + 1; +          return MUSTACH_OK; +        } +        break; +      default: +        break; +    } +  } +  sbuf->value = ""; +  return MUSTACH_OK; +} + +static struct mustach_itf itf = { +  .start = start, +  .put = NULL, +  .enter = enter, +  .next = next, +  .leave = leave, +  .partial =NULL, +  .get = get, +  .emit = NULL, +  .stop = NULL +}; + +static struct mustach_itf itfuw = { +  .start = start, +  .put = NULL, +  .enter = enter, +  .next = next, +  .leave = leave, +  .partial = NULL, +  .get = get, +  .emit = emituw, +  .stop = NULL +}; + +int fmustach_jansson (const char *template, json_t *root, FILE *file) +{ +  struct JanssonClosure e = { 0 }; +  e.root = root; +  return fmustach(template, &itf, &e, file); +} + +int fdmustach_jansson (const char *template, json_t *root, int fd) +{ +  struct JanssonClosure e = { 0 }; +  e.root = root; +  return fdmustach(template, &itf, &e, fd); +} + +int mustach_jansson (const char *template, json_t *root, char **result, size_t *size) +{ +  struct JanssonClosure e = { 0 }; +  e.root = root; +  e.writecb = NULL; +  return mustach(template, &itf, &e, result, size); +} + +int umustach_jansson (const char *template, json_t *root, mustach_jansson_write_cb writecb, void *closure) +{ +  struct JanssonClosure e = { 0 }; +  e.root = root; +  e.writecb = writecb; +  return fmustach(template, &itfuw, &e, closure); +} + diff --git a/src/templating/mustach-jansson.h b/src/templating/mustach-jansson.h new file mode 100644 index 00000000..27dcdd64 --- /dev/null +++ b/src/templating/mustach-jansson.h @@ -0,0 +1,82 @@ +/* + Copyright (C) 2020 Taler Systems SA + + Original license: + Author: José Bollo <jose.bollo@iot.bzh> + Author: José Bollo <jobol@nonadev.net> + + https://gitlab.com/jobol/mustach + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + +     http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#ifndef _mustach_jansson_h_included_ +#define _mustach_jansson_h_included_ + +#include <taler/taler_json_lib.h> +#include "mustach.h" + +/** + * fmustach_jansson - Renders the mustache 'template' in 'file' for 'root'. + * + * @template: the template string to instantiate + * @root:     the root json object to render + * \@file:     the file where to write the result + * + * Returns 0 in case of success, -1 with errno set in case of system error + * a other negative value in case of error. + */ +extern int fmustach_jansson(const char *template, json_t *root, FILE *file); + +/** + * fmustach_jansson - Renders the mustache 'template' in 'fd' for 'root'. + * + * @template: the template string to instantiate + * @root:     the root json object to render + * @fd:       the file descriptor number where to write the result + * + * Returns 0 in case of success, -1 with errno set in case of system error + * a other negative value in case of error. + */ +extern int fdmustach_jansson(const char *template, json_t *root, int fd); + + +/** + * fmustach_jansson - Renders the mustache 'template' in 'result' for 'root'. + * + * @template: the template string to instantiate + * @root:     the root json object to render + * @result:   the pointer receiving the result when 0 is returned + * @size:     the size of the returned result + * + * Returns 0 in case of success, -1 with errno set in case of system error + * a other negative value in case of error. + */ +extern int mustach_jansson(const char *template, json_t *root, char **result, size_t *size); + +/** + * umustach_jansson - Renders the mustache 'template' for 'root' to custom writer 'writecb' with 'closure'. + * + * @template: the template string to instantiate + * @root:     the root json object to render + * @writecb:  the function that write values + * @closure:  the closure for the write function + * + * Returns 0 in case of success, -1 with errno set in case of system error + * a other negative value in case of error. + */ +typedef int (*mustach_jansson_write_cb)(void *closure, const char *buffer, size_t size); +extern int umustach_jansson(const char *template, json_t *root, mustach_jansson_write_cb writecb, void *closure); + +#endif + diff --git a/src/templating/mustach-tool.c b/src/templating/mustach-tool.c new file mode 100644 index 00000000..364e34a8 --- /dev/null +++ b/src/templating/mustach-tool.c @@ -0,0 +1,155 @@ +/* + Author: José Bollo <jobol@nonadev.net> + Author: José Bollo <jose.bollo@iot.bzh> + + https://gitlab.com/jobol/mustach + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + +     http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#define _GNU_SOURCE + +#include <stdlib.h> +#include <stdio.h> +#include <sys/stat.h> +#include <unistd.h> +#include <fcntl.h> +#include <string.h> +#include <libgen.h> + +#include "mustach-json-c.h" + +static const size_t BLOCKSIZE = 8192; + +static const char *errors[] = { +	"??? unreferenced ???", +	"system", +	"unexpected end", +	"empty tag", +	"tag too long", +	"bad separators", +	"too depth", +	"closing", +	"bad unescape tag", +	"invalid interface", +	"item not found", +	"partial not found" +}; + +static void help(char *prog) +{ +	printf("usage: %s json-file mustach-templates...\n", basename(prog)); +	exit(0); +} + +static char *readfile(const char *filename) +{ +	int f; +	struct stat s; +	char *result; +	size_t size, pos; +	ssize_t rc; + +	result = NULL; +	if (filename[0] == '-' &&  filename[1] == 0) +		f = dup(0); +	else +		f = open(filename, O_RDONLY); +	if (f < 0) { +		fprintf(stderr, "Can't open file: %s\n", filename); +		exit(1); +	} + +	fstat(f, &s); +	switch (s.st_mode & S_IFMT) { +	case S_IFREG: +		size = s.st_size; +		break; +	case S_IFSOCK: +	case S_IFIFO: +		size = BLOCKSIZE; +		break; +	default: +		fprintf(stderr, "Bad file: %s\n", filename); +		exit(1); +	} + +	pos = 0; +	result = malloc(size + 1); +	do { +		if (result == NULL) { +			fprintf(stderr, "Out of memory\n"); +			exit(1); +		} +		rc = read(f, &result[pos], (size - pos) + 1); +		if (rc < 0) { +			fprintf(stderr, "Error while reading %s\n", filename); +			exit(1); +		} +		if (rc > 0) { +			pos += (size_t)rc; +			if (pos > size) { +				size = pos + BLOCKSIZE; +				result = realloc(result, size + 1); +			} +		} +	} while(rc > 0); + +	close(f); +	result[pos] = 0; +	return result; +} + +int main(int ac, char **av) +{ +	struct json_object *o; +	char *t; +	char *prog = *av; +	int s; + +	(void)ac; /* unused */ + +	if (*++av) { +		if (!strcmp(*av, "-h") || !strcmp(*av, "--help")) +			help(prog); +		if (av[0][0] == '-' && !av[0][1]) +			o = json_object_from_fd(0); +		else +			o = json_object_from_file(av[0]); +#if JSON_C_VERSION_NUM >= 0x000D00 +		if (json_util_get_last_err() != NULL) { +			fprintf(stderr, "Bad json: %s (file %s)\n", json_util_get_last_err(), av[0]); +			exit(1); +		} +		else +#endif +		if (o == NULL) { +			fprintf(stderr, "Aborted: null json (file %s)\n", av[0]); +			exit(1); +		} +		while(*++av) { +			t = readfile(*av); +			s = fmustach_json_c(t, o, stdout); +			if (s != 0) { +				s = -s; +				if (s < 1 || s >= (int)(sizeof errors / sizeof * errors)) +					s = 0; +				fprintf(stderr, "Template error %s (file %s)\n", errors[s], *av); +			} +			free(t); +		} +		json_object_put(o); +	} +	return 0; +} + diff --git a/src/templating/mustach.c b/src/templating/mustach.c new file mode 100644 index 00000000..caa80dcc --- /dev/null +++ b/src/templating/mustach.c @@ -0,0 +1,472 @@ +/* + Author: José Bollo <jobol@nonadev.net> + Author: José Bollo <jose.bollo@iot.bzh> + + https://gitlab.com/jobol/mustach + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + +     http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#define _GNU_SOURCE + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <ctype.h> +#ifdef _WIN32 +#include <malloc.h> +#endif +#ifdef __sun +# include <alloca.h> +#endif + +#include "mustach.h" + +#if defined(NO_EXTENSION_FOR_MUSTACH) +# undef  NO_COLON_EXTENSION_FOR_MUSTACH +# define NO_COLON_EXTENSION_FOR_MUSTACH +# undef  NO_ALLOW_EMPTY_TAG +# define NO_ALLOW_EMPTY_TAG +#endif + +struct iwrap { +	int (*emit)(void *closure, const char *buffer, size_t size, int escape, FILE *file); +	void *closure; /* closure for: enter, next, leave, emit, get */ +	int (*put)(void *closure, const char *name, int escape, FILE *file); +	void *closure_put; /* closure for put */ +	int (*enter)(void *closure, const char *name); +	int (*next)(void *closure); +	int (*leave)(void *closure); +	int (*get)(void *closure, const char *name, struct mustach_sbuf *sbuf); +	int (*partial)(void *closure, const char *name, struct mustach_sbuf *sbuf); +	void *closure_partial; /* closure for partial */ +}; + +#if !defined(NO_OPEN_MEMSTREAM) +static FILE *memfile_open(char **buffer, size_t *size) +{ +	return open_memstream(buffer, size); +} +static void memfile_abort(FILE *file, char **buffer, size_t *size) +{ +	fclose(file); +	free(*buffer); +	*buffer = NULL; +	*size = 0; +} +static int memfile_close(FILE *file, char **buffer, size_t *size) +{ +	int rc; + +	/* adds terminating null */ +	rc = fputc(0, file) ? MUSTACH_ERROR_SYSTEM : 0; +	fclose(file); +	if (rc == 0) +		/* removes terminating null of the length */ +		(*size)--; +	else { +		free(*buffer); +		*buffer = NULL; +		*size = 0; +	} +	return rc; +} +#else +static FILE *memfile_open(char **buffer, size_t *size) +{ +	/* +	 * We can't provide *buffer and *size as open_memstream does but +	 * at least clear them so the caller won't get bad data. +	 */ +	*buffer = NULL; +	*size = 0; + +	return tmpfile(); +} +static void memfile_abort(FILE *file, char **buffer, size_t *size) +{ +	fclose(file); +	*buffer = NULL; +	*size = 0; +} +static int memfile_close(FILE *file, char **buffer, size_t *size) +{ +	int rc; +	size_t s; +	char *b; + +	s = (size_t)ftell(file); +	b = malloc(s + 1); +	if (b == NULL) { +		rc = MUSTACH_ERROR_SYSTEM; +		errno = ENOMEM; +		s = 0; +	} else { +		rewind(file); +		if (1 == fread(b, s, 1, file)) { +			rc = 0; +			b[s] = 0; +		} else { +			rc = MUSTACH_ERROR_SYSTEM; +			free(b); +			b = NULL; +			s = 0; +		} +	} +	*buffer = b; +	*size = s; +	return rc; +} +#endif + +static inline void sbuf_reset(struct mustach_sbuf *sbuf) +{ +	sbuf->value = NULL; +	sbuf->freecb = NULL; +	sbuf->closure = NULL; +} + +static inline void sbuf_release(struct mustach_sbuf *sbuf) +{ +	if (sbuf->releasecb) +		sbuf->releasecb(sbuf->value, sbuf->closure); +} + +static int iwrap_emit(void *closure, const char *buffer, size_t size, int escape, FILE *file) +{ +	size_t i, j; + +	(void)closure; /* unused */ + +	if (!escape) +		return fwrite(buffer, size, 1, file) != 1 ? MUSTACH_ERROR_SYSTEM : MUSTACH_OK; + +	i = 0; +	while (i < size) { +		j = i; +		while (j < size && buffer[j] != '<' && buffer[j] != '>' && buffer[j] != '&') +			j++; +		if (j != i && fwrite(&buffer[i], j - i, 1, file) != 1) +			return MUSTACH_ERROR_SYSTEM; +		if (j < size) { +			switch(buffer[j++]) { +			case '<': +				if (fwrite("<", 4, 1, file) != 1) +					return MUSTACH_ERROR_SYSTEM; +				break; +			case '>': +				if (fwrite(">", 4, 1, file) != 1) +					return MUSTACH_ERROR_SYSTEM; +				break; +			case '&': +				if (fwrite("&", 5, 1, file) != 1) +					return MUSTACH_ERROR_SYSTEM; +				break; +			default: break; +			} +		} +		i = j; +	} +	return MUSTACH_OK; +} + +static int iwrap_put(void *closure, const char *name, int escape, FILE *file) +{ +	struct iwrap *iwrap = closure; +	int rc; +	struct mustach_sbuf sbuf; +	size_t length; + +	sbuf_reset(&sbuf); +	rc = iwrap->get(iwrap->closure, name, &sbuf); +	if (rc >= 0) { +		length = strlen(sbuf.value); +		if (length) +			rc = iwrap->emit(iwrap->closure, sbuf.value, length, escape, file); +		sbuf_release(&sbuf); +	} +	return rc; +} + +static int iwrap_partial(void *closure, const char *name, struct mustach_sbuf *sbuf) +{ +	struct iwrap *iwrap = closure; +	int rc; +	FILE *file; +	size_t size; +	char *result; + +	result = NULL; +	file = memfile_open(&result, &size); +	if (file == NULL) +		rc = MUSTACH_ERROR_SYSTEM; +	else { +		rc = iwrap->put(iwrap->closure_put, name, 0, file); +		if (rc < 0) +			memfile_abort(file, &result, &size); +		else { +			rc = memfile_close(file, &result, &size); +			if (rc == 0) { +				sbuf->value = result; +				sbuf->freecb = free; +			} +		} +	} +	return rc; +} + +static int process(const char *template, struct iwrap *iwrap, FILE *file, const char *opstr, const char *clstr) +{ +	struct mustach_sbuf sbuf; +	char name[MUSTACH_MAX_LENGTH + 1], c, *tmp; +	const char *beg, *term; +	struct { const char *name, *again; size_t length; int enabled, entered; } stack[MUSTACH_MAX_DEPTH]; +	size_t oplen, cllen, len, l; +	int depth, rc, enabled; + +	enabled = 1; +	oplen = strlen(opstr); +	cllen = strlen(clstr); +	depth = 0; +	for(;;) { +		beg = strstr(template, opstr); +		if (beg == NULL) { +			/* no more mustach */ +			if (enabled && template[0]) { +				rc = iwrap->emit(iwrap->closure, template, strlen(template), 0, file); +				if (rc < 0) +					return rc; +			} +			return depth ? MUSTACH_ERROR_UNEXPECTED_END : MUSTACH_OK; +		} +		if (enabled && beg != template) { +			rc = iwrap->emit(iwrap->closure, template, (size_t)(beg - template), 0, file); +			if (rc < 0) +				return rc; +		} +		beg += oplen; +		term = strstr(beg, clstr); +		if (term == NULL) +			return MUSTACH_ERROR_UNEXPECTED_END; +		template = term + cllen; +		len = (size_t)(term - beg); +		c = *beg; +		switch(c) { +		case '!': +		case '=': +			break; +		case '{': +			for (l = 0 ; clstr[l] == '}' ; l++); +			if (clstr[l]) { +				if (!len || beg[len-1] != '}') +					return MUSTACH_ERROR_BAD_UNESCAPE_TAG; +				len--; +			} else { +				if (term[l] != '}') +					return MUSTACH_ERROR_BAD_UNESCAPE_TAG; +				template++; +			} +			c = '&'; +			/*@fallthrough@*/ +		case '^': +		case '#': +		case '/': +		case '&': +		case '>': +#if !defined(NO_COLON_EXTENSION_FOR_MUSTACH) +		case ':': +#endif +			beg++; len--; +		default: +			while (len && isspace(beg[0])) { beg++; len--; } +			while (len && isspace(beg[len-1])) len--; +#if !defined(NO_ALLOW_EMPTY_TAG) +			if (len == 0) +				return MUSTACH_ERROR_EMPTY_TAG; +#endif +			if (len > MUSTACH_MAX_LENGTH) +				return MUSTACH_ERROR_TAG_TOO_LONG; +			memcpy(name, beg, len); +			name[len] = 0; +			break; +		} +		switch(c) { +		case '!': +			/* comment */ +			/* nothing to do */ +			break; +		case '=': +			/* defines separators */ +			if (len < 5 || beg[len - 1] != '=') +				return MUSTACH_ERROR_BAD_SEPARATORS; +			beg++; +			len -= 2; +			for (l = 0; l < len && !isspace(beg[l]) ; l++); +			if (l == len) +				return MUSTACH_ERROR_BAD_SEPARATORS; +			oplen = l; +			tmp = alloca(oplen + 1); +			memcpy(tmp, beg, oplen); +			tmp[oplen] = 0; +			opstr = tmp; +			while (l < len && isspace(beg[l])) l++; +			if (l == len) +				return MUSTACH_ERROR_BAD_SEPARATORS; +			cllen = len - l; +			tmp = alloca(cllen + 1); +			memcpy(tmp, beg + l, cllen); +			tmp[cllen] = 0; +			clstr = tmp; +			break; +		case '^': +		case '#': +			/* begin section */ +			if (depth == MUSTACH_MAX_DEPTH) +				return MUSTACH_ERROR_TOO_DEEP; +			rc = enabled; +			if (rc) { +				rc = iwrap->enter(iwrap->closure, name); +				if (rc < 0) +					return rc; +			} +			stack[depth].name = beg; +			stack[depth].again = template; +			stack[depth].length = len; +			stack[depth].enabled = enabled; +			stack[depth].entered = rc; +			if ((c == '#') == (rc == 0)) +				enabled = 0; +			depth++; +			break; +		case '/': +			/* end section */ +			if (depth-- == 0 || len != stack[depth].length || memcmp(stack[depth].name, name, len)) +				return MUSTACH_ERROR_CLOSING; +			rc = enabled && stack[depth].entered ? iwrap->next(iwrap->closure) : 0; +			if (rc < 0) +				return rc; +			if (rc) { +				template = stack[depth++].again; +			} else { +				enabled = stack[depth].enabled; +				if (enabled && stack[depth].entered) +					iwrap->leave(iwrap->closure); +			} +			break; +		case '>': +			/* partials */ +			if (enabled) { +				sbuf_reset(&sbuf); +				rc = iwrap->partial(iwrap->closure_partial, name, &sbuf); +				if (rc >= 0) { +					rc = process(sbuf.value, iwrap, file, opstr, clstr); +					sbuf_release(&sbuf); +				} +				if (rc < 0) +					return rc; +			} +			break; +		default: +			/* replacement */ +			if (enabled) { +				rc = iwrap->put(iwrap->closure_put, name, c != '&', file); +				if (rc < 0) +					return rc; +			} +			break; +		} +	} +} + +int fmustach(const char *template, struct mustach_itf *itf, void *closure, FILE *file) +{ +	int rc; +	struct iwrap iwrap; + +	/* check validity */ +	if (!itf->enter || !itf->next || !itf->leave || (!itf->put && !itf->get)) +		return MUSTACH_ERROR_INVALID_ITF; + +	/* init wrap structure */ +	iwrap.closure = closure; +	if (itf->put) { +		iwrap.put = itf->put; +		iwrap.closure_put = closure; +	} else { +		iwrap.put = iwrap_put; +		iwrap.closure_put = &iwrap; +	} +	if (itf->partial) { +		iwrap.partial = itf->partial; +		iwrap.closure_partial = closure; +	} else if (itf->get) { +		iwrap.partial = itf->get; +		iwrap.closure_partial = closure; +	} else { +		iwrap.partial = iwrap_partial; +		iwrap.closure_partial = &iwrap; +	} +	iwrap.emit = itf->emit ? itf->emit : iwrap_emit; +	iwrap.enter = itf->enter; +	iwrap.next = itf->next; +	iwrap.leave = itf->leave; +	iwrap.get = itf->get; + +	/* process */ +	rc = itf->start ? itf->start(closure) : 0; +	if (rc == 0) +		rc = process(template, &iwrap, file, "{{", "}}"); +	if (itf->stop) +		itf->stop(closure, rc); +	return rc; +} + +int fdmustach(const char *template, struct mustach_itf *itf, void *closure, int fd) +{ +	int rc; +	FILE *file; + +	file = fdopen(fd, "w"); +	if (file == NULL) { +		rc = MUSTACH_ERROR_SYSTEM; +		errno = ENOMEM; +	} else { +		rc = fmustach(template, itf, closure, file); +		fclose(file); +	} +	return rc; +} + +int mustach(const char *template, struct mustach_itf *itf, void *closure, char **result, size_t *size) +{ +	int rc; +	FILE *file; +	size_t s; + +	*result = NULL; +	if (size == NULL) +		size = &s; +	file = memfile_open(result, size); +	if (file == NULL) +		rc = MUSTACH_ERROR_SYSTEM; +	else { +		rc = fmustach(template, itf, closure, file); +		if (rc < 0) +			memfile_abort(file, result, size); +		else +			rc = memfile_close(file, result, size); +	} +	return rc; +} + diff --git a/src/templating/mustach.h b/src/templating/mustach.h new file mode 100644 index 00000000..ad952275 --- /dev/null +++ b/src/templating/mustach.h @@ -0,0 +1,241 @@ +/* + Author: José Bollo <jobol@nonadev.net> + Author: José Bollo <jose.bollo@iot.bzh> + + https://gitlab.com/jobol/mustach + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + +     http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#ifndef _mustach_h_included_ +#define _mustach_h_included_ + +struct mustach_sbuf; /* see below */ + +/** + * Current version of mustach and its derivates + */ +#define MUSTACH_VERSION 99 +#define MUSTACH_VERSION_MAJOR (MUSTACH_VERSION / 100) +#define MUSTACH_VERSION_MINOR (MUSTACH_VERSION % 100) + +/** + * Maximum nested imbrications supported + */ +#define MUSTACH_MAX_DEPTH  256 + +/** + * Maximum length of tags in mustaches {{...}} + */ +#define MUSTACH_MAX_LENGTH 1024 + +/** + * mustach_itf - interface for callbacks + * + * All of this function should return a negative value to stop + * the mustache processing. The returned negative value will be + * then returned to the caller of mustach as is. + * + * The functions enter and next should return 0 or 1. + * + * All other functions should normally return MUSTACH_OK (zero). + * If it returns a negative value, it means an error that stop + * the process and that is reported to the caller. + * + * @start: If defined (can be NULL), starts the mustach processing + *         of the closure, called at the very beginning before any + *         mustach processing occurs. + * + * @put: If defined (can be NULL), writes the value of 'name' + *       to 'file' with 'escape' or not. + *       As an extension (see NO_ALLOW_EMPTY_TAG), the 'name' can be + *       the empty string. In that later case an implementation can + *       return MUSTACH_ERROR_EMPTY_TAG to refuse empty names. + *       If NULL and 'get' NULL the error MUSTACH_ERROR_INVALID_ITF + *       is returned. + * + * @enter: Enters the section of 'name' if possible. + *         Musts return 1 if entered or 0 if not entered. + *         When 1 is returned, the function 'leave' will always be called. + *         Conversely 'leave' is never called when enter returns 0 or + *         a negative value. + *         When 1 is returned, the function must activate the first + *         item of the section. + * + * @next: Activates the next item of the section if it exists. + *        Musts return 1 when the next item is activated. + *        Musts return 0 when there is no item to activate. + * + * @leave: Leaves the last entered section + * + * @partial: If defined (can be NULL), returns in 'sbuf' the content of the + *           partial of 'name'. @see mustach_sbuf + *           If NULL but 'get' not NULL, 'get' is used instead of partial. + *           If NULL and 'get' NULL and 'put' not NULL, 'put' is called with + *           a true FILE. + * + * @emit: If defined (can be NULL), writes the 'buffer' of 'size' with 'escape'. + *        If NULL the standard function 'fwrite' is used with a true FILE. + *        If not NULL that function is called instead of 'fwrite' to output + *        text. + *        It implies that if you define either 'partial' or 'get' callback, + *        the meaning of 'FILE *file' is abstract for mustach's process and + *        then you can use 'FILE*file' pass any kind of pointer (including NULL) + *        to the function 'fmustach'. An example of a such behaviour is given by + *        the implementation of 'umustach_json_c'. + * + * @get: If defined (can be NULL), returns in 'sbuf' the value of 'name'. + *       As an extension (see NO_ALLOW_EMPTY_TAG), the 'name' can be + *       the empty string. In that later case an implementation can + *       return MUSTACH_ERROR_EMPTY_TAG to refuse empty names. + *       If NULL and 'put' NULL the error MUSTACH_ERROR_INVALID_ITF + *       is returned. + * + * @stop: If defined (can be NULL), stops the mustach processing + *        of the closure, called at the very end after all mustach + *        processing occurerd. The status returned by the processing + *        is passed to the stop. + * + * The array below summarize status of callbacks: + * + *    FULLY OPTIONAL:   start partial + *    MANDATORY:        enter next leave + *    COMBINATORIAL:    put emit get + * + * Not definig a MANDATORY callback returns error MUSTACH_ERROR_INVALID_ITF. + * + * For COMBINATORIAL callbacks the array below summarize possible combinations: + * + *  combination  : put     : emit    : get     : abstract FILE + *  -------------+---------+---------+---------+----------------------- + *  HISTORIC     : defined : NULL    : NULL    : NO: standard FILE + *  MINIMAL      : NULL    : NULL    : defined : NO: standard FILE + *  CUSTOM       : NULL    : defined : defined : YES: abstract FILE + *  DUCK         : defined : NULL    : defined : NO: standard FILE + *  DANGEROUS    : defined : defined : any     : YES or NO, depends on 'partial' + *  INVALID      : NULL    : any     : NULL    : - + * + * The DUCK case runs on one leg. 'get' is not used if 'partial' is defined + * but is used for 'partial' if 'partial' is NULL. Thus for clarity, do not use + * it that way but define 'partial' and let 'get' NULL. + * + * The DANGEROUS case is special: it allows abstract FILE if 'partial' is defined + * but forbids abstract FILE when 'partial' is NULL. + * + * The INVALID case returns error MUSTACH_ERROR_INVALID_ITF. + */ +struct mustach_itf { +	int (*start)(void *closure); +	int (*put)(void *closure, const char *name, int escape, FILE *file); +	int (*enter)(void *closure, const char *name); +	int (*next)(void *closure); +	int (*leave)(void *closure); +	int (*partial)(void *closure, const char *name, struct mustach_sbuf *sbuf); +	int (*emit)(void *closure, const char *buffer, size_t size, int escape, FILE *file); +	int (*get)(void *closure, const char *name, struct mustach_sbuf *sbuf); +	void (*stop)(void *closure, int status); +}; + +/** + * mustach_sbuf - Interface for handling zero terminated strings + * + * That structure is used for returning zero terminated strings -in 'value'- + * to mustach. The callee can provide a function for releasing the returned + * 'value'. Three methods for releasing the string are possible. + * + *  1. no release: set either 'freecb' or 'releasecb' with NULL (done by default) + *  2. release without closure: set 'freecb' to its expected value + *  3. release with closure: set 'releasecb' and 'closure' to their expected values + * + * @value: The value of the string. That value is not changed by mustach -const-. + * + * @freecb: The function to call for freeing the value without closure. + *          For convenience, signature of that callback is compatible with 'free'. + *          Can be NULL. + * + * @releasecb: The function to release with closure. + *             Can be NULL. + * + * @closure: The closure to use for 'releasecb'. + */ +struct mustach_sbuf { +	const char *value; +	union { +		void (*freecb)(void*); +		void (*releasecb)(const char *value, void *closure); +	}; +	void *closure; +}; + +/* + * Definition of error codes returned by mustach + */ +#define MUSTACH_OK                       0 +#define MUSTACH_ERROR_SYSTEM            -1 +#define MUSTACH_ERROR_UNEXPECTED_END    -2 +#define MUSTACH_ERROR_EMPTY_TAG         -3 +#define MUSTACH_ERROR_TAG_TOO_LONG      -4 +#define MUSTACH_ERROR_BAD_SEPARATORS    -5 +#define MUSTACH_ERROR_TOO_DEEP          -6 +#define MUSTACH_ERROR_CLOSING           -7 +#define MUSTACH_ERROR_BAD_UNESCAPE_TAG  -8 +#define MUSTACH_ERROR_INVALID_ITF       -9 +#define MUSTACH_ERROR_ITEM_NOT_FOUND    -10 +#define MUSTACH_ERROR_PARTIAL_NOT_FOUND -11 + +/* You can use definition below for user specific error */ +#define MUSTACH_ERROR_USER_BASE         -100 +#define MUSTACH_ERROR_USER(x)           (MUSTACH_ERROR_USER_BASE-(x)) + +/** + * fmustach - Renders the mustache 'template' in 'file' for 'itf' and 'closure'. + * + * @template: the template string to instantiate + * @itf:      the interface to the functions that mustach calls + * @closure:  the closure to pass to functions called + * \@file:     the file where to write the result + * + * Returns 0 in case of success, -1 with errno set in case of system error + * a other negative value in case of error. + */ +extern int fmustach(const char *template, struct mustach_itf *itf, void *closure, FILE *file); + +/** + * fmustach - Renders the mustache 'template' in 'fd' for 'itf' and 'closure'. + * + * @template: the template string to instantiate + * @itf:      the interface to the functions that mustach calls + * @closure:  the closure to pass to functions called + * @fd:       the file descriptor number where to write the result + * + * Returns 0 in case of success, -1 with errno set in case of system error + * a other negative value in case of error. + */ +extern int fdmustach(const char *template, struct mustach_itf *itf, void *closure, int fd); + +/** + * fmustach - Renders the mustache 'template' in 'result' for 'itf' and 'closure'. + * + * @template: the template string to instantiate + * @itf:      the interface to the functions that mustach calls + * @closure:  the closure to pass to functions called + * @result:   the pointer receiving the result when 0 is returned + * @size:     the size of the returned result + * + * Returns 0 in case of success, -1 with errno set in case of system error + * a other negative value in case of error. + */ +extern int mustach(const char *template, struct mustach_itf *itf, void *closure, char **result, size_t *size); + +#endif + diff --git a/src/templating/run-original-tests.sh b/src/templating/run-original-tests.sh new file mode 100755 index 00000000..9c7d34cd --- /dev/null +++ b/src/templating/run-original-tests.sh @@ -0,0 +1,10 @@ +#!/bin/bash +set -eu +# The build fails if libjson-c-dev is not installed. +# That's OK, we don't otherwise need it and don't +# even bother testing for it in configure.ac. +# However, in that case, skip the test suite. + +make -f Makefile.orig mustach || exit 77 +make -f Makefile.orig test +make -f Makefile.orig clean || true diff --git a/src/templating/templating_api.c b/src/templating/templating_api.c new file mode 100644 index 00000000..6d5c7c79 --- /dev/null +++ b/src/templating/templating_api.c @@ -0,0 +1,449 @@ +/* +  This file is part of TALER +  Copyright (C) 2020, 2022 Taler Systems SA + +  TALER is free software; you can redistribute it and/or modify it under the +  terms of the GNU General Public License as published by the Free Software +  Foundation; either version 3, or (at your option) any later version. + +  TALER 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 General Public License for more details. + +  You should have received a copy of the GNU General Public License along with +  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file templating_api.c + * @brief logic to load and complete HTML templates + * @author Christian Grothoff + */ +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include "taler_util.h" +#include "taler_mhd_lib.h" +#include "taler_templating_lib.h" +#include "mustach.h" +#include "mustach-jansson.h" +#include <gnunet/gnunet_mhd_compat.h> + + +/** + * Entry in a key-value array we use to cache templates. + */ +struct TVE +{ +  /** +   * A name, used as the key. NULL for the last entry. +   */ +  char *name; + +  /** +   * Language the template is in. +   */ +  char *lang; + +  /** +   * 0-terminated (!) file data to return for @e name and @e lang. +   */ +  char *value; + +}; + + +/** + * Array of templates loaded into RAM. + */ +static struct TVE *loaded; + +/** + * Length of the #loaded array. + */ +static unsigned int loaded_length; + + +/** + * Load Mustach template into memory.  Note that we intentionally cache + * failures, that is if we ever failed to load a template, we will never try + * again. + * + * @param connection the connection we act upon + * @param name name of the template file to load + *        (MUST be a 'static' string in memory!) + * @return NULL on error, otherwise the template + */ +static const char * +lookup_template (struct MHD_Connection *connection, +                 const char *name) +{ +  struct TVE *best = NULL; +  const char *lang; + +  lang = MHD_lookup_connection_value (connection, +                                      MHD_HEADER_KIND, +                                      MHD_HTTP_HEADER_ACCEPT_LANGUAGE); +  if (NULL == lang) +    lang = "en"; +  /* find best match by language */ +  for (unsigned int i = 0; i<loaded_length; i++) +  { +    if (0 != strcmp (loaded[i].name, +                     name)) +      continue; /* does not match by name */ +    if ( (NULL == best) || +         (TALER_language_matches (lang, +                                  loaded[i].lang) > +          TALER_language_matches (lang, +                                  best->lang) ) ) +      best = &loaded[i]; +  } +  if (NULL == best) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "No templates found in `%s'\n", +                name); +    return NULL; +  } +  return best->value; +} + + +/** + * Get the base URL for static resources. + * + * @param con the MHD connection + * @param instance_id the instance ID + * @returns the static files base URL, guaranteed + *          to have a trailing slash. + */ +static char * +make_static_url (struct MHD_Connection *con, +                 const char *instance_id) +{ +  const char *host; +  const char *forwarded_host; +  const char *uri_path; +  struct GNUNET_Buffer buf = { 0 }; + +  host = MHD_lookup_connection_value (con, +                                      MHD_HEADER_KIND, +                                      "Host"); +  forwarded_host = MHD_lookup_connection_value (con, +                                                MHD_HEADER_KIND, +                                                "X-Forwarded-Host"); + +  uri_path = MHD_lookup_connection_value (con, +                                          MHD_HEADER_KIND, +                                          "X-Forwarded-Prefix"); +  if (NULL != forwarded_host) +    host = forwarded_host; + +  if (NULL == host) +  { +    GNUNET_break (0); +    return NULL; +  } + +  GNUNET_assert (NULL != instance_id); + +  if (GNUNET_NO == TALER_mhd_is_https (con)) +    GNUNET_buffer_write_str (&buf, +                             "http://"); +  else +    GNUNET_buffer_write_str (&buf, +                             "https://"); +  GNUNET_buffer_write_str (&buf, +                           host); +  if (NULL != uri_path) +    GNUNET_buffer_write_path (&buf, +                              uri_path); +  if (0 != strcmp ("default", +                   instance_id)) +  { +    GNUNET_buffer_write_path (&buf, +                              "instances"); +    GNUNET_buffer_write_path (&buf, +                              instance_id); +  } +  GNUNET_buffer_write_path (&buf, +                            "static/"); +  return GNUNET_buffer_reap_str (&buf); +} + + +/** + * Load a @a template and substitute using @a root, returning + * the result to the @a connection with the given + * @a http_status code. + * + * @param connection the connection we act upon + * @param http_status code to use on success + * @param template basename of the template to load + * @param instance_id instance ID, used to compute static files URL + * @param taler_uri value for "Taler:" header to set, or NULL + * @param root JSON object to pass as the root context + * @return #GNUNET_OK on success (reply queued), #GNUNET_NO if an error was queued, + *         #GNUNET_SYSERR on failure (to queue an error) + */ +enum GNUNET_GenericReturnValue +TALER_TEMPLATING_reply (struct MHD_Connection *connection, +                        unsigned int http_status, +                        const char *template, +                        const char *instance_id, +                        const char *taler_uri, +                        json_t *root) +{ +  struct MHD_Response *reply; +  char *body; +  size_t body_size; + +  { +    const char *tmpl; +    int eno; + +    tmpl = lookup_template (connection, +                            template); +    if (NULL == tmpl) +    { +      GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                  "Failed to load template `%s'\n", +                  template); +      if (MHD_YES != +          TALER_MHD_reply_with_error (connection, +                                      MHD_HTTP_NOT_ACCEPTABLE, +                                      TALER_EC_GENERIC_FAILED_TO_LOAD_TEMPLATE, +                                      template)) +        return GNUNET_SYSERR; +      return GNUNET_NO; +    } +    /* Add default values to the context */ +    { +      char *static_url = make_static_url (connection, +                                          instance_id); +      json_object_set (root, +                       "static_url", +                       json_string (static_url)); +      GNUNET_free (static_url); +    } +    if (0 != +        (eno = mustach_jansson (tmpl, +                                root, +                                &body, +                                &body_size))) +    { +      GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                  "mustach failed on template `%s' with error %d\n", +                  template, +                  eno); +      if (MHD_YES != +          TALER_MHD_reply_with_error (connection, +                                      MHD_HTTP_INTERNAL_SERVER_ERROR, +                                      TALER_EC_GENERIC_FAILED_TO_EXPAND_TEMPLATE, +                                      template)) +        return GNUNET_SYSERR; +      return GNUNET_NO; +    } +  } + +  /* try to compress reply if client allows it */ +  { +    bool compressed = false; + +    if (MHD_YES == +        TALER_MHD_can_compress (connection)) +    { +      compressed = TALER_MHD_body_compress ((void **) &body, +                                            &body_size); +    } +    reply = MHD_create_response_from_buffer (body_size, +                                             body, +                                             MHD_RESPMEM_MUST_FREE); +    if (NULL == reply) +    { +      GNUNET_break (0); +      return GNUNET_SYSERR; +    } +    if (compressed) +    { +      if (MHD_NO == +          MHD_add_response_header (reply, +                                   MHD_HTTP_HEADER_CONTENT_ENCODING, +                                   "deflate")) +      { +        GNUNET_break (0); +        MHD_destroy_response (reply); +        return GNUNET_SYSERR; +      } +    } +  } + +  /* Add standard headers */ +  if (NULL != taler_uri) +    GNUNET_break (MHD_NO != +                  MHD_add_response_header (reply, +                                           "Taler", +                                           taler_uri)); +  GNUNET_break (MHD_NO != +                MHD_add_response_header (reply, +                                         MHD_HTTP_HEADER_CONTENT_TYPE, +                                         "text/html")); + +  /* Actually return reply */ +  { +    MHD_RESULT ret; + +    ret = MHD_queue_response (connection, +                              http_status, +                              reply); +    MHD_destroy_response (reply); +    if (MHD_NO == ret) +      return GNUNET_SYSERR; +  } +  return GNUNET_OK; +} + + +/** + * Function called with a template's filename. + * + * @param cls closure + * @param filename complete filename (absolute path) + * @return #GNUNET_OK to continue to iterate, + *  #GNUNET_NO to stop iteration with no error, + *  #GNUNET_SYSERR to abort iteration with error! + */ +static enum GNUNET_GenericReturnValue +load_template (void *cls, +               const char *filename) +{ +  char *lang; +  char *end; +  int fd; +  struct stat sb; +  char *map; +  const char *name; + +  if ('.' == filename[0]) +    return GNUNET_OK; + +  name = strrchr (filename, +                  '/'); +  if (NULL == name) +    name = filename; +  else +    name++; +  lang = strchr (name, +                 '.'); +  if (NULL == lang) +    return GNUNET_OK; /* name must include .$LANG */ +  lang++; +  end = strchr (lang, +                '.'); +  if ( (NULL == end) || +       (0 != strcmp (end, +                     ".must")) ) +    return GNUNET_OK; /* name must end with '.must' */ + +  /* finally open template */ +  fd = open (filename, +             O_RDONLY); +  if (-1 == fd) +  { +    GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, +                              "open", +                              filename); + +    return GNUNET_SYSERR; +  } +  if (0 != +      fstat (fd, +             &sb)) +  { +    GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, +                              "open", +                              filename); +    GNUNET_break (0 == close (fd)); +    return GNUNET_OK; +  } +  map = GNUNET_malloc_large (sb.st_size + 1); +  if (NULL == map) +  { +    GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, +                         "malloc"); +    GNUNET_break (0 == close (fd)); +    return GNUNET_SYSERR; +  } +  if (sb.st_size != +      read (fd, +            map, +            sb.st_size)) +  { +    GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, +                              "read", +                              filename); +    GNUNET_break (0 == close (fd)); +    return GNUNET_OK; +  } +  GNUNET_break (0 == close (fd)); +  GNUNET_array_grow (loaded, +                     loaded_length, +                     loaded_length + 1); +  loaded[loaded_length - 1].name = GNUNET_strndup (name, +                                                   (lang - 1) - name); +  loaded[loaded_length - 1].lang = GNUNET_strndup (lang, +                                                   end - lang); +  loaded[loaded_length - 1].value = map; +  return GNUNET_OK; +} + + +enum GNUNET_GenericReturnValue +TALER_TEMPLATING_init (const char *subsystem) +{ +  char *dn; +  int ret; + +  { +    char *path; + +    path = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_DATADIR); +    if (NULL == path) +    { +      GNUNET_break (0); +      return GNUNET_SYSERR; +    } +    GNUNET_asprintf (&dn, +                     "%s/%s/templates/", +                     subsystem, +                     path); +    GNUNET_free (path); +  } +  ret = GNUNET_DISK_directory_scan (dn, +                                    &load_template, +                                    NULL); +  GNUNET_free (dn); +  if (-1 == ret) +  { +    GNUNET_break (0); +    return GNUNET_SYSERR; +  } +  return GNUNET_OK; +} + + +void +TALER_TEMPLATING_done (void) +{ +  for (unsigned int i = 0; i<loaded_length; i++) +  { +    GNUNET_free (loaded[i].name); +    GNUNET_free (loaded[i].lang); +    GNUNET_free (loaded[i].value); +  } +  GNUNET_array_grow (loaded, +                     loaded_length, +                     0); +} + + +/* end of templating_api.c */ diff --git a/src/templating/test1/.gitignore b/src/templating/test1/.gitignore new file mode 100644 index 00000000..4d897daa --- /dev/null +++ b/src/templating/test1/.gitignore @@ -0,0 +1,2 @@ +resu.last +vg.last diff --git a/src/templating/test1/json b/src/templating/test1/json new file mode 100644 index 00000000..5b2e3d83 --- /dev/null +++ b/src/templating/test1/json @@ -0,0 +1,23 @@ +{ +  "name": "Chris", +  "value": 10000, +  "taxed_value": 6000, +  "in_ca": true, +  "person": false, +  "repo": [ +    { "name": "resque", "who": [ { "committer": "joe" }, { "reviewer": "avrel" }, { "committer": "william" } ] }, +    { "name": "hub", "who": [ { "committer": "jack" }, { "reviewer": "avrel" }, { "committer": "greg" } ]  }, +    { "name": "rip", "who": [ { "reviewer": "joe" }, { "reviewer": "jack" }, { "committer": "greg" } ]  } +  ], +  "person?": { "name": "Jon" }, +  "special": "----{{extra}}----", +  "extra": 3.14159, +  "#sharp": "#", +  "!bang": "!", +  "/slash": "/", +  "^circ": "^", +  "=equal": "=", +  ":colon": ":", +  ">greater": ">", +  "~tilde": "~" +} diff --git a/src/templating/test1/must b/src/templating/test1/must new file mode 100644 index 00000000..723f966c --- /dev/null +++ b/src/templating/test1/must @@ -0,0 +1,43 @@ +Hello {{name}} +You have just won {{value}} dollars! +{{#in_ca}} +Well, {{taxed_value}} dollars, after taxes. +{{/in_ca}} +Shown. +{{#person}} +  Never shown! +{{/person}} +{{^person}} +  No person +{{/person}} + +{{#repo}} +  <b>{{name}}</b> reviewers:{{#who}} {{reviewer}}{{/who}} committers:{{#who}} {{committer}}{{/who}} +{{/repo}} + +{{#person?}} +  Hi {{name}}! +{{/person?}} + +{{=%(% %)%=}} +===================================== +%(%! gros commentaire %)% +%(%#repo%)% +  <b>%(%name%)%</b> reviewers:%(%#who%)% %(%reviewer%)%%(%/who%)% committers:%(%#who%)% %(%committer%)%%(%/who%)% +%(%/repo%)% +===================================== +%(%={{ }}=%)% +ggggggggg +{{> special}} +jjjjjjjjj +end + +{{:#sharp}} +{{:!bang}} +{{:~tilde}} +{{:/~0tilde}} +{{:/~1slash}} see json pointers IETF RFC 6901 +{{:^circ}} +{{:\=equal}} +{{::colon}} +{{:>greater}} diff --git a/src/templating/test1/resu.ref b/src/templating/test1/resu.ref new file mode 100644 index 00000000..545e5857 --- /dev/null +++ b/src/templating/test1/resu.ref @@ -0,0 +1,49 @@ +Hello Chris +You have just won 10000 dollars! + +Well, 6000 dollars, after taxes. + +Shown. + + +  No person + + + +  <b>resque</b> reviewers:  avrel  committers: joe  william + +  <b>hub</b> reviewers:  avrel  committers: jack  greg + +  <b>rip</b> reviewers: joe jack  committers:   greg + + + +  Hi Jon! + + + +===================================== + + +  <b>resque</b> reviewers:  avrel  committers: joe  william + +  <b>hub</b> reviewers:  avrel  committers: jack  greg + +  <b>rip</b> reviewers: joe jack  committers:   greg + +===================================== + +ggggggggg +----3.14159---- +jjjjjjjjj +end + +# +! +~ +~ +/ see json pointers IETF RFC 6901 +^ += +: +> diff --git a/src/templating/test2/.gitignore b/src/templating/test2/.gitignore new file mode 100644 index 00000000..4d897daa --- /dev/null +++ b/src/templating/test2/.gitignore @@ -0,0 +1,2 @@ +resu.last +vg.last diff --git a/src/templating/test2/json b/src/templating/test2/json new file mode 100644 index 00000000..8c668b3b --- /dev/null +++ b/src/templating/test2/json @@ -0,0 +1,9 @@ +{ +  "header": "Colors", +  "items": [ +      {"name": "red", "first": true, "url": "#Red"}, +      {"name": "green", "link": true, "url": "#Green"}, +      {"name": "blue", "link": true, "url": "#Blue"} +  ], +  "empty": false +} diff --git a/src/templating/test2/must b/src/templating/test2/must new file mode 100644 index 00000000..aa6da707 --- /dev/null +++ b/src/templating/test2/must @@ -0,0 +1,17 @@ +<h1>{{header}}</h1> +{{#bug}} +{{/bug}} + +{{#items}} +  {{#first}} +    <li><strong>{{name}}</strong></li> +  {{/first}} +  {{#link}} +    <li><a href="{{url}}">{{name}}</a></li> +  {{/link}} +{{/items}} + +{{#empty}} +  <p>The list is empty.</p> +{{/empty}} + diff --git a/src/templating/test2/resu.ref b/src/templating/test2/resu.ref new file mode 100644 index 00000000..67d1f547 --- /dev/null +++ b/src/templating/test2/resu.ref @@ -0,0 +1,22 @@ +<h1>Colors</h1> + + + +   +    <li><strong>red</strong></li> +   +   + +   +   +    <li><a href="#Green">green</a></li> +   + +   +   +    <li><a href="#Blue">blue</a></li> +   + + + + diff --git a/src/templating/test3/.gitignore b/src/templating/test3/.gitignore new file mode 100644 index 00000000..4d897daa --- /dev/null +++ b/src/templating/test3/.gitignore @@ -0,0 +1,2 @@ +resu.last +vg.last diff --git a/src/templating/test3/json b/src/templating/test3/json new file mode 100644 index 00000000..79278817 --- /dev/null +++ b/src/templating/test3/json @@ -0,0 +1,7 @@ +{ +  "name": "Chris", +  "company": "<b>GitHub & Co</b>", +  "names": ["Chris", "Kross"], +  "skills": ["JavaScript", "PHP", "Java"], +  "age": 18 +} diff --git a/src/templating/test3/must b/src/templating/test3/must new file mode 100644 index 00000000..5c490469 --- /dev/null +++ b/src/templating/test3/must @@ -0,0 +1,15 @@ +* {{name}} +* {{age}} +* {{company}} +* {{&company}} +* {{{company}}} +{{=<% %>=}} +* <%company%> +* <%&company%> +* <%{company}%> + +<%={{ }}=%> +* <ul>{{#names}}<li>{{.}}</li>{{/names}}</ul> +* skills: <ul>{{#skills}}<li>{{.}}</li>{{/skills}}</ul> +{{#age}}* age: {{.}}{{/age}} + diff --git a/src/templating/test3/resu.ref b/src/templating/test3/resu.ref new file mode 100644 index 00000000..e89ce902 --- /dev/null +++ b/src/templating/test3/resu.ref @@ -0,0 +1,15 @@ +* Chris +* 18 +* <b>GitHub & Co</b> +* <b>GitHub & Co</b> +* <b>GitHub & Co</b> + +* <b>GitHub & Co</b> +* <b>GitHub & Co</b> +* <b>GitHub & Co</b> + + +* <ul><li>Chris</li><li>Kross</li></ul> +* skills: <ul><li>JavaScript</li><li>PHP</li><li>Java</li></ul> +* age: 18 + diff --git a/src/templating/test4/.gitignore b/src/templating/test4/.gitignore new file mode 100644 index 00000000..4d897daa --- /dev/null +++ b/src/templating/test4/.gitignore @@ -0,0 +1,2 @@ +resu.last +vg.last diff --git a/src/templating/test4/json b/src/templating/test4/json new file mode 100644 index 00000000..a1083607 --- /dev/null +++ b/src/templating/test4/json @@ -0,0 +1,13 @@ +{ +  "person": { "name": "Jon", "age": 25 }, +  "person.name": "Fred", +  "person.name=Fred": "The other Fred.", +  "persons": [ +	{ "name": "Jon", "age": 25, "lang": "en" }, +	{ "name": "Henry", "age": 27, "lang": "en" }, +	{ "name": "Amed", "age": 24, "lang": "fr" } ], +  "fellows": { +	"Jon": { "age": 25, "lang": "en" }, +	"Henry": { "age": 27, "lang": "en" }, +	"Amed": { "age": 24, "lang": "fr" } } +} diff --git a/src/templating/test4/must b/src/templating/test4/must new file mode 100644 index 00000000..003b9366 --- /dev/null +++ b/src/templating/test4/must @@ -0,0 +1,58 @@ +This are extensions!! + +{{person.name}} +{{person.age}} + +{{person\.name}} +{{person\.name\=Fred}} + +{{#person.name=Jon}} +Hello Jon +{{/person.name=Jon}} + +{{^person.name=Jon}} +No Jon? Hey Jon... +{{/person.name=Jon}} + +{{^person.name=Harry}} +No Harry? Hey Calahan... +{{/person.name=Harry}} + +{{#person\.name=Fred}} +Hello Fred +{{/person\.name=Fred}} + +{{^person\.name=Fred}} +No Fred? Hey Fred... +{{/person\.name=Fred}} + +{{#person\.name\=Fred=The other Fred.}} +Hello Fred#2 +{{/person\.name\=Fred=The other Fred.}} + +{{^person\.name\=Fred=The other Fred.}} +No Fred#2? Hey Fred#2... +{{/person\.name\=Fred=The other Fred.}} + +{{#persons}} +{{#lang=!fr}}Hello {{name}}, {{age}} years{{/lang=!fr}} +{{#lang=fr}}Salut {{name}}, {{age}} ans{{/lang=fr}} +{{/persons}} + +{{#persons}} +{{name}}: {{age=24}}/{{age}}/{{age=!27}} +{{/persons}} + +{{#fellows.*}} +{{*}}: {{age=24}}/{{age}}/{{age=!27}} +{{/fellows.*}} + +{{#*}} + (1) {{*}}: {{.}} +   {{#*}} +     (2) {{*}}: {{.}} +     {{#*}} +       (3) {{*}}: {{.}} +     {{/*}} +   {{/*}} +{{/*}} diff --git a/src/templating/test4/resu.ref b/src/templating/test4/resu.ref new file mode 100644 index 00000000..2d48918a --- /dev/null +++ b/src/templating/test4/resu.ref @@ -0,0 +1,100 @@ +This are extensions!! + +Jon +25 + +Fred +The other Fred. + + +Hello Jon + + + + + +No Harry? Hey Calahan... + + + +Hello Fred + + + + + +Hello Fred#2 + + + + + +Hello Jon, 25 years + + +Hello Henry, 27 years + + + +Salut Amed, 24 ans + + + +Jon: /25/25 + +Henry: /27/ + +Amed: 24/24/24 + + + +Jon: /25/25 + +Henry: /27/ + +Amed: 24/24/24 + + + + (1) person: { "name": "Jon", "age": 25 } +    +     (2) name: Jon +      +    +     (2) age: 25 +      +    + + (1) person.name: Fred +    + + (1) person.name=Fred: The other Fred. +    + + (1) persons: [ { "name": "Jon", "age": 25, "lang": "en" }, { "name": "Henry", "age": 27, "lang": "en" }, { "name": "Amed", "age": 24, "lang": "fr" } ] +    + + (1) fellows: { "Jon": { "age": 25, "lang": "en" }, "Henry": { "age": 27, "lang": "en" }, "Amed": { "age": 24, "lang": "fr" } } +    +     (2) Jon: { "age": 25, "lang": "en" } +      +       (3) age: 25 +      +       (3) lang: en +      +    +     (2) Henry: { "age": 27, "lang": "en" } +      +       (3) age: 27 +      +       (3) lang: en +      +    +     (2) Amed: { "age": 24, "lang": "fr" } +      +       (3) age: 24 +      +       (3) lang: fr +      +    + diff --git a/src/templating/test5/.gitignore b/src/templating/test5/.gitignore new file mode 100644 index 00000000..4d897daa --- /dev/null +++ b/src/templating/test5/.gitignore @@ -0,0 +1,2 @@ +resu.last +vg.last diff --git a/src/templating/test5/json b/src/templating/test5/json new file mode 100644 index 00000000..5b2e3d83 --- /dev/null +++ b/src/templating/test5/json @@ -0,0 +1,23 @@ +{ +  "name": "Chris", +  "value": 10000, +  "taxed_value": 6000, +  "in_ca": true, +  "person": false, +  "repo": [ +    { "name": "resque", "who": [ { "committer": "joe" }, { "reviewer": "avrel" }, { "committer": "william" } ] }, +    { "name": "hub", "who": [ { "committer": "jack" }, { "reviewer": "avrel" }, { "committer": "greg" } ]  }, +    { "name": "rip", "who": [ { "reviewer": "joe" }, { "reviewer": "jack" }, { "committer": "greg" } ]  } +  ], +  "person?": { "name": "Jon" }, +  "special": "----{{extra}}----", +  "extra": 3.14159, +  "#sharp": "#", +  "!bang": "!", +  "/slash": "/", +  "^circ": "^", +  "=equal": "=", +  ":colon": ":", +  ">greater": ">", +  "~tilde": "~" +} diff --git a/src/templating/test5/must b/src/templating/test5/must new file mode 100644 index 00000000..44305df2 --- /dev/null +++ b/src/templating/test5/must @@ -0,0 +1,23 @@ +===================================== +from json +{{> special}} +===================================== +not found +{{> notfound}} +===================================== +without extension first +{{> must2 }} +===================================== +last with extension +{{> must3 }} +===================================== +Ensure must3 didn't change specials + +{{#person?}} +  Hi {{name}}! +{{/person?}} + +%(%#person?%)% +  Hi %(%name%)%! +%(%/person?%)% + diff --git a/src/templating/test5/must2 b/src/templating/test5/must2 new file mode 100644 index 00000000..d4a1d378 --- /dev/null +++ b/src/templating/test5/must2 @@ -0,0 +1,14 @@ +must2 == BEGIN +Hello {{name}} +You have just won {{value}} dollars! +{{#in_ca}} +Well, {{taxed_value}} dollars, after taxes. +{{/in_ca}} +Shown. +{{#person}} +  Never shown! +{{/person}} +{{^person}} +  No person +{{/person}} +must2 == END diff --git a/src/templating/test5/must2.mustache b/src/templating/test5/must2.mustache new file mode 100644 index 00000000..33f1ead3 --- /dev/null +++ b/src/templating/test5/must2.mustache @@ -0,0 +1 @@ +must2.mustache ==SHOULD NOT BE SEEN== diff --git a/src/templating/test5/must3.mustache b/src/templating/test5/must3.mustache new file mode 100644 index 00000000..821aaac3 --- /dev/null +++ b/src/templating/test5/must3.mustache @@ -0,0 +1,17 @@ +must3.mustache == BEGIN +{{#repo}} +  <b>{{name}}</b> reviewers:{{#who}} {{reviewer}}{{/who}} committers:{{#who}} {{committer}}{{/who}} +{{/repo}} + +{{#person?}} +  Hi {{name}}! +{{/person?}} + +{{=%(% %)%=}} +===================================== +%(%! big comment %)% +%(%#repo%)% +  <b>%(%name%)%</b> reviewers:%(%#who%)% %(%reviewer%)%%(%/who%)% committers:%(%#who%)% %(%committer%)%%(%/who%)% +%(%/repo%)% +===================================== +must3.mustache == END diff --git a/src/templating/test5/resu.ref b/src/templating/test5/resu.ref new file mode 100644 index 00000000..afc39659 --- /dev/null +++ b/src/templating/test5/resu.ref @@ -0,0 +1,60 @@ +===================================== +from json +----3.14159---- +===================================== +not found + +===================================== +without extension first +must2 == BEGIN +Hello Chris +You have just won 10000 dollars! + +Well, 6000 dollars, after taxes. + +Shown. + + +  No person + +must2 == END + +===================================== +last with extension +must3.mustache == BEGIN + +  <b>resque</b> reviewers:  avrel  committers: joe  william + +  <b>hub</b> reviewers:  avrel  committers: jack  greg + +  <b>rip</b> reviewers: joe jack  committers:   greg + + + +  Hi Jon! + + + +===================================== + + +  <b>resque</b> reviewers:  avrel  committers: joe  william + +  <b>hub</b> reviewers:  avrel  committers: jack  greg + +  <b>rip</b> reviewers: joe jack  committers:   greg + +===================================== +must3.mustache == END + +===================================== +Ensure must3 didn't change specials + + +  Hi Jon! + + +%(%#person?%)% +  Hi %(%name%)%! +%(%/person?%)% + diff --git a/src/templating/test5/special b/src/templating/test5/special new file mode 100644 index 00000000..02d9975c --- /dev/null +++ b/src/templating/test5/special @@ -0,0 +1 @@ +special ==SHOULD NOT BE SEEN== diff --git a/src/templating/test5/special.mustache b/src/templating/test5/special.mustache new file mode 100644 index 00000000..70a771fd --- /dev/null +++ b/src/templating/test5/special.mustache @@ -0,0 +1 @@ +special.mustache ==SHOULD NOT BE SEEN== diff --git a/src/templating/test6/.gitignore b/src/templating/test6/.gitignore new file mode 100644 index 00000000..62f4d919 --- /dev/null +++ b/src/templating/test6/.gitignore @@ -0,0 +1,4 @@ +resu.last +vg.last +test-custom-write +!test-custom-write.c diff --git a/src/templating/test6/json b/src/templating/test6/json new file mode 100644 index 00000000..5b2e3d83 --- /dev/null +++ b/src/templating/test6/json @@ -0,0 +1,23 @@ +{ +  "name": "Chris", +  "value": 10000, +  "taxed_value": 6000, +  "in_ca": true, +  "person": false, +  "repo": [ +    { "name": "resque", "who": [ { "committer": "joe" }, { "reviewer": "avrel" }, { "committer": "william" } ] }, +    { "name": "hub", "who": [ { "committer": "jack" }, { "reviewer": "avrel" }, { "committer": "greg" } ]  }, +    { "name": "rip", "who": [ { "reviewer": "joe" }, { "reviewer": "jack" }, { "committer": "greg" } ]  } +  ], +  "person?": { "name": "Jon" }, +  "special": "----{{extra}}----", +  "extra": 3.14159, +  "#sharp": "#", +  "!bang": "!", +  "/slash": "/", +  "^circ": "^", +  "=equal": "=", +  ":colon": ":", +  ">greater": ">", +  "~tilde": "~" +} diff --git a/src/templating/test6/must b/src/templating/test6/must new file mode 100644 index 00000000..723f966c --- /dev/null +++ b/src/templating/test6/must @@ -0,0 +1,43 @@ +Hello {{name}} +You have just won {{value}} dollars! +{{#in_ca}} +Well, {{taxed_value}} dollars, after taxes. +{{/in_ca}} +Shown. +{{#person}} +  Never shown! +{{/person}} +{{^person}} +  No person +{{/person}} + +{{#repo}} +  <b>{{name}}</b> reviewers:{{#who}} {{reviewer}}{{/who}} committers:{{#who}} {{committer}}{{/who}} +{{/repo}} + +{{#person?}} +  Hi {{name}}! +{{/person?}} + +{{=%(% %)%=}} +===================================== +%(%! gros commentaire %)% +%(%#repo%)% +  <b>%(%name%)%</b> reviewers:%(%#who%)% %(%reviewer%)%%(%/who%)% committers:%(%#who%)% %(%committer%)%%(%/who%)% +%(%/repo%)% +===================================== +%(%={{ }}=%)% +ggggggggg +{{> special}} +jjjjjjjjj +end + +{{:#sharp}} +{{:!bang}} +{{:~tilde}} +{{:/~0tilde}} +{{:/~1slash}} see json pointers IETF RFC 6901 +{{:^circ}} +{{:\=equal}} +{{::colon}} +{{:>greater}} diff --git a/src/templating/test6/resu.ref b/src/templating/test6/resu.ref new file mode 100644 index 00000000..345d3aef --- /dev/null +++ b/src/templating/test6/resu.ref @@ -0,0 +1,147 @@ +HELLO CHRIS +YOU HAVE JUST WON 10000 DOLLARS! + +WELL, 6000 DOLLARS, AFTER TAXES. + +SHOWN. + + +  NO PERSON + + + +  <B>RESQUE</B> REVIEWERS:  AVREL  COMMITTERS: JOE  WILLIAM + +  <B>HUB</B> REVIEWERS:  AVREL  COMMITTERS: JACK  GREG + +  <B>RIP</B> REVIEWERS: JOE JACK  COMMITTERS:   GREG + + + +  HI JON! + + + +===================================== + + +  <B>RESQUE</B> REVIEWERS:  AVREL  COMMITTERS: JOE  WILLIAM + +  <B>HUB</B> REVIEWERS:  AVREL  COMMITTERS: JACK  GREG + +  <B>RIP</B> REVIEWERS: JOE JACK  COMMITTERS:   GREG + +===================================== + +GGGGGGGGG +----3.14159---- +JJJJJJJJJ +END + +# +! +~ +~ +/ SEE JSON POINTERS IETF RFC 6901 +^ += +: +> +hello chris +you have just won 10000 dollars! + +well, 6000 dollars, after taxes. + +shown. + + +  no person + + + +  <b>resque</b> reviewers:  avrel  committers: joe  william + +  <b>hub</b> reviewers:  avrel  committers: jack  greg + +  <b>rip</b> reviewers: joe jack  committers:   greg + + + +  hi jon! + + + +===================================== + + +  <b>resque</b> reviewers:  avrel  committers: joe  william + +  <b>hub</b> reviewers:  avrel  committers: jack  greg + +  <b>rip</b> reviewers: joe jack  committers:   greg + +===================================== + +ggggggggg +----3.14159---- +jjjjjjjjj +end + +# +! +~ +~ +/ see json pointers ietf rfc 6901 +^ += +: +> +Hello Chris +You have just won 10000 dollars! + +Well, 6000 dollars, after taxes. + +Shown. + + +  No person + + + +  <b>resque</b> reviewers:  avrel  committers: joe  william + +  <b>hub</b> reviewers:  avrel  committers: jack  greg + +  <b>rip</b> reviewers: joe jack  committers:   greg + + + +  Hi Jon! + + + +===================================== + + +  <b>resque</b> reviewers:  avrel  committers: joe  william + +  <b>hub</b> reviewers:  avrel  committers: jack  greg + +  <b>rip</b> reviewers: joe jack  committers:   greg + +===================================== + +ggggggggg +----3.14159---- +jjjjjjjjj +end + +# +! +~ +~ +/ see json pointers IETF RFC 6901 +^ += +: +> diff --git a/src/templating/test6/test-custom-write.c b/src/templating/test6/test-custom-write.c new file mode 100644 index 00000000..cc50a47c --- /dev/null +++ b/src/templating/test6/test-custom-write.c @@ -0,0 +1,145 @@ +/* + Author: José Bollo <jobol@nonadev.net> + Author: José Bollo <jose.bollo@iot.bzh> + + https://gitlab.com/jobol/mustach + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + +     http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#define _GNU_SOURCE + +#include <stdlib.h> +#include <stdio.h> +#include <ctype.h> +#include <sys/stat.h> +#include <unistd.h> +#include <fcntl.h> +#include <string.h> +#include <libgen.h> + +#include "../mustach-json-c.h" + +static const size_t BLOCKSIZE = 8192; + +static char *readfile(const char *filename) +{ +	int f; +	struct stat s; +	char *result; +	size_t size, pos; +	ssize_t rc; + +	result = NULL; +	if (filename[0] == '-' &&  filename[1] == 0) +		f = dup(0); +	else +		f = open(filename, O_RDONLY); +	if (f < 0) { +		fprintf(stderr, "Can't open file: %s\n", filename); +		exit(1); +	} + +	fstat(f, &s); +	switch (s.st_mode & S_IFMT) { +	case S_IFREG: +		size = s.st_size; +		break; +	case S_IFSOCK: +	case S_IFIFO: +		size = BLOCKSIZE; +		break; +	default: +		fprintf(stderr, "Bad file: %s\n", filename); +		exit(1); +	} + +	pos = 0; +	result = malloc(size + 1); +	do { +		if (result == NULL) { +			fprintf(stderr, "Out of memory\n"); +			exit(1); +		} +		rc = read(f, &result[pos], (size - pos) + 1); +		if (rc < 0) { +			fprintf(stderr, "Error while reading %s\n", filename); +			exit(1); +		} +		if (rc > 0) { +			pos += (size_t)rc; +			if (pos > size) { +				size = pos + BLOCKSIZE; +				result = realloc(result, size + 1); +			} +		} +	} while(rc > 0); + +	close(f); +	result[pos] = 0; +	return result; +} + +enum { None, Upper, Lower } mode = None; + +int uwrite(void *closure, const char *buffer, size_t size) +{ +	switch(mode) { +	case None: +		fwrite(buffer, size, 1, stdout); +		break; +	case Upper: +		while(size--) +			fputc(toupper(*buffer++), stdout); +		break; +	case Lower: +		while(size--) +			fputc(tolower(*buffer++), stdout); +		break; +	} +	return 0; +} + +int main(int ac, char **av) +{ +	struct json_object *o; +	char *t; +	char *prog = *av; +	int s; + +	if (*++av) { +		o = json_object_from_file(av[0]); +		if (o == NULL) { +			fprintf(stderr, "Aborted: null json (file %s)\n", av[0]); +			exit(1); +		} +		while(*++av) { +			if (!strcmp(*av, "-U")) +				mode = Upper; +			else if  (!strcmp(*av, "-l")) +				mode = Lower; +			else if  (!strcmp(*av, "-x")) +				mode = None; +			else { +				t = readfile(*av); +				s = umustach_json_c(t, o, uwrite, NULL); +				if (s != 0) +					fprintf(stderr, "Template error %d\n", s); +				free(t); +			} +		} +		json_object_put(o); +	} +	return 0; +} + diff --git a/src/templating/test_mustach_jansson.c b/src/templating/test_mustach_jansson.c new file mode 100644 index 00000000..11af86fa --- /dev/null +++ b/src/templating/test_mustach_jansson.c @@ -0,0 +1,163 @@ +/* +  This file is part of TALER +  Copyright (C) 2014-2020 Taler Systems SA + +  TALER is free software; you can redistribute it and/or modify +  it under the terms of the GNU General Public License as +  published by the Free Software Foundation; either version 3, or +  (at your option) any later version. + +  TALER 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 General Public License for more details. + +  You should have received a copy of the GNU General Public +  License along with TALER; see the file COPYING.  If not, see +  <http://www.gnu.org/licenses/> +*/ + +/** + * @file test_mustach_jansson.c + * @brief testcase to test the mustach/jansson integration + * @author Florian Dold + */ +#include "platform.h" +#include "mustach-jansson.h" + + +static void +assert_template (const char *template, +                 json_t *root, +                 const char *expected) +{ +  char *r; +  size_t sz; + +  GNUNET_assert (0 == mustach_jansson (template, +                                       root, +                                       &r, +                                       &sz)); +  GNUNET_assert (0 == strcmp (r, +                              expected)); +  GNUNET_free (r); +} + + +int +main (int argc, +      char *const *argv) +{ +  json_t *root = json_object (); +  json_t *arr = json_array (); +  json_t *obj = json_object (); +  json_t *contract; +  /* test 1 */ +  const char *t1 = "hello world"; +  const char *x1 = "hello world"; +  /* test 2 */ +  const char *t2 = "hello {{ v1 }}"; +  const char *x2 = "hello world"; +  /* test 3 */ +  const char *t3 = "hello {{ v3.x }}"; +  const char *x3 = "hello baz"; +  /* test 4 */ +  const char *t4 = "hello {{# v2 }}{{ . }}{{/ v2 }}"; +  const char *x4 = "hello foobar"; +  /* test 5 */ +  const char *t5 = "hello {{# v3 }}{{ y }}/{{ x }}{{ z }}{{/ v3 }}"; +  const char *x5 = "hello quux/baz"; +  /* test 6 */ +  const char *t6 = "hello {{ v2!stringify }}"; +  const char *x6 = "hello [\n  \"foo\",\n  \"bar\"\n]"; +  /* test 7 */ +  const char *t7 = "amount: {{ amt!amount_decimal }} {{ amt!amount_currency }}"; +  const char *x7 = "amount: 123.00 EUR"; +  /* test 8 */ +  const char *t8 = "{{^ v4 }}fallback{{/ v4 }}"; +  const char *x8 = "fallback"; + +  /* contract test 8 (contract) */ +  const char *tc = "summary: {{ summary!i18n }}"; +  const char *xc_en = "summary: ENGLISH"; +  const char *xc_de = "summary: DEUTSCH"; +  const char *xc_fr = "summary: FRANCAISE"; + +  GNUNET_assert (NULL != root); +  GNUNET_assert (NULL != arr); +  GNUNET_assert (NULL != obj); +  GNUNET_assert (0 == +                 json_object_set_new (root, +                                      "v1", +                                      json_string ("world"))); +  GNUNET_assert (0 == +                 json_object_set_new (root, +                                      "v4", +                                      json_array ())); +  GNUNET_assert (0 == +                 json_array_append_new (arr, +                                        json_string ("foo"))); +  GNUNET_assert (0 == +                 json_array_append_new (arr, +                                        json_string ("bar"))); +  GNUNET_assert (0 == +                 json_object_set_new (root, +                                      "v2", +                                      arr)); +  GNUNET_assert (0 == +                 json_object_set_new (root, +                                      "v3", +                                      obj)); +  GNUNET_assert (0 == +                 json_object_set_new (root, +                                      "amt", +                                      json_string ("EUR:123.00"))); +  GNUNET_assert (0 == +                 json_object_set_new (obj, +                                      "x", +                                      json_string ("baz"))); +  GNUNET_assert (0 == +                 json_object_set_new (obj, +                                      "y", +                                      json_string ("quux"))); +  contract = json_pack ("{ s:s, s:{s:s, s:s}}", +                        "summary", +                        "ENGLISH", +                        "summary_i18n", +                        "de", +                        "DEUTSCH", +                        "fr", +                        "FRANCAISE"); +  GNUNET_assert (NULL != contract); + +  assert_template (t1, root, x1); +  assert_template (t2, root, x2); +  assert_template (t3, root, x3); +  assert_template (t4, root, x4); +  assert_template (t5, root, x5); +  assert_template (t6, root, x6); +  assert_template (t7, root, x7); +  assert_template (t8, root, x8); +  assert_template (tc, contract, xc_en); + +  GNUNET_assert (0 == +                 json_object_set_new (contract, +                                      "$language", +                                      json_string ("de"))); +  assert_template (tc, contract, xc_de); + +  GNUNET_assert (0 == +                 json_object_set_new (contract, +                                      "$language", +                                      json_string ("fr"))); +  assert_template (tc, contract, xc_fr); + +  GNUNET_assert (0 == +                 json_object_set_new (contract, +                                      "$language", +                                      json_string ("it"))); +  assert_template (tc, contract, xc_en); +  json_decref (root); +  json_decref (contract); +  return 0; +}  | 
