1 /*
2 * Copyright 2019 Matthieu Gautier <mgautier@kymeria.fr>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 3 of the License, or
7 * any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
17 * MA 02110-1301, USA.
18 */
19
20 #include "internalServer.h"
21 #include <netinet/in.h>
22
23 #ifdef _WIN32
24 # if !defined(__MINGW32__) && (_MSC_VER < 1600)
25 # include "stdint4win.h"
26 # endif
27 # include <winsock2.h>
28 # include <ws2tcpip.h>
29 # ifdef __GNUC__
30 // inet_pton is not declared in mingw, even if the function exists.
31 extern "C" {
32 WINSOCK_API_LINKAGE INT WSAAPI inet_pton( INT Family, PCSTR pszAddrString, PVOID pAddrBuf);
33 }
34 # endif
35 typedef UINT64 uint64_t;
36 typedef UINT16 uint16_t;
37 #endif
38
39 extern "C" {
40 #include "microhttpd_wrapper.h"
41 }
42
43 #include "tools/otherTools.h"
44 #include "tools/pathTools.h"
45 #include "tools/regexTools.h"
46 #include "tools/stringTools.h"
47 #include "library.h"
48 #include "name_mapper.h"
49 #include "entry.h"
50 #include "searcher.h"
51 #include "search_renderer.h"
52 #include "opds_dumper.h"
53
54 #include <zim/uuid.h>
55
56 #include <mustache.hpp>
57
58 #include <pthread.h>
59 #include <atomic>
60 #include <string>
61 #include <vector>
62 #include <chrono>
63 #include "kiwixlib-resources.h"
64
65 #ifndef _WIN32
66 # include <arpa/inet.h>
67 #endif
68
69 #include "request_context.h"
70 #include "response.h"
71
72 #define MAX_SEARCH_LEN 140
73 #define KIWIX_MIN_CONTENT_SIZE_TO_DEFLATE 100
74
75 namespace kiwix {
76
77 static IdNameMapper defaultNameMapper;
78
79 static MHD_Result staticHandlerCallback(void* cls,
80 struct MHD_Connection* connection,
81 const char* url,
82 const char* method,
83 const char* version,
84 const char* upload_data,
85 size_t* upload_data_size,
86 void** cont_cls);
87
88
InternalServer(Library * library,NameMapper * nameMapper,std::string addr,int port,std::string root,int nbThreads,bool verbose,bool withTaskbar,bool withLibraryButton,bool blockExternalLinks)89 InternalServer::InternalServer(Library* library,
90 NameMapper* nameMapper,
91 std::string addr,
92 int port,
93 std::string root,
94 int nbThreads,
95 bool verbose,
96 bool withTaskbar,
97 bool withLibraryButton,
98 bool blockExternalLinks) :
99 m_addr(addr),
100 m_port(port),
101 m_root(root),
102 m_nbThreads(nbThreads),
103 m_verbose(verbose),
104 m_withTaskbar(withTaskbar),
105 m_withLibraryButton(withLibraryButton),
106 m_blockExternalLinks(blockExternalLinks),
107 mp_daemon(nullptr),
108 mp_library(library),
109 mp_nameMapper(nameMapper ? nameMapper : &defaultNameMapper)
110 {}
111
start()112 bool InternalServer::start() {
113 #ifdef _WIN32
114 int flags = MHD_USE_SELECT_INTERNALLY;
115 #else
116 int flags = MHD_USE_POLL_INTERNALLY;
117 #endif
118 if (m_verbose.load())
119 flags |= MHD_USE_DEBUG;
120
121
122 struct sockaddr_in sockAddr;
123 memset(&sockAddr, 0, sizeof(sockAddr));
124 sockAddr.sin_family = AF_INET;
125 sockAddr.sin_port = htons(m_port);
126 if (m_addr.empty()) {
127 if (0 != INADDR_ANY)
128 sockAddr.sin_addr.s_addr = htonl(INADDR_ANY);
129 } else {
130 if (inet_pton(AF_INET, m_addr.c_str(), &(sockAddr.sin_addr.s_addr)) == 0) {
131 std::cerr << "Ip address " << m_addr << " is not a valid ip address" << std::endl;
132 return false;
133 }
134 }
135
136 mp_daemon = MHD_start_daemon(flags,
137 m_port,
138 NULL,
139 NULL,
140 &staticHandlerCallback,
141 this,
142 MHD_OPTION_SOCK_ADDR, &sockAddr,
143 MHD_OPTION_THREAD_POOL_SIZE, m_nbThreads,
144 MHD_OPTION_END);
145 if (mp_daemon == nullptr) {
146 std::cerr << "Unable to instantiate the HTTP daemon. The port " << m_port
147 << " is maybe already occupied or need more permissions to be open. "
148 "Please try as root or with a port number higher or equal to 1024."
149 << std::endl;
150 return false;
151 }
152 auto server_start_time = std::chrono::system_clock::now().time_since_epoch();
153 m_server_id = kiwix::to_string(server_start_time.count());
154 return true;
155 }
156
stop()157 void InternalServer::stop()
158 {
159 MHD_stop_daemon(mp_daemon);
160 }
161
staticHandlerCallback(void * cls,struct MHD_Connection * connection,const char * url,const char * method,const char * version,const char * upload_data,size_t * upload_data_size,void ** cont_cls)162 static MHD_Result staticHandlerCallback(void* cls,
163 struct MHD_Connection* connection,
164 const char* url,
165 const char* method,
166 const char* version,
167 const char* upload_data,
168 size_t* upload_data_size,
169 void** cont_cls)
170 {
171 InternalServer* _this = static_cast<InternalServer*>(cls);
172
173 return _this->handlerCallback(connection,
174 url,
175 method,
176 version,
177 upload_data,
178 upload_data_size,
179 cont_cls);
180 }
181
handlerCallback(struct MHD_Connection * connection,const char * url,const char * method,const char * version,const char * upload_data,size_t * upload_data_size,void ** cont_cls)182 MHD_Result InternalServer::handlerCallback(struct MHD_Connection* connection,
183 const char* url,
184 const char* method,
185 const char* version,
186 const char* upload_data,
187 size_t* upload_data_size,
188 void** cont_cls)
189 {
190 auto start_time = std::chrono::steady_clock::now();
191 if (m_verbose.load() ) {
192 printf("======================\n");
193 printf("Requesting : \n");
194 printf("full_url : %s\n", url);
195 }
196 RequestContext request(connection, m_root, url, method, version);
197
198 if (m_verbose.load() ) {
199 request.print_debug_info();
200 }
201 /* Unexpected method */
202 if (request.get_method() != RequestMethod::GET
203 && request.get_method() != RequestMethod::POST
204 && request.get_method() != RequestMethod::HEAD) {
205 printf("Reject request because of unhandled request method.\n");
206 printf("----------------------\n");
207 return MHD_NO;
208 }
209
210 auto response = handle_request(request);
211
212 if (response->getReturnCode() == MHD_HTTP_INTERNAL_SERVER_ERROR) {
213 printf("========== INTERNAL ERROR !! ============\n");
214 if (!m_verbose.load()) {
215 printf("Requesting : \n");
216 printf("full_url : %s\n", url);
217 request.print_debug_info();
218 }
219 }
220
221 if (response->getReturnCode() == MHD_HTTP_OK && !etag_not_needed(request))
222 response->set_server_id(m_server_id);
223
224 auto ret = response->send(request, connection);
225 auto end_time = std::chrono::steady_clock::now();
226 auto time_span = std::chrono::duration_cast<std::chrono::duration<double>>(end_time - start_time);
227 if (m_verbose.load()) {
228 printf("Request time : %fs\n", time_span.count());
229 printf("----------------------\n");
230 }
231 return ret;
232 }
233
handle_request(const RequestContext & request)234 std::unique_ptr<Response> InternalServer::handle_request(const RequestContext& request)
235 {
236 try {
237 if (! request.is_valid_url())
238 return Response::build_404(*this, request, "");
239
240 const ETag etag = get_matching_if_none_match_etag(request);
241 if ( etag )
242 return Response::build_304(*this, etag);
243
244 if (kiwix::startsWith(request.get_url(), "/skin/"))
245 return handle_skin(request);
246
247 if (startsWith(request.get_url(), "/catalog"))
248 return handle_catalog(request);
249
250 if (request.get_url() == "/meta")
251 return handle_meta(request);
252
253 if (request.get_url() == "/search")
254 return handle_search(request);
255
256 if (request.get_url() == "/suggest")
257 return handle_suggest(request);
258
259 if (request.get_url() == "/random")
260 return handle_random(request);
261
262 if (request.get_url() == "/catch/external")
263 return handle_captured_external(request);
264
265 return handle_content(request);
266 } catch (std::exception& e) {
267 fprintf(stderr, "===== Unhandled error : %s\n", e.what());
268 return Response::build_500(*this, e.what());
269 } catch (...) {
270 fprintf(stderr, "===== Unhandled unknown error\n");
271 return Response::build_500(*this, "Unknown error");
272 }
273 }
274
get_default_data() const275 MustacheData InternalServer::get_default_data() const
276 {
277 MustacheData data;
278 data.set("root", m_root);
279 return data;
280 }
281
homepage_data() const282 MustacheData InternalServer::homepage_data() const
283 {
284 auto data = get_default_data();
285
286 MustacheData books{MustacheData::type::list};
287 for (auto& bookId: mp_library->filter(kiwix::Filter().local(true).valid(true))) {
288 auto& currentBook = mp_library->getBookById(bookId);
289
290 MustacheData book;
291 book.set("name", mp_nameMapper->getNameForId(bookId));
292 book.set("title", currentBook.getTitle());
293 book.set("description", currentBook.getDescription());
294 book.set("articleCount", beautifyInteger(currentBook.getArticleCount()));
295 book.set("mediaCount", beautifyInteger(currentBook.getMediaCount()));
296 books.push_back(book);
297 }
298
299 data.set("books", books);
300 return data;
301 }
302
etag_not_needed(const RequestContext & request) const303 bool InternalServer::etag_not_needed(const RequestContext& request) const
304 {
305 const std::string url = request.get_url();
306 return kiwix::startsWith(url, "/catalog")
307 || url == "/search"
308 || url == "/suggest"
309 || url == "/random"
310 || url == "/catch/external";
311 }
312
313 ETag
get_matching_if_none_match_etag(const RequestContext & r) const314 InternalServer::get_matching_if_none_match_etag(const RequestContext& r) const
315 {
316 try {
317 const std::string etag_list = r.get_header(MHD_HTTP_HEADER_IF_NONE_MATCH);
318 return ETag::match(etag_list, m_server_id);
319 } catch (const std::out_of_range&) {
320 return ETag();
321 }
322 }
323
build_homepage(const RequestContext & request)324 std::unique_ptr<Response> InternalServer::build_homepage(const RequestContext& request)
325 {
326 return ContentResponse::build(*this, RESOURCE::templates::index_html, homepage_data(), "text/html; charset=utf-8");
327 }
328
handle_meta(const RequestContext & request)329 std::unique_ptr<Response> InternalServer::handle_meta(const RequestContext& request)
330 {
331 std::string bookName;
332 std::string bookId;
333 std::string meta_name;
334 std::shared_ptr<Reader> reader;
335 try {
336 bookName = request.get_argument("content");
337 bookId = mp_nameMapper->getIdForName(bookName);
338 meta_name = request.get_argument("name");
339 reader = mp_library->getReaderById(bookId);
340 } catch (const std::out_of_range& e) {
341 return Response::build_404(*this, request, bookName);
342 }
343
344 if (reader == nullptr) {
345 return Response::build_404(*this, request, bookName);
346 }
347
348 std::string content;
349 std::string mimeType = "text";
350
351 if (meta_name == "title") {
352 content = reader->getTitle();
353 } else if (meta_name == "description") {
354 content = reader->getDescription();
355 } else if (meta_name == "language") {
356 content = reader->getLanguage();
357 } else if (meta_name == "name") {
358 content = reader->getName();
359 } else if (meta_name == "tags") {
360 content = reader->getTags();
361 } else if (meta_name == "date") {
362 content = reader->getDate();
363 } else if (meta_name == "creator") {
364 content = reader->getCreator();
365 } else if (meta_name == "publisher") {
366 content = reader->getPublisher();
367 } else if (meta_name == "favicon") {
368 reader->getFavicon(content, mimeType);
369 } else {
370 return Response::build_404(*this, request, bookName);
371 }
372
373 auto response = ContentResponse::build(*this, content, mimeType);
374 response->set_cacheable();
375 return std::move(response);
376 }
377
handle_suggest(const RequestContext & request)378 std::unique_ptr<Response> InternalServer::handle_suggest(const RequestContext& request)
379 {
380 if (m_verbose.load()) {
381 printf("** running handle_suggest\n");
382 }
383
384 std::string content;
385 std::string mimeType;
386 unsigned int maxSuggestionCount = 10;
387 unsigned int suggestionCount = 0;
388
389 std::string bookName;
390 std::string bookId;
391 std::string term;
392 std::shared_ptr<Reader> reader;
393 try {
394 bookName = request.get_argument("content");
395 bookId = mp_nameMapper->getIdForName(bookName);
396 term = request.get_argument("term");
397 reader = mp_library->getReaderById(bookId);
398 } catch (const std::out_of_range&) {
399 return Response::build_404(*this, request, bookName);
400 }
401
402 if (m_verbose.load()) {
403 printf("Searching suggestions for: \"%s\"\n", term.c_str());
404 }
405
406 MustacheData results{MustacheData::type::list};
407
408 bool first = true;
409 if (reader != nullptr) {
410 /* Get the suggestions */
411 SuggestionsList_t suggestions;
412 reader->searchSuggestionsSmart(term, maxSuggestionCount, suggestions);
413 for(auto& suggestion:suggestions) {
414 MustacheData result;
415 result.set("label", suggestion[0]);
416 result.set("value", suggestion[0]);
417 result.set("first", first);
418 first = false;
419 results.push_back(result);
420 suggestionCount++;
421 }
422 }
423
424 /* Propose the fulltext search if possible */
425 if (reader->hasFulltextIndex()) {
426 MustacheData result;
427 result.set("label", "containing '" + term + "'...");
428 result.set("value", term + " ");
429 result.set("first", first);
430 results.push_back(result);
431 }
432
433 auto data = get_default_data();
434 data.set("suggestions", results);
435
436 auto response = ContentResponse::build(*this, RESOURCE::templates::suggestion_json, data, "application/json; charset=utf-8");
437 return std::move(response);
438 }
439
handle_skin(const RequestContext & request)440 std::unique_ptr<Response> InternalServer::handle_skin(const RequestContext& request)
441 {
442 if (m_verbose.load()) {
443 printf("** running handle_skin\n");
444 }
445
446 auto resourceName = request.get_url().substr(1);
447 try {
448 auto response = ContentResponse::build(
449 *this,
450 getResource(resourceName),
451 getMimeTypeForFile(resourceName));
452 response->set_cacheable();
453 return std::move(response);
454 } catch (const ResourceNotFound& e) {
455 return Response::build_404(*this, request, "");
456 }
457 }
458
handle_search(const RequestContext & request)459 std::unique_ptr<Response> InternalServer::handle_search(const RequestContext& request)
460 {
461 if (m_verbose.load()) {
462 printf("** running handle_search\n");
463 }
464
465 std::string bookName;
466 std::string bookId;
467 try {
468 bookName = request.get_argument("content");
469 bookId = mp_nameMapper->getIdForName(bookName);
470 } catch (const std::out_of_range&) {}
471
472 std::string patternString;
473 try {
474 patternString = request.get_argument("pattern");
475 } catch (const std::out_of_range&) {}
476
477 /* Retrive geo search */
478 bool has_geo_query = false;
479 float latitude = 0;
480 float longitude = 0;
481 float distance = 0;
482 try {
483 latitude = request.get_argument<float>("latitude");
484 longitude = request.get_argument<float>("longitude");
485 distance = request.get_argument<float>("distance");
486 has_geo_query = true;
487 } catch(const std::out_of_range&) {}
488 catch(const std::invalid_argument&) {}
489
490 std::shared_ptr<Reader> reader(nullptr);
491 try {
492 reader = mp_library->getReaderById(bookId);
493 } catch (const std::out_of_range&) {}
494
495 /* Try first to load directly the article */
496 if (reader != nullptr && !patternString.empty()) {
497 std::string patternCorrespondingUrl;
498 auto variants = reader->getTitleVariants(patternString);
499 auto variantsItr = variants.begin();
500
501 while (patternCorrespondingUrl.empty() && variantsItr != variants.end()) {
502 try {
503 auto entry = reader->getEntryFromTitle(*variantsItr);
504 entry = entry.getFinalEntry();
505 patternCorrespondingUrl = entry.getPath();
506 break;
507 } catch(kiwix::NoEntry& e) {
508 variantsItr++;
509 }
510 }
511
512 /* If article found then redirect directly to it */
513 if (!patternCorrespondingUrl.empty()) {
514 auto redirectUrl = m_root + "/" + bookName + "/" + patternCorrespondingUrl;
515 return Response::build_redirect(*this, redirectUrl);
516 }
517 }
518
519 /* Make the search */
520 if ( (!reader && !bookName.empty())
521 || (patternString.empty() && ! has_geo_query) ) {
522 auto data = get_default_data();
523 data.set("pattern", encodeDiples(patternString));
524 auto response = ContentResponse::build(*this, RESOURCE::templates::no_search_result_html, data, "text/html; charset=utf-8");
525 response->set_taskbar(bookName, reader ? reader->getTitle() : "");
526 response->set_code(MHD_HTTP_NOT_FOUND);
527 return std::move(response);
528 }
529
530 Searcher searcher;
531 if (reader) {
532 searcher.add_reader(reader.get());
533 } else {
534 for (auto& bookId: mp_library->filter(kiwix::Filter().local(true).valid(true))) {
535 auto currentReader = mp_library->getReaderById(bookId);
536 if (currentReader) {
537 searcher.add_reader(currentReader.get());
538 }
539 }
540 }
541
542 auto start = 0;
543 try {
544 start = request.get_argument<unsigned int>("start");
545 } catch (const std::exception&) {}
546
547 auto pageLength = 25;
548 try {
549 pageLength = request.get_argument<unsigned int>("pageLength");
550 } catch (const std::exception&) {}
551 if (pageLength > MAX_SEARCH_LEN) {
552 pageLength = MAX_SEARCH_LEN;
553 }
554 if (pageLength == 0) {
555 pageLength = 25;
556 }
557
558 auto end = start + pageLength;
559
560 /* Get the results */
561 try {
562 if (patternString.empty()) {
563 searcher.geo_search(latitude, longitude, distance,
564 start, end, m_verbose.load());
565 } else {
566 searcher.search(patternString,
567 start, end, m_verbose.load());
568 }
569 SearchRenderer renderer(&searcher, mp_nameMapper);
570 renderer.setSearchPattern(patternString);
571 renderer.setSearchContent(bookName);
572 renderer.setProtocolPrefix(m_root + "/");
573 renderer.setSearchProtocolPrefix(m_root + "/search?");
574 renderer.setPageLength(pageLength);
575 auto response = ContentResponse::build(*this, renderer.getHtml(), "text/html; charset=utf-8");
576 response->set_taskbar(bookName, reader ? reader->getTitle() : "");
577 //changing status code if no result obtained
578 if(searcher.getEstimatedResultCount() == 0)
579 {
580 response->set_code(MHD_HTTP_NO_CONTENT);
581 }
582
583 return std::move(response);
584 } catch (const std::exception& e) {
585 std::cerr << e.what() << std::endl;
586 return Response::build_500(*this, e.what());
587 }
588 }
589
handle_random(const RequestContext & request)590 std::unique_ptr<Response> InternalServer::handle_random(const RequestContext& request)
591 {
592 if (m_verbose.load()) {
593 printf("** running handle_random\n");
594 }
595
596 std::string bookName;
597 std::string bookId;
598 std::shared_ptr<Reader> reader;
599 try {
600 bookName = request.get_argument("content");
601 bookId = mp_nameMapper->getIdForName(bookName);
602 reader = mp_library->getReaderById(bookId);
603 } catch (const std::out_of_range&) {
604 return Response::build_404(*this, request, bookName);
605 }
606
607 if (reader == nullptr) {
608 return Response::build_404(*this, request, bookName);
609 }
610
611 try {
612 auto entry = reader->getRandomPage();
613 return build_redirect(bookName, entry.getFinalEntry());
614 } catch(kiwix::NoEntry& e) {
615 return Response::build_404(*this, request, bookName);
616 }
617 }
618
handle_captured_external(const RequestContext & request)619 std::unique_ptr<Response> InternalServer::handle_captured_external(const RequestContext& request)
620 {
621 std::string source = "";
622 try {
623 source = kiwix::urlDecode(request.get_argument("source"));
624 } catch (const std::out_of_range& e) {}
625
626 if (source.empty())
627 return Response::build_404(*this, request, "");
628
629 auto data = get_default_data();
630 data.set("source", source);
631 return ContentResponse::build(*this, RESOURCE::templates::captured_external_html, data, "text/html; charset=utf-8");
632 }
633
handle_catalog(const RequestContext & request)634 std::unique_ptr<Response> InternalServer::handle_catalog(const RequestContext& request)
635 {
636 if (m_verbose.load()) {
637 printf("** running handle_catalog");
638 }
639
640 std::string host;
641 std::string url;
642 try {
643 host = request.get_header("Host");
644 url = request.get_url_part(1);
645 } catch (const std::out_of_range&) {
646 return Response::build_404(*this, request, "");
647 }
648
649 if (url != "searchdescription.xml" && url != "root.xml" && url != "search") {
650 return Response::build_404(*this, request, "");
651 }
652
653 if (url == "searchdescription.xml") {
654 auto response = ContentResponse::build(*this, RESOURCE::opensearchdescription_xml, get_default_data(), "application/opensearchdescription+xml");
655 return std::move(response);
656 }
657
658 zim::Uuid uuid;
659 kiwix::OPDSDumper opdsDumper;
660 opdsDumper.setRootLocation(m_root);
661 opdsDumper.setSearchDescriptionUrl("catalog/searchdescription.xml");
662 opdsDumper.setLibrary(mp_library);
663 std::vector<std::string> bookIdsToDump;
664 if (url == "root.xml") {
665 opdsDumper.setTitle("All zims");
666 uuid = zim::Uuid::generate(host);
667 bookIdsToDump = mp_library->filter(kiwix::Filter().valid(true).local(true).remote(true));
668 } else if (url == "search") {
669 auto filter = kiwix::Filter().valid(true).local(true).remote(true);
670 string query("<Empty query>");
671 size_t count(10);
672 size_t startIndex(0);
673 try {
674 query = request.get_argument("q");
675 filter.query(query);
676 } catch (const std::out_of_range&) {}
677 try {
678 filter.maxSize(extractFromString<unsigned long>(request.get_argument("maxsize")));
679 } catch (...) {}
680 try {
681 filter.name(request.get_argument("name"));
682 } catch (const std::out_of_range&) {}
683 try {
684 filter.lang(request.get_argument("lang"));
685 } catch (const std::out_of_range&) {}
686 try {
687 count = extractFromString<unsigned long>(request.get_argument("count"));
688 } catch (...) {}
689 try {
690 startIndex = extractFromString<unsigned long>(request.get_argument("start"));
691 } catch (...) {}
692 try {
693 filter.acceptTags(kiwix::split(request.get_argument("tag"), ";"));
694 } catch (...) {}
695 try {
696 filter.rejectTags(kiwix::split(request.get_argument("notag"), ";"));
697 } catch (...) {}
698 opdsDumper.setTitle("Search result for " + query);
699 uuid = zim::Uuid::generate();
700 bookIdsToDump = mp_library->filter(filter);
701 auto totalResults = bookIdsToDump.size();
702 bookIdsToDump.erase(bookIdsToDump.begin(), bookIdsToDump.begin()+startIndex);
703 if (count>0 && bookIdsToDump.size() > count) {
704 bookIdsToDump.resize(count);
705 }
706 opdsDumper.setOpenSearchInfo(totalResults, startIndex, bookIdsToDump.size());
707 }
708
709 opdsDumper.setId(kiwix::to_string(uuid));
710 auto response = ContentResponse::build(
711 *this,
712 opdsDumper.dumpOPDSFeed(bookIdsToDump),
713 "application/atom+xml; profile=opds-catalog; kind=acquisition; charset=utf-8");
714 return std::move(response);
715 }
716
717 namespace
718 {
719
get_book_name(const RequestContext & request)720 std::string get_book_name(const RequestContext& request)
721 {
722 try {
723 return request.get_url_part(0);
724 } catch (const std::out_of_range& e) {
725 return std::string();
726 }
727 }
728
729 } // unnamed namespace
730
731 std::shared_ptr<Reader>
get_reader(const std::string & bookName) const732 InternalServer::get_reader(const std::string& bookName) const
733 {
734 std::shared_ptr<Reader> reader;
735 try {
736 const std::string bookId = mp_nameMapper->getIdForName(bookName);
737 reader = mp_library->getReaderById(bookId);
738 } catch (const std::out_of_range& e) {
739 }
740 return reader;
741 }
742
743 std::unique_ptr<Response>
build_redirect(const std::string & bookName,const kiwix::Entry & entry) const744 InternalServer::build_redirect(const std::string& bookName, const kiwix::Entry& entry) const
745 {
746 auto redirectUrl = m_root + "/" + bookName + "/" + kiwix::urlEncode(entry.getPath());
747 return Response::build_redirect(*this, redirectUrl);
748 }
749
handle_content(const RequestContext & request)750 std::unique_ptr<Response> InternalServer::handle_content(const RequestContext& request)
751 {
752 if (m_verbose.load()) {
753 printf("** running handle_content\n");
754 }
755
756 const std::string bookName = get_book_name(request);
757 if (bookName.empty())
758 return build_homepage(request);
759
760 const std::shared_ptr<Reader> reader = get_reader(bookName);
761 if (reader == nullptr) {
762 return Response::build_404(*this, request, bookName);
763 }
764
765 auto urlStr = request.get_url().substr(bookName.size()+1);
766 if (urlStr[0] == '/') {
767 urlStr = urlStr.substr(1);
768 }
769
770 kiwix::Entry entry;
771
772 try {
773 entry = reader->getEntryFromPath(urlStr);
774 if (entry.isRedirect() || urlStr.empty()) {
775 // If urlStr is empty, we want to mainPage.
776 // We must do a redirection to the real page.
777 return build_redirect(bookName, entry.getFinalEntry());
778 }
779 } catch(kiwix::NoEntry& e) {
780 if (m_verbose.load())
781 printf("Failed to find %s\n", urlStr.c_str());
782
783 return Response::build_404(*this, request, bookName);
784 }
785
786 auto response = EntryResponse::build(*this, request, entry);
787 try {
788 dynamic_cast<ContentResponse&>(*response).set_taskbar(bookName, reader->getTitle());
789 } catch (std::bad_cast& e) {}
790
791 if (m_verbose.load()) {
792 printf("Found %s\n", entry.getPath().c_str());
793 printf("mimeType: %s\n", entry.getMimetype().c_str());
794 }
795
796 return response;
797 }
798
799 }
800