Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
L
libvmod-frozen
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
uplex-varnish
libvmod-frozen
Commits
824451a2
Commit
824451a2
authored
Aug 27, 2018
by
Nils Goroll
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
initial release
parent
008606f9
Changes
9
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
962 additions
and
18 deletions
+962
-18
LICENSE
LICENSE
+23
-0
README.rst
README.rst
+253
-0
configure.ac
configure.ac
+1
-0
Makefile.am
src/Makefile.am
+3
-1
tbl_enum_type.h
src/tbl_enum_type.h
+7
-0
vmod_frozen.c
src/vmod_frozen.c
+482
-0
vmod_frozen.h
src/vmod_frozen.h
+6
-0
vmod_frozen.vcc
src/vmod_frozen.vcc
+121
-12
vmod_frozen.vtc
src/vtc/vmod_frozen.vtc
+66
-5
No files found.
LICENSE
0 → 100644
View file @
824451a2
Copyright 2018 UPLEX Nils Goroll Systemoptimierung
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
README.rst
0 → 100644
View file @
824451a2
.. role:: ref(emphasis)
.. _vmod_frozen(3):
===========
vmod_frozen
===========
---------------------
Varnish frozen Module
---------------------
:Manual section: 3
SYNOPSIS
========
::
import frozen [from "path"] ;
new xparser = parser(INT depth)
VOID xparser.expect(STRING path, ENUM type, BOOL null, BOOL required)
BOOL xparser.parse(STRING)
STRING xparser.extract(STRING path, STRING null, STRING undef)
STRING xparser.type(STRING path)
DESCRIPTION
===========
This vmod makes available to VCL the _frozen_ JSON parser with low
overhead: By specifying a set of expected JSON paths, a callback to
the parser is used to track only paths of interest, which can then be
extracted.
Example
::
import frozen;
sub vcl_init {
new jwt_hdr = frozen.parser();
jwt_hdr.expect(".alg", type=STRING, null=false, required=true);
jwt_hdr.expect(".typ", type=STRING, null=false, required=true);
new jwt_payload = frozen.parser();
jwt_payload.expect(".attr");
}
sub vcl_recv {
if (! jwt_hdr.parse(...) ||
jwt_hdr.extract(".alg") != "RS256" ||
jwt_hdr.extract(".typ") != "JWT") {
return (synth(400, "unsupported jwt alg/typ"));
}
if (! jwt_payload.parse(...)) {
return (synth(400, "parse error"));
}
set req.http.something =
jwt_payload.extract(".attr",
null = "<null>", undef = "<undef>");
# ...
}
INSTALL
=======
Building from source
~~~~~~~~~~~~~~~~~~~~
This VMOD is using the standard build procedure, but includes the
`frozen` parser as a submodule, so the source should be cloned using::
git clone --recurse-submodules $URL
Quick start
-----------
This sequence should be enough in typical setups:
1. ``./bootstrap``
2. ``make``
3. ``make check`` (regression tests)
4. ``make install`` (may require root: sudo make install)
Alternative configs
-------------------
If you have installed Varnish to a non-standard directory, call
``autogen.sh`` and ``configure`` with ``PKG_CONFIG_PATH`` pointing to
the appropriate path. For example, when varnishd configure was called
with ``--prefix=$PREFIX``, use::
PKG_CONFIG_PATH=${PREFIX}/lib/pkgconfig
ACLOCAL_PATH=${PREFIX}/share/aclocal
export PKG_CONFIG_PATH ACLOCAL_PATH
CONTENTS
========
* :ref:`obj_parser`
* :ref:`func_parser.expect`
* :ref:`func_parser.extract`
* :ref:`func_parser.parse`
* :ref:`func_parser.type`
.. _obj_parser:
new xparser = parser(INT depth=10)
----------------------------------
Instiantiate a JSON parser object.
The `depth` argument specifies the maximum recursion depth. This
should be set to the lowest possible value based on the expected input
to avoid stack overflows.
Without additional configuration using the `.expect()` method, a JSON
parser obect will just check the syntactic validity of JSON input.
.. _func_parser.expect:
parser.expect(...)
------------------
::
VOID xparser.expect(
STRING path,
ENUM {ANY, STRING, NUMBER, BOOL, OBJECT, ARRAY} type=ANY,
BOOL null=1,
BOOL required=0
)
Prepare a parser object for extration of a value at `path`, which is
constructed as follows:
* the root element has the empty path ``""``
* for objects, ``.`` (dot) is appended to the path
* for object keys, the key name is appended to the path
* for arrays, [ELEMENT_INDEX] is appended for each element
The `type` argument may be use to restrict matches to a particular
JSON type.
The `null` argument specifies if ``null`` is to be accepted as a valid
value. For ``type = ANY``, `null` is _not_ implied.
The `required` argument speifies if presence of this value is required
for successful `.match()`
This method may only be called from ``vcl_init {}``.
For best efficiency, call `.expect` according to the most likely order
of elements in JSON input to be parsed.
Errors will cause a VCL failure.
=== path examples ===
Consider this json string::
{ "foo": 123, "bar": [ 1, 2, { "baz": true } ] }
The following examples are valid paths (values given for clarity)
* ``".foo" == "123"``
* ``".bar[0]" == "1"``
* ``".bar[2].baz" == "true"``
.. _func_parser.parse:
BOOL xparser.parse(STRING)
--------------------------
Parse the given string with the parser object.
The method will return `true`, if
* The string represents valid JSON
* The recursion depth is not exceeded
* All expected paths with the `required` argument set to `true`
have been found.
For expected paths, the first match is recorded.
Details on parse errors are logged as ``VCL_Error``
.. _func_parser.extract:
parser.extract(...)
-------------------
::
STRING xparser.extract(
STRING path,
STRING null="",
STRING undef=""
)
After a successful `.parse()`, extract the given path, which must have
been declared in ``vcl_init {}`` using `.expect()`.
For JSON ``null`` values, the `null` argument is returned.
For paths which are not found and not defined as required, the `undef`
argument is returned.
.. _func_parser.type:
STRING xparser.type(STRING path)
--------------------------------
Return the JSON type of an extracted path or NULL otherwise.
This is particularly relevant for paths expected to be of type ANY if
strings need to be differentiated from other types.
SEE ALSO
========vcl\(7),varnishd\(1)
COPYRIGHT
=========
::
Copyright 2018 UPLEX Nils Goroll Systemoptimierung
All rights reserved
Author: Nils Goroll <nils.goroll@uplex.de>
See LICENSE
configure.ac
View file @
824451a2
AC_PREREQ([2.68])
AC_COPYRIGHT([Copyright 2018 UPLEX Nils Goroll Systemoptimierung])
AC_INIT([libvmod-frozen], [0.1])
AC_CONFIG_MACRO_DIR([m4])
AC_CONFIG_AUX_DIR([build-aux])
...
...
src/Makefile.am
View file @
824451a2
...
...
@@ -8,7 +8,9 @@ vmod_LTLIBRARIES = \
libvmod_frozen.la
libvmod_frozen_la_LDFLAGS
=
$(VMOD_LDFLAGS)
libvmod_frozen_la_SOURCES
=
vmod_frozen.c
libvmod_frozen_la_SOURCES
=
\
vmod_frozen.c
\
vmod_frozen.h
nodist_libvmod_frozen_la_SOURCES
=
\
vcc_frozen_if.c
\
vcc_frozen_if.h
\
...
...
src/tbl_enum_type.h
0 → 100644
View file @
824451a2
VMODENUM
(
ANY
)
VMODENUM
(
ARRAY
)
VMODENUM
(
BOOL
)
VMODENUM
(
NUMBER
)
VMODENUM
(
OBJECT
)
VMODENUM
(
STRING
)
#undef VMODENUM
src/vmod_frozen.c
View file @
824451a2
/*-
* Copyright 2018 UPLEX - Nils Goroll Systemoptimierung
* All rights reserved
*
* Author: Nils Goroll <nils.goroll@uplex.de>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include "config.h"
#include <stdlib.h>
#include <string.h>
#include <cache/cache.h>
#include <vcl.h>
#include "vcc_frozen_if.h"
#include "vmod_frozen.h"
#include "frozen/frozen.h"
static
enum
type_e
type_parse
(
VCL_ENUM
e
)
{
#define VMODENUM(n) if (e == vmod_enum_ ## n) return(type_ ## n);
#include "tbl_enum_type.h"
WRONG
(
"illegal type enum"
);
}
static
const
char
const
*
type_s
[
_TYPE_E_MAX
]
=
{
[
_TYPE_E_INVALID
]
=
NULL
,
#define VMODENUM(n) [type_ ## n] = #n,
#include "tbl_enum_type.h"
};
/*
* for the vmod object, this struct is the specification
*
* .parse memcopies to array of expects and fills out the bottom
*/
struct
vmod_frozen_expect
{
unsigned
magic
;
#define VMOD_FROZEN_EXPECT_MAGIC 0x7d6dcdeb
unsigned
int
nullok
:
1
;
unsigned
int
required
:
1
;
unsigned
int
valid
:
1
;
// written by .parse
unsigned
int
null
:
1
;
// written by .parse
const
char
*
path
;
// written by .parse
const
char
*
val
;
int
len
;
// expected type initially, then overwritten
enum
type_e
type
;
};
struct
vmod_frozen_parser
{
unsigned
magic
;
#define VMOD_FROZEN_PARSER_MAGIC 0x3c438bd9
int
limit
;
char
*
vcl_name
;
int
n_expect
;
int
s_expect
;
struct
vmod_frozen_expect
*
expects
;
};
struct
vmod_frozen_task
{
unsigned
magic
;
#define VMOD_FROZEN_TASK_MAGIC 0x5028fbb3
int
n_expect
;
int
n_seen
;
int
first
;
struct
vmod_frozen_expect
*
vals
;
};
VCL_VOID
vmod_parser__init
(
VRT_CTX
,
struct
vmod_frozen_parser
**
vfpap
,
const
char
*
vcl_name
,
VCL_INT
limit
)
{
struct
vmod_frozen_parser
*
vfpa
;
struct
vmod_frozen_expect
*
vfex
;
const
int
min_s_expect
=
64
/
sizeof
(
*
vfpa
);
AN
(
vfpap
);
AZ
(
*
vfpap
);
AN
(
min_s_expect
);
if
(
limit
<
1
)
{
VRT_fail
(
ctx
,
"frozen.parser limit must be 1 or higher"
);
return
;
}
ALLOC_OBJ
(
vfpa
,
VMOD_FROZEN_PARSER_MAGIC
);
if
(
vfpa
==
NULL
)
{
VRT_fail
(
ctx
,
"frozen.parser obj alloc failed"
);
return
;
}
REPLACE
(
vfpa
->
vcl_name
,
vcl_name
);
if
(
vfpa
->
vcl_name
==
NULL
)
{
VRT_fail
(
ctx
,
"frozen.parser dup vcl_name failed"
);
goto
err_dup
;
}
vfex
=
malloc
(
sizeof
(
*
vfex
)
*
min_s_expect
);
if
(
vfex
==
NULL
)
{
VRT_fail
(
ctx
,
"frozen.parser alloc expects failed"
);
goto
err_exp
;
}
vfpa
->
limit
=
limit
;
vfpa
->
s_expect
=
min_s_expect
;
vfpa
->
expects
=
vfex
;
*
vfpap
=
vfpa
;
return
;
err_exp:
free
(
vfpa
->
vcl_name
);
err_dup:
FREE_OBJ
(
vfpa
);
}
VCL_VOID
vmod_parser__fini
(
struct
vmod_frozen_parser
**
vfpap
)
{
struct
vmod_frozen_parser
*
vfpa
=
*
vfpap
;
struct
vmod_frozen_expect
*
vfex
;
int
i
;
*
vfpap
=
NULL
;
if
(
vfpa
==
NULL
)
return
;
CHECK_OBJ
(
vfpa
,
VMOD_FROZEN_PARSER_MAGIC
);
for
(
i
=
0
;
i
<
vfpa
->
n_expect
;
i
++
)
{
vfex
=
&
vfpa
->
expects
[
i
];
free
(
TRUST_ME
(
vfex
->
path
));
}
free
(
vfpa
->
expects
);
free
(
vfpa
->
vcl_name
);
FREE_OBJ
(
vfpa
);
}
VCL_VOID
vmod_parser_expect
(
VRT_CTX
,
struct
vmod_frozen_parser
*
vfpa
,
VCL_STRING
path
,
VCL_ENUM
type_s
,
VCL_BOOL
null
,
VCL_BOOL
required
)
{
struct
vmod_frozen_expect
*
vfex
;
enum
type_e
type
=
type_parse
(
type_s
);
struct
vmod_priv
*
task
;
int
n
;
CHECK_OBJ_NOTNULL
(
ctx
,
VRT_CTX_MAGIC
);
CHECK_OBJ_NOTNULL
(
vfpa
,
VMOD_FROZEN_PARSER_MAGIC
);
if
(
ctx
->
method
!=
VCL_MET_INIT
)
{
VRT_fail
(
ctx
,
"%s.expect() may only be called from vcl_init {}"
,
vfpa
->
vcl_name
);
return
;
}
if
(
vfpa
->
n_expect
==
vfpa
->
s_expect
)
{
n
=
vfpa
->
s_expect
<<
1
;
vfex
=
realloc
(
vfpa
->
expects
,
n
*
sizeof
(
*
vfex
));
if
(
vfex
==
NULL
)
{
VRT_fail
(
ctx
,
"%s.expect() realloc %d elements failed"
,
vfpa
->
vcl_name
,
n
);
return
;
}
vfpa
->
expects
=
vfex
;
vfpa
->
s_expect
=
n
;
}
/* invalidate parse task in case expect is called after parse */
task
=
VRT_priv_task
(
ctx
,
vfpa
);
if
(
task
!=
NULL
)
task
->
priv
=
NULL
;
assert
(
vfpa
->
s_expect
>
vfpa
->
n_expect
);
vfex
=
&
vfpa
->
expects
[
vfpa
->
n_expect
++
];
INIT_OBJ
(
vfex
,
VMOD_FROZEN_EXPECT_MAGIC
);
vfex
->
path
=
strdup
(
path
);
vfex
->
type
=
type
;
vfex
->
nullok
=
!!
null
;
vfex
->
required
=
!!
required
;
}
static
void
vfex_cb
(
void
*
,
const
char
*
,
size_t
,
const
char
*
,
const
struct
json_token
*
);
VCL_BOOL
vmod_parser_parse
(
VRT_CTX
,
struct
vmod_frozen_parser
*
vfpaa
,
const
char
*
s
)
{
const
struct
vmod_frozen_parser
*
vfpa
=
vfpaa
;
struct
vmod_frozen_task
*
vfta
;
struct
vmod_frozen_expect
*
vfex
;
struct
vmod_priv
*
task
;
struct
frozen_args
args
[
1
];
VCL_BOOL
r
;
int
i
;
CHECK_OBJ_NOTNULL
(
ctx
,
VRT_CTX_MAGIC
);
CHECK_OBJ_NOTNULL
(
vfpa
,
VMOD_FROZEN_PARSER_MAGIC
);
INIT_FROZEN_ARGS
(
args
);
args
->
limit
=
vfpa
->
limit
;
if
(
vfpa
->
n_expect
==
0
)
return
(
!!
json_walk_args
(
s
,
strlen
(
s
),
args
));
assert
(
vfpa
->
n_expect
>
0
);
task
=
VRT_priv_task
(
ctx
,
vfpa
);
if
(
task
==
NULL
)
{
VRT_fail
(
ctx
,
"no priv_task"
);
return
(
0
);
}
if
(
task
->
priv
)
{
CAST_OBJ_NOTNULL
(
vfta
,
task
->
priv
,
VMOD_FROZEN_TASK_MAGIC
);
vfex
=
vfta
->
vals
;
INIT_OBJ
(
vfta
,
VMOD_FROZEN_TASK_MAGIC
);
}
else
{
vfta
=
WS_Alloc
(
ctx
->
ws
,
sizeof
*
vfta
);
if
(
vfta
==
NULL
)
{
VRT_fail
(
ctx
,
"%s.parse() WS_Alloc task failed"
,
vfpa
->
vcl_name
);
return
(
0
);
}
INIT_OBJ
(
vfta
,
VMOD_FROZEN_TASK_MAGIC
);
vfex
=
WS_Alloc
(
ctx
->
ws
,
vfpa
->
n_expect
*
sizeof
*
vfex
);
if
(
vfex
==
NULL
)
{
VRT_fail
(
ctx
,
"%s.parse() WS_Alloc task failed"
,
vfpa
->
vcl_name
);
return
(
0
);
}
task
->
priv
=
vfta
;
}
AN
(
vfex
);
vfta
->
n_expect
=
vfpa
->
n_expect
;
vfta
->
vals
=
vfex
;
memcpy
(
vfex
,
vfpa
->
expects
,
vfpa
->
n_expect
*
sizeof
*
vfex
);
args
->
callback
=
vfex_cb
;
args
->
callback_data
=
vfta
;
r
=
!!
json_walk_args
(
s
,
strlen
(
s
),
args
);
// prep for extract
vfta
->
first
=
0
;
if
(
r
==
0
)
return
(
r
);
if
(
vfta
->
n_expect
==
vfta
->
n_seen
)
return
(
r
);
for
(
i
=
0
;
i
<
vfta
->
n_expect
;
i
++
)
{
vfex
=
&
vfta
->
vals
[
i
];
if
(
vfex
->
required
==
1
&&
vfex
->
valid
==
0
)
return
(
0
);
}
return
(
r
);
}
static
const
enum
type_e
json_type_2_type_e
[
JSON_TYPES_CNT
]
=
{
[
JSON_TYPE_INVALID
]
=
_TYPE_E_INVALID
,
[
JSON_TYPE_STRING
]
=
type_STRING
,
[
JSON_TYPE_NUMBER
]
=
type_NUMBER
,
[
JSON_TYPE_TRUE
]
=
type_BOOL
,
[
JSON_TYPE_FALSE
]
=
type_BOOL
,
[
JSON_TYPE_NULL
]
=
_TYPE_E_INVALID
,
[
JSON_TYPE_OBJECT_START
]
=
_TYPE_E_INVALID
,
[
JSON_TYPE_OBJECT_END
]
=
type_OBJECT
,
[
JSON_TYPE_ARRAY_START
]
=
_TYPE_E_INVALID
,
[
JSON_TYPE_ARRAY_END
]
=
type_ARRAY
};
static
void
vfex_cb
(
void
*
callback_data
,
const
char
*
name
,
size_t
name_len
,
const
char
*
path
,
const
struct
json_token
*
token
)
{
struct
vmod_frozen_task
*
vfta
;
struct
vmod_frozen_expect
*
vfex
;
enum
type_e
type
;
int
i
;
CAST_OBJ_NOTNULL
(
vfta
,
callback_data
,
VMOD_FROZEN_TASK_MAGIC
);
(
void
)
name
;
(
void
)
name_len
;
if
(
vfta
->
n_expect
==
vfta
->
n_seen
)
return
;
AN
(
token
);
assert
(
token
->
type
!=
JSON_TYPE_INVALID
);
if
(
token
->
type
==
JSON_TYPE_OBJECT_START
||
token
->
type
==
JSON_TYPE_ARRAY_START
)
return
;
assert
(
vfta
->
n_expect
>
vfta
->
n_seen
);
/* vfta->first marks the first expect not yet valid */
for
(
i
=
vfta
->
first
;
i
<
vfta
->
n_expect
;
i
++
)
{
vfex
=
&
vfta
->
vals
[
i
];
if
(
vfex
->
valid
)
continue
;
if
(
token
->
type
==
JSON_TYPE_NULL
&&
vfex
->
nullok
==
0
)
continue
;
type
=
json_type_2_type_e
[
token
->
type
];
if
(
vfex
->
type
!=
type_ANY
&&
vfex
->
type
!=
type
)
continue
;
if
(
strcmp
(
vfex
->
path
,
path
))
continue
;
vfex
->
valid
=
1
;
vfex
->
val
=
token
->
ptr
;
vfex
->
len
=
token
->
len
;
if
(
token
->
type
==
JSON_TYPE_NULL
)
vfex
->
null
=
1
;
else
vfex
->
type
=
type
;
vfta
->
n_seen
++
;
/* advance vfta->first only if this is the first vfex */
if
(
i
!=
vfta
->
first
)
return
;
while
(
++
i
<
vfta
->
n_expect
)
{
vfex
=
&
vfta
->
vals
[
i
];
if
(
vfex
->
valid
==
0
)
{
vfta
->
first
=
i
;
return
;
}
}
vfta
->
first
=
i
;
assert
(
vfta
->
n_seen
==
vfta
->
n_expect
);
return
;
}
}
VCL_STRING
vmod_parser_extract
(
VRT_CTX
,
struct
vmod_frozen_parser
*
vfpaa
,
VCL_STRING
path
,
VCL_STRING
r_null
,
VCL_STRING
r_undef
)
{
const
struct
vmod_frozen_parser
*
vfpa
=
vfpaa
;
struct
vmod_frozen_task
*
vfta
;
struct
vmod_frozen_expect
*
vfex
;
struct
vmod_priv
*
task
;
int
i
,
n
;
CHECK_OBJ_NOTNULL
(
ctx
,
VRT_CTX_MAGIC
);
CHECK_OBJ_NOTNULL
(
vfpa
,
VMOD_FROZEN_PARSER_MAGIC
);
task
=
VRT_priv_task
(
ctx
,
vfpa
);
if
(
task
==
NULL
)
{
VRT_fail
(
ctx
,
"no priv_task"
);
return
(
r_undef
);
}
if
(
task
->
priv
==
NULL
)
{
VRT_fail
(
ctx
,
"no state from .parse()"
);
return
(
r_undef
);
}
CAST_OBJ_NOTNULL
(
vfta
,
task
->
priv
,
VMOD_FROZEN_TASK_MAGIC
);
if
(
vfta
->
n_seen
==
0
)
return
(
r_undef
);
for
(
n
=
0
;
n
<
vfta
->
n_expect
;
n
++
)
{
i
=
(
vfta
->
first
+
n
)
%
vfta
->
n_expect
;
vfex
=
&
vfta
->
vals
[
i
];
if
(
strcmp
(
vfex
->
path
,
path
))
continue
;
if
(
vfex
->
valid
==
0
)
return
(
r_undef
);
if
(
vfex
->
null
==
1
)
return
(
r_null
);
vfta
->
first
=
i
;
if
(
vfex
->
val
==
NULL
||
vfex
->
len
==
0
)
return
(
r_undef
);
return
(
WS_Printf
(
ctx
->
ws
,
"%.*s"
,
vfex
->
len
,
vfex
->
val
));
}
return
(
r_undef
);
}
VCL_STRING
vmod_parser_type
(
VRT_CTX
,
struct
vmod_frozen_parser
*
vfpaa
,
VCL_STRING
path
)
{
const
struct
vmod_frozen_parser
*
vfpa
=
vfpaa
;
struct
vmod_frozen_task
*
vfta
;
struct
vmod_frozen_expect
*
vfex
;
struct
vmod_priv
*
task
;
int
i
,
n
;
CHECK_OBJ_NOTNULL
(
ctx
,
VRT_CTX_MAGIC
);
CHECK_OBJ_NOTNULL
(
vfpa
,
VMOD_FROZEN_PARSER_MAGIC
);
task
=
VRT_priv_task
(
ctx
,
vfpa
);
if
(
task
==
NULL
)
{
VRT_fail
(
ctx
,
"no priv_task"
);
return
(
NULL
);
}
if
(
task
->
priv
==
NULL
)
{
VRT_fail
(
ctx
,
"no state from .match()"
);
return
(
NULL
);
}
CAST_OBJ_NOTNULL
(
vfta
,
task
->
priv
,
VMOD_FROZEN_TASK_MAGIC
);
if
(
vfta
->
n_seen
==
0
)
return
(
NULL
);
for
(
n
=
0
;
n
<
vfta
->
n_expect
;
n
++
)
{
i
=
(
vfta
->
first
+
n
)
%
vfta
->
n_expect
;
vfex
=
&
vfta
->
vals
[
i
];
if
(
strcmp
(
vfex
->
path
,
path
))
continue
;
if
(
vfex
->
valid
==
0
)
return
(
NULL
);
if
(
vfex
->
null
==
1
)
return
(
NULL
);
assert
(
vfex
->
type
!=
type_ANY
);
return
(
type_s
[
vfex
->
type
]);
}
return
(
NULL
);
}
// ---------------------------------------------------------------------------
VCL_STRING
vmod_hello
(
VRT_CTX
)
{
...
...
src/vmod_frozen.h
0 → 100644
View file @
824451a2
enum
type_e
{
#define VMODENUM(x) type_ ## x,
_TYPE_E_INVALID
,
#include "tbl_enum_type.h"
_TYPE_E_MAX
};
src/vmod_frozen.vcc
View file @
824451a2
#-
# Copyright 2018 UPLEX Nils Goroll Systemoptimierung
# All rights reserved
#
# Author: Nils Goroll <nils.goroll@uplex.de>
#
# See LICENSE
#
$Module frozen 3 Varnish frozen Module
DESCRIPTION
===========
This
VCC file was generated by VCDK, it is used to for both the VMOD
interface and its manual using reStructuredText.
XXX: document vmod-frozen
This
vmod makes available to VCL the _frozen_ JSON parser with low
overhead: By specifying a set of expected JSON paths, a callback to
the parser is used to track only paths of interest, which can then be
extracted.
Example
::
import frozen;
import frozen;
sub vcl_init {
new jwt_hdr = frozen.parser();
jwt_hdr.expect(".alg", type=STRING, null=false, required=true);
jwt_hdr.expect(".typ", type=STRING, null=false, required=true);
new jwt_payload = frozen.parser();
jwt_payload.expect(".attr");
}
sub vcl_recv {
if (! jwt_hdr.parse(...) ||
jwt_hdr.extract(".alg") != "RS256" ||
jwt_hdr.extract(".typ") != "JWT") {
return (synth(400, "unsupported jwt alg/typ"));
}
if (! jwt_payload.parse(...)) {
return (synth(400, "parse error"));
}
set req.http.something =
jwt_payload.extract(".attr",
null = "<null>", undef = "<undef>");
# ...
}
$Object parser(INT depth=10)
Instiantiate a JSON parser object.
The `depth` argument specifies the maximum recursion depth. This
should be set to the lowest possible value based on the expected input
to avoid stack overflows.
Without additional configuration using the `.expect()` method, a JSON
parser obect will just check the syntactic validity of JSON input.
$Method VOID .expect(STRING path,
ENUM {ANY, STRING, NUMBER, BOOL, OBJECT, ARRAY} type = ANY,
BOOL null = 1, BOOL required = 0)
Prepare a parser object for extration of a value at `path`, which is
constructed as follows:
* the root element has the empty path ``""``
* for objects, ``.`` (dot) is appended to the path
* for object keys, the key name is appended to the path
* for arrays, [ELEMENT_INDEX] is appended for each element
The `type` argument may be use to restrict matches to a particular
JSON type.
The `null` argument specifies if ``null`` is to be accepted as a valid
value. For ``type = ANY``, `null` is _not_ implied.
The `required` argument speifies if presence of this value is required
for successful `.match()`
This method may only be called from ``vcl_init {}``.
For best efficiency, call `.expect` according to the most likely order
of elements in JSON input to be parsed.
Errors will cause a VCL failure.
=== path examples ===
Consider this json string::
{ "foo": 123, "bar": [ 1, 2, { "baz": true } ] }
The following examples are valid paths (values given for clarity)
* ``".foo" == "123"``
* ``".bar[0]" == "1"``
* ``".bar[2].baz" == "true"``
$Method BOOL .parse(STRING)
Parse the given string with the parser object.
The method will return `true`, if
* The string represents valid JSON
* The recursion depth is not exceeded
* All expected paths with the `required` argument set to `true`
have been found.
For expected paths, the first match is recorded.
Details on parse errors are logged as ``VCL_Error``
$Method STRING .extract(STRING path, STRING null = "", STRING undef = "")
After a successful `.parse()`, extract the given path, which must have
been declared in ``vcl_init {}`` using `.expect()`.
For JSON ``null`` values, the `null` argument is returned.
sub vcl_deliver {
set resp.http.Hello = frozen.hello();
}
For paths which are not found and not defined as required, the `undef`
argument is returned.
XXX: define vmod-frozen interface
$Method STRING .type(STRING path)
$Function STRING hello()
Return the JSON type of an extracted path or NULL otherwise.
Description
Hello world for vmod-frozen
This is particularly relevant for paths expected to be of type ANY if
strings need to be differentiated from other types.
SEE ALSO
========vcl\(7),varnishd\(1)
src/vtc/vmod_frozen.vtc
View file @
824451a2
...
...
@@ -6,16 +6,77 @@ server s1 {
} -start
varnish v1 -vcl+backend {
import frozen;
import frozen;
sub vcl_deliver {
set resp.http.Hello = frozen.hello();
}
sub vcl_init {
new p_test = frozen.parser();
p_test.expect(".a");
p_test.expect(".b");
p_test.expect(".c");
p_test.expect(".d");
p_test.expect(".e");
p_test.expect(".f[0]");
p_test.expect(".f[1]");
p_test.expect(".g.1");
p_test.expect(".g.h[0]");
}
sub vcl_recv {
return (synth(200));
}
sub vcl_synth {
set resp.http.parse =
p_test.parse({"{ a: 1, b: "hi there", c: true, d: false, "} +
{" e : null, f: [ 1, -2, 3], g: { "1": [], h: [ 7 ] } } "});
set resp.http.a = p_test.extract(".a");
set resp.http.a-type = p_test.type(".a");
set resp.http.b = p_test.extract(".b");
set resp.http.b-type = p_test.type(".b");
set resp.http.c = p_test.extract(".c");
set resp.http.c-type = p_test.type(".c");
set resp.http.d = p_test.extract(".d");
set resp.http.d-type = p_test.type(".d");
set resp.http.e = p_test.extract(".e", null="<null>");
set resp.http.e-type = p_test.type(".e");
set resp.http.f0 = p_test.extract(".f[0]");
set resp.http.f0-type = p_test.type(".f[0]");
set resp.http.f1 = p_test.extract(".f[1]");
set resp.http.f1-type = p_test.type(".f[1]");
set resp.http.g1 = p_test.extract({".g.1"});
set resp.http.g1-type = p_test.type({".g.1"});
set resp.http.gh0 = p_test.extract(".g.h[0]");
set resp.http.gh0-type = p_test.type(".g.h[0]");
}
} -start
client c1 {
txreq
rxresp
expect resp.status == 200
expect resp.http.Hello == "vmod-frozen"
expect resp.http.a == 1
expect resp.http.a-type == NUMBER
expect resp.http.b == "hi there"
expect resp.http.b-type == STRING
expect resp.http.c == true
expect resp.http.c-type == BOOL
expect resp.http.d == false
expect resp.http.d-type == BOOL
expect resp.http.e == "<null>"
expect resp.http.e-type == ""
expect resp.http.f0 == 1
expect resp.http.f0-type == NUMBER
expect resp.http.f1 == -2
expect resp.http.f1-type == NUMBER
expect resp.http.g1 == "[]"
expect resp.http.g1-type == ARRAY
expect resp.http.gh0 == 7
expect resp.http.gh0-type == NUMBER
} -run
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment