Logo ROOT   6.30.04
Reference Guide
 All Namespaces Files Pages
TRootSniffer.cxx
Go to the documentation of this file.
1 // $Id$
2 // Author: Sergey Linev 22/12/2013
3 
4 /*************************************************************************
5  * Copyright (C) 1995-2013, Rene Brun and Fons Rademakers. *
6  * All rights reserved. *
7  * *
8  * For the licensing terms see $ROOTSYS/LICENSE. *
9  * For the list of contributors see $ROOTSYS/README/CREDITS. *
10  *************************************************************************/
11 
12 #include "TRootSniffer.h"
13 
14 #include "TDirectoryFile.h"
15 #include "TKey.h"
16 #include "TList.h"
17 #include "TBufferJSON.h"
18 #include "TROOT.h"
19 #include "TFolder.h"
20 #include "TClass.h"
21 #include "TRealData.h"
22 #include "TDataMember.h"
23 #include "TDataType.h"
24 #include "TObjString.h"
25 #include "TUrl.h"
26 #include "TImage.h"
27 #include "TVirtualMutex.h"
28 #include "TRootSnifferStore.h"
29 #include "THttpCallArg.h"
30 #include "ROOT/RMakeUnique.hxx"
31 
32 #include <stdlib.h>
33 #include <vector>
34 #include <string.h>
35 
36 const char *item_prop_kind = "_kind";
37 const char *item_prop_more = "_more";
38 const char *item_prop_title = "_title";
39 const char *item_prop_hidden = "_hidden";
40 const char *item_prop_typename = "_typename";
41 const char *item_prop_arraydim = "_arraydim";
42 const char *item_prop_realname = "_realname"; // real object name
43 const char *item_prop_user = "_username";
44 const char *item_prop_autoload = "_autoload";
45 const char *item_prop_rootversion = "_root_version";
46 
47 //////////////////////////////////////////////////////////////////////////
48 // //
49 // TRootSnifferScanRec //
50 // //
51 // Structure used to scan hierarchies of ROOT objects //
52 // Represents single level of hierarchy //
53 // //
54 //////////////////////////////////////////////////////////////////////////
55 
56 ////////////////////////////////////////////////////////////////////////////////
57 /// constructor
58 
59 TRootSnifferScanRec::TRootSnifferScanRec()
60 {
61  fItemsNames.SetOwner(kTRUE);
62 }
63 
64 ////////////////////////////////////////////////////////////////////////////////
65 /// destructor
66 
67 TRootSnifferScanRec::~TRootSnifferScanRec()
68 {
69  CloseNode();
70 }
71 
72 ////////////////////////////////////////////////////////////////////////////////
73 /// record field for current element
74 
75 void TRootSnifferScanRec::SetField(const char *name, const char *value, Bool_t with_quotes)
76 {
77  if (CanSetFields())
78  fStore->SetField(fLevel, name, value, with_quotes);
79  fNumFields++;
80 }
81 
82 ////////////////////////////////////////////////////////////////////////////////
83 /// indicates that new child for current element will be started
84 
85 void TRootSnifferScanRec::BeforeNextChild()
86 {
87  if (CanSetFields())
88  fStore->BeforeNextChild(fLevel, fNumChilds, fNumFields);
89  fNumChilds++;
90 }
91 
92 ////////////////////////////////////////////////////////////////////////////////
93 /// constructs item name from object name
94 /// if special symbols like '/', '#', ':', '&', '?' are used in object name
95 /// they will be replaced with '_'.
96 /// To avoid item name duplication, additional id number can be appended
97 
98 void TRootSnifferScanRec::MakeItemName(const char *objname, TString &itemname)
99 {
100  std::string nnn = objname;
101 
102  size_t pos;
103 
104  // replace all special symbols which can make problem to navigate in hierarchy
105  while ((pos = nnn.find_first_of("- []<>#:&?/\'\"\\")) != std::string::npos)
106  nnn.replace(pos, 1, "_");
107 
108  itemname = nnn.c_str();
109  Int_t cnt = 0;
110 
111  while (fItemsNames.FindObject(itemname.Data())) {
112  itemname.Form("%s_%d", nnn.c_str(), cnt++);
113  }
114 
115  fItemsNames.Add(new TObjString(itemname.Data()));
116 }
117 
118 ////////////////////////////////////////////////////////////////////////////////
119 /// Produce full name, including all parents
120 
121 void TRootSnifferScanRec::BuildFullName(TString &buf, TRootSnifferScanRec *prnt)
122 {
123  if (!prnt)
124  prnt = fParent;
125 
126  if (prnt) {
127  prnt->BuildFullName(buf);
128 
129  buf.Append("/");
130  buf.Append(fItemName);
131  }
132 }
133 
134 ////////////////////////////////////////////////////////////////////////////////
135 /// creates new node with specified name
136 /// if special symbols like "[]&<>" are used, node name
137 /// will be replaced by default name like "extra_item_N" and
138 /// original node name will be recorded as "_original_name" field
139 /// Optionally, object name can be recorded as "_realname" field
140 
141 void TRootSnifferScanRec::CreateNode(const char *_node_name)
142 {
143  if (!CanSetFields())
144  return;
145 
146  fNodeStarted = kTRUE;
147 
148  if (fParent)
149  fParent->BeforeNextChild();
150 
151  if (fStore)
152  fStore->CreateNode(fLevel, _node_name);
153 }
154 
155 ////////////////////////////////////////////////////////////////////////////////
156 /// close started node
157 
158 void TRootSnifferScanRec::CloseNode()
159 {
160  if (fStore && fNodeStarted) {
161  fStore->CloseNode(fLevel, fNumChilds);
162  fNodeStarted = kFALSE;
163  }
164 }
165 
166 ////////////////////////////////////////////////////////////////////////////////
167 /// set root class name as node kind
168 /// in addition, path to master item (streamer info) specified
169 /// Such master item required to correctly unstream data on JavaScript
170 
171 void TRootSnifferScanRec::SetRootClass(TClass *cl)
172 {
173  if (cl && CanSetFields())
174  SetField(item_prop_kind, TString::Format("ROOT.%s", cl->GetName()));
175 }
176 
177 ////////////////////////////////////////////////////////////////////////////////
178 /// returns true if scanning is done
179 /// Can happen when searched element is found
180 
181 Bool_t TRootSnifferScanRec::Done() const
182 {
183  if (!fStore)
184  return kFALSE;
185 
186  if ((fMask & kSearch) && fStore->GetResPtr())
187  return kTRUE;
188 
189  if ((fMask & kCheckChilds) && fStore->GetResPtr() && (fStore->GetResNumChilds() >= 0))
190  return kTRUE;
191 
192  return kFALSE;
193 }
194 
195 ////////////////////////////////////////////////////////////////////////////////
196 /// Checks if result will be accepted.
197 /// Used to verify if sniffer should read object from the file
198 
199 Bool_t TRootSnifferScanRec::IsReadyForResult() const
200 {
201  if (Done())
202  return kFALSE;
203 
204  // only when doing search, result will be propagated
205  if ((fMask & (kSearch | kCheckChilds)) == 0)
206  return kFALSE;
207 
208  // only when full search path is scanned
209  if (fSearchPath)
210  return kFALSE;
211 
212  if (!fStore)
213  return kFALSE;
214 
215  return kTRUE;
216 }
217 
218 ////////////////////////////////////////////////////////////////////////////////
219 /// set results of scanning
220 /// when member should be specified, use SetFoundResult instead
221 
222 Bool_t TRootSnifferScanRec::SetResult(void *obj, TClass *cl, TDataMember *member)
223 {
224  if (!member)
225  return SetFoundResult(obj, cl);
226 
227  fStore->Error("SetResult",
228  "When member specified, pointer on object (not member) should be provided; use SetFoundResult");
229  return kFALSE;
230 }
231 
232 ////////////////////////////////////////////////////////////////////////////////
233 /// set results of scanning
234 /// when member specified, obj is pointer on object to which member belongs
235 
236 Bool_t TRootSnifferScanRec::SetFoundResult(void *obj, TClass *cl, TDataMember *member)
237 {
238  if (Done())
239  return kTRUE;
240 
241  if (!IsReadyForResult())
242  return kFALSE;
243 
244  fStore->SetResult(obj, cl, member, fNumChilds, fRestriction);
245 
246  return Done();
247 }
248 
249 ////////////////////////////////////////////////////////////////////////////////
250 /// returns current depth of scanned hierarchy
251 
252 Int_t TRootSnifferScanRec::Depth() const
253 {
254  Int_t cnt = 0;
255  const TRootSnifferScanRec *rec = this;
256  while (rec->fParent) {
257  rec = rec->fParent;
258  cnt++;
259  }
260 
261  return cnt;
262 }
263 
264 ////////////////////////////////////////////////////////////////////////////////
265 /// returns true if current item can be expanded - means one could explore
266 /// objects members
267 
268 Bool_t TRootSnifferScanRec::CanExpandItem()
269 {
270  if (fMask & (kExpand | kSearch | kCheckChilds))
271  return kTRUE;
272 
273  if (!fHasMore)
274  return kFALSE;
275 
276  // if parent has expand mask, allow to expand item
277  if (fParent && (fParent->fMask & kExpand))
278  return kTRUE;
279 
280  return kFALSE;
281 }
282 
283 ////////////////////////////////////////////////////////////////////////////////
284 /// returns read-only flag for current item
285 /// Depends from default value and current restrictions
286 
287 Bool_t TRootSnifferScanRec::IsReadOnly(Bool_t dflt)
288 {
289  if (fRestriction == 0)
290  return dflt;
291 
292  return fRestriction != 2;
293 }
294 
295 ////////////////////////////////////////////////////////////////////////////////
296 /// Method verifies if new level of hierarchy
297 /// should be started with provided object.
298 /// If required, all necessary nodes and fields will be created
299 /// Used when different collection kinds should be scanned
300 
301 Bool_t
302 TRootSnifferScanRec::GoInside(TRootSnifferScanRec &super, TObject *obj, const char *obj_name, TRootSniffer *sniffer)
303 {
304  if (super.Done())
305  return kFALSE;
306 
307  if (obj && !obj_name)
308  obj_name = obj->GetName();
309 
310  // exclude zero names
311  if (!obj_name || (*obj_name == 0))
312  return kFALSE;
313 
314  const char *full_name = nullptr;
315 
316  // remove slashes from file names
317  if (obj && obj->InheritsFrom(TDirectoryFile::Class())) {
318  const char *slash = strrchr(obj_name, '/');
319  if (slash) {
320  full_name = obj_name;
321  obj_name = slash + 1;
322  if (*obj_name == 0)
323  obj_name = "file";
324  }
325  }
326 
327  super.MakeItemName(obj_name, fItemName);
328 
329  if (sniffer && sniffer->HasRestriction(fItemName.Data())) {
330  // check restriction more precisely
331  TString fullname;
332  BuildFullName(fullname, &super);
333  fRestriction = sniffer->CheckRestriction(fullname.Data());
334  if (fRestriction < 0)
335  return kFALSE;
336  }
337 
338  fParent = &super;
339  fLevel = super.fLevel;
340  fStore = super.fStore;
341  fSearchPath = super.fSearchPath;
342  fMask = super.fMask & kActions;
343  if (fRestriction == 0)
344  fRestriction = super.fRestriction; // get restriction from parent
345  Bool_t topelement(kFALSE);
346 
347  if (fMask & kScan) {
348  // if scanning only fields, ignore all childs
349  if (super.ScanOnlyFields())
350  return kFALSE;
351  // only when doing scan, increment level, used for text formatting
352  fLevel++;
353  } else {
354  if (!fSearchPath)
355  return kFALSE;
356 
357  if (strncmp(fSearchPath, fItemName.Data(), fItemName.Length()) != 0)
358  return kFALSE;
359 
360  const char *separ = fSearchPath + fItemName.Length();
361 
362  Bool_t isslash = kFALSE;
363  while (*separ == '/') {
364  separ++;
365  isslash = kTRUE;
366  }
367 
368  if (*separ == 0) {
369  fSearchPath = nullptr;
370  if (fMask & kExpand) {
371  topelement = kTRUE;
372  fMask = (fMask & kOnlyFields) | kScan;
373  fHasMore = (fMask & kOnlyFields) == 0;
374  }
375  } else {
376  if (!isslash)
377  return kFALSE;
378  fSearchPath = separ;
379  }
380  }
381 
382  CreateNode(fItemName.Data());
383 
384  if (obj_name && (fItemName != obj_name))
385  SetField(item_prop_realname, obj_name);
386 
387  if (full_name)
388  SetField("_fullname", full_name);
389 
390  if (topelement)
391  SetField(item_prop_rootversion, TString::Format("%d", gROOT->GetVersionCode()), kFALSE);
392 
393  if (topelement && sniffer->GetAutoLoad())
394  SetField(item_prop_autoload, sniffer->GetAutoLoad());
395 
396  return kTRUE;
397 }
398 
399 // ====================================================================
400 
401 //////////////////////////////////////////////////////////////////////////
402 // //
403 // TRootSniffer //
404 // //
405 // Sniffer of ROOT objects, data provider for THttpServer //
406 // Provides methods to scan different structures like folders, //
407 // directories, files, trees, collections //
408 // Can locate objects (or its data member) per name //
409 // Can be extended to application-specific classes //
410 // //
411 //////////////////////////////////////////////////////////////////////////
412 
413 ClassImp(TRootSniffer);
414 
415 ////////////////////////////////////////////////////////////////////////////////
416 /// constructor
417 
418 TRootSniffer::TRootSniffer(const char *name, const char *objpath)
419  : TNamed(name, "sniffer of root objects"), fObjectsPath(objpath)
420 {
421  fRestrictions.SetOwner(kTRUE);
422 }
423 
424 ////////////////////////////////////////////////////////////////////////////////
425 /// destructor
426 
427 TRootSniffer::~TRootSniffer()
428 {
429 }
430 
431 ////////////////////////////////////////////////////////////////////////////////
432 /// set current http arguments, which then used in different process methods
433 /// For instance, if user authorized with some user name,
434 /// depending from restrictions some objects will be invisible
435 /// or user get full access to the element
436 
437 void TRootSniffer::SetCurrentCallArg(THttpCallArg *arg)
438 {
439  fCurrentArg = arg;
440  fCurrentRestrict = 0;
441  fCurrentAllowedMethods = "";
442 }
443 
444 ////////////////////////////////////////////////////////////////////////////////
445 /// Restrict access to the specified location
446 ///
447 /// Hides or provides read-only access to different parts of the hierarchy
448 /// Restriction done base on user-name specified with http requests
449 /// Options can be specified in URL style (separated with &)
450 /// Following parameters can be specified:
451 /// visible = [all|user(s)] - make item visible for all users or only specified user
452 /// hidden = [all|user(s)] - make item hidden from all users or only specified user
453 /// readonly = [all|user(s)] - make item read-only for all users or only specified user
454 /// allow = [all|user(s)] - make full access for all users or only specified user
455 /// allow_method = method(s) - allow method(s) execution even when readonly flag specified for the object
456 /// Like make command seen by all but can be executed only by admin
457 /// sniff->Restrict("/CmdReset","allow=admin");
458 /// Or fully hide command from guest account
459 /// sniff->Restrict("/CmdRebin","hidden=guest");
460 
461 void TRootSniffer::Restrict(const char *path, const char *options)
462 {
463  const char *rslash = strrchr(path, '/');
464  if (rslash)
465  rslash++;
466  if (!rslash || (*rslash == 0))
467  rslash = path;
468 
469  fRestrictions.Add(new TNamed(rslash, TString::Format("%s%s%s", path, "%%%", options).Data()));
470 }
471 
472 ////////////////////////////////////////////////////////////////////////////////
473 /// When specified, _autoload attribute will be always add
474 /// to top element of h.json/h.hml requests
475 /// Used to instruct browser automatically load special code
476 
477 void TRootSniffer::SetAutoLoad(const char *scripts)
478 {
479  fAutoLoad = scripts ? scripts : "";
480 }
481 
482 ////////////////////////////////////////////////////////////////////////////////
483 /// return name of configured autoload scripts (or 0)
484 
485 const char *TRootSniffer::GetAutoLoad() const
486 {
487  return fAutoLoad.Length() > 0 ? fAutoLoad.Data() : nullptr;
488 }
489 
490 ////////////////////////////////////////////////////////////////////////////////
491 /// Made fast check if item with specified name is in restriction list
492 /// If returns true, requires precise check with CheckRestriction() method
493 
494 Bool_t TRootSniffer::HasRestriction(const char *item_name)
495 {
496  if (!item_name || (*item_name == 0) || !fCurrentArg)
497  return kFALSE;
498 
499  return fRestrictions.FindObject(item_name) != nullptr;
500 }
501 
502 ////////////////////////////////////////////////////////////////////////////////
503 /// return 2 when option match to current user name
504 /// return 1 when option==all
505 /// return 0 when option does not match user name
506 
507 Int_t TRootSniffer::WithCurrentUserName(const char *option)
508 {
509  const char *username = fCurrentArg ? fCurrentArg->GetUserName() : nullptr;
510 
511  if (!username || !option || (*option == 0))
512  return 0;
513 
514  if (strcmp(option, "all") == 0)
515  return 1;
516 
517  if (strcmp(username, option) == 0)
518  return 2;
519 
520  if (strstr(option, username) == 0)
521  return -1;
522 
523  TObjArray *arr = TString(option).Tokenize(",");
524 
525  Bool_t find = arr->FindObject(username) != nullptr;
526 
527  delete arr;
528 
529  return find ? 2 : -1;
530 }
531 
532 ////////////////////////////////////////////////////////////////////////////////
533 /// Checked if restriction is applied to the item
534 /// full_item_name should have full path to the item
535 ///
536 /// Returns -1 - object invisible, cannot be accessed or listed
537 /// 0 - no explicit restrictions, use default
538 /// 1 - read-only access
539 /// 2 - full access
540 
541 Int_t TRootSniffer::CheckRestriction(const char *full_item_name)
542 {
543  if (!full_item_name || (*full_item_name == 0))
544  return 0;
545 
546  const char *item_name = strrchr(full_item_name, '/');
547  if (item_name)
548  item_name++;
549  if (!item_name || (*item_name == 0))
550  item_name = full_item_name;
551 
552  TString pattern1 = TString("*/") + item_name + "%%%";
553  TString pattern2 = TString(full_item_name) + "%%%";
554 
555  const char *options = nullptr;
556  TIter iter(&fRestrictions);
557  TObject *obj;
558 
559  while ((obj = iter()) != nullptr) {
560  const char *title = obj->GetTitle();
561 
562  if (strstr(title, pattern1.Data()) == title) {
563  options = title + pattern1.Length();
564  break;
565  }
566  if (strstr(title, pattern2.Data()) == title) {
567  options = title + pattern2.Length();
568  break;
569  }
570  }
571 
572  if (!options)
573  return 0;
574 
575  TUrl url;
576  url.SetOptions(options);
577  url.ParseOptions();
578 
579  Int_t can_see =
580  WithCurrentUserName(url.GetValueFromOptions("visible")) - WithCurrentUserName(url.GetValueFromOptions("hidden"));
581 
582  Int_t can_access =
583  WithCurrentUserName(url.GetValueFromOptions("allow")) - WithCurrentUserName(url.GetValueFromOptions("readonly"));
584 
585  if (can_access > 0)
586  return 2; // first of all, if access enabled, provide it
587  if (can_see < 0)
588  return -1; // if object to be hidden, do it
589 
590  const char *methods = url.GetValueFromOptions("allow_method");
591  if (methods)
592  fCurrentAllowedMethods = methods;
593 
594  if (can_access < 0)
595  return 1; // read-only access
596 
597  return 0; // default behavior
598 }
599 
600 ////////////////////////////////////////////////////////////////////////////////
601 /// scan object data members
602 /// some members like enum or static members will be excluded
603 
604 void TRootSniffer::ScanObjectMembers(TRootSnifferScanRec &rec, TClass *cl, char *ptr)
605 {
606  if (!cl || !ptr || rec.Done())
607  return;
608 
609  // ensure that real class data (including parents) exists
610  if (!(cl->Property() & kIsAbstract))
611  cl->BuildRealData();
612 
613  // scan only real data
614  TObject *obj = nullptr;
615  TIter iter(cl->GetListOfRealData());
616  while ((obj = iter()) != nullptr) {
617  TRealData *rdata = dynamic_cast<TRealData *>(obj);
618  if (!rdata || strchr(rdata->GetName(), '.'))
619  continue;
620 
621  TDataMember *member = rdata->GetDataMember();
622  // exclude enum or static variables
623  if (!member || (member->Property() & (kIsStatic | kIsEnum | kIsUnion)))
624  continue;
625  char *member_ptr = ptr + rdata->GetThisOffset();
626 
627  if (member->IsaPointer())
628  member_ptr = *((char **)member_ptr);
629 
630  TRootSnifferScanRec chld;
631 
632  if (chld.GoInside(rec, member, 0, this)) {
633 
634  TClass *mcl = (member->IsBasic() || member->IsSTLContainer()) ? nullptr : gROOT->GetClass(member->GetTypeName());
635 
636  Int_t coll_offset = mcl ? mcl->GetBaseClassOffset(TCollection::Class()) : -1;
637  if (coll_offset >= 0) {
638  chld.SetField(item_prop_more, "true", kFALSE);
639  chld.fHasMore = kTRUE;
640  }
641 
642  if (chld.SetFoundResult(ptr, cl, member))
643  break;
644 
645  const char *title = member->GetTitle();
646  if (title && (strlen(title) != 0))
647  chld.SetField(item_prop_title, title);
648 
649  if (member->GetTypeName())
650  chld.SetField(item_prop_typename, member->GetTypeName());
651 
652  if (member->GetArrayDim() > 0) {
653  // store array dimensions in form [N1,N2,N3,...]
654  TString dim("[");
655  for (Int_t n = 0; n < member->GetArrayDim(); n++) {
656  if (n > 0)
657  dim.Append(",");
658  dim.Append(TString::Format("%d", member->GetMaxIndex(n)));
659  }
660  dim.Append("]");
661  chld.SetField(item_prop_arraydim, dim, kFALSE);
662  } else if (member->GetArrayIndex() != 0) {
663  TRealData *idata = cl->GetRealData(member->GetArrayIndex());
664  TDataMember *imember = idata ? idata->GetDataMember() : nullptr;
665  if (imember && (strcmp(imember->GetTrueTypeName(), "int") == 0)) {
666  Int_t arraylen = *((int *)(ptr + idata->GetThisOffset()));
667  chld.SetField(item_prop_arraydim, TString::Format("[%d]", arraylen), kFALSE);
668  }
669  }
670 
671  chld.SetRootClass(mcl);
672 
673  if (chld.CanExpandItem()) {
674  if (coll_offset >= 0) {
675  // chld.SetField("#members", "true", kFALSE);
676  ScanCollection(chld, (TCollection *)(member_ptr + coll_offset));
677  }
678  }
679 
680  if (chld.SetFoundResult(ptr, cl, member))
681  break;
682  }
683  }
684 }
685 
686 ////////////////////////////////////////////////////////////////////////////////
687 /// scans object properties
688 /// here such fields as _autoload or _icon properties depending on class or object name could be assigned
689 /// By default properties, coded in the Class title are scanned. Example:
690 /// ClassDef(UserClassName, 1) // class comments *SNIFF* _field1=value _field2="string value"
691 /// Here *SNIFF* mark is important. After it all expressions like field=value are parsed
692 /// One could use double quotes to code string values with spaces.
693 /// Fields separated from each other with spaces
694 
695 void TRootSniffer::ScanObjectProperties(TRootSnifferScanRec &rec, TObject *obj)
696 {
697  TClass *cl = obj ? obj->IsA() : nullptr;
698 
699  const char *pos = strstr(cl ? cl->GetTitle() : "", "*SNIFF*");
700  if (!pos)
701  return;
702 
703  pos += 7;
704  while (*pos != 0) {
705  if (*pos == ' ') {
706  pos++;
707  continue;
708  }
709  // first locate identifier
710  const char *pos0 = pos;
711  while ((*pos != 0) && (*pos != '='))
712  pos++;
713  if (*pos == 0)
714  return;
715  TString name(pos0, pos - pos0);
716  pos++;
717  Bool_t quotes = (*pos == '\"');
718  if (quotes)
719  pos++;
720  pos0 = pos;
721  // then value with or without quotes
722  while ((*pos != 0) && (*pos != (quotes ? '\"' : ' ')))
723  pos++;
724  TString value(pos0, pos - pos0);
725  rec.SetField(name, value);
726  if (quotes)
727  pos++;
728  pos++;
729  }
730 }
731 
732 ////////////////////////////////////////////////////////////////////////////////
733 /// scans key properties
734 /// in special cases load objects from the file
735 
736 void TRootSniffer::ScanKeyProperties(TRootSnifferScanRec &rec, TKey *key, TObject *&obj, TClass *&obj_class)
737 {
738  if (strcmp(key->GetClassName(), "TDirectoryFile") == 0) {
739  if (rec.fLevel == 0) {
740  TDirectory *dir = dynamic_cast<TDirectory *>(key->ReadObj());
741  if (dir) {
742  obj = dir;
743  obj_class = dir->IsA();
744  }
745  } else {
746  rec.SetField(item_prop_more, "true", kFALSE);
747  rec.fHasMore = kTRUE;
748  }
749  }
750 }
751 
752 ////////////////////////////////////////////////////////////////////////////////
753 /// scans object childs (if any)
754 /// here one scans collection, branches, trees and so on
755 
756 void TRootSniffer::ScanObjectChilds(TRootSnifferScanRec &rec, TObject *obj)
757 {
758  if (obj->InheritsFrom(TFolder::Class())) {
759  ScanCollection(rec, ((TFolder *)obj)->GetListOfFolders());
760  } else if (obj->InheritsFrom(TDirectory::Class())) {
761  TDirectory *dir = (TDirectory *)obj;
762  ScanCollection(rec, dir->GetList(), nullptr, dir->GetListOfKeys());
763  }
764  if (rec.CanExpandItem()) {
765  ScanObjectMembers(rec, obj->IsA(), (char *)obj);
766  }
767 }
768 
769 ////////////////////////////////////////////////////////////////////////////////
770 /// scan collection content
771 
772 void TRootSniffer::ScanCollection(TRootSnifferScanRec &rec, TCollection *lst, const char *foldername,
773  TCollection *keys_lst)
774 {
775  if ((!lst || (lst->GetSize() == 0)) && (!keys_lst || (keys_lst->GetSize() == 0)))
776  return;
777 
778  TRootSnifferScanRec folderrec;
779  if (foldername) {
780  if (!folderrec.GoInside(rec, nullptr, foldername, this))
781  return;
782  }
783 
784  TRootSnifferScanRec &master = foldername ? folderrec : rec;
785 
786  if (lst) {
787  TIter iter(lst);
788  TObject *next = iter();
789  Bool_t isany = kFALSE;
790 
791  while (next) {
792  if (IsItemField(next)) {
793  // special case - in the beginning one could have items for master folder
794  if (!isany && (next->GetName() != nullptr) && ((*(next->GetName()) == '_') || master.ScanOnlyFields()))
795  master.SetField(next->GetName(), next->GetTitle());
796  next = iter();
797  continue;
798  }
799 
800  isany = kTRUE;
801  TObject *obj = next;
802 
803  TRootSnifferScanRec chld;
804  if (!chld.GoInside(master, obj, nullptr, this)) {
805  next = iter();
806  continue;
807  }
808 
809  if (chld.SetResult(obj, obj->IsA()))
810  return;
811 
812  Bool_t has_kind(kFALSE), has_title(kFALSE);
813 
814  ScanObjectProperties(chld, obj);
815  // now properties, coded as TNamed objects, placed after object in the hierarchy
816  while ((next = iter()) != nullptr) {
817  if (!IsItemField(next))
818  break;
819  if ((next->GetName() != nullptr) && ((*(next->GetName()) == '_') || chld.ScanOnlyFields())) {
820  // only fields starting with _ are stored
821  chld.SetField(next->GetName(), next->GetTitle());
822  if (strcmp(next->GetName(), item_prop_kind) == 0)
823  has_kind = kTRUE;
824  if (strcmp(next->GetName(), item_prop_title) == 0)
825  has_title = kTRUE;
826  }
827  }
828 
829  if (!has_kind)
830  chld.SetRootClass(obj->IsA());
831  if (!has_title && obj->GetTitle())
832  chld.SetField(item_prop_title, obj->GetTitle());
833 
834  ScanObjectChilds(chld, obj);
835 
836  if (chld.SetResult(obj, obj->IsA()))
837  return;
838  }
839  }
840 
841  if (keys_lst) {
842  TIter iter(keys_lst);
843  TObject *kobj = nullptr;
844 
845  while ((kobj = iter()) != nullptr) {
846  TKey *key = dynamic_cast<TKey *>(kobj);
847  if (!key)
848  continue;
849  TObject *obj = lst ? lst->FindObject(key->GetName()) : nullptr;
850 
851  // even object with the name exists, it should also match with class name
852  if (obj && (strcmp(obj->ClassName(), key->GetClassName()) != 0))
853  obj = nullptr;
854 
855  // if object of that name and of that class already in the list, ignore appropriate key
856  if (obj && (master.fMask & TRootSnifferScanRec::kScan))
857  continue;
858 
859  Bool_t iskey = kFALSE;
860  // if object not exists, provide key itself for the scan
861  if (!obj) {
862  obj = key;
863  iskey = kTRUE;
864  }
865 
866  TRootSnifferScanRec chld;
867  TString fullname = TString::Format("%s;%d", key->GetName(), key->GetCycle());
868 
869  if (chld.GoInside(master, obj, fullname.Data(), this)) {
870 
871  if (!chld.IsReadOnly(fReadOnly) && iskey && chld.IsReadyForResult()) {
872  TObject *keyobj = key->ReadObj();
873  if (keyobj)
874  if (chld.SetResult(keyobj, keyobj->IsA()))
875  return;
876  }
877 
878  if (chld.SetResult(obj, obj->IsA()))
879  return;
880 
881  TClass *obj_class = obj->IsA();
882 
883  ScanObjectProperties(chld, obj);
884 
885  if (obj->GetTitle())
886  chld.SetField(item_prop_title, obj->GetTitle());
887 
888  // special handling of TKey class - in non-readonly mode
889  // sniffer allowed to fetch objects
890  if (!chld.IsReadOnly(fReadOnly) && iskey)
891  ScanKeyProperties(chld, key, obj, obj_class);
892 
893  rec.SetRootClass(obj_class);
894 
895  ScanObjectChilds(chld, obj);
896 
897  // here we should know how many childs are accumulated
898  if (chld.SetResult(obj, obj_class))
899  return;
900  }
901  }
902  }
903 }
904 
905 ////////////////////////////////////////////////////////////////////////////////
906 /// Create own TFolder structures independent from gROOT
907 /// This allows to have many independent TRootSniffer instances
908 /// At the same time such sniffer lost access to all global lists and folders
909 
910 void TRootSniffer::CreateOwnTopFolder()
911 {
912  if (fTopFolder) return;
913 
914  SetScanGlobalDir(kFALSE);
915 
916  // this only works with c++14, use ROOT wrapper
917  fTopFolder = std::make_unique<TFolder>("http","Dedicated instance");
918 
919  // not sure if we have to add that private folder to global list of cleanups
920 
921  // R__LOCKGUARD(gROOTMutex);
922  // gROOT->GetListOfCleanups()->Add(fTopFolder.get());
923 
924 }
925 
926 ////////////////////////////////////////////////////////////////////////////////
927 /// Returns top TFolder instance for the sniffer
928 
929 TFolder *TRootSniffer::GetTopFolder(Bool_t force)
930 {
931  if (fTopFolder) return fTopFolder.get();
932 
933  TFolder *topf = gROOT->GetRootFolder();
934 
935  if (!topf) {
936  Error("RegisterObject", "Not found top ROOT folder!!!");
937  return nullptr;
938  }
939 
940  TFolder *httpfold = dynamic_cast<TFolder *>(topf->FindObject("http"));
941  if (!httpfold) {
942  if (!force)
943  return nullptr;
944  httpfold = topf->AddFolder("http", "ROOT http server");
945  httpfold->SetBit(kCanDelete);
946  // register top folder in list of cleanups
947  R__LOCKGUARD(gROOTMutex);
948  gROOT->GetListOfCleanups()->Add(httpfold);
949  }
950 
951  return httpfold;
952 }
953 
954 ////////////////////////////////////////////////////////////////////////////////
955 /// scan complete ROOT objects hierarchy
956 /// For the moment it includes objects in gROOT directory
957 /// and list of canvases and files
958 /// Also all registered objects are included.
959 /// One could reimplement this method to provide alternative
960 /// scan methods or to extend some collection kinds
961 
962 void TRootSniffer::ScanRoot(TRootSnifferScanRec &rec)
963 {
964  rec.SetField(item_prop_kind, "ROOT.Session");
965  if (fCurrentArg && fCurrentArg->GetUserName())
966  rec.SetField(item_prop_user, fCurrentArg->GetUserName());
967 
968  // should be on the top while //root/http folder could have properties for itself
969  TFolder *topf = GetTopFolder();
970  if (topf) {
971  rec.SetField(item_prop_title, topf->GetTitle());
972  ScanCollection(rec, topf->GetListOfFolders());
973  }
974 
975  if (HasStreamerInfo()) {
976  TRootSnifferScanRec chld;
977  if (chld.GoInside(rec, nullptr, "StreamerInfo", this)) {
978  chld.SetField(item_prop_kind, "ROOT.TStreamerInfoList");
979  chld.SetField(item_prop_title, "List of streamer infos for binary I/O");
980  chld.SetField(item_prop_hidden, "true", kFALSE);
981  chld.SetField("_after_request", "JSROOT.MarkAsStreamerInfo");
982  }
983  }
984 
985  if (IsScanGlobalDir()) {
986  ScanCollection(rec, gROOT->GetList());
987 
988  ScanCollection(rec, gROOT->GetListOfCanvases(), "Canvases");
989 
990  ScanCollection(rec, gROOT->GetListOfFiles(), "Files");
991  }
992 }
993 
994 ////////////////////////////////////////////////////////////////////////////////
995 /// scan ROOT hierarchy with provided store object
996 
997 void TRootSniffer::ScanHierarchy(const char *topname, const char *path, TRootSnifferStore *store,
998  Bool_t only_fields)
999 {
1000  TRootSnifferScanRec rec;
1001  rec.fSearchPath = path;
1002  if (rec.fSearchPath) {
1003  while (*rec.fSearchPath == '/')
1004  rec.fSearchPath++;
1005  if (*rec.fSearchPath == 0)
1006  rec.fSearchPath = nullptr;
1007  }
1008 
1009  // if path non-empty, we should find item first and than start scanning
1010  rec.fMask = !rec.fSearchPath ? TRootSnifferScanRec::kScan : TRootSnifferScanRec::kExpand;
1011  if (only_fields)
1012  rec.fMask |= TRootSnifferScanRec::kOnlyFields;
1013 
1014  rec.fStore = store;
1015 
1016  rec.CreateNode(topname);
1017 
1018  if (!rec.fSearchPath)
1019  rec.SetField(item_prop_rootversion, TString::Format("%d", ROOT_VERSION_CODE), kFALSE);
1020 
1021  if (!rec.fSearchPath && GetAutoLoad())
1022  rec.SetField(item_prop_autoload, GetAutoLoad());
1023 
1024  ScanRoot(rec);
1025 
1026  rec.CloseNode();
1027 }
1028 
1029 ////////////////////////////////////////////////////////////////////////////////
1030 /// Search element with specified path
1031 /// Returns pointer on element
1032 /// Optionally one could obtain element class, member description
1033 /// and number of childs. When chld!=0, not only element is searched,
1034 /// but also number of childs are counted. When member!=0, any object
1035 /// will be scanned for its data members (disregard of extra options)
1036 
1037 void *TRootSniffer::FindInHierarchy(const char *path, TClass **cl, TDataMember **member, Int_t *chld)
1038 {
1039  TRootSnifferStore store;
1040 
1041  TRootSnifferScanRec rec;
1042  rec.fSearchPath = path;
1043  rec.fMask = chld ? TRootSnifferScanRec::kCheckChilds : TRootSnifferScanRec::kSearch;
1044  if (*rec.fSearchPath == '/')
1045  rec.fSearchPath++;
1046  rec.fStore = &store;
1047 
1048  ScanRoot(rec);
1049 
1050  TDataMember *res_member = store.GetResMember();
1051  TClass *res_cl = store.GetResClass();
1052  void *res = store.GetResPtr();
1053 
1054  if (res_member && res_cl && !member) {
1055  res_cl = (res_member->IsBasic() || res_member->IsSTLContainer()) ? nullptr : gROOT->GetClass(res_member->GetTypeName());
1056  TRealData *rdata = res_cl ? res_cl->GetRealData(res_member->GetName()) : nullptr;
1057  if (rdata) {
1058  res = (char *)res + rdata->GetThisOffset();
1059  if (res_member->IsaPointer())
1060  res = *((char **)res);
1061  } else {
1062  res = nullptr; // should never happen
1063  }
1064  }
1065 
1066  if (cl)
1067  *cl = res_cl;
1068  if (member)
1069  *member = res_member;
1070  if (chld)
1071  *chld = store.GetResNumChilds();
1072 
1073  // remember current restriction
1074  fCurrentRestrict = store.GetResRestrict();
1075 
1076  return res;
1077 }
1078 
1079 ////////////////////////////////////////////////////////////////////////////////
1080 /// Search element in hierarchy, derived from TObject
1081 
1082 TObject *TRootSniffer::FindTObjectInHierarchy(const char *path)
1083 {
1084  TClass *cl = nullptr;
1085 
1086  void *obj = FindInHierarchy(path, &cl);
1087 
1088  return cl && (cl->GetBaseClassOffset(TObject::Class()) == 0) ? (TObject *)obj : nullptr;
1089 }
1090 
1091 ////////////////////////////////////////////////////////////////////////////////
1092 /// Get hash function for specified item
1093 /// used to detect any changes in the specified object
1094 
1095 ULong_t TRootSniffer::GetItemHash(const char *itemname)
1096 {
1097  TObject *obj = FindTObjectInHierarchy(itemname);
1098 
1099  return !obj ? 0 : TString::Hash(obj, obj->IsA()->Size());
1100 }
1101 
1102 ////////////////////////////////////////////////////////////////////////////////
1103 /// Method verifies if object can be drawn
1104 
1105 Bool_t TRootSniffer::CanDrawItem(const char *path)
1106 {
1107  TClass *obj_cl = nullptr;
1108  void *res = FindInHierarchy(path, &obj_cl);
1109  return (res != nullptr) && CanDrawClass(obj_cl);
1110 }
1111 
1112 ////////////////////////////////////////////////////////////////////////////////
1113 /// Method returns true when object has childs or
1114 /// one could try to expand item
1115 
1116 Bool_t TRootSniffer::CanExploreItem(const char *path)
1117 {
1118  TClass *obj_cl = nullptr;
1119  Int_t obj_chld(-1);
1120  void *res = FindInHierarchy(path, &obj_cl, nullptr, &obj_chld);
1121  return res && (obj_chld > 0);
1122 }
1123 
1124 ////////////////////////////////////////////////////////////////////////////////
1125 /// Produce JSON data for specified item
1126 /// For object conversion TBufferJSON is used
1127 
1128 Bool_t TRootSniffer::ProduceJson(const std::string &path, const std::string &options, std::string &res)
1129 {
1130  if (path.empty())
1131  return kFALSE;
1132 
1133  const char *path_ = path.c_str();
1134  if (*path_ == '/')
1135  path_++;
1136 
1137  TUrl url;
1138  url.SetOptions(options.c_str());
1139  url.ParseOptions();
1140  Int_t compact = -1;
1141  if (url.GetValueFromOptions("compact"))
1142  compact = url.GetIntValueFromOptions("compact");
1143 
1144  TClass *obj_cl = nullptr;
1145  TDataMember *member = nullptr;
1146  void *obj_ptr = FindInHierarchy(path_, &obj_cl, &member);
1147  if (!obj_ptr || (!obj_cl && !member))
1148  return kFALSE;
1149 
1150  // TODO: implement direct storage into std::string
1151  TString buf = TBufferJSON::ConvertToJSON(obj_ptr, obj_cl, compact >= 0 ? compact : 0, member ? member->GetName() : nullptr);
1152  res = buf.Data();
1153 
1154  return !res.empty();
1155 }
1156 
1157 ////////////////////////////////////////////////////////////////////////////////
1158 /// Execute command marked as _kind=='Command'
1159 
1160 Bool_t TRootSniffer::ExecuteCmd(const std::string &path, const std::string &options, std::string &res)
1161 {
1162  TFolder *parent = nullptr;
1163  TObject *obj = GetItem(path.c_str(), parent, kFALSE, kFALSE);
1164 
1165  const char *kind = GetItemField(parent, obj, item_prop_kind);
1166  if ((kind == 0) || (strcmp(kind, "Command") != 0)) {
1167  if (gDebug > 0)
1168  Info("ExecuteCmd", "Entry %s is not a command", path.c_str());
1169  res = "false";
1170  return kTRUE;
1171  }
1172 
1173  const char *cmethod = GetItemField(parent, obj, "method");
1174  if (!cmethod || (strlen(cmethod) == 0)) {
1175  if (gDebug > 0)
1176  Info("ExecuteCmd", "Entry %s do not defines method for execution", path.c_str());
1177  res = "false";
1178  return kTRUE;
1179  }
1180 
1181  // if read-only specified for the command, it is not allowed for execution
1182  if (fRestrictions.GetLast() >= 0) {
1183  FindInHierarchy(path.c_str()); // one need to call method to check access rights
1184  if (fCurrentRestrict == 1) {
1185  if (gDebug > 0)
1186  Info("ExecuteCmd", "Entry %s not allowed for specified user", path.c_str());
1187  res = "false";
1188  return kTRUE;
1189  }
1190  }
1191 
1192  TString method = cmethod;
1193 
1194  const char *cnumargs = GetItemField(parent, obj, "_numargs");
1195  Int_t numargs = cnumargs ? TString(cnumargs).Atoi() : 0;
1196  if (numargs > 0) {
1197  TUrl url;
1198  url.SetOptions(options.c_str());
1199  url.ParseOptions();
1200 
1201  for (Int_t n = 0; n < numargs; n++) {
1202  TString argname = TString::Format("arg%d", n + 1);
1203  const char *argvalue = url.GetValueFromOptions(argname);
1204  if (!argvalue) {
1205  if (gDebug > 0)
1206  Info("ExecuteCmd", "For command %s argument %s not specified in options %s", path.c_str(), argname.Data(),
1207  options.c_str());
1208  res = "false";
1209  return kTRUE;
1210  }
1211 
1212  TString svalue = DecodeUrlOptionValue(argvalue, kTRUE);
1213  argname = TString("%") + argname + TString("%");
1214  method.ReplaceAll(argname, svalue);
1215  }
1216  }
1217 
1218  if (gDebug > 0)
1219  Info("ExecuteCmd", "Executing command %s method:%s", path.c_str(), method.Data());
1220 
1221  TObject *item_obj = nullptr;
1222  Ssiz_t separ = method.Index("/->");
1223 
1224  if (method.Index("this->") == 0) {
1225  // if command name started with this-> means method of sniffer will be executed
1226  item_obj = this;
1227  separ = 3;
1228  } else if (separ != kNPOS) {
1229  item_obj = FindTObjectInHierarchy(TString(method.Data(), separ).Data());
1230  }
1231 
1232  if (item_obj) {
1233  method =
1234  TString::Format("((%s*)%lu)->%s", item_obj->ClassName(), (long unsigned)item_obj, method.Data() + separ + 3);
1235  if (gDebug > 2)
1236  Info("ExecuteCmd", "Executing %s", method.Data());
1237  }
1238 
1239  Long_t v = gROOT->ProcessLineSync(method.Data());
1240 
1241  res = std::to_string(v);
1242 
1243  return kTRUE;
1244 }
1245 
1246 ////////////////////////////////////////////////////////////////////////////////
1247 /// Produce JSON/XML for specified item
1248 /// contrary to h.json request, only fields for specified item are stored
1249 
1250 Bool_t TRootSniffer::ProduceItem(const std::string &path, const std::string &options, std::string &res, Bool_t asjson)
1251 {
1252  TString buf; // TODO: implement direct storage into std::string
1253  if (asjson) {
1254  TRootSnifferStoreJson store(buf, options.find("compact") != std::string::npos);
1255  ScanHierarchy("top", path.c_str(), &store, kTRUE);
1256  } else {
1257  TRootSnifferStoreXml store(buf, options.find("compact") != std::string::npos);
1258  ScanHierarchy("top", path.c_str(), &store, kTRUE);
1259  }
1260  res = buf.Data();
1261  return !res.empty();
1262 }
1263 
1264 ////////////////////////////////////////////////////////////////////////////////
1265 /// Produce XML data for specified item
1266 /// For object conversion TBufferXML is used
1267 ///
1268 /// Method implemented only in TRootSnifferFull class
1269 
1270 Bool_t TRootSniffer::ProduceXml(const std::string &/* path */, const std::string & /* options */, std::string & /* res */)
1271 {
1272  return kFALSE;
1273 }
1274 
1275 ////////////////////////////////////////////////////////////////////////////////
1276 /// method replaces all kind of special symbols, which could appear in URL options
1277 
1278 TString TRootSniffer::DecodeUrlOptionValue(const char *value, Bool_t remove_quotes)
1279 {
1280  if (!value || (strlen(value) == 0))
1281  return TString();
1282 
1283  TString res = value;
1284 
1285  res.ReplaceAll("%27", "\'");
1286  res.ReplaceAll("%22", "\"");
1287  res.ReplaceAll("%3E", ">");
1288  res.ReplaceAll("%3C", "<");
1289  res.ReplaceAll("%20", " ");
1290  res.ReplaceAll("%5B", "[");
1291  res.ReplaceAll("%5D", "]");
1292  res.ReplaceAll("%3D", "=");
1293 
1294  if (remove_quotes && (res.Length() > 1) && ((res[0] == '\'') || (res[0] == '\"')) &&
1295  (res[0] == res[res.Length() - 1])) {
1296  res.Remove(res.Length() - 1);
1297  res.Remove(0, 1);
1298  }
1299 
1300  return res;
1301 }
1302 
1303 ////////////////////////////////////////////////////////////////////////////////
1304 /// Execute command for specified object
1305 /// Options include method and extra list of parameters
1306 /// sniffer should be not-readonly to allow execution of the commands
1307 /// reskind defines kind of result 0 - debug, 1 - json, 2 - binary
1308 ///
1309 /// Method implemented only in TRootSnifferFull class
1310 
1311 Bool_t TRootSniffer::ProduceExe(const std::string & /*path*/, const std::string & /*options*/, Int_t /*reskind*/,
1312  std::string & /*res*/)
1313 {
1314  return kFALSE;
1315 }
1316 
1317 ////////////////////////////////////////////////////////////////////////////////
1318 /// Process several requests, packing all results into binary or JSON buffer
1319 /// Input parameters should be coded in the POST block and has
1320 /// individual request relative to current path, separated with '\n' symbol like
1321 /// item1/root.bin\n
1322 /// item2/exe.bin?method=GetList\n
1323 /// item3/exe.bin?method=GetTitle\n
1324 /// Request requires 'number' URL option which contains number of requested items
1325 ///
1326 /// In case of binary request output buffer looks like:
1327 /// 4bytes length + payload, 4bytes length + payload, ...
1328 /// In case of JSON request output is array with results for each item
1329 /// multi.json request do not support binary requests for the items
1330 
1331 Bool_t TRootSniffer::ProduceMulti(const std::string &path, const std::string &options, std::string &str, Bool_t asjson)
1332 {
1333  if (!fCurrentArg || (fCurrentArg->GetPostDataLength() <= 0) || !fCurrentArg->GetPostData())
1334  return kFALSE;
1335 
1336  const char *args = (const char *)fCurrentArg->GetPostData();
1337  const char *ends = args + fCurrentArg->GetPostDataLength();
1338 
1339  TUrl url;
1340  url.SetOptions(options.c_str());
1341 
1342  Int_t number = 0;
1343  if (url.GetValueFromOptions("number"))
1344  number = url.GetIntValueFromOptions("number");
1345 
1346  // binary buffers required only for binary requests, json output can be produced as is
1347  std::vector<std::string> mem;
1348 
1349  if (asjson)
1350  str = "[";
1351 
1352  for (Int_t n = 0; n < number; n++) {
1353  const char *next = args;
1354  while ((next < ends) && (*next != '\n'))
1355  next++;
1356  if (next == ends) {
1357  Error("ProduceMulti", "Not enough arguments in POST block");
1358  break;
1359  }
1360 
1361  std::string file1(args, next - args);
1362  args = next + 1;
1363 
1364  std::string path1, opt1;
1365 
1366  // extract options
1367  std::size_t pos = file1.find_first_of('?');
1368  if (pos != std::string::npos) {
1369  opt1 = file1.substr(pos + 1, file1.length() - pos);
1370  file1.resize(pos);
1371  }
1372 
1373  // extract extra path
1374  pos = file1.find_last_of('/');
1375  if (pos != std::string::npos) {
1376  path1 = file1.substr(0, pos);
1377  file1.erase(0, pos + 1);
1378  }
1379 
1380  if (!path.empty())
1381  path1 = path + "/" + path1;
1382 
1383  std::string res1;
1384 
1385  // produce next item request
1386  Produce(path1, file1, opt1, res1);
1387 
1388  if (asjson) {
1389  if (n > 0)
1390  str.append(", ");
1391  if (res1.empty())
1392  str.append("null");
1393  else
1394  str.append(res1);
1395  } else {
1396  mem.emplace_back(std::move(res1));
1397  }
1398  }
1399 
1400  if (asjson) {
1401  str.append("]");
1402  } else {
1403  Int_t length = 0;
1404  for (unsigned n = 0; n < mem.size(); n++)
1405  length += 4 + mem[n].length();
1406  str.resize(length);
1407  char *curr = (char *)str.data();
1408  for (unsigned n = 0; n < mem.size(); n++) {
1409  Long_t l = mem[n].length();
1410  *curr++ = (char)(l & 0xff);
1411  l = l >> 8;
1412  *curr++ = (char)(l & 0xff);
1413  l = l >> 8;
1414  *curr++ = (char)(l & 0xff);
1415  l = l >> 8;
1416  *curr++ = (char)(l & 0xff);
1417  if (!mem[n].empty())
1418  memcpy(curr, mem[n].data(), mem[n].length());
1419  curr += mem[n].length();
1420  }
1421  }
1422 
1423  return kTRUE;
1424 }
1425 
1426 ////////////////////////////////////////////////////////////////////////////////
1427 /// Produce binary data for specified item
1428 /// if "zipped" option specified in query, buffer will be compressed
1429 ///
1430 /// Implemented only in TRootSnifferFull class
1431 
1432 Bool_t TRootSniffer::ProduceBinary(const std::string & /*path*/, const std::string & /*query*/, std::string & /*res*/)
1433 {
1434  return kFALSE;
1435 }
1436 
1437 ////////////////////////////////////////////////////////////////////////////////
1438 /// Method to produce image from specified object
1439 ///
1440 /// Parameters:
1441 /// kind - image kind TImage::kPng, TImage::kJpeg, TImage::kGif
1442 /// path - path to object
1443 /// options - extra options
1444 ///
1445 /// By default, image 300x200 is produced
1446 /// In options string one could provide following parameters:
1447 /// w - image width
1448 /// h - image height
1449 /// opt - draw options
1450 /// For instance:
1451 /// http://localhost:8080/Files/hsimple.root/hpx/get.png?w=500&h=500&opt=lego1
1452 ///
1453 /// Return is memory with produced image
1454 /// Memory must be released by user with free(ptr) call
1455 ///
1456 /// Method implemented only in TRootSnifferFull class
1457 
1458 Bool_t TRootSniffer::ProduceImage(Int_t /*kind*/, const std::string & /*path*/, const std::string & /*options*/, std::string & /*res*/)
1459 {
1460  return kFALSE;
1461 }
1462 
1463 ////////////////////////////////////////////////////////////////////////////////
1464 /// Method produce different kind of data out of object
1465 /// Parameter 'path' specifies object or object member
1466 /// Supported 'file' (case sensitive):
1467 /// "root.bin" - binary data
1468 /// "root.png" - png image
1469 /// "root.jpeg" - jpeg image
1470 /// "root.gif" - gif image
1471 /// "root.xml" - xml representation
1472 /// "root.json" - json representation
1473 /// "exe.json" - method execution with json reply
1474 /// "exe.bin" - method execution with binary reply
1475 /// "exe.txt" - method execution with debug output
1476 /// "cmd.json" - execution of registered commands
1477 /// Result returned in std::string - can be binary or text.
1478 
1479 Bool_t TRootSniffer::Produce(const std::string &path, const std::string &file, const std::string &options, std::string &res)
1480 {
1481  if (file.empty())
1482  return kFALSE;
1483 
1484  if (file == "root.bin")
1485  return ProduceBinary(path, options, res);
1486 
1487  if (file == "root.png")
1488  return ProduceImage(TImage::kPng, path, options, res);
1489 
1490  if (file == "root.jpeg")
1491  return ProduceImage(TImage::kJpeg, path, options, res);
1492 
1493  if (file == "root.gif")
1494  return ProduceImage(TImage::kGif, path, options, res);
1495 
1496  if (file == "exe.bin")
1497  return ProduceExe(path, options, 2, res);
1498 
1499  if (file == "root.xml")
1500  return ProduceXml(path, options, res);
1501 
1502  if (file == "root.json")
1503  return ProduceJson(path, options, res);
1504 
1505  // used for debugging
1506  if (file == "exe.txt")
1507  return ProduceExe(path, options, 0, res);
1508 
1509  if (file == "exe.json")
1510  return ProduceExe(path, options, 1, res);
1511 
1512  if (file == "cmd.json")
1513  return ExecuteCmd(path, options, res);
1514 
1515  if (file == "item.json")
1516  return ProduceItem(path, options, res, kTRUE);
1517 
1518  if (file == "item.xml")
1519  return ProduceItem(path, options, res, kFALSE);
1520 
1521  if (file == "multi.bin")
1522  return ProduceMulti(path, options, res, kFALSE);
1523 
1524  if (file == "multi.json")
1525  return ProduceMulti(path, options, res, kTRUE);
1526 
1527  return kFALSE;
1528 }
1529 
1530 ////////////////////////////////////////////////////////////////////////////////
1531 /// return item from the subfolders structure
1532 
1533 TObject *TRootSniffer::GetItem(const char *fullname, TFolder *&parent, Bool_t force, Bool_t within_objects)
1534 {
1535  TFolder *httpfold = GetTopFolder(force);
1536  if (!httpfold) return nullptr;
1537 
1538  parent = httpfold;
1539  TObject *obj = httpfold;
1540 
1541  if (!fullname)
1542  return httpfold;
1543 
1544  // when full path started not with slash, "Objects" subfolder is appended
1545  TString path = fullname;
1546  if (within_objects && ((path.Length() == 0) || (path[0] != '/')))
1547  path = fObjectsPath + "/" + path;
1548 
1549  TString tok;
1550  Ssiz_t from(0);
1551 
1552  while (path.Tokenize(tok, from, "/")) {
1553  if (tok.Length() == 0)
1554  continue;
1555 
1556  TFolder *fold = dynamic_cast<TFolder *>(obj);
1557  if (!fold)
1558  return nullptr;
1559 
1560  TIter iter(fold->GetListOfFolders());
1561  while ((obj = iter()) != nullptr) {
1562  if (IsItemField(obj))
1563  continue;
1564  if (tok.CompareTo(obj->GetName()) == 0)
1565  break;
1566  }
1567 
1568  if (!obj) {
1569  if (!force)
1570  return nullptr;
1571  obj = fold->AddFolder(tok, "sub-folder");
1572  obj->SetBit(kCanDelete);
1573  }
1574 
1575  parent = fold;
1576  }
1577 
1578  return obj;
1579 }
1580 
1581 ////////////////////////////////////////////////////////////////////////////////
1582 /// creates subfolder where objects can be registered
1583 
1584 TFolder *TRootSniffer::GetSubFolder(const char *subfolder, Bool_t force)
1585 {
1586  TFolder *parent = nullptr;
1587 
1588  return dynamic_cast<TFolder *>(GetItem(subfolder, parent, force));
1589 }
1590 
1591 ////////////////////////////////////////////////////////////////////////////////
1592 /// Register object in subfolder structure
1593 /// subfolder parameter can have many levels like:
1594 ///
1595 /// TRootSniffer* sniff = new TRootSniffer("sniff");
1596 /// sniff->RegisterObject("my/sub/subfolder", h1);
1597 ///
1598 /// Such objects can be later found in "Objects" folder of sniffer like
1599 ///
1600 /// h1 = sniff->FindTObjectInHierarchy("/Objects/my/sub/subfolder/h1");
1601 ///
1602 /// If subfolder name starts with '/', object will be registered starting from top folder.
1603 ///
1604 /// One could provide additional fields for registered objects
1605 /// For instance, setting "_more" field to true let browser
1606 /// explore objects members. For instance:
1607 ///
1608 /// TEvent* ev = new TEvent("ev");
1609 /// sniff->RegisterObject("Events", ev);
1610 /// sniff->SetItemField("Events/ev", "_more", "true");
1611 
1612 Bool_t TRootSniffer::RegisterObject(const char *subfolder, TObject *obj)
1613 {
1614  TFolder *f = GetSubFolder(subfolder, kTRUE);
1615  if (!f)
1616  return kFALSE;
1617 
1618  // If object will be destroyed, it will be removed from the folders automatically
1619  obj->SetBit(kMustCleanup);
1620 
1621  f->Add(obj);
1622 
1623  return kTRUE;
1624 }
1625 
1626 ////////////////////////////////////////////////////////////////////////////////
1627 /// unregister (remove) object from folders structures
1628 /// folder itself will remain even when it will be empty
1629 
1630 Bool_t TRootSniffer::UnregisterObject(TObject *obj)
1631 {
1632  if (!obj)
1633  return kTRUE;
1634 
1635  TFolder *topf = GetTopFolder();
1636 
1637  if (!topf) {
1638  Error("UnregisterObject", "Not found top folder");
1639  return kFALSE;
1640  }
1641 
1642  // TODO - probably we should remove all set properties as well
1643  topf->RecursiveRemove(obj);
1644 
1645  return kTRUE;
1646 }
1647 
1648 ////////////////////////////////////////////////////////////////////////////////
1649 /// create item element
1650 
1651 Bool_t TRootSniffer::CreateItem(const char *fullname, const char *title)
1652 {
1653  TFolder *f = GetSubFolder(fullname, kTRUE);
1654  if (!f)
1655  return kFALSE;
1656 
1657  if (title)
1658  f->SetTitle(title);
1659 
1660  return kTRUE;
1661 }
1662 
1663 ////////////////////////////////////////////////////////////////////////////////
1664 /// return true when object is TNamed with kItemField bit set
1665 /// such objects used to keep field values for item
1666 
1667 Bool_t TRootSniffer::IsItemField(TObject *obj) const
1668 {
1669  return (obj != nullptr) && (obj->IsA() == TNamed::Class()) && obj->TestBit(kItemField);
1670 }
1671 
1672 ////////////////////////////////////////////////////////////////////////////////
1673 /// set or get field for the child
1674 /// each field coded as TNamed object, placed after chld in the parent hierarchy
1675 
1676 Bool_t TRootSniffer::AccessField(TFolder *parent, TObject *chld, const char *name, const char *value, TNamed **only_get)
1677 {
1678  if (!parent)
1679  return kFALSE;
1680 
1681  if (!chld) {
1682  Info("AccessField", "Should be special case for top folder, support later");
1683  return kFALSE;
1684  }
1685 
1686  TIter iter(parent->GetListOfFolders());
1687 
1688  TObject *obj = nullptr;
1689  Bool_t find(kFALSE), last_find(kFALSE);
1690  // this is special case of top folder - fields are on very top
1691  if (parent == chld) {
1692  last_find = find = kTRUE;
1693  }
1694  TNamed *curr = nullptr;
1695  while ((obj = iter()) != nullptr) {
1696  if (IsItemField(obj)) {
1697  if (last_find && obj->GetName() && !strcmp(name, obj->GetName()))
1698  curr = (TNamed *)obj;
1699  } else {
1700  last_find = (obj == chld);
1701  if (last_find)
1702  find = kTRUE;
1703  if (find && !last_find)
1704  break; // no need to continue
1705  }
1706  }
1707 
1708  // object must be in childs list
1709  if (!find)
1710  return kFALSE;
1711 
1712  if (only_get) {
1713  *only_get = curr;
1714  return curr != nullptr;
1715  }
1716 
1717  if (curr) {
1718  if (value) {
1719  curr->SetTitle(value);
1720  } else {
1721  parent->Remove(curr);
1722  delete curr;
1723  }
1724  return kTRUE;
1725  }
1726 
1727  curr = new TNamed(name, value);
1728  curr->SetBit(kItemField);
1729 
1730  if (last_find) {
1731  // object is on last place, therefore just add property
1732  parent->Add(curr);
1733  return kTRUE;
1734  }
1735 
1736  // only here we do dynamic cast to the TList to use AddAfter
1737  TList *lst = dynamic_cast<TList *>(parent->GetListOfFolders());
1738  if (!lst) {
1739  Error("AccessField", "Fail cast to TList");
1740  return kFALSE;
1741  }
1742 
1743  if (parent == chld)
1744  lst->AddFirst(curr);
1745  else
1746  lst->AddAfter(chld, curr);
1747 
1748  return kTRUE;
1749 }
1750 
1751 ////////////////////////////////////////////////////////////////////////////////
1752 /// set field for specified item
1753 
1754 Bool_t TRootSniffer::SetItemField(const char *fullname, const char *name, const char *value)
1755 {
1756  if (!fullname || !name)
1757  return kFALSE;
1758 
1759  TFolder *parent = nullptr;
1760  TObject *obj = GetItem(fullname, parent);
1761 
1762  if (!parent || !obj)
1763  return kFALSE;
1764 
1765  if (strcmp(name, item_prop_title) == 0) {
1766  TNamed *n = dynamic_cast<TNamed *>(obj);
1767  if (n) {
1768  n->SetTitle(value);
1769  return kTRUE;
1770  }
1771  }
1772 
1773  return AccessField(parent, obj, name, value);
1774 }
1775 
1776 ////////////////////////////////////////////////////////////////////////////////
1777 /// return field for specified item
1778 
1779 const char *TRootSniffer::GetItemField(TFolder *parent, TObject *obj, const char *name)
1780 {
1781  if (!parent || !obj || !name)
1782  return nullptr;
1783 
1784  TNamed *field = nullptr;
1785 
1786  if (!AccessField(parent, obj, name, nullptr, &field))
1787  return nullptr;
1788 
1789  return field ? field->GetTitle() : nullptr;
1790 }
1791 
1792 ////////////////////////////////////////////////////////////////////////////////
1793 /// return field for specified item
1794 
1795 const char *TRootSniffer::GetItemField(const char *fullname, const char *name)
1796 {
1797  if (!fullname)
1798  return nullptr;
1799 
1800  TFolder *parent = nullptr;
1801  TObject *obj = GetItem(fullname, parent);
1802 
1803  return GetItemField(parent, obj, name);
1804 }
1805 
1806 ////////////////////////////////////////////////////////////////////////////////
1807 /// Register command which can be executed from web interface
1808 ///
1809 /// As method one typically specifies string, which is executed with
1810 /// gROOT->ProcessLine() method. For instance
1811 /// serv->RegisterCommand("Invoke","InvokeFunction()");
1812 ///
1813 /// Or one could specify any method of the object which is already registered
1814 /// to the server. For instance:
1815 /// serv->Register("/", hpx);
1816 /// serv->RegisterCommand("/ResetHPX", "/hpx/->Reset()");
1817 /// Here symbols '/->' separates item name from method to be executed
1818 ///
1819 /// One could specify additional arguments in the command with
1820 /// syntax like %arg1%, %arg2% and so on. For example:
1821 /// serv->RegisterCommand("/ResetHPX", "/hpx/->SetTitle(\"%arg1%\")");
1822 /// serv->RegisterCommand("/RebinHPXPY", "/hpxpy/->Rebin2D(%arg1%,%arg2%)");
1823 /// Such parameter(s) will be requested when command clicked in the browser.
1824 ///
1825 /// Once command is registered, one could specify icon which will appear in the browser:
1826 /// serv->SetIcon("/ResetHPX", "rootsys/icons/ed_execute.png");
1827 ///
1828 /// One also can set extra property '_fastcmd', that command appear as
1829 /// tool button on the top of the browser tree:
1830 /// serv->SetItemField("/ResetHPX", "_fastcmd", "true");
1831 /// Or it is equivalent to specifying extra argument when register command:
1832 /// serv->RegisterCommand("/ResetHPX", "/hpx/->Reset()", "button;rootsys/icons/ed_delete.png");
1833 
1834 Bool_t TRootSniffer::RegisterCommand(const char *cmdname, const char *method, const char *icon)
1835 {
1836  CreateItem(cmdname, Form("command %s", method));
1837  SetItemField(cmdname, "_kind", "Command");
1838  if (icon) {
1839  if (strncmp(icon, "button;", 7) == 0) {
1840  SetItemField(cmdname, "_fastcmd", "true");
1841  icon += 7;
1842  }
1843  if (*icon != 0)
1844  SetItemField(cmdname, "_icon", icon);
1845  }
1846  SetItemField(cmdname, "method", method);
1847  Int_t numargs = 0;
1848  do {
1849  TString nextname = TString::Format("%sarg%d%s", "%", numargs + 1, "%");
1850  if (strstr(method, nextname.Data()) == nullptr)
1851  break;
1852  numargs++;
1853  } while (numargs < 100);
1854  if (numargs > 0)
1855  SetItemField(cmdname, "_numargs", TString::Format("%d", numargs));
1856 
1857  return kTRUE;
1858 }