libdap Updated for version 3.21.0
libdap4 is an implementation of OPeNDAP's DAP protocol.
D4ConstraintEvaluator.cc
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 <string>
26#include <sstream>
27#include <iterator>
28
29//#define DODS_DEBUG
30
31#include "D4CEScanner.h"
32#include "D4ConstraintEvaluator.h"
33#include "d4_ce_parser.tab.hh"
34
35#include "DMR.h"
36#include "D4Group.h"
37#include "D4Dimensions.h"
38#include "D4Maps.h"
39#include "BaseType.h"
40#include "Array.h"
41#include "Constructor.h"
42#include "D4Sequence.h"
43
44#include "D4RValue.h"
45#include "D4FilterClause.h"
46
47#include "escaping.h"
48#include "parser.h" // for get_ull()
49#include "debug.h"
50
51// Always define this for a production release.
52#define PREVENT_XXS_VIA_CE 1
53#if NDEBUG && !PREVENT_XXS_VIA_CE
54#error("Never release libdap with PREVENT_XXS_VIA_CE turned off")
55#endif
56
57namespace libdap {
58
59bool D4ConstraintEvaluator::parse(const std::string &expr)
60{
61
62 d_expr = expr; // set for error messages. See the %initial-action section of .yy
63
64 DBG(cerr << "Entering D4ConstraintEvaluator::parse: " << endl);
65 DBG(cerr << "Entering D4ConstraintEvaluator::expr: " << expr <<endl);
66 std::istringstream iss(expr);
67 D4CEScanner scanner(iss);
68 D4CEParser parser(scanner, *this /* driver */);
69
70 if (trace_parsing()) {
71 parser.set_debug_level(1);
72 parser.set_debug_stream(std::cerr);
73 }
74
75 if(expr.empty())
76 d_dmr->set_ce_empty(true);
77
78 return parser.parse() == 0;
79}
80
87void D4ConstraintEvaluator::throw_not_found(const string &/* id */, const string &/* ident */)
88{
89#if PREVENT_XXS_VIA_CE
90 throw Error(no_such_variable, string("The constraint expression referenced a variable that was not found in the dataset."));
91#else
92 throw Error(no_such_variable, d_expr + ": The variable " + id + " was not found in the dataset (" + ident + ").");
93#endif
94}
95
96void D4ConstraintEvaluator::throw_not_array(const string &/* id */, const string &/* ident */)
97{
98#if PREVENT_XXS_VIA_CE
99 throw Error(no_such_variable, string("The constraint expression referenced an Array that was not found in the dataset."));
100#else
101 throw Error(no_such_variable, d_expr + ": The variable '" + id + "' is not an Array variable (" + ident + ").");
102#endif
103}
104
105void D4ConstraintEvaluator::search_for_and_mark_arrays(BaseType *btp)
106{
107 DBG(cerr << "Entering D4ConstraintEvaluator::search_for_and_mark_arrays...(" << btp->name() << ")" << endl);
108
109 assert(btp->is_constructor_type());
110
111 Constructor *ctor = static_cast<Constructor*>(btp);
112 for (Constructor::Vars_iter i = ctor->var_begin(), e = ctor->var_end(); i != e; ++i) {
113 switch ((*i)->type()) {
114 case dods_array_c:
115 DBG(cerr << "Found an array: " << (*i)->name() << endl);
116 mark_array_variable(*i);
117 break;
118 case dods_structure_c:
119 case dods_sequence_c:
120 DBG(cerr << "Found a ctor: " << (*i)->name() << endl);
121 search_for_and_mark_arrays(*i);
122 break;
123 default:
124 break;
125 }
126 }
127}
128
138BaseType *
139D4ConstraintEvaluator::mark_variable(BaseType *btp)
140{
141 assert(btp);
142
143 DBG(cerr << "In D4ConstraintEvaluator::mark_variable... (" << btp->name() << "; " << btp->type_name() << ")" << endl);
144
145 btp->set_send_p(true);
146
147 if (btp->type() == dods_array_c) {
148 mark_array_variable(btp);
149 }
150
151 // Test for Constructors and marks arrays they contain
152 if (btp->is_constructor_type()) {
153 search_for_and_mark_arrays(btp);
154 }
155 else if (btp->type() == dods_array_c && btp->var() && btp->var()->is_constructor_type()) {
156 search_for_and_mark_arrays(btp->var());
157 }
158
159 // Now set the parent variables
160 BaseType *parent = btp->get_parent();
161 while (parent) {
162 parent->BaseType::set_send_p(true); // Just set the parent using BaseType's impl.
163 parent = parent->get_parent();
164 }
165
166 return btp;
167}
168
169static bool array_uses_shared_dimension(Array *map, D4Dimension *source_dim)
170{
171 for (Array::Dim_iter d = map->dim_begin(), e = map->dim_end(); d != e; ++d) {
172 if (source_dim->name() == (*d).name) return true;
173 }
174
175 return false;
176}
177
191// Note: If a Map is not part of the current projection, do not include mention of it
192// in the response DMR (CDMR)
193BaseType *
194D4ConstraintEvaluator::mark_array_variable(BaseType *btp)
195{
196 assert(btp->type() == dods_array_c);
197
198 Array *a = static_cast<Array*>(btp);
199
200 // If an array appears in a CE without the slicing operators ([]) we still have to
201 // call add_constraint(...) for all of it's sdims for them to appear in
202 // the Constrained DMR.
203 if (d_indexes.empty()) {
204 for (Array::Dim_iter d = a->dim_begin(), de = a->dim_end(); d != de; ++d) {
205 D4Dimension *dim = a->dimension_D4dim(d);
206 if (dim) {
207 a->add_constraint(d, dim);
208 }
209 }
210 }
211 else {
212 // Test that the indexes and dimensions match in number
213 if (d_indexes.size() != a->dimensions())
214 throw Error(malformed_expr, "The index constraint for '" + btp->name() + "' does not match its rank.");
215
216 Array::Dim_iter d = a->dim_begin();
217
218 DBG(cerr << "dimension size " << a->dimension_size_ll(d,false) << endl);
219
220 for (vector<index>::iterator i = d_indexes.begin(), e = d_indexes.end(); i != e; ++i) {
221#if 0
222 if ((*i).stride > (unsigned long long) (a->dimension_stop(d, false) - a->dimension_start(d, false)) + 1)
223#endif
224 if ((*i).stride > (unsigned long long) (a->dimension_stop_ll(d, false) - a->dimension_start_ll(d, false)) + 1)
225 throw Error(malformed_expr,
226 "For '" + btp->name()
227 + "', the index stride value is greater than the number of elements in the Array");
228 if (!(*i).rest
229 && ((*i).stop) > (unsigned long long) (a->dimension_stop_ll(d, false) - a->dimension_start_ll(d, false)) + 1)
230#if 0
231 && ((*i).stop) > (unsigned long long) (a->dimension_stop(d, false) - a->dimension_start(d, false)) + 1)
232#endif
233 throw Error(malformed_expr,
234 "For '" + btp->name()
235 + "', the index stop value is greater than the number of elements in the Array");
236
237 D4Dimension *dim = a->dimension_D4dim(d);
238
239 // In a DAP4 CE, specifying '[]' as an array dimension slice has two meanings.
240 // It can mean 'all the elements' of the dimension or 'apply the slicing inherited
241 // from the shared dimension'. The latter might be provide 'all the elements'
242 // but regardless, the Array object must record the CE correctly.
243
244 if (dim && (*i).empty) {
245 // This case corresponds to a CE that uses the '[]' notation for a
246 // particular dimension - meaning, use the Shared Dimension size for
247 // this dimension's 'slice'.
248 a->add_constraint(d, dim); // calls set_used_by_projected_var(true) + more
249 }
250 else {
251 // This case corresponds to a 'local dimension slice' (See sections 8.6.2 and
252 // 8.7 of the spec as of 4/12/16). When a local dimension slice is used, drop
253 // the Map(s) that include that dimension. This enables people to constrain
254 // an Array when some of the Array's dimensions don't use Shared Dimensions
255 // but others do.
256
257 DBG(cerr << "Entering: LOCAL D4 constraint" << endl);
258 // First apply the constraint to the Array's dimension
259#if 0
260 a->add_constraint(d, (*i).start, (*i).stride, (*i).rest ? -1 : (*i).stop);
261#endif
262 a->add_constraint_ll(d, (*i).start, (*i).stride, (*i).rest ? -1 : (*i).stop);
263
264 // Then, if the Array has Maps, scan those Maps for any that use dimensions
265 // that match the name of this particular dimension. If any such Maps are found
266 // remove them. This ensure that the Array can be constrained using the 'local
267 // dimension slice' without the constrained DMR containing references to Maps
268 // that don't exist (or are otherwise nonsensical).
269 //
270 // This code came about as a fix for problems discovered during testing of
271 // local dimension slices. See https://opendap.atlassian.net/browse/HYRAX-98
272 // jhrg 4/12/16
273 if (!a->maps()->empty()) {
274 int map_size = a->maps()->size();
275
276 // Some variables may have several maps that shares the same dimension.
277 // When local contraint applies, all these maps should be removed.
278 // TODO: Ideally we can just use typical erase-remove in the an inner-loop to handle this.
279 // Somehow this doesn't work. Maybe we need to add public methods. KY 2023-03-13
280 for (int map_index = 0; map_index <map_size; map_index++) {
281 for (D4Maps::D4MapsIter m = a->maps()->map_begin(), e = a->maps()->map_end(); m != e; ++m) {
282#if 0
283 if ((*m)->array() == 0)
284 throw Error(malformed_expr,
285 "An array with Maps was found, but one of the Maps was not defined correctly.");
286#endif
287 auto root = dynamic_cast<D4Group*>(a->get_ancestor());
288 if (!root)
289 throw InternalErr(__FILE__, __LINE__, "Expected a valid ancestor Group.");
290 auto *map = (*m)->array(root);
291
292 // Added a test to ensure 'dim' is not null. This could be the case if
293 // execution gets here and the index *i was not empty. jhrg 4/18/17
294 if (dim && array_uses_shared_dimension(map, dim)) {
295 D4Map *map_to_be_removed = *m;
296 a->maps()->remove_map(map_to_be_removed); // Invalidates the iterator
297 delete map_to_be_removed; // removed from container; delete
298 break; // must leave the for loop because 'm' is now invalid
299 }
300 }
301 }
302 }
303 }
304
305 ++d;
306 }
307 }
308
309 d_indexes.clear(); // Clear the info so the next slice expression can be parsed.
310
311 return btp;
312}
313
322D4Dimension *
323D4ConstraintEvaluator::slice_dimension(const std::string &id, const index &i)
324{
325 D4Dimension *dim = dmr()->root()->find_dim(id);
326
327 if (i.stride > dim->size())
328 throw Error(malformed_expr,
329 "For '" + id + "', the index stride value is greater than the size of the dimension");
330 if (!i.rest && (i.stop > dim->size() - 1))
331 throw Error(malformed_expr, "For '" + id + "', the index stop value is greater than the size of the dimension");
332
333 dim->set_constraint(i.start, i.stride, i.rest ? dim->size() - 1 : i.stop);
334
335 return dim;
336}
337
338D4ConstraintEvaluator::index D4ConstraintEvaluator::make_index(const std::string &i)
339{
340 unsigned long long v = get_uint64(i.c_str());
341 return index(v, 1, v, false, false /*empty*/, "");
342}
343
344D4ConstraintEvaluator::index D4ConstraintEvaluator::make_index(const std::string &i, const std::string &s,
345 const std::string &e)
346{
347 int64_t initial = get_uint64(i.c_str());
348 int64_t end = get_uint64(e.c_str());
349 if (initial > end)
350 throw Error(malformed_expr, string("The start value of an array index is past the stop value."));
351
352 return index(initial, get_uint64(s.c_str()), end, false, false /*empty*/, "");
353}
354
355D4ConstraintEvaluator::index D4ConstraintEvaluator::make_index(const std::string &i, unsigned long long s,
356 const std::string &e)
357{
358 int64_t initial = get_uint64(i.c_str());
359 int64_t end = get_uint64(e.c_str());
360 if (initial > end)
361 throw Error(malformed_expr, string("The start value of an array index is past the stop value."));
362
363 return index(initial, s, end, false, false /*empty*/, "");
364}
365
366D4ConstraintEvaluator::index D4ConstraintEvaluator::make_index(const std::string &i, const std::string &s)
367{
368 return index(get_uint64(i.c_str()), get_uint64(s.c_str()), 0, true, false /*empty*/, "");
369}
370
371D4ConstraintEvaluator::index D4ConstraintEvaluator::make_index(const std::string &i, unsigned long long s)
372{
373 return index(get_uint64(i.c_str()), s, 0, true, false /*empty*/, "");
374}
375
376static string expr_msg(const std::string &op, const std::string &arg1, const std::string &arg2)
377{
378 return "(" + arg1 + " " + op + " " + arg2 + ").";
379}
380
398static D4FilterClause::ops get_op_code(const std::string &op)
399{
400 DBGN(cerr << "Entering " << __PRETTY_FUNCTION__ << endl << "op: " << op << endl);
401
402 if (op == "<")
403 return D4FilterClause::less;
404 else if (op == ">")
405 return D4FilterClause::greater;
406 else if (op == "<=")
407 return D4FilterClause::less_equal;
408 else if (op == ">=")
409 return D4FilterClause::greater_equal;
410 else if (op == "==")
411 return D4FilterClause::equal;
412 else if (op == "!=")
413 return D4FilterClause::not_equal;
414 else if (op == "~=")
415 return D4FilterClause::match;
416 else
417 throw Error(malformed_expr, "The opertator '" + op + "' is not supported.");
418}
419
441void D4ConstraintEvaluator::add_filter_clause(const std::string &op, const std::string &arg1, const std::string &arg2)
442{
443 DBG(cerr << "Entering: " << __PRETTY_FUNCTION__ << endl);
444
445 // Check that there really is a D4Sequence associated with this filter clause.
446 D4Sequence *s = dynamic_cast<D4Sequence*>(top_basetype());
447 if (!s)
448 throw Error(malformed_expr,
449 "When a filter expression is used, it must be bound to a Sequence variable: " + expr_msg(op, arg1, arg2));
450
451 DBG(cerr << "s->name(): " << s->name() << endl);
452
453 // Check that arg1 and 2 are valid
454 BaseType *a1 = s->var(arg1);
455 BaseType *a2 = s->var(arg2);
456 DBG(cerr << "a1: " << a1 << ", a2: " << a2 << endl);
457
458 if (a1 && a2)
459 throw Error(malformed_expr,
460 "One of the arguments in a filter expression must be a constant: " + expr_msg(op, arg1, arg2));
461 if (!(a1 || a2))
462 throw Error(malformed_expr,
463 "One of the arguments in a filter expression must be a variable in a Sequence: "
464 + expr_msg(op, arg1, arg2));
465
466 // Now we know a1 XOR a2 is true
467 if (a1) {
468 s->clauses().add_clause(new D4FilterClause(get_op_code(op), new D4RValue(a1), D4RValueFactory(arg2)));
469 }
470 else {
471 s->clauses().add_clause(new D4FilterClause(get_op_code(op), D4RValueFactory(arg1), new D4RValue(a2)));
472 }
473}
474
481string &
482D4ConstraintEvaluator::remove_quotes(string &s)
483{
484 if (*s.begin() == '\"' && *(s.end() - 1) == '\"') {
485 s.erase(s.begin());
486 s.erase(s.end() - 1);
487 }
488
489 return s;
490}
491
492// This method is called from the parser (see d4_ce_parser.yy, down in the code
493// section). This will be called during the call to D4CEParser::parse(), that
494// is inside D4ConstraintEvaluator::parse(...)
495//
496// Including the value passed in for 'l' allows the CE text to leak into
497// the error message, a potential XSS attack vector. jhrg 4/15/20
498void D4ConstraintEvaluator::error(const libdap::location &, const std::string &m)
499{
500 ostringstream oss;
501#if PREVENT_XXS_VIA_CE
502 oss << "Constraint expression parse error: " << m << ends;
503#else
504 oss << l << ": " << m << ends;
505#endif
506 throw Error(malformed_expr, oss.str());
507}
508
509} /* namespace libdap */
std::vector< dimension >::iterator Dim_iter
Definition: Array.h:241
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:272
void set_ce_empty(bool ce_empty)
Set the flag that marks the expression constraint as empty.
Definition: DMR.h:216
D4Group * root()
Definition: DMR.cc:249
top level DAP object to house generic methods
Definition: AlarmHandler.h:36
D4RValue * D4RValueFactory(std::string cpps)
Build an appropriate RValue.
Definition: D4RValue.cc:218