1 /**
2  * Orthanc - A Lightweight, RESTful DICOM Store
3  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
4  * Department, University Hospital of Liege, Belgium
5  * Copyright (C) 2017-2021 Osimis S.A., Belgium
6  *
7  * This program is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public License
9  * as published by the Free Software Foundation, either version 3 of
10  * the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this program. If not, see
19  * <http://www.gnu.org/licenses/>.
20  **/
21 
22 
23 #include "../PrecompiledHeaders.h"
24 #include "RestApi.h"
25 
26 #include "../HttpServer/StringHttpOutput.h"
27 #include "../Logging.h"
28 #include "../OrthancException.h"
29 
30 #include <boost/algorithm/string/replace.hpp>
31 #include <boost/math/special_functions/round.hpp>
32 #include <stdlib.h>   // To define "_exit()" under Windows
33 #include <stdio.h>
34 
35 namespace Orthanc
36 {
37   namespace
38   {
39     // Anonymous namespace to avoid clashes between compilation modules
40     class HttpHandlerVisitor : public RestApiHierarchy::IVisitor
41     {
42     private:
43       RestApi& api_;
44       RestApiOutput& output_;
45       RequestOrigin origin_;
46       const char* remoteIp_;
47       const char* username_;
48       HttpMethod method_;
49       const HttpToolbox::Arguments& headers_;
50       const HttpToolbox::Arguments& getArguments_;
51       const void* bodyData_;
52       size_t bodySize_;
53 
54     public:
HttpHandlerVisitor(RestApi & api,RestApiOutput & output,RequestOrigin origin,const char * remoteIp,const char * username,HttpMethod method,const HttpToolbox::Arguments & headers,const HttpToolbox::Arguments & getArguments,const void * bodyData,size_t bodySize)55       HttpHandlerVisitor(RestApi& api,
56                          RestApiOutput& output,
57                          RequestOrigin origin,
58                          const char* remoteIp,
59                          const char* username,
60                          HttpMethod method,
61                          const HttpToolbox::Arguments& headers,
62                          const HttpToolbox::Arguments& getArguments,
63                          const void* bodyData,
64                          size_t bodySize) :
65         api_(api),
66         output_(output),
67         origin_(origin),
68         remoteIp_(remoteIp),
69         username_(username),
70         method_(method),
71         headers_(headers),
72         getArguments_(getArguments),
73         bodyData_(bodyData),
74         bodySize_(bodySize)
75       {
76       }
77 
Visit(const RestApiHierarchy::Resource & resource,const UriComponents & uri,bool hasTrailing,const HttpToolbox::Arguments & components,const UriComponents & trailing)78       virtual bool Visit(const RestApiHierarchy::Resource& resource,
79                          const UriComponents& uri,
80                          bool hasTrailing,
81                          const HttpToolbox::Arguments& components,
82                          const UriComponents& trailing)
83       {
84         if (resource.HasHandler(method_))
85         {
86           switch (method_)
87           {
88             case HttpMethod_Get:
89             {
90               RestApiGetCall call(output_, api_, origin_, remoteIp_, username_,
91                                   headers_, components, trailing, uri, getArguments_);
92               resource.Handle(call);
93               return true;
94             }
95 
96             case HttpMethod_Post:
97             {
98               RestApiPostCall call(output_, api_, origin_, remoteIp_, username_,
99                                    headers_, components, trailing, uri, bodyData_, bodySize_);
100               resource.Handle(call);
101               return true;
102             }
103 
104             case HttpMethod_Delete:
105             {
106               RestApiDeleteCall call(output_, api_, origin_, remoteIp_, username_,
107                                      headers_, components, trailing, uri);
108               resource.Handle(call);
109               return true;
110             }
111 
112             case HttpMethod_Put:
113             {
114               RestApiPutCall call(output_, api_, origin_, remoteIp_, username_,
115                                   headers_, components, trailing, uri, bodyData_, bodySize_);
116               resource.Handle(call);
117               return true;
118             }
119 
120             default:
121               return false;
122           }
123         }
124 
125         return false;
126       }
127     };
128 
129 
130 
131     class DocumentationVisitor : public RestApiHierarchy::IVisitor
132     {
133     private:
134       RestApi&    restApi_;
135       size_t      successPathsCount_;
136       size_t      totalPathsCount_;
137 
138     protected:
139       virtual bool HandleCall(RestApiCall& call,
140                               const std::set<std::string>& uriArgumentsNames) = 0;
141 
142     public:
DocumentationVisitor(RestApi & restApi)143       explicit DocumentationVisitor(RestApi& restApi) :
144         restApi_(restApi),
145         successPathsCount_(0),
146         totalPathsCount_(0)
147       {
148       }
149 
Visit(const RestApiHierarchy::Resource & resource,const UriComponents & uri,bool hasTrailing,const HttpToolbox::Arguments & components,const UriComponents & trailing)150       virtual bool Visit(const RestApiHierarchy::Resource& resource,
151                          const UriComponents& uri,
152                          bool hasTrailing,
153                          const HttpToolbox::Arguments& components,
154                          const UriComponents& trailing)
155       {
156         std::string path = Toolbox::FlattenUri(uri);
157         if (hasTrailing)
158         {
159           path += "/{...}";
160         }
161 
162         std::set<std::string> uriArgumentsNames;
163         HttpToolbox::Arguments uriArguments;
164 
165         for (HttpToolbox::Arguments::const_iterator
166                it = components.begin(); it != components.end(); ++it)
167         {
168           assert(it->second.empty());
169           uriArgumentsNames.insert(it->first.c_str());
170           uriArguments[it->first] = "";
171         }
172 
173         if (hasTrailing)
174         {
175           uriArgumentsNames.insert("...");
176           uriArguments["..."] = "";
177         }
178 
179         if (resource.HasHandler(HttpMethod_Get))
180         {
181           totalPathsCount_ ++;
182 
183           StringHttpOutput o1;
184           HttpOutput o2(o1, false);
185           RestApiOutput o3(o2, HttpMethod_Get);
186           RestApiGetCall call(o3, restApi_, RequestOrigin_Documentation, "" /* remote IP */,
187                               "" /* username */, HttpToolbox::Arguments() /* HTTP headers */,
188                               uriArguments, UriComponents() /* trailing */,
189                               uri, HttpToolbox::Arguments() /* GET arguments */);
190 
191           bool ok = false;
192 
193           try
194           {
195             ok = (resource.Handle(call) &&
196                   HandleCall(call, uriArgumentsNames));
197           }
198           catch (OrthancException& e)
199           {
200             LOG(ERROR) << "Exception while documenting GET " << path << ": " << e.What();
201           }
202           catch (boost::bad_lexical_cast&)
203           {
204             LOG(ERROR) << "Bad lexical cast while documenting GET " << path;
205           }
206 
207           if (ok)
208           {
209             successPathsCount_ ++;
210           }
211           else
212           {
213             LOG(WARNING) << "Ignoring URI without API documentation: GET " << path;
214           }
215         }
216 
217         if (resource.HasHandler(HttpMethod_Post))
218         {
219           totalPathsCount_ ++;
220 
221           StringHttpOutput o1;
222           HttpOutput o2(o1, false);
223           RestApiOutput o3(o2, HttpMethod_Post);
224           RestApiPostCall call(o3, restApi_, RequestOrigin_Documentation, "" /* remote IP */,
225                                "" /* username */, HttpToolbox::Arguments() /* HTTP headers */,
226                                uriArguments, UriComponents() /* trailing */,
227                                uri, NULL /* body */, 0 /* body size */);
228 
229           bool ok = false;
230 
231           try
232           {
233             ok = (resource.Handle(call) &&
234                   HandleCall(call, uriArgumentsNames));
235           }
236           catch (OrthancException& e)
237           {
238             LOG(ERROR) << "Exception while documenting POST " << path << ": " << e.What();
239           }
240           catch (boost::bad_lexical_cast&)
241           {
242             LOG(ERROR) << "Bad lexical cast while documenting POST " << path;
243           }
244 
245           if (ok)
246           {
247             successPathsCount_ ++;
248           }
249           else
250           {
251             LOG(WARNING) << "Ignoring URI without API documentation: POST " << path;
252           }
253         }
254 
255         if (resource.HasHandler(HttpMethod_Delete))
256         {
257           totalPathsCount_ ++;
258 
259           StringHttpOutput o1;
260           HttpOutput o2(o1, false);
261           RestApiOutput o3(o2, HttpMethod_Delete);
262           RestApiDeleteCall call(o3, restApi_, RequestOrigin_Documentation, "" /* remote IP */,
263                                  "" /* username */, HttpToolbox::Arguments() /* HTTP headers */,
264                                  uriArguments, UriComponents() /* trailing */, uri);
265 
266           bool ok = false;
267 
268           try
269           {
270             ok = (resource.Handle(call) &&
271                   HandleCall(call, uriArgumentsNames));
272           }
273           catch (OrthancException& e)
274           {
275             LOG(ERROR) << "Exception while documenting DELETE " << path << ": " << e.What();
276           }
277           catch (boost::bad_lexical_cast&)
278           {
279             LOG(ERROR) << "Bad lexical cast while documenting DELETE " << path;
280           }
281 
282           if (ok)
283           {
284             successPathsCount_ ++;
285           }
286           else
287           {
288             LOG(WARNING) << "Ignoring URI without API documentation: DELETE " << path;
289           }
290         }
291 
292         if (resource.HasHandler(HttpMethod_Put))
293         {
294           totalPathsCount_ ++;
295 
296           StringHttpOutput o1;
297           HttpOutput o2(o1, false);
298           RestApiOutput o3(o2, HttpMethod_Put);
299           RestApiPutCall call(o3, restApi_, RequestOrigin_Documentation, "" /* remote IP */,
300                               "" /* username */, HttpToolbox::Arguments() /* HTTP headers */,
301                               uriArguments, UriComponents() /* trailing */, uri,
302                               NULL /* body */, 0 /* body size */);
303 
304           bool ok = false;
305 
306           try
307           {
308             ok = (resource.Handle(call) &&
309                   HandleCall(call, uriArgumentsNames));
310           }
311           catch (OrthancException& e)
312           {
313             LOG(ERROR) << "Exception while documenting PUT " << path << ": " << e.What();
314           }
315           catch (boost::bad_lexical_cast&)
316           {
317             LOG(ERROR) << "Bad lexical cast while documenting PUT " << path;
318           }
319 
320           if (ok)
321           {
322             successPathsCount_ ++;
323           }
324           else
325           {
326             LOG(WARNING) << "Ignoring URI without API documentation: PUT " << path;
327           }
328         }
329 
330         return true;
331       }
332 
GetSuccessPathsCount() const333       size_t GetSuccessPathsCount() const
334       {
335         return successPathsCount_;
336       }
337 
GetTotalPathsCount() const338       size_t GetTotalPathsCount() const
339       {
340         return totalPathsCount_;
341       }
342 
LogStatistics() const343       void LogStatistics() const
344       {
345         assert(GetSuccessPathsCount() <= GetTotalPathsCount());
346         size_t total = GetTotalPathsCount();
347         if (total == 0)
348         {
349           total = 1;  // Avoid division by zero
350         }
351         float coverage = (100.0f * static_cast<float>(GetSuccessPathsCount()) /
352                           static_cast<float>(total));
353 
354         LOG(WARNING) << "The documentation of the REST API contains " << GetSuccessPathsCount()
355                      << " paths over a total of " << GetTotalPathsCount() << " paths "
356                      << "(coverage: " << static_cast<unsigned int>(boost::math::iround(coverage)) << "%)";
357       }
358     };
359 
360 
361     class OpenApiVisitor : public DocumentationVisitor
362     {
363     private:
364       Json::Value paths_;
365 
366     protected:
HandleCall(RestApiCall & call,const std::set<std::string> & uriArgumentsNames)367       virtual bool HandleCall(RestApiCall& call,
368                               const std::set<std::string>& uriArgumentsNames) ORTHANC_OVERRIDE
369       {
370         const std::string path = Toolbox::FlattenUri(call.GetFullUri());
371 
372         Json::Value v;
373         if (call.GetDocumentation().FormatOpenApi(v, uriArgumentsNames, path))
374         {
375           std::string method;
376 
377           switch (call.GetMethod())
378           {
379             case HttpMethod_Get:
380               method = "get";
381               break;
382 
383             case HttpMethod_Post:
384               method = "post";
385               break;
386 
387             case HttpMethod_Delete:
388               method = "delete";
389               break;
390 
391             case HttpMethod_Put:
392               method = "put";
393               break;
394 
395             default:
396               throw OrthancException(ErrorCode_ParameterOutOfRange);
397           }
398 
399           if ((paths_.isMember(path) &&
400                paths_[path].type() != Json::objectValue) ||
401               paths_[path].isMember(method))
402           {
403             throw OrthancException(ErrorCode_InternalError);
404           }
405 
406           paths_[path][method] = v;
407 
408           return true;
409         }
410         else
411         {
412           return false;
413         }
414       }
415 
416     public:
OpenApiVisitor(RestApi & restApi)417       explicit OpenApiVisitor(RestApi& restApi) :
418         DocumentationVisitor(restApi),
419         paths_(Json::objectValue)
420       {
421       }
422 
GetPaths() const423       const Json::Value& GetPaths() const
424       {
425         return paths_;
426       }
427     };
428 
429 
430     class ReStructuredTextCheatSheet : public DocumentationVisitor
431     {
432     private:
433       class Path
434       {
435       private:
436         bool        hasGet_;
437         bool        hasPost_;
438         bool        hasDelete_;
439         bool        hasPut_;
440         std::string getTag_;
441         std::string postTag_;
442         std::string deleteTag_;
443         std::string putTag_;
444         std::string summary_;
445         bool        getDeprecated_;
446         bool        postDeprecated_;
447         bool        deleteDeprecated_;
448         bool        putDeprecated_;
449         HttpMethod  summaryOrigin_;
450 
451       public:
Path()452         Path() :
453           hasGet_(false),
454           hasPost_(false),
455           hasDelete_(false),
456           hasPut_(false),
457           getDeprecated_(false),
458           postDeprecated_(false),
459           deleteDeprecated_(false),
460           putDeprecated_(false),
461           summaryOrigin_(HttpMethod_Get)  // Dummy initialization
462         {
463         }
464 
AddMethod(HttpMethod method,const std::string & tag,bool deprecated)465         void AddMethod(HttpMethod method,
466                        const std::string& tag,
467                        bool deprecated)
468         {
469           switch (method)
470           {
471             case HttpMethod_Get:
472               if (hasGet_)
473               {
474                 throw OrthancException(ErrorCode_InternalError);
475               }
476 
477               hasGet_ = true;
478               getTag_ = tag;
479               getDeprecated_ = deprecated;
480               break;
481 
482             case HttpMethod_Post:
483               if (hasPost_)
484               {
485                 throw OrthancException(ErrorCode_InternalError);
486               }
487 
488               hasPost_ = true;
489               postTag_ = tag;
490               postDeprecated_ = deprecated;
491               break;
492 
493             case HttpMethod_Delete:
494               if (hasDelete_)
495               {
496                 throw OrthancException(ErrorCode_InternalError);
497               }
498 
499               hasDelete_ = true;
500               deleteTag_ = tag;
501               deleteDeprecated_ = deprecated;
502               break;
503 
504             case HttpMethod_Put:
505               if (hasPut_)
506               {
507                 throw OrthancException(ErrorCode_InternalError);
508               }
509 
510               hasPut_ = true;
511               putTag_ = tag;
512               putDeprecated_ = deprecated;
513               break;
514 
515             default:
516               throw OrthancException(ErrorCode_ParameterOutOfRange);
517           }
518         }
519 
SetSummary(const std::string & summary,HttpMethod newOrigin)520         void SetSummary(const std::string& summary,
521                         HttpMethod newOrigin)
522         {
523           if (!summary.empty())
524           {
525             bool replace;
526 
527             if (summary_.empty())
528             {
529               // We don't have a summary so far
530               replace = true;
531             }
532             else
533             {
534               // We already have a summary. Replace it if the new
535               // summary is associated with a HTTP method of higher
536               // weight (GET > POST > DELETE > PUT)
537               switch (summaryOrigin_)
538               {
539                 case HttpMethod_Get:
540                   replace = false;
541                   break;
542 
543                 case HttpMethod_Post:
544                   replace = (newOrigin == HttpMethod_Get);
545                   break;
546 
547                 case HttpMethod_Delete:
548                   replace = (newOrigin == HttpMethod_Get ||
549                              newOrigin == HttpMethod_Post);
550                   break;
551 
552                 case HttpMethod_Put:
553                   replace = (newOrigin == HttpMethod_Get ||
554                              newOrigin == HttpMethod_Post ||
555                              newOrigin == HttpMethod_Delete);
556                   break;
557 
558                 default:
559                   throw OrthancException(ErrorCode_ParameterOutOfRange);
560               }
561             }
562 
563             if (replace)
564             {
565               summary_ = summary;
566               summaryOrigin_ = newOrigin;
567             }
568           }
569         }
570 
GetSummary() const571         const std::string& GetSummary() const
572         {
573           return summary_;
574         }
575 
FormatTag(const std::string & tag)576         static std::string FormatTag(const std::string& tag)
577         {
578           if (tag.empty())
579           {
580             return tag;
581           }
582           else
583           {
584             std::string s;
585             s.reserve(tag.size());
586             s.push_back(tag[0]);
587 
588             for (size_t i = 1; i < tag.size(); i++)
589             {
590               if (tag[i] == ' ')
591               {
592                 s.push_back('-');
593               }
594               else if (isupper(tag[i]) &&
595                        tag[i - 1] == ' ')
596               {
597                 s.push_back(tolower(tag[i]));
598               }
599               else
600               {
601                 s.push_back(tag[i]);
602               }
603             }
604 
605             return s;
606           }
607         }
608 
Format(const std::string & openApiUrl,HttpMethod method,const std::string & uri) const609         std::string Format(const std::string& openApiUrl,
610                            HttpMethod method,
611                            const std::string& uri) const
612         {
613           std::string p = uri;
614           boost::replace_all(p, "/", "~1");
615 
616           std::string verb;
617           std::string url;
618 
619           switch (method)
620           {
621             case HttpMethod_Get:
622               if (hasGet_)
623               {
624                 verb = (getDeprecated_ ? "(get)" : "GET");
625                 url = openApiUrl + "#tag/" + FormatTag(getTag_) + "/paths/" + p + "/get";
626               }
627               break;
628 
629             case HttpMethod_Post:
630               if (hasPost_)
631               {
632                 verb = (postDeprecated_ ? "(post)" : "POST");
633                 url = openApiUrl + "#tag/" + FormatTag(postTag_) + "/paths/" + p + "/post";
634               }
635               break;
636 
637             case HttpMethod_Delete:
638               if (hasDelete_)
639               {
640                 verb = (deleteDeprecated_ ? "(delete)" : "DELETE");
641                 url = openApiUrl + "#tag/" + FormatTag(deleteTag_) + "/paths/" + p + "/delete";
642               }
643               break;
644 
645             case HttpMethod_Put:
646               if (hasPut_)
647               {
648                 verb = (putDeprecated_ ? "(put)" : "PUT");
649                 url = openApiUrl + "#tag/" + FormatTag(putTag_) + "/paths/" + p + "/put";
650               }
651               break;
652 
653             default:
654               throw OrthancException(ErrorCode_InternalError);
655           }
656 
657           if (verb.empty())
658           {
659             return "";
660           }
661           else if (openApiUrl.empty())
662           {
663             return verb;
664           }
665           else
666           {
667             return "`" + verb + " <" + url + ">`__";
668           }
669         }
670 
HasDeprecated() const671         bool HasDeprecated() const
672         {
673           return ((hasGet_ && getDeprecated_) ||
674                   (hasPost_ && postDeprecated_) ||
675                   (hasDelete_ && deleteDeprecated_) ||
676                   (hasPut_ && putDeprecated_));
677         }
678       };
679 
680       typedef std::map<std::string, Path>  Paths;
681 
682       Paths paths_;
683 
684     protected:
HandleCall(RestApiCall & call,const std::set<std::string> & uriArgumentsNames)685       virtual bool HandleCall(RestApiCall& call,
686                               const std::set<std::string>& uriArgumentsNames) ORTHANC_OVERRIDE
687       {
688         Path& path = paths_[ Toolbox::FlattenUri(call.GetFullUri()) ];
689 
690         path.AddMethod(call.GetMethod(), call.GetDocumentation().GetTag(), call.GetDocumentation().IsDeprecated());
691 
692         if (call.GetDocumentation().HasSummary())
693         {
694           path.SetSummary(call.GetDocumentation().GetSummary(), call.GetMethod());
695         }
696 
697         return true;
698       }
699 
700     public:
ReStructuredTextCheatSheet(RestApi & restApi)701       explicit ReStructuredTextCheatSheet(RestApi& restApi) :
702         DocumentationVisitor(restApi)
703       {
704       }
705 
Format(std::string & target,const std::string & openApiUrl) const706       void Format(std::string& target,
707                   const std::string& openApiUrl) const
708       {
709         target += "Path,GET,POST,DELETE,PUT,Summary\n";
710         for (Paths::const_iterator it = paths_.begin(); it != paths_.end(); ++it)
711         {
712           target += "``" + it->first + "``,";
713           target += it->second.Format(openApiUrl, HttpMethod_Get, it->first) + ",";
714           target += it->second.Format(openApiUrl, HttpMethod_Post, it->first) + ",";
715           target += it->second.Format(openApiUrl, HttpMethod_Delete, it->first) + ",";
716           target += it->second.Format(openApiUrl, HttpMethod_Put, it->first) + ",";
717 
718           if (it->second.HasDeprecated())
719           {
720             target += "*(deprecated)* ";
721           }
722 
723           target += it->second.GetSummary() + "\n";
724         }
725       }
726     };
727   }
728 
729 
730 
AddMethod(std::string & target,const std::string & method)731   static void AddMethod(std::string& target,
732                         const std::string& method)
733   {
734     if (target.size() > 0)
735       target += "," + method;
736     else
737       target = method;
738   }
739 
MethodsToString(const std::set<HttpMethod> & methods)740   static std::string  MethodsToString(const std::set<HttpMethod>& methods)
741   {
742     std::string s;
743 
744     if (methods.find(HttpMethod_Get) != methods.end())
745     {
746       AddMethod(s, "GET");
747     }
748 
749     if (methods.find(HttpMethod_Post) != methods.end())
750     {
751       AddMethod(s, "POST");
752     }
753 
754     if (methods.find(HttpMethod_Put) != methods.end())
755     {
756       AddMethod(s, "PUT");
757     }
758 
759     if (methods.find(HttpMethod_Delete) != methods.end())
760     {
761       AddMethod(s, "DELETE");
762     }
763 
764     return s;
765   }
766 
767 
768 
CreateChunkedRequestReader(std::unique_ptr<IChunkedRequestReader> & target,RequestOrigin origin,const char * remoteIp,const char * username,HttpMethod method,const UriComponents & uri,const HttpToolbox::Arguments & headers)769   bool RestApi::CreateChunkedRequestReader(std::unique_ptr<IChunkedRequestReader>& target,
770                                            RequestOrigin origin,
771                                            const char* remoteIp,
772                                            const char* username,
773                                            HttpMethod method,
774                                            const UriComponents& uri,
775                                            const HttpToolbox::Arguments& headers)
776   {
777     return false;
778   }
779 
780 
Handle(HttpOutput & output,RequestOrigin origin,const char * remoteIp,const char * username,HttpMethod method,const UriComponents & uri,const HttpToolbox::Arguments & headers,const HttpToolbox::GetArguments & getArguments,const void * bodyData,size_t bodySize)781   bool RestApi::Handle(HttpOutput& output,
782                        RequestOrigin origin,
783                        const char* remoteIp,
784                        const char* username,
785                        HttpMethod method,
786                        const UriComponents& uri,
787                        const HttpToolbox::Arguments& headers,
788                        const HttpToolbox::GetArguments& getArguments,
789                        const void* bodyData,
790                        size_t bodySize)
791   {
792     RestApiOutput wrappedOutput(output, method);
793 
794 #if ORTHANC_ENABLE_PUGIXML == 1
795     {
796       // Look if the client wishes XML answers instead of JSON
797       // http://www.w3.org/Protocols/HTTP/HTRQ_Headers.html#z3
798       HttpToolbox::Arguments::const_iterator it = headers.find("accept");
799       if (it != headers.end())
800       {
801         std::vector<std::string> accepted;
802         Toolbox::TokenizeString(accepted, it->second, ';');
803         for (size_t i = 0; i < accepted.size(); i++)
804         {
805           if (accepted[i] == MIME_XML)
806           {
807             wrappedOutput.SetConvertJsonToXml(true);
808           }
809 
810           if (accepted[i] == MIME_JSON)
811           {
812             wrappedOutput.SetConvertJsonToXml(false);
813           }
814         }
815       }
816     }
817 #endif
818 
819     HttpToolbox::Arguments compiled;
820     HttpToolbox::CompileGetArguments(compiled, getArguments);
821 
822     HttpHandlerVisitor visitor(*this, wrappedOutput, origin, remoteIp, username,
823                                method, headers, compiled, bodyData, bodySize);
824 
825     if (root_.LookupResource(uri, visitor))
826     {
827       wrappedOutput.Finalize();
828       return true;
829     }
830 
831     std::set<HttpMethod> methods;
832     root_.GetAcceptedMethods(methods, uri);
833 
834     if (methods.empty())
835     {
836       return false;  // This URI is not served by this REST API
837     }
838     else
839     {
840       LOG(INFO) << "REST method " << EnumerationToString(method)
841                 << " not allowed on: " << Toolbox::FlattenUri(uri);
842 
843       output.SendMethodNotAllowed(MethodsToString(methods));
844 
845       return true;
846     }
847   }
848 
Register(const std::string & path,RestApiGetCall::Handler handler)849   void RestApi::Register(const std::string& path,
850                          RestApiGetCall::Handler handler)
851   {
852     root_.Register(path, handler);
853   }
854 
Register(const std::string & path,RestApiPutCall::Handler handler)855   void RestApi::Register(const std::string& path,
856                          RestApiPutCall::Handler handler)
857   {
858     root_.Register(path, handler);
859   }
860 
Register(const std::string & path,RestApiPostCall::Handler handler)861   void RestApi::Register(const std::string& path,
862                          RestApiPostCall::Handler handler)
863   {
864     root_.Register(path, handler);
865   }
866 
Register(const std::string & path,RestApiDeleteCall::Handler handler)867   void RestApi::Register(const std::string& path,
868                          RestApiDeleteCall::Handler handler)
869   {
870     root_.Register(path, handler);
871   }
872 
AutoListChildren(RestApiGetCall & call)873   void RestApi::AutoListChildren(RestApiGetCall& call)
874   {
875     call.GetDocumentation()
876       .SetTag("Other")
877       .SetSummary("List operations")
878       .SetDescription("List the available operations under URI `" + call.FlattenUri() + "`")
879       .AddAnswerType(MimeType_Json, "List of the available operations");
880 
881     RestApi& context = call.GetContext();
882 
883     Json::Value directory;
884     if (context.root_.GetDirectory(directory, call.GetFullUri()))
885     {
886       if (call.IsDocumentation())
887       {
888         call.GetDocumentation().SetSample(directory);
889 
890         std::set<std::string> c;
891         call.GetUriComponentsNames(c);
892         for (std::set<std::string>::const_iterator it = c.begin(); it != c.end(); ++it)
893         {
894           call.GetDocumentation().SetUriArgument(*it, RestApiCallDocumentation::Type_String, "");
895         }
896       }
897       else
898       {
899         call.GetOutput().AnswerJson(directory);
900       }
901     }
902   }
903 
904 
GenerateOpenApiDocumentation(Json::Value & target)905   void RestApi::GenerateOpenApiDocumentation(Json::Value& target)
906   {
907     OpenApiVisitor visitor(*this);
908 
909     UriComponents root;
910     std::set<std::string> uriArgumentsNames;
911     root_.ExploreAllResources(visitor, root, uriArgumentsNames);
912 
913     target = Json::objectValue;
914 
915     target["info"] = Json::objectValue;
916     target["openapi"] = "3.0.0";
917     target["servers"] = Json::arrayValue;
918     target["paths"] = visitor.GetPaths();
919 
920     visitor.LogStatistics();
921   }
922 
923 
GenerateReStructuredTextCheatSheet(std::string & target,const std::string & openApiUrl)924   void RestApi::GenerateReStructuredTextCheatSheet(std::string& target,
925                                                    const std::string& openApiUrl)
926   {
927     ReStructuredTextCheatSheet visitor(*this);
928 
929     UriComponents root;
930     std::set<std::string> uriArgumentsNames;
931     root_.ExploreAllResources(visitor, root, uriArgumentsNames);
932 
933     visitor.Format(target, openApiUrl);
934 
935     visitor.LogStatistics();
936   }
937 }
938