libdap  Updated for version 3.20.6
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 
57 namespace libdap {
58 
59 bool D4ConstraintEvaluator::parse(const std::string &expr)
60 {
61  d_expr = expr; // set for error messages. See the %initial-action section of .yy
62 
63  std::istringstream iss(expr);
64  D4CEScanner scanner(iss);
65  D4CEParser parser(scanner, *this /* driver */);
66 
67  if (trace_parsing()) {
68  parser.set_debug_level(1);
69  parser.set_debug_stream(std::cerr);
70  }
71 
72  return parser.parse() == 0;
73 }
74 
81 void D4ConstraintEvaluator::throw_not_found(const string &/* id */, const string &/* ident */)
82 {
83 #if PREVENT_XXS_VIA_CE
84  throw Error(no_such_variable, string("The constraint expression referenced a variable that was not found in the dataset."));
85 #else
86  throw Error(no_such_variable, d_expr + ": The variable " + id + " was not found in the dataset (" + ident + ").");
87 #endif
88 }
89 
90 void D4ConstraintEvaluator::throw_not_array(const string &/* id */, const string &/* ident */)
91 {
92 #if PREVENT_XXS_VIA_CE
93  throw Error(no_such_variable, string("The constraint expression referenced an Array that was not found in the dataset."));
94 #else
95  throw Error(no_such_variable, d_expr + ": The variable '" + id + "' is not an Array variable (" + ident + ").");
96 #endif
97 }
98 
99 void D4ConstraintEvaluator::search_for_and_mark_arrays(BaseType *btp)
100 {
101  DBG(cerr << "Entering D4ConstraintEvaluator::search_for_and_mark_arrays...(" << btp->name() << ")" << endl);
102 
103  assert(btp->is_constructor_type());
104 
105  Constructor *ctor = static_cast<Constructor*>(btp);
106  for (Constructor::Vars_iter i = ctor->var_begin(), e = ctor->var_end(); i != e; ++i) {
107  switch ((*i)->type()) {
108  case dods_array_c:
109  DBG(cerr << "Found an array: " << (*i)->name() << endl);
110  mark_array_variable(*i);
111  break;
112  case dods_structure_c:
113  case dods_sequence_c:
114  DBG(cerr << "Found a ctor: " << (*i)->name() << endl);
115  search_for_and_mark_arrays(*i);
116  break;
117  default:
118  break;
119  }
120  }
121 }
122 
132 BaseType *
133 D4ConstraintEvaluator::mark_variable(BaseType *btp)
134 {
135  assert(btp);
136 
137  DBG(cerr << "In D4ConstraintEvaluator::mark_variable... (" << btp->name() << "; " << btp->type_name() << ")" << endl);
138 
139  btp->set_send_p(true);
140 
141  if (btp->type() == dods_array_c) {
142  mark_array_variable(btp);
143  }
144 
145  // Test for Constructors and marks arrays they contain
146  if (btp->is_constructor_type()) {
147  search_for_and_mark_arrays(btp);
148  }
149  else if (btp->type() == dods_array_c && btp->var() && btp->var()->is_constructor_type()) {
150  search_for_and_mark_arrays(btp->var());
151  }
152 
153  // Now set the parent variables
154  BaseType *parent = btp->get_parent();
155  while (parent) {
156  parent->BaseType::set_send_p(true); // Just set the parent using BaseType's impl.
157  parent = parent->get_parent();
158  }
159 
160  return btp;
161 }
162 
163 static bool array_uses_shared_dimension(Array *map, D4Dimension *source_dim)
164 {
165  for (Array::Dim_iter d = map->dim_begin(), e = map->dim_end(); d != e; ++d) {
166  if (source_dim->name() == (*d).name) return true;
167  }
168 
169  return false;
170 }
171 
185 // Note: If a Map is not part of the current projection, do not include mention of it
186 // in the response DMR (CDMR)
187 BaseType *
188 D4ConstraintEvaluator::mark_array_variable(BaseType *btp)
189 {
190  assert(btp->type() == dods_array_c);
191 
192  Array *a = static_cast<Array*>(btp);
193 
194  // If an array appears in a CE without the slicing operators ([]) we still have to
195  // call add_constraint(...) for all of it's sdims for them to appear in
196  // the Constrained DMR.
197  if (d_indexes.empty()) {
198  for (Array::Dim_iter d = a->dim_begin(), de = a->dim_end(); d != de; ++d) {
199  D4Dimension *dim = a->dimension_D4dim(d);
200  if (dim) {
201  a->add_constraint(d, dim);
202  }
203  }
204  }
205  else {
206  // Test that the indexes and dimensions match in number
207  if (d_indexes.size() != a->dimensions())
208  throw Error(malformed_expr, "The index constraint for '" + btp->name() + "' does not match its rank.");
209 
210  Array::Dim_iter d = a->dim_begin();
211  for (vector<index>::iterator i = d_indexes.begin(), e = d_indexes.end(); i != e; ++i) {
212  if ((*i).stride > (unsigned long long) (a->dimension_stop(d, false) - a->dimension_start(d, false)) + 1)
213  throw Error(malformed_expr,
214  "For '" + btp->name()
215  + "', the index stride value is greater than the number of elements in the Array");
216  if (!(*i).rest
217  && ((*i).stop) > (unsigned long long) (a->dimension_stop(d, false) - a->dimension_start(d, false)) + 1)
218  throw Error(malformed_expr,
219  "For '" + btp->name()
220  + "', the index stop value is greater than the number of elements in the Array");
221 
222  D4Dimension *dim = a->dimension_D4dim(d);
223 
224  // In a DAP4 CE, specifying '[]' as an array dimension slice has two meanings.
225  // It can mean 'all the elements' of the dimension or 'apply the slicing inherited
226  // from the shared dimension'. The latter might be provide 'all the elements'
227  // but regardless, the Array object must record the CE correctly.
228 
229  if (dim && (*i).empty) {
230  // This case corresponds to a CE that uses the '[]' notation for a
231  // particular dimension - meaning, use the Shared Dimension size for
232  // this dimension's 'slice'.
233  a->add_constraint(d, dim); // calls set_used_by_projected_var(true) + more
234  }
235  else {
236  // This case corresponds to a 'local dimension slice' (See sections 8.6.2 and
237  // 8.7 of the spec as of 4/12/16). When a local dimension slice is used, drop
238  // the Map(s) that include that dimension. This enables people to constrain
239  // an Array when some of the Array's dimensions don't use Shared Dimensions
240  // but others do.
241 
242  // First apply the constraint to the Array's dimension
243  a->add_constraint(d, (*i).start, (*i).stride, (*i).rest ? -1 : (*i).stop);
244 
245  // Then, if the Array has Maps, scan those Maps for any that use dimensions
246  // that match the name of this particular dimension. If any such Maps are found
247  // remove them. This ensure that the Array can be constrained using the 'local
248  // dimension slice' without the constrained DMR containing references to Maps
249  // that don't exist (or are otherwise nonsensical).
250  //
251  // This code came about as a fix for problems discovered during testing of
252  // local dimension slices. See https://opendap.atlassian.net/browse/HYRAX-98
253  // jhrg 4/12/16
254  if (!a->maps()->empty()) {
255  for (D4Maps::D4MapsIter m = a->maps()->map_begin(), e = a->maps()->map_end(); m != e; ++m) {
256  if ((*m)->array() == 0)
257  throw Error(malformed_expr,
258  "An array with Maps was found, but one of the Maps was not defined correctly.");
259 
260  Array *map = const_cast<Array*>((*m)->array()); // Array lacks const iterator support
261  // Added a test to ensure 'dim' is not null. This could be the case if
262  // execution gets here and the index *i was not empty. jhrg 4/18/17
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  }
272 
273  ++d;
274  }
275  }
276 
277  d_indexes.clear(); // Clear the info so the next slice expression can be parsed.
278 
279  return btp;
280 }
281 
290 D4Dimension *
291 D4ConstraintEvaluator::slice_dimension(const std::string &id, const index &i)
292 {
293  D4Dimension *dim = dmr()->root()->find_dim(id);
294 
295  if (i.stride > dim->size())
296  throw Error(malformed_expr,
297  "For '" + id + "', the index stride value is greater than the size of the dimension");
298  if (!i.rest && (i.stop > dim->size() - 1))
299  throw Error(malformed_expr, "For '" + id + "', the index stop value is greater than the size of the dimension");
300 
301  dim->set_constraint(i.start, i.stride, i.rest ? dim->size() - 1 : i.stop);
302 
303  return dim;
304 }
305 
306 D4ConstraintEvaluator::index D4ConstraintEvaluator::make_index(const std::string &i)
307 {
308  unsigned long long v = get_int64(i.c_str());
309  return index(v, 1, v, false, false /*empty*/, "");
310 }
311 
312 D4ConstraintEvaluator::index D4ConstraintEvaluator::make_index(const std::string &i, const std::string &s,
313  const std::string &e)
314 {
315  return index(get_int64(i.c_str()), get_int64(s.c_str()), get_int64(e.c_str()), false, false /*empty*/, "");
316 }
317 
318 D4ConstraintEvaluator::index D4ConstraintEvaluator::make_index(const std::string &i, unsigned long long s,
319  const std::string &e)
320 {
321  return index(get_int64(i.c_str()), s, get_int64(e.c_str()), false, false /*empty*/, "");
322 }
323 
324 D4ConstraintEvaluator::index D4ConstraintEvaluator::make_index(const std::string &i, const std::string &s)
325 {
326  return index(get_int64(i.c_str()), get_int64(s.c_str()), 0, true, false /*empty*/, "");
327 }
328 
329 D4ConstraintEvaluator::index D4ConstraintEvaluator::make_index(const std::string &i, unsigned long long s)
330 {
331  return index(get_uint64(i.c_str()), s, 0, true, false /*empty*/, "");
332 }
333 
334 static string expr_msg(const std::string &op, const std::string &arg1, const std::string &arg2)
335 {
336  return "(" + arg1 + " " + op + " " + arg2 + ").";
337 }
338 
356 static D4FilterClause::ops get_op_code(const std::string &op)
357 {
358  DBGN(cerr << "Entering " << __PRETTY_FUNCTION__ << endl << "op: " << op << endl);
359 
360  if (op == "<")
361  return D4FilterClause::less;
362  else if (op == ">")
363  return D4FilterClause::greater;
364  else if (op == "<=")
365  return D4FilterClause::less_equal;
366  else if (op == ">=")
367  return D4FilterClause::greater_equal;
368  else if (op == "==")
369  return D4FilterClause::equal;
370  else if (op == "!=")
371  return D4FilterClause::not_equal;
372  else if (op == "~=")
373  return D4FilterClause::match;
374  else
375  throw Error(malformed_expr, "The opertator '" + op + "' is not supported.");
376 }
377 
399 void D4ConstraintEvaluator::add_filter_clause(const std::string &op, const std::string &arg1, const std::string &arg2)
400 {
401  DBG(cerr << "Entering: " << __PRETTY_FUNCTION__ << endl);
402 
403  // Check that there really is a D4Sequence associated with this filter clause.
404  D4Sequence *s = dynamic_cast<D4Sequence*>(top_basetype());
405  if (!s)
406  throw Error(malformed_expr,
407  "When a filter expression is used, it must be bound to a Sequence variable: " + expr_msg(op, arg1, arg2));
408 
409  DBG(cerr << "s->name(): " << s->name() << endl);
410 
411  // Check that arg1 and 2 are valid
412  BaseType *a1 = s->var(arg1);
413  BaseType *a2 = s->var(arg2);
414  DBG(cerr << "a1: " << a1 << ", a2: " << a2 << endl);
415 
416  if (a1 && a2)
417  throw Error(malformed_expr,
418  "One of the arguments in a filter expression must be a constant: " + expr_msg(op, arg1, arg2));
419  if (!(a1 || a2))
420  throw Error(malformed_expr,
421  "One of the arguments in a filter expression must be a variable in a Sequence: "
422  + expr_msg(op, arg1, arg2));
423 
424  // Now we know a1 XOR a2 is true
425  if (a1) {
426  s->clauses().add_clause(new D4FilterClause(get_op_code(op), new D4RValue(a1), D4RValueFactory(arg2)));
427  }
428  else {
429  s->clauses().add_clause(new D4FilterClause(get_op_code(op), D4RValueFactory(arg1), new D4RValue(a2)));
430  }
431 }
432 
439 string &
440 D4ConstraintEvaluator::remove_quotes(string &s)
441 {
442  if (*s.begin() == '\"' && *(s.end() - 1) == '\"') {
443  s.erase(s.begin());
444  s.erase(s.end() - 1);
445  }
446 
447  return s;
448 }
449 
450 // This method is called from the parser (see d4_ce_parser.yy, down in the code
451 // section). This will be called during the call to D4CEParser::parse(), that
452 // is inside D4ConstraintEvaluator::parse(...)
453 //
454 // Including the value passed in for 'l' allows the CE text to leak into
455 // the error message, a potential XSS attack vector. jhrg 4/15/20
456 void D4ConstraintEvaluator::error(const libdap::location &, const std::string &m)
457 {
458  ostringstream oss;
459 #if PREVENT_XXS_VIA_CE
460  oss << "Constraint expression parse error: " << m << ends;
461 #else
462  oss << l << ": " << m << ends;
463 #endif
464  throw Error(malformed_expr, oss.str());
465 }
466 
467 } /* namespace libdap */
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:277
D4Group * root()
Definition: DMR.cc:407
top level DAP object to house generic methods
Definition: AISConnect.cc:30
std::vector< dimension >::iterator Dim_iter
Definition: Array.h:206
D4RValue * D4RValueFactory(std::string cpps)
Build an appropriate RValue.
Definition: D4RValue.cc:218