[8182] | 1 | #include "oaiaction.h"
|
---|
| 2 | #include "oaitools.h"
|
---|
| 3 | #include "OIDtools.h"
|
---|
| 4 | #include "recptconfig.h"
|
---|
| 5 |
|
---|
| 6 | #include <string>
|
---|
[8241] | 7 | #if defined(GSDL_USE_IOS_H)
|
---|
| 8 | # if defined(__WIN32__)
|
---|
| 9 | # include <strstrea.h> // vc4
|
---|
| 10 | # else
|
---|
| 11 | # include <strstream.h>
|
---|
| 12 | # endif
|
---|
| 13 | #else
|
---|
| 14 | # include <sstream>
|
---|
| 15 | #endif
|
---|
[8182] | 16 |
|
---|
| 17 | #include <time.h>
|
---|
| 18 |
|
---|
| 19 | oaiaction::oaiaction(const text_t &name)
|
---|
| 20 | {
|
---|
| 21 | this->logout = new ofstream("oai.log", ios::app);
|
---|
| 22 | this->configuration = NULL;
|
---|
| 23 | this->name = name;
|
---|
| 24 | }
|
---|
| 25 |
|
---|
| 26 | //----------------------------------------------------------------------------------------------
|
---|
| 27 |
|
---|
| 28 | // Over-ridden by child classes
|
---|
| 29 | bool oaiaction::validateAction(recptproto *protocol, oaiargs ¶ms)
|
---|
| 30 | {
|
---|
| 31 | this->errorType = "";
|
---|
| 32 | return true;
|
---|
| 33 | }
|
---|
| 34 |
|
---|
| 35 | //----------------------------------------------------------------------------------------------
|
---|
| 36 |
|
---|
| 37 | /**********
|
---|
| 38 | * Compare the supplied metadataPrefix to all those that
|
---|
| 39 | * are supported. If there is NO match, return true. If
|
---|
| 40 | * it DOES match one, return false.
|
---|
| 41 | */
|
---|
| 42 | bool oaiaction::formatNotSupported(text_t &metaFormat)
|
---|
| 43 | {
|
---|
| 44 | if (metaFormat == "oai_dc") return false;
|
---|
| 45 | else if (metaFormat == "rfc1807") return false;
|
---|
| 46 |
|
---|
| 47 | return true;
|
---|
| 48 | }
|
---|
| 49 |
|
---|
| 50 | //----------------------------------------------------------------------------------------------
|
---|
| 51 |
|
---|
| 52 | /**********
|
---|
| 53 | * Function for outputting the appropriate error(s) with the (version 2.0) request.
|
---|
| 54 | * The error(s) MUST be included in the response, and take the form:
|
---|
| 55 | * <error code="errorType">Description of error</error>
|
---|
| 56 | */
|
---|
| 57 | void oaiaction::output_error(ostream &output, text_t &errorType)
|
---|
| 58 | {
|
---|
| 59 | text_t description = "";
|
---|
| 60 |
|
---|
| 61 | if(errorType == "badArgument"){
|
---|
| 62 | description = "The request includes illegal arguments, is missing required arguments, repeats arguments or the value for an argument has an illegal syntax";
|
---|
| 63 | }
|
---|
| 64 | else if(errorType == "noRecordsMatch"){
|
---|
| 65 | description = "No record matches all the requested parameters";
|
---|
| 66 | }
|
---|
| 67 | else if(errorType == "cannotDisseminateFormat"){
|
---|
| 68 | description = "The metadata format specified is not supported by the item or by the repository";
|
---|
| 69 | }
|
---|
| 70 | else if(errorType == "idDoesNotExist"){
|
---|
| 71 | description = "The value of the identifier is unknown or illegal in this repository";
|
---|
| 72 | }
|
---|
| 73 | else if(errorType == "badVerb"){
|
---|
| 74 | description = "Value of the verb argument is illegal, missing, or repeated";
|
---|
| 75 | }
|
---|
| 76 | else if(errorType == "noMetadataFormats"){
|
---|
| 77 | description = "There are no metadata formats available for the item";
|
---|
| 78 | }
|
---|
| 79 | else if(errorType == "badResumptionToken"){
|
---|
| 80 | description = "The value of the resumptionToken argument is invalid or expired";
|
---|
| 81 | }
|
---|
| 82 | else if(errorType == "noSetHierarchy"){
|
---|
| 83 | description = "The repository does not support sets";
|
---|
| 84 | }
|
---|
| 85 |
|
---|
| 86 | output << " <error code=\"" << errorType << "\">" << description << "</error>\n";
|
---|
| 87 | }
|
---|
| 88 |
|
---|
| 89 | //----------------------------------------------------------------------------------------------
|
---|
| 90 |
|
---|
| 91 | text_t oaiaction::getName()
|
---|
| 92 | {
|
---|
| 93 | return this->name;
|
---|
| 94 | }
|
---|
| 95 |
|
---|
| 96 | //----------------------------------------------------------------------------------------------
|
---|
| 97 |
|
---|
| 98 | /**********
|
---|
| 99 | * Used in version 2.0 to provide the <datestamp> tag for each document. The function is passed
|
---|
| 100 | * the 'last modified' date of the file in epoch time (time in seconds since 1970-01-01 on Unix
|
---|
| 101 | * systems) and converts it to YYYY-MM-DD format.
|
---|
| 102 | */
|
---|
| 103 | text_t oaiaction::parseDatestamp(time_t &rawtime)
|
---|
| 104 | {
|
---|
| 105 | text_t year, month, day, lastModified;
|
---|
| 106 | tm *ptm;
|
---|
| 107 | ptm = gmtime(&rawtime);
|
---|
| 108 | int raw_month = ptm->tm_mon + 1;
|
---|
| 109 | int raw_day = ptm->tm_mday;
|
---|
| 110 |
|
---|
| 111 | year = (ptm->tm_year + 1900); // Takes off 1900 for year by default, so replace it to get YYYY format
|
---|
| 112 |
|
---|
| 113 | // Need the month in MM format, so if month is 1..9, add a 0 to the front
|
---|
| 114 | if(raw_month < 10){
|
---|
| 115 | month = "0";
|
---|
| 116 | month += raw_month;
|
---|
| 117 | }
|
---|
| 118 | else month = raw_month;
|
---|
| 119 |
|
---|
| 120 | if(raw_day < 10){
|
---|
| 121 | day = "0";
|
---|
| 122 | day += raw_day;
|
---|
| 123 | }
|
---|
| 124 | else day = raw_day;
|
---|
| 125 |
|
---|
| 126 | lastModified = year + "-" + month + "-" + day;
|
---|
| 127 |
|
---|
| 128 | return lastModified;
|
---|
| 129 | }
|
---|
| 130 |
|
---|
| 131 | //----------------------------------------------------------------------------------------------
|
---|
| 132 | /**********
|
---|
| 133 | * Used by both versions to get the date & time of the client's request. The <responseDate> tag is
|
---|
| 134 | * in the format YYYY-MM-DDTHH:MM:SSZ, where T is simply the letter 'T' and Z is the local offset from
|
---|
| 135 | * GMT (now UTC). Examples of Z are +12:00 (NZ) or -03:00 for version 1.1. In version 2.0, the time
|
---|
| 136 | * is expected to be in UTC, and Z should simply be the character 'Z'.
|
---|
| 137 | */
|
---|
| 138 | void oaiaction::getResponseDate(text_t &date)
|
---|
| 139 | {
|
---|
| 140 | time_t rawtime;
|
---|
| 141 | tm *ptm;
|
---|
| 142 |
|
---|
| 143 | time(&rawtime); // Get the epoch time
|
---|
| 144 |
|
---|
| 145 | ptm = gmtime(&rawtime); // Convert to UTC-time formatted tm object
|
---|
| 146 |
|
---|
| 147 | text_t month, day, hour, minute, second;
|
---|
| 148 | int raw_month = ptm->tm_mon + 1; // Note Jan = 0 ... Dec = 11, so add 1
|
---|
| 149 | int raw_day = ptm->tm_mday;
|
---|
| 150 | int raw_hour = ptm->tm_hour;
|
---|
| 151 | int raw_minute = ptm->tm_min;
|
---|
| 152 | int raw_second = ptm->tm_sec;
|
---|
| 153 |
|
---|
| 154 | // Need the month in MM format, so if month is 1..9, add a 0 to the front
|
---|
| 155 | if(raw_month < 10){
|
---|
| 156 | month = "0";
|
---|
| 157 | }
|
---|
| 158 | month += raw_month;
|
---|
| 159 |
|
---|
| 160 | // Same for days, hours, minutes and seconds
|
---|
| 161 | if(raw_day < 10){
|
---|
| 162 | day = "0";
|
---|
| 163 | }
|
---|
| 164 | day += raw_day;
|
---|
| 165 |
|
---|
| 166 | if(raw_hour < 10){
|
---|
| 167 | hour = "0";
|
---|
| 168 | }
|
---|
| 169 | hour += raw_hour;
|
---|
| 170 |
|
---|
| 171 | if(raw_minute < 10){
|
---|
| 172 | minute = "0";
|
---|
| 173 | }
|
---|
| 174 | minute += raw_minute;
|
---|
| 175 |
|
---|
| 176 | if(raw_second < 10){
|
---|
| 177 | second = "0";
|
---|
| 178 | }
|
---|
| 179 | second += raw_second;
|
---|
| 180 |
|
---|
| 181 | // Want YYYY-MM-DDTHH:MM:SS+UTC, where UTC is num hours from UTC(GMT) to localtime
|
---|
| 182 | date += (ptm->tm_year + 1900); // Takes off 1900 for year, so replace it to get YYYY format
|
---|
| 183 | date += "-";
|
---|
| 184 | date += month;
|
---|
| 185 | date += "-";
|
---|
| 186 | date += day;
|
---|
| 187 | date += "T";
|
---|
| 188 | date += hour;
|
---|
| 189 | date += ":";
|
---|
| 190 | date += minute;
|
---|
| 191 | date += ":";
|
---|
| 192 | date += second;
|
---|
| 193 | // If we're using v1.1, then tack on local time offset, otherwise don't
|
---|
| 194 | if(this->configuration->getOAIVersion() == 110){
|
---|
| 195 | date += _LOCALTIME_; // Defined in oaiaction.h. States how many hours localtime is from
|
---|
| 196 | // UTC (GMT), e.g. "+8:00", "-5:00"
|
---|
| 197 | }
|
---|
| 198 | else
|
---|
| 199 | date += "Z"; // If v2.0, we put 'Z' on the end rather than the localtime offset
|
---|
| 200 | }
|
---|
| 201 |
|
---|
| 202 | //----------------------------------------------------------------------------------------------
|
---|
| 203 | /**********
|
---|
| 204 | * Does different request tags depending on the version of the OAI protocol running
|
---|
| 205 | */
|
---|
| 206 | void oaiaction::getRequestURL(oaiargs ¶ms, text_t &requestURL)
|
---|
| 207 | {
|
---|
| 208 | // Iterators for moving through the list of parameters (keys) specified
|
---|
| 209 | text_tmap::const_iterator here;
|
---|
| 210 | text_tmap::const_iterator end;
|
---|
| 211 | int numArgs = params.getSize();
|
---|
| 212 | text_t gsdlhome, httpdomain, httpprefix;
|
---|
| 213 |
|
---|
| 214 | here = params.begin();
|
---|
| 215 | end = params.end();
|
---|
| 216 |
|
---|
| 217 | // Read stuff from the gsdlsite.cfg file. We do this to get the base URL (httpdomain)
|
---|
| 218 | site_cfg_read(gsdlhome, httpdomain, httpprefix);
|
---|
| 219 |
|
---|
| 220 | int version = this->configuration->getOAIVersion();
|
---|
| 221 |
|
---|
| 222 | switch(version){
|
---|
| 223 | case 110:
|
---|
| 224 | /* Takes the form:
|
---|
| 225 | * <requestURL>http://baseURL.com/oaimain?verb="someVerb"&key=value</requestURL>
|
---|
| 226 | */
|
---|
| 227 | requestURL = " <requestURL>" + httpdomain + "/oaimain";
|
---|
| 228 |
|
---|
| 229 | if(numArgs == 0) break; // If no args, all done - the error will be picked up later
|
---|
| 230 |
|
---|
| 231 | // The following lines will give us the "label=value" syntax
|
---|
| 232 | requestURL += "?";
|
---|
| 233 | requestURL += here->first;
|
---|
| 234 | requestURL += "=";
|
---|
| 235 | requestURL += html_safe(here->second); // parse the argument to ensure it is URL-encoding compliant
|
---|
[9608] | 236 | ++here;
|
---|
[8182] | 237 |
|
---|
| 238 | while(here != end){
|
---|
| 239 | requestURL +="&"; // Stick in the ampersand in URL encoding
|
---|
| 240 | requestURL += (here->first + "=" + html_safe(here->second));
|
---|
[9608] | 241 | ++here;
|
---|
[8182] | 242 | }
|
---|
| 243 | requestURL += "</requestURL>\n";
|
---|
| 244 | break;
|
---|
| 245 |
|
---|
| 246 | case 200:
|
---|
| 247 | default:
|
---|
| 248 | /* Takes the form:
|
---|
| 249 | * <request verb="someVerb" key="value" key="value">
|
---|
| 250 | * http://baseURL.com/oaimain</request>
|
---|
| 251 | */
|
---|
| 252 | if(numArgs == 0) {
|
---|
| 253 | requestURL = " <request>" + httpdomain + "/oaimain</request>\n";
|
---|
| 254 | break;
|
---|
| 255 | }
|
---|
| 256 | requestURL = " <request " + here->first + "=\"" + here->second + "\"";
|
---|
[9608] | 257 | ++here;
|
---|
[8182] | 258 | while(here != end){
|
---|
| 259 | requestURL += (" " + here->first + "=\"" + here->second + "\"");
|
---|
[9608] | 260 | ++here;
|
---|
[8182] | 261 | }
|
---|
| 262 | requestURL += ">\n " + httpdomain + "/oaimain</request>\n";
|
---|
| 263 | break;
|
---|
| 264 | }
|
---|
| 265 | }
|
---|
| 266 |
|
---|
| 267 | //----------------------------------------------------------------------------------------------
|
---|
| 268 | /**********
|
---|
| 269 | * Send the (OAI version-dependent) response text to the output stream
|
---|
| 270 | */
|
---|
| 271 | void oaiaction::getResponse(ostream &output, recptproto *protocol, oaiargs ¶ms)
|
---|
| 272 | {
|
---|
| 273 | bool error;
|
---|
| 274 | text_t date, requestURL;
|
---|
| 275 |
|
---|
| 276 | // Write the response date & time into 'date'
|
---|
| 277 | this->getResponseDate(date);
|
---|
| 278 | int version = this->configuration->getOAIVersion();
|
---|
| 279 |
|
---|
| 280 | // validate the action
|
---|
| 281 | error = !this->validateAction(protocol, params);
|
---|
| 282 |
|
---|
| 283 | // raise an error for duplicated arguments and set the
|
---|
| 284 | // error type "manually" here...
|
---|
| 285 | if (params.hasDuplicateArg() && !error) {
|
---|
| 286 | this->errorType = "badArgument";
|
---|
| 287 | error = true;
|
---|
| 288 | }
|
---|
| 289 |
|
---|
| 290 | // start with the required http header
|
---|
| 291 | if (version <= 110 && error){
|
---|
| 292 | output << "Status: 400 " << this->errorType << "\n";
|
---|
| 293 | output << "Content-Type: text/xml\n\n";
|
---|
| 294 | return;
|
---|
| 295 | }
|
---|
| 296 |
|
---|
| 297 | output << "Status: 200\n";
|
---|
| 298 | output << "Content-Type: text/xml\n\n";
|
---|
| 299 |
|
---|
| 300 | // output xml header parts
|
---|
| 301 | output << "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n";
|
---|
| 302 |
|
---|
| 303 | if(version <= 110){
|
---|
| 304 | // output OAI v1.1 action header tag
|
---|
| 305 | output << "<" << this->name;
|
---|
| 306 | output << "\n xmlns=\"http://www.openarchives.com/OAI/1.1/OAI_" << this->name << "\" ";
|
---|
| 307 | output << "\n xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" ";
|
---|
| 308 | output << "\n xsi:schemaLocation=\"http://www.openarchives.org/OAI/1.1/OAI_" << this->name;
|
---|
| 309 | output << "\n http://www.openarchives.org/OAI/1.1/OAI_" << this->name << ".xsd\">\n";
|
---|
| 310 | }
|
---|
| 311 | else {
|
---|
| 312 | // output OAI v2.0 action header tag
|
---|
| 313 | output << "<OAI-PMH xmlns=\"http://www.openarchives.com/OAI/2.0\"\n"
|
---|
| 314 | << " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
|
---|
| 315 | << " xsi:schemaLocation=\"http://www.openarchives.org/OAI/2.0/\n"
|
---|
| 316 | << " http://www.openarchives.org/OAI/2.0/OAI-PMH.xsd\">\n";
|
---|
| 317 | }
|
---|
| 318 | // output current time for response
|
---|
| 319 | output << " <responseDate>" << date << "</responseDate>\n";
|
---|
| 320 |
|
---|
| 321 | // output request URL. This differs depending on the OAI protocol version currently running, so
|
---|
| 322 | // the entire field - including tags - must be put into the text_t variable by getRequestURL()
|
---|
| 323 | this->getRequestURL(params, requestURL);
|
---|
| 324 |
|
---|
| 325 | output << requestURL ;
|
---|
| 326 |
|
---|
| 327 | if (error == false) {
|
---|
| 328 | // a string stream to write the content of the action to; this is done so that we can
|
---|
| 329 | // avoid outputting the action tag if the action's body is unsuccessful, in which
|
---|
| 330 | // case the leading tag must be suppressed
|
---|
[8306] | 331 | #if defined(GSDL_USE_IOS_H)
|
---|
| 332 | ostrstream outstream;
|
---|
| 333 | #else
|
---|
[8182] | 334 | ostringstream outstream;
|
---|
[8306] | 335 | #endif
|
---|
[8182] | 336 |
|
---|
| 337 | // Version 2.0 needs an <Identify>, etc. tag after the OAI-PMH header, IF there is no error
|
---|
| 338 | //
|
---|
| 339 | // An action that outputs no content should raise an error state to suppress the
|
---|
| 340 | // matching opening and close tags if it outputs no content in OAI 2.0
|
---|
| 341 | error = !this->output_content(outstream, protocol, params);
|
---|
| 342 |
|
---|
| 343 | // output the leading tag if no error occurred
|
---|
| 344 | if (error == false) {
|
---|
| 345 | if (version >= 200) {
|
---|
| 346 | this->output_action_tag(output, true);
|
---|
| 347 | }
|
---|
| 348 | }
|
---|
| 349 |
|
---|
| 350 | // now output the body of the action content
|
---|
[8316] | 351 | #if defined(GSDL_USE_IOS_H)
|
---|
[8308] | 352 | outstream << ends; // Ensure outstream is null-terminated correctly
|
---|
[8316] | 353 | #endif
|
---|
[8182] | 354 | output << outstream.str();
|
---|
| 355 | }
|
---|
| 356 | else {
|
---|
| 357 | if (version >= 200) {
|
---|
| 358 | this->output_error(output, this->errorType);
|
---|
| 359 | }
|
---|
| 360 | }
|
---|
| 361 |
|
---|
| 362 | // close out our response - both versions need this line, but v2.0 only needs it if there was no error
|
---|
| 363 | if((version == 110) || (version >= 200 && error == false)){
|
---|
| 364 | this->output_action_tag(output, false);
|
---|
| 365 | }
|
---|
| 366 | if(version >= 200){
|
---|
| 367 | output << "</OAI-PMH>\n";
|
---|
| 368 | }
|
---|
| 369 | }
|
---|
| 370 |
|
---|
| 371 | void oaiaction::output_action_tag(ostream &output, bool openTag)
|
---|
| 372 | {
|
---|
| 373 | output << " <";
|
---|
| 374 | if (!openTag) {
|
---|
| 375 | output << "/";
|
---|
| 376 | }
|
---|
| 377 | output << this->name << ">" << endl;
|
---|
| 378 | }
|
---|
| 379 |
|
---|
| 380 | void oaiaction::output_record_header(ostream &output, const text_t &oaiLabel, const text_t &lastModified,
|
---|
| 381 | const text_tarray &memberOf, int oaiVersion)
|
---|
| 382 | {
|
---|
| 383 | output << " <header>" << endl;
|
---|
| 384 | output << " <identifier>" << oaiLabel << "</identifier>" << endl;
|
---|
| 385 | output << " <datestamp>" << lastModified << "</datestamp>" << endl;
|
---|
| 386 |
|
---|
| 387 | // copy the collection name off the front of oaiLabel
|
---|
| 388 | text_t::const_iterator colon = findchar(oaiLabel.begin(), oaiLabel.end(), ':');
|
---|
| 389 | text_t collection = substr(oaiLabel.begin(), colon);
|
---|
| 390 |
|
---|
| 391 | if(oaiVersion >= 200){
|
---|
| 392 | text_tarray::const_iterator member = memberOf.begin();
|
---|
| 393 | text_tarray::const_iterator memEnd = memberOf.end();
|
---|
| 394 |
|
---|
| 395 | // As well as all the subsets that a doc appears in, it is also a member of the 'collection' set
|
---|
| 396 | output << " <setSpec>" << collection << "</setSpec>" << endl;
|
---|
| 397 | while (member != memEnd) {
|
---|
| 398 | text_t oaiSet = *member;
|
---|
| 399 | oaiclassifier::toOAI(collection, oaiSet);
|
---|
| 400 | output << " <setSpec>" << oaiSet << "</setSpec>" << endl;
|
---|
[9608] | 401 | ++member;
|
---|
[8182] | 402 | }
|
---|
| 403 | }
|
---|
| 404 | output << " </header>" << endl;
|
---|
| 405 | }
|
---|
| 406 |
|
---|
| 407 | void oaiaction::getLastModifiedDate(ResultDocInfo_t &doc_info, text_t &lastModified)
|
---|
| 408 | {
|
---|
| 409 | text_t temp;
|
---|
| 410 |
|
---|
| 411 |
|
---|
| 412 | MetadataInfo_tmap::iterator current = doc_info.metadata.begin();
|
---|
| 413 | MetadataInfo_tmap::iterator end = doc_info.metadata.end();
|
---|
| 414 |
|
---|
| 415 | while(current != end){
|
---|
| 416 | temp = current->first;
|
---|
| 417 | lc(temp);
|
---|
| 418 | if(temp == "dc.date"){
|
---|
| 419 | lastModified = current->second.values[0];
|
---|
| 420 | return;
|
---|
| 421 | }
|
---|
| 422 | else{
|
---|
| 423 | if (temp == "lastmodified" && lastModified == "" && current->second.values.size() >= 1) {
|
---|
| 424 | lastModified = current->second.values[0];
|
---|
| 425 | time_t raw_time = (time_t)lastModified.getint();
|
---|
| 426 | lastModified = this->parseDatestamp(raw_time);
|
---|
| 427 | return;
|
---|
| 428 | }
|
---|
| 429 | }
|
---|
[9608] | 430 | ++current;
|
---|
[8182] | 431 | }
|
---|
| 432 | }
|
---|
| 433 |
|
---|
| 434 | bool oaiaction::inDateRange(const text_t &from, const text_t &until, const text_t &collection,
|
---|
| 435 | const text_t &OID, recptproto *protocol, ostream &logout)
|
---|
| 436 | {
|
---|
| 437 | FilterResponse_t response;
|
---|
| 438 | text_tset metadata;
|
---|
| 439 | bool status_ok = get_info(OID, collection, "", metadata, false, protocol, response, logout);
|
---|
| 440 | bool not_too_early = false, not_too_recent = false;
|
---|
| 441 |
|
---|
| 442 | if(status_ok) {
|
---|
| 443 | ResultDocInfo_t doc_info = response.docInfo[0];
|
---|
| 444 | text_t lastModDate;
|
---|
| 445 | this->getLastModifiedDate(doc_info, lastModDate);
|
---|
| 446 |
|
---|
| 447 | // All date fields should be in the form YYYY-MM-DD, which allows for a direct comparison
|
---|
| 448 | if(from != ""){
|
---|
| 449 | if(from <= lastModDate)
|
---|
| 450 | not_too_early = true;
|
---|
| 451 | }
|
---|
| 452 | else
|
---|
| 453 | not_too_early = true; // If there's no FROM field, then the record can't be too early
|
---|
| 454 |
|
---|
| 455 | if(until != ""){
|
---|
| 456 | if(lastModDate <= until)
|
---|
| 457 | not_too_recent = true;
|
---|
| 458 | }
|
---|
| 459 | else
|
---|
| 460 | not_too_recent = true; // If there's no UNTIL field, then the record can't be too recent
|
---|
| 461 |
|
---|
| 462 | if(not_too_early && not_too_recent)
|
---|
| 463 | return true;
|
---|
| 464 | else
|
---|
| 465 | return false;
|
---|
| 466 | }
|
---|
| 467 | else
|
---|
| 468 | return false;
|
---|
| 469 | }
|
---|