bes Updated for version 3.21.1
The Backend Server (BES) is the lower two tiers of the Hyrax data server
GatewayPathInfoResponseHandler.cc
1// -*- mode: c++; c-basic-offset:4 -*-
2//
3// GatewayPathInfoResponseHandler.cc
4//
5// This file is part of BES dap package
6//
7// Copyright (c) 2015v OPeNDAP, Inc.
8// Author: Nathan Potter <ndp@opendap.org>
9//
10// This library is free software; you can redistribute it and/or
11// modify it under the terms of the GNU Lesser General Public
12// License as published by the Free Software Foundation; either
13// version 2.1 of the License, or (at your option) any later version.
14//
15// This library 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 GNU
18// Lesser General Public License for more details.
19//
20// You should have received a copy of the GNU Lesser General Public
21// License along with this library; if not, write to the Free Software
22// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
23//
24// You can contact OPeNDAP, Inc. at PO Box 112, Saunderstown, RI. 02874-0112.
25// Please read the full copyright statement in the file COPYRIGHT_URI.
26//
27
28#include "config.h"
29
30#include <sstream>
31#include <fstream>
32#include <time.h>
33
34#include <cerrno>
35#include <cstring>
36
37#include "GatewayPathInfoResponseHandler.h"
38
39#include "BESDebug.h"
40
41#include "BESInfoList.h"
42#include "BESInfo.h"
43#include "BESUtil.h"
44#include "BESRequestHandlerList.h"
45#include "BESRequestHandler.h"
46#include "BESNames.h"
47#include "BESDapNames.h"
48#include "BESDataNames.h"
49#include "BESCatalogList.h"
50#include "BESCatalog.h"
51#include "BESCatalogEntry.h"
52#include "BESCatalogUtils.h"
53#include "BESSyntaxUserError.h"
54#include "BESForbiddenError.h"
55#include "BESNotFoundError.h"
56#include "BESStopWatch.h"
57
58using std::endl;
59using std::map;
60using std::string;
61using std::list;
62using std::ostream;
63
64#define PATH_INFO_RESPONSE "PathInfo"
65#define PATH "path"
66#define VALID_PATH "validPath"
67#define REMAINDER "remainder"
68#define IS_DATA "isData"
69#define IS_FILE "isFile"
70#define IS_DIR "isDir"
71#define IS_ACCESSIBLE "access"
72#define SIZE "size"
73#define LMT "lastModified"
74
75#define prolog std::string("GatewayPathInfoResponseHandler::").append(__func__).append("() - ")
76
77GatewayPathInfoResponseHandler::GatewayPathInfoResponseHandler(const string &name) :
78 BESResponseHandler(name), _response(0)
79{
80}
81
82GatewayPathInfoResponseHandler::~GatewayPathInfoResponseHandler()
83{
84}
85
97{
98 BES_STOPWATCH_START(SPI_DEBUG_KEY, prolog + "Timer");
99
100 BESDEBUG(SPI_DEBUG_KEY, prolog << "BEGIN" << endl );
101
102 BESInfo *info = BESInfoList::TheList()->build_info();
103 _response = info;
104
105 string container = dhi.data[CONTAINER];
106#if 0
107 string catname;
108 string defcatname = BESCatalogList::TheCatalogList()->default_catalog_name();
109#endif
110
111 BESCatalog *defcat = BESCatalogList::TheCatalogList()->default_catalog();
112 if (!defcat)
113 throw BESInternalError("Not able to find the default catalog.", __FILE__, __LINE__);
114
115 // remove all of the leading slashes from the container name
116 string::size_type notslash = container.find_first_not_of("/", 0);
117 if (notslash != string::npos) {
118 container = container.substr(notslash);
119 }
120
121 // see if there is a catalog name here. It's only a possible catalog name
122 string catname;
123 string::size_type slash = container.find_first_of("/", 0);
124 if (slash != string::npos) {
125 catname = container.substr(0, slash);
126 }
127 else {
128 catname = container;
129 }
130
131 // see if this catalog exists. If it does, then remove the catalog
132 // name from the container (node)
133 BESCatalog *catobj = BESCatalogList::TheCatalogList()->find_catalog(catname);
134 if (catobj) {
135 if (slash != string::npos) {
136 container = container.substr(slash + 1);
137
138 // remove repeated slashes
139 notslash = container.find_first_not_of("/", 0);
140 if (notslash != string::npos) {
141 container = container.substr(notslash);
142 }
143 }
144 else {
145 container = "";
146 }
147 }
148
149 if (container.empty()) container = "/";
150
151 if (container[0] != '/') container = "/" + container;
152
153 BESDEBUG(SPI_DEBUG_KEY, prolog << "container: " << container << endl );
154
155 info->begin_response(SHOW_GATEWAY_PATH_INFO_RESPONSE_STR, dhi);
156
157 map<string, string, std::less<>> pathInfoAttrs;
158 pathInfoAttrs[PATH] = container;
159
160 info->begin_tag(PATH_INFO_RESPONSE, &pathInfoAttrs);
161
162 string validPath, remainder;
163 bool isFile, isDir, canRead;
164 long long size, time;
165
166#if 0
167 BESCatalogUtils *utils = BESCatalogUtils::Utils(defcatname);
168#endif
169
170 BESCatalogUtils *utils = BESCatalogList::TheCatalogList()->default_catalog()->get_catalog_utils();
171 eval_resource_path(container, utils->get_root_dir(), utils->follow_sym_links(), validPath, isFile, isDir, size,
172 time, canRead, remainder);
173
174 // Now that we know what part of the path is actually something
175 // we can access, find out if the BES sees it as a dataset
176 bool isData = false;
177
178 // If the valid path is an empty string then we KNOW it's not a dataset
179 if (validPath.size() != 0) {
180
181 // Get the catalog entry.
182 BESCatalogEntry *entry = 0;
183 // string coi = dhi.data[CATALOG];
184 entry = defcat->show_catalog(validPath, /*coi,*/entry);
185 if (!entry) {
186 string err = (string) "Failed to find the validPath node " + validPath
187 + " this should not be possible. Some thing BAD is happening.";
188 throw BESInternalError(err, __FILE__, __LINE__);
189 }
190
191 // Retrieve the valid services list
192 list<string> services = entry->get_service_list();
193
194 // See if there's an OPENDAP_SERVICE available for the node.
195 if (services.size()) {
196 list<string>::const_iterator si = services.begin();
197 list<string>::const_iterator se = services.end();
198 for (; si != se; si++) {
199 if ((*si) == OPENDAP_SERVICE) isData = true;
200 }
201 }
202 }
203
204 map<string, string, std::less<>> validPathAttrs;
205 validPathAttrs[IS_DATA] = isData ? "true" : "false";
206 validPathAttrs[IS_FILE] = isFile ? "true" : "false";
207 validPathAttrs[IS_DIR] = isDir ? "true" : "false";
208 validPathAttrs[IS_ACCESSIBLE] = canRead ? "true" : "false";
209
210 // Convert size to string and add as attribute
211 std::ostringstream os_size;
212 os_size << size;
213 validPathAttrs[SIZE] = os_size.str();
214
215 // Convert lmt to string and add as attribute
216 std::ostringstream os_time;
217 os_time << time;
218 validPathAttrs[LMT] = os_time.str();
219
220 info->add_tag(VALID_PATH, validPath, &validPathAttrs);
221 info->add_tag(REMAINDER, remainder);
222
223 info->end_tag(PATH_INFO_RESPONSE);
224
225 // end the response object
226 info->end_response();
227
228 BESDEBUG(SPI_DEBUG_KEY, prolog << "END" << endl );
229
230 }
231
244{
245 if (_response) {
246 BESInfo *info = dynamic_cast<BESInfo *>(_response);
247 if (!info) throw BESInternalError("cast error", __FILE__, __LINE__);
248 info->transmit(transmitter, dhi);
249 }
250}
251
259{
260 strm << BESIndent::LMarg << "GatewayPathInfoResponseHandler::dump - (" << (void *) this << ")" << std::endl;
261 BESIndent::Indent();
263 BESIndent::UnIndent();
264}
265
267GatewayPathInfoResponseHandler::GatewayPathInfoResponseBuilder(const string &name)
268{
269 return new GatewayPathInfoResponseHandler(name);
270}
271
275void GatewayPathInfoResponseHandler::eval_resource_path(const string &resource_path, const string &catalog_root,
276 const bool follow_sym_links, string &validPath, bool &isFile, bool &isDir, long long &size,
277 long long &lastModifiedTime, bool &canRead, string &remainder)
278{
279
280 BESDEBUG(SPI_DEBUG_KEY, prolog << "CatalogRoot: "<< catalog_root << endl);
281
282 BESDEBUG(SPI_DEBUG_KEY, prolog << "resourceID: "<< resource_path << endl);
283
284 // nothing valid yet...
285 validPath = "";
286 size = -1;
287 lastModifiedTime = -1;
288
289 // It's all remainder at this point...
290 string rem = resource_path;
291 remainder = rem;
292
293 // Rather than have two basically identical code paths for the two cases (follow and !follow symlinks)
294 // We evaluate the follow_sym_links switch and use a function pointer to get the correct "stat"
295 // function for the eval operation.
296 int (*ye_old_stat_function)(const char *pathname, struct stat *buf);
297 if (follow_sym_links) {
298 BESDEBUG(SPI_DEBUG_KEY, prolog << "Using 'stat' function (follow_sym_links = true)" << endl);
299 ye_old_stat_function = &stat;
300 }
301 else {
302 BESDEBUG(SPI_DEBUG_KEY, prolog << "Using 'lstat' function (follow_sym_links = false)" << endl);
303 ye_old_stat_function = &lstat;
304 }
305
306 // if nothing is passed in path, then the path checks out since root is
307 // assumed to be valid.
308 if (resource_path == "") {
309 BESDEBUG(SPI_DEBUG_KEY, prolog << "The resourceID is empty" << endl);
310 return;
311 }
312
313 // make sure there are no ../ in the path, backing up in any way is
314 // not allowed.
315 string::size_type dotdot = resource_path.find("..");
316 if (dotdot != string::npos) {
317 BESDEBUG(SPI_DEBUG_KEY,
318 prolog << "ERROR: The resourceID '" << resource_path <<
319 "' contains the substring '..' This is Forbidden." << endl);
320 string s = (string) "Invalid node name '" + resource_path + "' ACCESS IS FORBIDDEN";
321 throw BESForbiddenError(s, __FILE__, __LINE__);
322 }
323
324 // What I want to do is to take each part of path and check to see if it
325 // is a symbolic link and it is accessible. If everything is ok, add the
326 // next part of the path.
327 bool done = false;
328
329 // Full file system path to check
330 string fullpath = catalog_root;
331
332 // localId that we are checking
333 string checking;
334
335 isFile = false;
336 isDir = false;
337
338 while (!done) {
339 size_t slash = rem.find('/');
340 if (slash == string::npos) {
341 BESDEBUG(SPI_DEBUG_KEY, prolog << "Checking final path component: " << rem << endl);
342 fullpath = BESUtil::assemblePath(fullpath, rem, true);
343 checking = BESUtil::assemblePath(validPath, rem, true);
344 rem = "";
345 done = true;
346 }
347 else {
348 fullpath = BESUtil::assemblePath(fullpath, rem.substr(0, slash), true);
349 checking = BESUtil::assemblePath(validPath, rem.substr(0, slash), true);
350 rem = rem.substr(slash + 1, rem.size() - slash);
351 }
352
353 BESDEBUG(SPI_DEBUG_KEY, prolog << "validPath: "<< validPath << endl);
354 BESDEBUG(SPI_DEBUG_KEY, prolog << " checking: "<< checking << endl);
355 BESDEBUG(SPI_DEBUG_KEY, prolog << " fullpath: "<< fullpath << endl);
356
357 BESDEBUG(SPI_DEBUG_KEY, prolog << " rem: "<< rem << endl);
358
359 BESDEBUG(SPI_DEBUG_KEY, prolog << "remainder: "<< remainder << endl);
360
361 struct stat sb;
362 int statret = ye_old_stat_function(fullpath.c_str(), &sb);
363
364 if (statret != -1) {
365 // No Error then keep chugging along.
366 validPath = checking;
367 remainder = rem;
368 }
369 else {
370 int errsv = errno;
371 // stat failed, so not accessible. Get the error string,
372 // store in error, and throw exception
373 char *s_err = strerror(errsv);
374 string error = "Unable to access node " + checking + ": ";
375 if (s_err) {
376 error = error + s_err;
377 }
378 else {
379 error = error + "unknown access error";
380 }
381 BESDEBUG(SPI_DEBUG_KEY, prolog << " error: " << error << " errno: " << errno << endl);
382
383 BESDEBUG(SPI_DEBUG_KEY, prolog << "remainder: '" << remainder << "'" << endl);
384
385 // ENOENT means that the node wasn't found. Otherwise, access
386 // is denied for some reason
387 if (errsv != ENOENT && errsv != ENOTDIR) {
388 throw BESForbiddenError(error, __FILE__, __LINE__);
389 }
390
391 // Are there slashes in the remainder?
392 size_t s_loc = remainder.find('/');
393 if (s_loc == string::npos) {
394 // if there are no more slashes, we check to see if this final path component contains "."
395 string basename = remainder;
396 bool moreDots = true;
397 while (moreDots) {
398 // working back from end of string, drop each dot (".") suffix until file system match or string gone
399 size_t d_loc = basename.find_last_of(".");
400 if (d_loc != string::npos) {
401 basename = basename.substr(0, d_loc);
402 BESDEBUG(SPI_DEBUG_KEY, prolog << "basename: "<< basename << endl);
403
404 string candidate_remainder = remainder.substr(basename.size());
405 BESDEBUG(SPI_DEBUG_KEY, prolog << "candidate_remainder: "<< candidate_remainder << endl);
406
407 string candidate_path = BESUtil::assemblePath(validPath, basename, true);
408 BESDEBUG(SPI_DEBUG_KEY, prolog << "candidate_path: "<< candidate_path << endl);
409
410 string full_candidate_path = BESUtil::assemblePath(catalog_root, candidate_path, true);
411 BESDEBUG(SPI_DEBUG_KEY, prolog << "full_candidate_path: "<< full_candidate_path << endl);
412
413 struct stat sb1;
414 int statret1 = ye_old_stat_function(full_candidate_path.c_str(), &sb1);
415 if (statret1 != -1) {
416 validPath = candidate_path;
417 remainder = candidate_remainder;
418 moreDots = false;
419 }
420 }
421 else {
422 BESDEBUG(SPI_DEBUG_KEY, prolog << "No dots in remainder: "<< remainder << endl);
423 moreDots = false;
424 }
425 }
426 }
427 else {
428 BESDEBUG(SPI_DEBUG_KEY, prolog << "Remainder has slash pollution: "<< remainder << endl);
429 done = true;
430 }
431 }
432 fullpath = BESUtil::assemblePath(catalog_root, validPath, true);
433
434 statret = ye_old_stat_function(fullpath.c_str(), &sb);
435 if (S_ISREG(sb.st_mode)) {
436 BESDEBUG(SPI_DEBUG_KEY, prolog << "'"<< fullpath << "' Is regular file." << endl);
437 isFile = true;
438 isDir = false;
439 }
440 else if (S_ISDIR(sb.st_mode)) {
441 BESDEBUG(SPI_DEBUG_KEY, prolog << "'"<< fullpath << "' Is directory." << endl);
442 isFile = false;
443 isDir = true;
444 }
445 else if (S_ISLNK(sb.st_mode)) {
446 BESDEBUG(SPI_DEBUG_KEY, prolog << "'"<< fullpath << "' Is symbolic Link." << endl);
447 string error = "Service not configured to traverse symbolic links as embodied by the node '" + checking
448 + "' ACCESS IS FORBIDDEN";
449 throw BESForbiddenError(error, __FILE__, __LINE__);
450 }
451 // sb.st_uid;
452 // sb.st_uid;
453
454 // Can we read le file?
455 std::ifstream ifile(fullpath.c_str());
456 canRead = ifile.good();
457
458 size = sb.st_size;
459 // I'm pretty sure that assigning st_mtime to a long long (when it is a time_t) is not a
460 // good plan - time_t is either a 32- or 64-bit signed integer.
461 //
462 // But, see ESCatalogUtils::bes_get_stat_info(BESCatalogEntry *entry, struct stat &buf)
463 // for code that probably does a more universal version. of this (and other things relative
464 // to stat, like symbolic link following).
465 //
466 // I added this #if ... #endif because Linux does not have st_mtimespec in struct stat.
467 // jhrg 2.24.18
468#if !defined(_POSIX_C_SOURCE) || defined(_DARWIN_C_SOURCE)
469 // Compute LMT by converting the time to milliseconds since epoch - because OLFS is picky
470 lastModifiedTime = (sb.st_mtimespec.tv_sec * 1000) + (sb.st_mtimespec.tv_nsec / 1000000);
471#else
472 lastModifiedTime = sb.st_mtime;
473#endif
474 }
475 BESDEBUG(SPI_DEBUG_KEY, prolog << " fullpath: " << fullpath << endl);
476 BESDEBUG(SPI_DEBUG_KEY, prolog << "validPath: " << validPath << endl);
477 BESDEBUG(SPI_DEBUG_KEY, prolog << "remainder: " << remainder << endl);
478 BESDEBUG(SPI_DEBUG_KEY, prolog << " rem: " << rem << endl);
479 BESDEBUG(SPI_DEBUG_KEY, prolog << " isFile: " << (isFile?"true":"false") << endl);
480 BESDEBUG(SPI_DEBUG_KEY, prolog << " isDir: " << (isDir?"true":"false") << endl);
481 BESDEBUG(SPI_DEBUG_KEY, prolog << " access: " << (canRead?"true":"false") << endl);
482 BESDEBUG(SPI_DEBUG_KEY, prolog << " size: " << size << endl);
483 BESDEBUG(SPI_DEBUG_KEY, prolog << " LMT: " << lastModifiedTime << endl);
484
485}
const std::string & get_root_dir() const
Get the root directory of the catalog.
Catalogs provide a hierarchical organization for data.
Definition BESCatalog.h:51
virtual BESCatalogEntry * show_catalog(const std::string &container, BESCatalogEntry *entry)=0
Structure storing information used by the BES to handle the request.
std::map< std::string, std::string > data
the map of string data that will be required for the current request.
informational response object
Definition BESInfo.h:63
virtual void transmit(BESTransmitter *transmitter, BESDataHandlerInterface &dhi)=0
transmit the informational object
virtual void begin_response(const std::string &response_name, BESDataHandlerInterface &dhi)
begin the informational response
Definition BESInfo.cc:120
exception thrown if internal error encountered
handler object that knows how to create a specific response object
void dump(std::ostream &strm) const override
dumps information about this object
static std::string assemblePath(const std::string &firstPart, const std::string &secondPart, bool leadingSlash=false, bool trailingSlash=false)
Assemble path fragments making sure that they are separated by a single '/' character.
Definition BESUtil.cc:804
response handler that returns nodes or leaves within the catalog either at the root or at a specified...
virtual void dump(std::ostream &strm) const
dumps information about this object
virtual void transmit(BESTransmitter *transmitter, BESDataHandlerInterface &dhi)
transmit the response object built by the execute command using the specified transmitter object
virtual void execute(BESDataHandlerInterface &dhi)
executes the command 'show catalog|leaves [for <node>];' by returning nodes or leaves at the top leve...
STL iterator class.