libdap Updated for version 3.21.1
libdap4 is an implementation of OPeNDAP's DAP protocol.
D4ConstraintEvaluator.cc
Go to the documentation of this file.
1// -*- mode: c++; c-basic-offset:4 -*-
2
3// This file is part of libdap, A C++ implementation of the OPeNDAP Data
4// Access Protocol.
5
6// Copyright (c) 2002,2003 OPeNDAP, Inc.
7// Author: James Gallagher <jgallagher@opendap.org>
8//
9// This library is free software; you can redistribute it and/or
10// modify it under the terms of the GNU Lesser General Public
11// License as published by the Free Software Foundation; either
12// version 2.1 of the License, or (at your option) any later version.
13//
14// This library is distributed in the hope that it will be useful,
15// but WITHOUT ANY WARRANTY; without even the implied warranty of
16// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17// Lesser General Public License for more details.
18//
19// You should have received a copy of the GNU Lesser General Public
20// License along with this library; if not, write to the Free Software
21// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22//
23// You can contact OPeNDAP, Inc. at PO Box 112, Saunderstown, RI. 02874-0112.
24
25#include <iterator>
26#include <sstream>
27#include <string>
28
29#include "D4CEScanner.h"
31#include "d4_ce_parser.tab.hh"
32
33#include "D4Group.h"
34#include "D4Maps.h"
35#include "D4Sequence.h"
36#include "DMR.h"
37
38#include "D4FilterClause.h"
39#include "D4RValue.h"
40
41// Always define this for a production release.
42#define PREVENT_XXS_VIA_CE 1
43#if NDEBUG && !PREVENT_XXS_VIA_CE
44#error("Never release libdap with PREVENT_XXS_VIA_CE turned off")
45#endif
46
47namespace libdap {
48
49bool D4ConstraintEvaluator::parse(const std::string &expr) {
50 d_expr = expr; // set for error messages. See the %initial-action section of .yy
51
52 // empty expressions are valid but fail the parser. jhrg 2/23/24
53 if (expr.empty()) {
54 d_dmr->set_ce_empty(true);
55 return true;
56 }
57
58 std::istringstream iss(expr);
59 D4CEScanner scanner(iss);
60 D4CEParser parser(scanner, *this /* driver */);
61
62 if (trace_parsing()) {
63 parser.set_debug_level(1);
64 parser.set_debug_stream(std::cerr);
65 }
66
67 return parser.parse() == 0;
68}
69
76[[noreturn]] void D4ConstraintEvaluator::throw_not_found(const string & /* id */, const string & /* ident */) {
77#if PREVENT_XXS_VIA_CE
79 string("The constraint expression referenced a variable that was not found in the dataset."));
80#else
81 throw Error(no_such_variable, d_expr + ": The variable " + id + " was not found in the dataset (" + ident + ").");
82#endif
83}
84
85[[noreturn]] void D4ConstraintEvaluator::throw_not_array(const string & /* id */, const string & /* ident */) {
86#if PREVENT_XXS_VIA_CE
87 throw Error(no_such_variable,
88 string("The constraint expression referenced an Array that was not found in the dataset."));
89#else
90 throw Error(no_such_variable, d_expr + ": The variable '" + id + "' is not an Array variable (" + ident + ").");
91#endif
92}
93
94void D4ConstraintEvaluator::search_for_and_mark_arrays(BaseType *btp) {
95 DBG(cerr << "Entering D4ConstraintEvaluator::search_for_and_mark_arrays...(" << btp->name() << ")" << endl);
96
97 if (!btp->is_constructor_type())
98 throw InternalErr(__FILE__, __LINE__,
99 "D4ConstraintEvaluator::search_for_and_mark_arrays(): Expected a Constructor type.");
100
101 auto ctor = static_cast<Constructor *>(btp);
102 for (auto var : ctor->variables()) {
103 switch (var->type()) {
104 case dods_array_c:
105 DBG(cerr << "Found an array: " << (*i)->name() << endl);
106 mark_array_variable(var);
107 break;
108 case dods_structure_c:
109 case dods_sequence_c:
110 DBG(cerr << "Found a ctor: " << (*i)->name() << endl);
111 search_for_and_mark_arrays(var);
112 break;
113 default:
114 break;
115 }
116 }
117}
118
128BaseType *D4ConstraintEvaluator::mark_variable(BaseType *btp) {
129 if (!btp)
130 throw InternalErr(__FILE__, __LINE__,
131 "D4ConstraintEvaluator::mark_variable(): Expected a non-null BaseType pointer.");
132
133 DBG(cerr << "In D4ConstraintEvaluator::mark_variable... (" << btp->name() << "; " << btp->type_name() << ")"
134 << endl);
135
136 btp->set_send_p(true);
137
138 if (btp->type() == dods_array_c) {
139 mark_array_variable(btp);
140 }
141
142 // Test for Constructors and marks arrays they contain
143 if (btp->is_constructor_type()) {
144 search_for_and_mark_arrays(btp);
145 } else if (btp->type() == dods_array_c && btp->var() && btp->var()->is_constructor_type()) {
146 search_for_and_mark_arrays(btp->var());
147 }
148
149 // Now set the parent variables
150 BaseType *parent = btp->get_parent();
151 while (parent) {
152 parent->BaseType::set_send_p(true); // Just set the parent using BaseType's impl.
153 parent = parent->get_parent();
154 }
155
156 return btp;
157}
158
159static bool array_uses_shared_dimension(Array *map, const D4Dimension *source_dim) {
160 for (auto d = map->dim_begin(), e = map->dim_end(); d != e; ++d) {
161 if (source_dim->name() == (*d).name)
162 return true;
163 }
164
165 return false;
166}
167
180
181// Note: If a Map is not part of the current projection, do not include mention of it
182// in the response DMR (CDMR)
183BaseType *D4ConstraintEvaluator::mark_array_variable(BaseType *btp) {
184 if (btp->type() != dods_array_c)
185 throw InternalErr(__FILE__, __LINE__, "D4ConstraintEvaluator::mark_array_variable(): Expected an Array type.");
186
187 auto *a = static_cast<Array *>(btp);
188
189 // If an array appears in a CE without the slicing operators ([]) we still have to
190 // call add_constraint(...) for all of it's sdims for them to appear in
191 // the Constrained DMR.
192 if (d_indexes.empty()) {
193 for (auto d = a->dim_begin(), de = a->dim_end(); d != de; ++d) {
194 D4Dimension *dim = a->dimension_D4dim(d);
195 if (dim) {
196 a->add_constraint(d, dim);
197 }
198 }
199 } else {
200 // Test that the indexes and dimensions match in number
201 if (d_indexes.size() != a->dimensions())
202 throw Error(malformed_expr, "The index constraint for '" + btp->name() + "' does not match its rank.");
203
204 auto d = a->dim_begin();
205 for (auto const &index : d_indexes) {
206 if (d == a->dim_end())
207 throw Error(malformed_expr, "The index constraint for '" + btp->name() + "' does not match its rank.");
208
209 if (index.stride > (a->dimension_stop_ll(d, false) - a->dimension_start_ll(d, false)) + 1)
210 throw Error(malformed_expr,
211 "For '" + btp->name() +
212 "', the index stride value is greater than the number of elements in the Array");
213 if (!index.rest && index.stop > (a->dimension_stop_ll(d, false) - a->dimension_start_ll(d, false)) + 1)
214 throw Error(malformed_expr,
215 "For '" + btp->name() +
216 "', the index stop value is greater than the number of elements in the Array");
217
218 D4Dimension *dim = a->dimension_D4dim(d);
219
220 // In a DAP4 CE, specifying '[]' as an array dimension slice has two meanings.
221 // It can mean 'all the elements' of the dimension or 'apply the slicing inherited
222 // from the shared dimension'. The latter might be provide 'all the elements'
223 // but regardless, the Array object must record the CE correctly.
224
225 if (dim && index.empty) {
226 // This case corresponds to a CE that uses the '[]' notation for a
227 // particular dimension - meaning, use the Shared Dimension size for
228 // this dimension's 'slice'.
229 a->add_constraint(d, dim); // calls set_used_by_projected_var(true) + more
230 } else {
231 // This case corresponds to a 'local dimension slice' (See sections 8.6.2 and
232 // 8.7 of the spec as of 4/12/16). When a local dimension slice is used, drop
233 // the Map(s) that include that dimension. This enables people to constrain
234 // an Array when some of the Array's dimensions don't use Shared Dimensions
235 // but others do.
236 use_explicit_projection(a, d, index);
237 }
238
239 ++d;
240 }
241 }
242
243 d_indexes.clear(); // Clear the info so the next slice expression can be parsed.
244
245 return btp;
246}
247
254static void array_map_remover(Array *a, const D4Dimension *dim) {
255 auto root = dynamic_cast<D4Group *>(a->get_ancestor());
256 if (!root)
257 throw InternalErr(__FILE__, __LINE__, "Expected a valid ancestor Group.");
258
259 for (auto m = a->maps()->map_begin(), e = a->maps()->map_end(); m != e; ++m) {
260 // Added a test to ensure 'dim' is not null. This could be the case if
261 // execution gets here and the index *i was not empty. jhrg 4/18/17
262 auto *map = (*m)->array(root);
263 if (dim && array_uses_shared_dimension(map, dim)) {
264 D4Map *map_to_be_removed = *m;
265 a->maps()->remove_map(map_to_be_removed); // Invalidates the iterator
266 delete map_to_be_removed; // removed from container; delete
267 break; // must leave the for loop because 'm' is now invalid
268 }
269 }
270}
271
279void D4ConstraintEvaluator::use_explicit_projection(Array *a, const Array::Dim_iter &dim_iter,
280 const D4ConstraintEvaluator::index &index) {
281 // First apply the constraint to the Array's dimension
282 a->add_constraint_ll(dim_iter, index.start, index.stride, index.rest ? -1 : index.stop);
283
284 // Then, if the Array has Maps, scan those Maps for any that use dimensions
285 // that match the name of this particular dimension. If any such Maps are found
286 // remove them. This ensures that the Array can be constrained using the 'local
287 // dimension slice' without the constrained DMR containing references to Maps
288 // that don't exist (or are otherwise nonsensical).
289 //
290 // This code came about as a fix for problems discovered during testing of
291 // local dimension slices. See https://opendap.atlassian.net/browse/HYRAX-98
292 // jhrg 4/12/16
293 if (!a->maps()->empty()) {
294 // Some variables may have several maps that shares the same dimension.
295 // When local constraint applies, all these maps should be removed.
296 int map_size = a->maps()->size();
297 for (int map_index = 0; map_index < map_size; map_index++) {
298 array_map_remover(a, a->dimension_D4dim(dim_iter));
299 }
300 }
301}
302
311D4Dimension *D4ConstraintEvaluator::slice_dimension(const std::string &id, const index &i) {
312 D4Dimension *dim = dmr()->root()->find_dim(id);
313
314 if ((uint64_t)i.stride > dim->size())
315 throw Error(malformed_expr,
316 "For '" + id + "', the index stride value is greater than the size of the dimension");
317 if (!i.rest && ((uint64_t)i.stop > dim->size() - 1))
318 throw Error(malformed_expr, "For '" + id + "', the index stop value is greater than the size of the dimension");
319
320 dim->set_constraint(i.start, i.stride, i.rest ? dim->size() - 1 : i.stop);
321
322 return dim;
323}
324
325D4ConstraintEvaluator::index D4ConstraintEvaluator::make_index(const std::string &i) {
326 int64_t v = get_int64(i.c_str());
327 return {v, 1, v, false, false /*empty*/, ""};
328}
329
330D4ConstraintEvaluator::index D4ConstraintEvaluator::make_index(const std::string &i, const std::string &s,
331 const std::string &e) {
332 int64_t initial = get_int64(i.c_str());
333 int64_t end = get_int64(e.c_str());
334 if (initial > end)
335 throw Error(malformed_expr, string("The start value of an array index is past the stop value."));
336
337 return {initial, get_int64(s.c_str()), end, false, false /*empty*/, ""};
338}
339
340D4ConstraintEvaluator::index D4ConstraintEvaluator::make_index(const std::string &i, int64_t s, const std::string &e) {
341 int64_t initial = get_int64(i.c_str());
342 int64_t end = get_int64(e.c_str());
343 if (initial > end)
344 throw Error(malformed_expr, string("The start value of an array index is past the stop value."));
345
346 return {initial, s, end, false, false /*empty*/, ""};
347}
348
349D4ConstraintEvaluator::index D4ConstraintEvaluator::make_index(const std::string &i, const std::string &s) {
350 return {get_int64(i.c_str()), get_int64(s.c_str()), 0, true, false /*empty*/, ""};
351}
352
353D4ConstraintEvaluator::index D4ConstraintEvaluator::make_index(const std::string &i, int64_t s) {
354 return {get_int64(i.c_str()), s, 0, true, false /*empty*/, ""};
355}
356
357static string expr_msg(const std::string &op, const std::string &arg1, const std::string &arg2) {
358 return "(" + arg1 + " " + op + " " + arg2 + ").";
359}
360
378static D4FilterClause::ops get_op_code(const std::string &op) {
379 DBGN(cerr << "Entering " << __PRETTY_FUNCTION__ << endl << "op: " << op << endl);
380
381 if (op == "<")
383 else if (op == ">")
385 else if (op == "<=")
387 else if (op == ">=")
389 else if (op == "==")
391 else if (op == "!=")
393 else if (op == "~=")
395 else
396 throw Error(malformed_expr, "The operator '" + op + "' is not supported.");
397}
398
420void D4ConstraintEvaluator::add_filter_clause(const std::string &op, const std::string &arg1,
421 const std::string &arg2) const {
422 DBG(cerr << "Entering: " << __PRETTY_FUNCTION__ << endl);
423
424 // Check that there really is a D4Sequence associated with this filter clause.
425 auto s = dynamic_cast<D4Sequence *>(top_basetype());
426 if (!s)
427 throw Error(malformed_expr, "When a filter expression is used, it must be bound to a Sequence variable: " +
428 expr_msg(op, arg1, arg2));
429
430 DBG(cerr << "s->name(): " << s->name() << endl);
431
432 // Check that arg1 and 2 are valid
433 BaseType *a1 = s->var(arg1);
434 BaseType *a2 = s->var(arg2);
435 DBG(cerr << "a1: " << a1 << ", a2: " << a2 << endl);
436
437 if (a1 && a2)
438 throw Error(malformed_expr,
439 "One of the arguments in a filter expression must be a constant: " + expr_msg(op, arg1, arg2));
440 if (!(a1 || a2))
441 throw Error(malformed_expr, "One of the arguments in a filter expression must be a variable in a Sequence: " +
442 expr_msg(op, arg1, arg2));
443
444 // Now we know a1 XOR a2 is true
445 if (a1) {
446 s->clauses().add_clause(new D4FilterClause(get_op_code(op), new D4RValue(a1), D4RValueFactory(arg2)));
447 } else {
448 s->clauses().add_clause(new D4FilterClause(get_op_code(op), D4RValueFactory(arg1), new D4RValue(a2)));
449 }
450}
451
458string &D4ConstraintEvaluator::remove_quotes(string &s) {
459 if (*s.begin() == '\"' && *(s.end() - 1) == '\"') {
460 s.erase(s.begin());
461 s.erase(s.end() - 1);
462 }
463
464 return s;
465}
466
467// This method is called from the parser (see d4_ce_parser.yy, down in the code
468// section). This will be called during the call to D4CEParser::parse(), that
469// is inside D4ConstraintEvaluator::parse(...)
470//
471// Including the value passed in for 'l' allows the CE text to leak into
472// the error message, a potential XSS attack vector. jhrg 4/15/20
473[[noreturn]] void D4ConstraintEvaluator::error(const libdap::location &, const std::string &m) {
474 ostringstream oss;
475#if PREVENT_XXS_VIA_CE
476 oss << "Constraint expression parse error: " << m << ends;
477#else
478 oss << l << ": " << m << ends;
479#endif
480 throw Error(malformed_expr, oss.str());
481}
482
483} /* namespace libdap */
#define malformed_expr
(400)
Definition Error.h:66
#define no_such_variable
(400)
Definition Error.h:65
A multidimensional array of identical data types.
Definition Array.h:121
std::vector< dimension >::iterator Dim_iter
Definition Array.h:225
The basic data type for the DODS DAP types.
Definition BaseType.h:118
bool parse(const std::string &expr)
static void error(const libdap::location &l, const std::string &m)
D4Dimension * find_dim(const string &path)
Find the dimension using a path. Using the DAP4 name syntax, lookup a dimension. The dimension must b...
Definition D4Group.cc:233
D4Group * root()
Definition DMR.cc:228
A class for error processing.
Definition Error.h:92
A class for software fault reporting.
Definition InternalErr.h:61
#define DBGN(x)
Definition debug.h:59
#define DBG(x)
Definition debug.h:58
long long get_int64(const char *val)
top level DAP object to house generic methods
Definition AISConnect.cc:30
@ dods_sequence_c
Definition Type.h:108
@ dods_structure_c
Definition Type.h:106
@ dods_array_c
Definition Type.h:107
D4RValue * D4RValueFactory(std::string cpps)
Build an appropriate RValue.
Definition D4RValue.cc:197