root/main/trunk/greenstone2/runtime-src/src/recpt/sqlqueryaction.cpp @ 28899

Revision 28899, 13.5 KB (checked in by ak19, 4 years ago)

Third commit for security, for ensuring cgiargs macros are websafe. This time all the changes to the runtime action classes.

Line 
1/**********************************************************************
2 *
3 * sqlqueryaction.cpp --
4 * Copyright (C) 2010  The New Zealand Digital Library Project
5 *
6 * A component of the Greenstone digital library software
7 * from the New Zealand Digital Library Project at the
8 * University of Waikato, New Zealand.
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23 *
24 *********************************************************************/
25
26#include "sqlqueryaction.h"
27#include "querytools.h"
28#include "formattools.h"
29#include "cgiutils.h"
30#include "OIDtools.h"
31#include "fileutil.h"
32#include "text_t.h"
33#include "historydb.h"
34#include "htmlutils.h" // for html_safe in do_action
35#include "gsdltools.h"
36#include <stdlib.h> // for strtol
37#include <assert.h>
38
39
40sqlqueryaction::sqlqueryaction ()
41  : basequeryaction()
42{
43  cgiarginfo arg_ainfo;
44
45  // this action uses cgi variable "a"
46  arg_ainfo.shortname = "a";
47  arg_ainfo.longname = "action";
48  arg_ainfo.multiplechar = true;
49  arg_ainfo.multiplevalue = false;
50  arg_ainfo.defaultstatus = cgiarginfo::weak;
51  arg_ainfo.argdefault = "sqlq";
52  arg_ainfo.savedarginfo = cgiarginfo::must;
53  argsinfo.addarginfo (NULL, arg_ainfo);
54
55  // "sqlqto" - 0 = not available, 1 = available
56  arg_ainfo.shortname = "sqlqto";
57  arg_ainfo.longname = "sqlquery type options";
58  arg_ainfo.multiplechar = true; // can be empty or single char
59  arg_ainfo.multiplevalue = false;
60  arg_ainfo.defaultstatus = cgiarginfo::weak;
61  arg_ainfo.argdefault = g_EmptyText;
62  arg_ainfo.savedarginfo = cgiarginfo::must;
63  argsinfo.addarginfo (NULL, arg_ainfo);
64   
65  // "sqlfqn" - number of fields in the query form
66  arg_ainfo.shortname = "sqlfqn";
67  arg_ainfo.longname = "sql form query num fields";
68  arg_ainfo.multiplechar = true;
69  arg_ainfo.multiplevalue = false;
70  arg_ainfo.defaultstatus = cgiarginfo::weak;
71  arg_ainfo.argdefault = "4";
72  arg_ainfo.savedarginfo = cgiarginfo::must;
73  argsinfo.addarginfo (NULL, arg_ainfo);
74
75  // "sqlfqf" - the list of field names in the form query
76  // - a comma separated list
77  arg_ainfo.shortname = "sqlfqf";
78  arg_ainfo.longname = "sql form query fields";
79  arg_ainfo.multiplechar = true;
80  arg_ainfo.multiplevalue = false;
81  arg_ainfo.defaultstatus = cgiarginfo::weak;
82  arg_ainfo.argdefault = g_EmptyText;
83  arg_ainfo.savedarginfo = cgiarginfo::must;
84  argsinfo.addarginfo (NULL, arg_ainfo);
85
86  // "sqlfqv" - the list of values in the form query
87  // - a comma separated list
88  arg_ainfo.shortname = "sqlfqv";
89  arg_ainfo.longname = "sql form query values";
90  arg_ainfo.multiplechar = true;
91  arg_ainfo.multiplevalue = false;
92  arg_ainfo.defaultstatus = cgiarginfo::weak;
93  arg_ainfo.argdefault = g_EmptyText;
94  arg_ainfo.savedarginfo = cgiarginfo::must;
95  argsinfo.addarginfo (NULL, arg_ainfo);
96
97  // "sqlfqc" - the list of boolean operators in the form query
98  // - a comma separated list
99  arg_ainfo.shortname = "sqlfqc";
100  arg_ainfo.longname = "sql form query combines";
101  arg_ainfo.multiplechar = true;
102  arg_ainfo.multiplevalue = false;
103  arg_ainfo.defaultstatus = cgiarginfo::weak;
104  arg_ainfo.argdefault = g_EmptyText;
105  arg_ainfo.savedarginfo = cgiarginfo::must;
106  argsinfo.addarginfo (NULL, arg_ainfo);
107
108  // ****
109  // Looks like 'multiplevalue' is left undefined (e.g. not set in
110  // constructor).  This looks like a fairly major problem
111  // in terms of leaving it undefine
112  // => in the case of "sqlsf" leads to conflict over value it has
113
114  // "sqlsf" - Sort field. Set to field to be used for sorting search reult
115  // set
116  arg_ainfo.shortname = "sqlsf";
117  arg_ainfo.longname = "sql sort field";
118  arg_ainfo.multiplechar = true;
119  arg_ainfo.multiplevalue = false;
120  arg_ainfo.defaultstatus = cgiarginfo::weak;
121  arg_ainfo.argdefault = g_EmptyText;
122  arg_ainfo.savedarginfo = cgiarginfo::must;
123  argsinfo.addarginfo (NULL, arg_ainfo);
124
125   // "sqlfilter" - boolean flag to control whether "delete all" or
126    //              "keep all" functionality is available
127  arg_ainfo.shortname = "sqlfilter";
128  arg_ainfo.longname = "whether sql filtering is active or not";
129  arg_ainfo.multiplechar = false;
130  arg_ainfo.multiplevalue = false;
131  arg_ainfo.defaultstatus = cgiarginfo::weak;
132  arg_ainfo.argdefault = "0";
133  arg_ainfo.savedarginfo = cgiarginfo::must;
134  argsinfo.addarginfo (NULL, arg_ainfo);
135}
136
137sqlqueryaction::~sqlqueryaction ()
138{
139}
140
141void sqlqueryaction::configure (const text_t &key, const text_tarray &cfgline) {
142  action::configure (key, cfgline);
143}
144
145bool sqlqueryaction::init (ostream &logout)
146{
147  return basequeryaction::init (logout);
148}
149
150
151bool sqlqueryaction::check_cgiargs (cgiargsinfoclass &argsinfo,
152                    cgiargsclass &args,
153                    recptprotolistclass* protos,
154                    ostream &logout)
155{
156
157  // check sqlfqn argument
158  int arg_sqlfqn = args.getintarg("sqlfqn");
159  if (arg_sqlfqn < -1) {
160    logout << "Warning: \"sqlfqn\" argument less than -1 (" << arg_sqlfqn << ")\n";
161    cgiarginfo *sqlfqninfo = argsinfo.getarginfo ("sqlfqn");
162    if (sqlfqninfo != NULL) args["sqlfqn"] = sqlfqninfo->argdefault;
163  }
164
165  return basequeryaction::check_cgiargs(argsinfo,args,protos,logout);
166}
167
168
169
170void sqlqueryaction::define_external_macros (displayclass &disp,
171                         cgiargsclass &args,
172                         recptprotolistclass *protos,
173                         ostream &logout)
174{
175  recptproto *collectproto = protos->getrecptproto (args["c"], logout);
176  if (collectproto == NULL) return;
177
178  ColInfoResponse_t *colinfo = recpt->get_collectinfo_ptr(collectproto,
179                              args["c"],
180                              logout);
181  comerror_t err;
182  InfoFilterOptionsResponse_t response;
183  InfoFilterOptionsRequest_t request;
184  request.filterName = "SQLQueryFilter";
185 
186  collectproto->get_filteroptions (args["c"], request, response, err, logout);
187  if (err == noError) {
188    FilterOption_tmap::const_iterator it_dom;
189    FilterOption_tmap::const_iterator it_ran;
190    FilterOption_tmap::const_iterator end = response.filterOptions.end();
191
192    // _sqlfqfselection_ field list
193    it_dom = response.filterOptions.find("IndexFieldDomain");
194    it_ran = response.filterOptions.find("IndexFieldRange");
195    if ((it_dom!=end) && (it_ran!=end)) {
196
197      set_option_macro ("sqlfqf",args["sqlfqf"], true,true,
198                (*it_dom).second, (*it_ran).second, disp);
199
200      if (args["b"] == "1") {
201    // set the sort field macro
202    set_sfselection_macro(args["sqlsf"], (*it_dom).second,(*it_ran).second,
203                  disp);
204      }
205    }
206  }
207
208
209}
210
211
212void sqlqueryaction::set_sfselection_macro(text_t current_value,
213                       const FilterOption_t &option_domain,
214                       const FilterOption_t &option_range,
215                       displayclass &disp)
216{
217
218  // we need at least one option here to continue
219  if (option_domain.validValues.size() < 1) { return; }
220  if (option_range.validValues.size() < 1) { return; }
221
222  text_t macrovalue = "<select name=\"sqlsf\">\n";
223 
224  if (current_value.empty()) current_value = "";
225 
226  text_tarray::const_iterator dom_thisvalue = option_domain.validValues.begin();
227  text_tarray::const_iterator dom_endvalue = option_domain.validValues.end();
228
229  text_tarray::const_iterator ran_thisvalue = option_range.validValues.begin();
230  text_tarray::const_iterator ran_endvalue = option_range.validValues.end();
231
232  int valid_count = 0;
233  while ((dom_thisvalue != dom_endvalue) && (ran_thisvalue != ran_endvalue)) {
234
235    if (*ran_thisvalue != "ZZ" && *ran_thisvalue != "TX") {
236      ++valid_count;
237
238      text_t option_val = *dom_thisvalue;
239      option_val.replace(",","/");
240
241      macrovalue += "<option value=\"" + option_val + "\"";
242      if (current_value == *dom_thisvalue)
243    macrovalue += " selected";
244      macrovalue += ">_" + *ran_thisvalue + "_\n";
245    }
246    ++dom_thisvalue;
247    ++ran_thisvalue;
248  }
249  macrovalue += "</select>";
250  if (valid_count > 0) {
251    disp.setmacro ("sqlsfselection", displayclass::defaultpackage, macrovalue);
252  } 
253}
254
255
256void sqlqueryaction::get_formatted_query_string (text_t& formattedstring,
257                          bool segment,
258                          cgiargsclass& args,
259                          displayclass& disp,
260                          ostream& logout)
261{
262  // A great many characters have meanings in SQL queries, including > and %,
263  // where % stands for a multi-char wildcard
264  // http://docs.oracle.com/cd/B10501_01/text.920/a96518/cqspcl.htm
265  // Further, Greenstone's Advanced SQLite Search allows <, >, %, ' (rounded brackets and more)
266  // So it's best to url-decode all encoded cgi-args
267  // We do so here if normal text search or explicit query, and in the
268  // parse_sql_query_form functions if dealing with forms.
269
270  if (args["qt"]=="0" && args["sqlqto"] != "1") { // normal text search
271    unsafe_cgi_arg("ALL", args["q"]);
272    formattedstring = "SELECT DISTINCT docOID FROM document_metadata WHERE " + encodeForSQL(args["q"]);   
273  }
274  else if (args["qt"]=="1" || args["sqlqto"]=="1"){ // form search
275
276    if (args["b"]=="1" && args["fqa"]=="1") { // explicit query
277      formattedstring = args["q"];
278      unsafe_cgi_arg("ALL", formattedstring);
279    }
280    else { // form search
281      // *****
282      // Consider making this its own method in basequeryaction
283      // then make parse_.*reg_query_form virtual method in class
284
285      if (args["b"]=="0") { // regular form
286    parse_sqlreg_query_form(formattedstring, args, segment);
287      }
288      else  { // advanced form
289    parse_sqladv_query_form(formattedstring, args, segment);
290      }
291      args["q"] = formattedstring;
292     
293      // reset the cgiargfqv macro - need to escape any quotes in it
294      disp.setmacro("cgiargfqv", "query", escape_quotes(args["fqv"]));
295
296      // also reset the _cgiargq_ macro as it has changed now
297      disp.setmacro("cgiargq", displayclass::defaultpackage, html_safe(args["q"]));
298
299      // reset the compressed options to include the q arg
300      text_t compressedoptions = recpt->get_compressed_arg(args, logout);
301      if (!compressedoptions.empty()) {
302    disp.setmacro ("compressedoptions", displayclass::defaultpackage, dm_safe(compressedoptions));
303    // need a decoded version of compressedoptions for use within forms
304    // as browsers encode values from forms before sending to server
305    // (e.g. %25 becomes %2525)
306    decode_cgi_arg (compressedoptions);
307    if (args["w"] == "utf-8") { // if the encoding was utf-8, then compressed options was utf-8, and we need unicode.
308    // if encoding wasn't utf-8, then compressed opotions may be screwed up, but seems to work for 8 bit encodings?
309      compressedoptions = to_uni(compressedoptions);
310    }
311   
312    text_t dmacrovalue = dm_safe(compressedoptions);
313    disp.setmacro ("decodedcompressedoptions", displayclass::defaultpackage, dmacrovalue);
314    disp.setmacro ("decodedcompressedoptionsAttrsafe", displayclass::defaultpackage, encodeForHTMLAttr(dmacrovalue));
315      }
316    } // form search
317  } // args["qt"]=1
318  else {
319    logout << "ERROR (sqlqueryaction::get_formatted_query_string): querytype not defined\n";
320  }
321
322}
323
324
325// request.filterResultOptions and request.fields (if required) should
326// be set from the calling code
327void sqlqueryaction::set_queryfilter_options (FilterRequest_t &request,
328                          const text_t &querystring,
329                          cgiargsclass &args)
330{
331  request.filterName = "SQLQueryFilter";
332
333  OptionValue_t option;
334
335  option.name = "SQLWhere";
336  option.value = querystring;
337  request.filterOptions.push_back (option);
338
339  set_sql_queryfilter_options(request, args);
340}
341
342
343
344// should this change for cross coll search??
345bool sqlqueryaction::save_search_history (cgiargsclass &args, int numdocs,
346                      isapprox isApprox)
347{
348
349
350  if (args["q"]=="") return true; // null query, dont save
351  if (args["hs"]=="0") return true; // only save when submit query pressed
352 
353  // get userid
354  text_t userid = args["z"];
355
356  // the number of docs goes on the front of the query string
357  text_t query = text_t(numdocs);
358  if (isApprox==MoreThan) { // there were more docs found
359    query.push_back('+');
360  }
361  query += "c="+args["c"];
362 
363  text_t qstring = args["q"];
364  //text_t formattedquery =cgi_safe(qstring);
365  //query += "&amp;q="+formattedquery;
366  query += ";q="+qstring;
367  bool display=false;
368  int hd = args.getintarg("hd");
369  if (hd > 0) display=true;
370  if (set_history_info(userid, query, dbhome, display)) return true;
371  else return false;
372
373}
374
375void sqlqueryaction::define_form_macros (displayclass &disp,
376                     cgiargsclass &args,
377                     recptprotolistclass *protos,
378                     ostream &logout)
379{
380  // defines the following macros
381  // _sqlregformlist_
382  // _sqladvformlist_
383
384  text_t form = "";
385  int argsqlfqn = args.getintarg("sqlfqn");
386 
387  if (args["b"] == "1") { // advanced form
388    form += "_firstsqladvformelement_\n";
389    for (int i=1; i<argsqlfqn; ++i) {     
390    form += "_sqladvformelement_\n";
391    }
392    disp.setmacro("sqladvformlist", "query", form);
393  }
394  else { // simple form
395    for (int i=0; i<argsqlfqn; ++i) {
396      form += "_sqlregformelement_\n";
397    }
398    disp.setmacro("sqlregformlist", "query", form);
399  }
400 
401}
402
403
404bool sqlqueryaction::do_action (cgiargsclass &args,
405                recptprotolistclass *protos,
406                browsermapclass *browsers, displayclass &disp,
407                outconvertclass &outconvert, ostream &textout,
408                ostream &logout)
409{
410  return search_single_collection (args, args["c"], protos, browsers, disp,
411                   outconvert, textout, logout);
412}
413
414
415
Note: See TracBrowser for help on using the browser.