mirror of
https://github.com/kiwix/kiwix-tools.git
synced 2026-01-21 20:37:52 -05:00
Compare commits
56 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
773b9e5443 | ||
|
|
fbddabb10f | ||
|
|
3ad50ccf17 | ||
|
|
c265eb8b24 | ||
|
|
6f49e78eb4 | ||
|
|
d25329ecb4 | ||
|
|
bd8d0c3805 | ||
|
|
a01906d273 | ||
|
|
4bd18ce466 | ||
|
|
fb8c14a1e4 | ||
|
|
2acd276753 | ||
|
|
53b2dadfce | ||
|
|
eccf5e194c | ||
|
|
8356277562 | ||
|
|
c6ace1eafc | ||
|
|
8552985cd4 | ||
|
|
9997a6ec18 | ||
|
|
8b6abc0e35 | ||
|
|
426796dc37 | ||
|
|
712cdf5e7b | ||
|
|
74cc1d0708 | ||
|
|
3ce668af66 | ||
|
|
2b69cc8ffc | ||
|
|
71ffaaa05a | ||
|
|
f3fb5d61cc | ||
|
|
05c63536de | ||
|
|
16e12ad463 | ||
|
|
c91285fc29 | ||
|
|
5c11ff1a30 | ||
|
|
9eaf512bf9 | ||
|
|
9a29874f9f | ||
|
|
aa9647e88c | ||
|
|
da0c697963 | ||
|
|
77d25686cb | ||
|
|
3df2fa708b | ||
|
|
9021f87715 | ||
|
|
04b05b7902 | ||
|
|
a8e73aab98 | ||
|
|
234a9d9719 | ||
|
|
c93617473d | ||
|
|
09e2039aff | ||
|
|
5c6a1870b9 | ||
|
|
4fffaf41b6 | ||
|
|
05c734fc31 | ||
|
|
fa9b027a39 | ||
|
|
1fcc1ad9d4 | ||
|
|
5fd8dd3c36 | ||
|
|
f03ed85972 | ||
|
|
52308b764d | ||
|
|
03bac00019 | ||
|
|
905b83b3b1 | ||
|
|
894ed12f37 | ||
|
|
573b826861 | ||
|
|
8051773124 | ||
|
|
69dd6cd869 | ||
|
|
8d32bfd16e |
20
.travis.yml
20
.travis.yml
@@ -2,10 +2,22 @@ language: cpp
|
||||
dist: trusty
|
||||
sudo: required
|
||||
cache: ccache
|
||||
before_install:
|
||||
- eval "${MATRIX_EVAL}"
|
||||
- ${CXX} --version
|
||||
install: travis/install_deps.sh
|
||||
script: travis/compile.sh
|
||||
env:
|
||||
- PLATFORM="native_static"
|
||||
- PLATFORM="native_dyn"
|
||||
- PLATFORM="win32_static"
|
||||
- PLATFORM="win32_dyn"
|
||||
global:
|
||||
- MATRIX_EVAL="CC=gcc-5 && CXX=g++-5"
|
||||
matrix:
|
||||
- PLATFORM="native_static"
|
||||
- PLATFORM="native_dyn"
|
||||
- PLATFORM="win32_static"
|
||||
- PLATFORM="win32_dyn"
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
packages:
|
||||
- g++-5
|
||||
|
||||
40
Changelog
40
Changelog
@@ -1,3 +1,43 @@
|
||||
kiwix-tools 0.5.0
|
||||
=================
|
||||
|
||||
* Build kiwix-tools setting the RPATH
|
||||
* Compile without warning.
|
||||
|
||||
|
||||
kiwix-server
|
||||
------------
|
||||
|
||||
* Serve metadata information using the "/meta" url.
|
||||
* Serve an OPDS stream of all zim handled by kiwix-serve
|
||||
All informations cannot be infer from the zim file itself,
|
||||
you should use a library.xml to provide needed information (url, ...)
|
||||
* Update kiwix-serve to use the new API of kiwix-lib
|
||||
|
||||
kiwix-tools 0.4.0
|
||||
=================
|
||||
|
||||
* Use gcc-5 on travis.
|
||||
|
||||
kiwix-serve
|
||||
-----------
|
||||
|
||||
* Accept zim file with `&` in the name
|
||||
* Do not cache (on client side) global search (as it can depends on the zim
|
||||
files handled)
|
||||
* Fix HTTP byte range handling. (#91)
|
||||
* Fix opening of relative path (#70)
|
||||
* Add a small (and hidden) API to do geo search.
|
||||
* Better request parsing. (#91)
|
||||
* Better handling of invalid request (#116)
|
||||
* Various bug fixes (#146, #150, #153, #165, #168, #165)
|
||||
|
||||
kiwix-search
|
||||
------------
|
||||
|
||||
* Add an option `--suggestion` to do suggestion search with
|
||||
kiwix-search.(#132)
|
||||
|
||||
kiwix-tools 0.3.0
|
||||
=================
|
||||
|
||||
|
||||
28
meson.build
28
meson.build
@@ -1,7 +1,7 @@
|
||||
project('kiwix-tools', 'cpp',
|
||||
version : '0.3.0',
|
||||
version : '0.5.0',
|
||||
license : 'GPL',
|
||||
default_options: ['c_std=c11', 'cpp_std=c++11'])
|
||||
default_options: ['c_std=c11', 'cpp_std=c++11', 'werror=true'])
|
||||
|
||||
compiler = meson.get_compiler('cpp')
|
||||
|
||||
@@ -11,26 +11,18 @@ if static_linkage
|
||||
endif
|
||||
|
||||
thread_dep = dependency('threads')
|
||||
kiwixlib_dep = dependency('kiwix', version:'>=1.0.0', static:static_linkage)
|
||||
kiwixlib_dep = dependency('kiwix', version:'>=2.0.0', static:static_linkage)
|
||||
microhttpd_dep = dependency('libmicrohttpd', static:static_linkage)
|
||||
z_dep = dependency('zlib', static:static_linkage)
|
||||
|
||||
# This is a temporary workaround until xapian fix it's xapian-core.pc
|
||||
# For a correct dependency checking we should test if uuid is really installed
|
||||
# but for a workaround, we assume that in xapian is installed, uuid also.
|
||||
if meson.is_cross_build() and host_machine.system() == 'windows'
|
||||
# xapian doesn't use uuid on windows.
|
||||
uuid_dep = declare_dependency()
|
||||
else
|
||||
xapian_dep = dependency('xapian-core', required : false)
|
||||
if xapian_dep.found()
|
||||
uuid_dep = declare_dependency(link_args:['-luuid', '-lrt'])
|
||||
else
|
||||
uuid_dep = declare_dependency()
|
||||
endif
|
||||
endif
|
||||
all_deps = [thread_dep, kiwixlib_dep, microhttpd_dep, z_dep]
|
||||
|
||||
all_deps = [thread_dep, kiwixlib_dep, microhttpd_dep, z_dep, uuid_dep]
|
||||
if static_linkage
|
||||
librt = compiler.find_library('rt', required:false)
|
||||
if librt.found()
|
||||
all_deps += librt
|
||||
endif
|
||||
endif
|
||||
|
||||
#subdir('include')
|
||||
subdir('static')
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
executable('kiwix-install', ['kiwix-install.cpp'],
|
||||
dependencies:all_deps,
|
||||
install:true)
|
||||
install:true,
|
||||
install_rpath: join_paths(get_option('prefix'), get_option('libdir')))
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
.TH KIWIX 1 "21 May 2012"
|
||||
.SH NAME
|
||||
kiwix-compact \- Compacteur d'index pour fichier ZIM
|
||||
.SH SYNOPSIS
|
||||
.IX Header SYNOPSIS
|
||||
kiwix-compact INDEX_PATH
|
||||
.SH DESCRIPTION
|
||||
.PP
|
||||
Compacte les fichiers d'index des ZIM à l'aide de xapian-compact.
|
||||
|
||||
.TP
|
||||
\fBINDEX_PATH\fR
|
||||
Chemin de l'index plein texte à compacter.
|
||||
|
||||
.SH SEE ALSO
|
||||
kiwix(1) kiwix-index(1) kiwix-manage(1) xapian-compact(1)
|
||||
.br
|
||||
kiwix-install(1) kiwix-serve(1)
|
||||
|
||||
.SH AUTHOR
|
||||
Emmanuel Engelhart <kelson@kiwix.org>
|
||||
.br
|
||||
Vasudev Kamath <kamathvasudev@gmail.com> (Manual)
|
||||
@@ -1,31 +0,0 @@
|
||||
.TH KIWIX 1 "21 May 2012"
|
||||
.SH NAME
|
||||
kiwix\-index \- Indexeur de fichiers ZIM
|
||||
.SH SYNOPSIS
|
||||
.IX Header SYNOPSIS
|
||||
kiwix\-index [\-\-backend|\-b=xapian|clucene] ZIM_PATH INDEX_PATH
|
||||
.SH DESCRIPTION
|
||||
.PP
|
||||
Génère un index plein texte xapian ou cLucene pour un fichier ZIM.
|
||||
|
||||
.TP
|
||||
\fB\-\-backend=xapian|clucene\fR
|
||||
Séléctionne un moteur d'indexation.
|
||||
|
||||
.TP
|
||||
\fBZIM_PATH\fR
|
||||
Chemin du fichier ZIM à indexer.
|
||||
|
||||
.TP
|
||||
\fBINDEX_PATH\fR
|
||||
Chemin où enregistrer l'index plein texte.
|
||||
|
||||
.SH SEE ALSO
|
||||
kiwix\-compact(1) kiwix\-manage(1) kiwix\-serve(1)
|
||||
.br
|
||||
kiwix(1) kiwix\-install(1)
|
||||
|
||||
.SH AUTHOR
|
||||
Emmanuel Engelhart <kelson@kiwix.org>
|
||||
.br
|
||||
Vasudev Kamath <kamathvasudev@gmail.com> (Manual)
|
||||
@@ -33,8 +33,8 @@ Chemin d'accès dossier kiwix/.
|
||||
Le chemin doit contenit le binaire statique de Kiwix.
|
||||
|
||||
.SH SEE ALSO
|
||||
kiwix(1) kiwix\-index(1) kiwix\-serve(1) kiwix\-compact(1) kiwix\-manage(1)
|
||||
kiwix(1) kiwix\-serve(1) kiwix\-manage(1)
|
||||
.SH AUTHOR
|
||||
Emmanuel Engelhart <kelson@kiwix.org>
|
||||
.br
|
||||
Vasudev Kamath <kamathvasudev@gmail.com> (Manual)
|
||||
Vasudev Kamath <kamathvasudev@gmail.com> (Manual)
|
||||
|
||||
@@ -57,8 +57,8 @@ Chemin vers l'index plein texte correspondant au fichier ZIM.
|
||||
Définit l'adresse URL corresponsant au fichier ZIM pour pouvoir être téléchargé depuis Kiwix.
|
||||
|
||||
.SH SEE ALSO
|
||||
kiwix(1) kiwix\-index(1) kiwix\-install(1) kiwix\-serve(1) kiwix\-compact(1)
|
||||
kiwix(1) kiwix\-install(1) kiwix\-serve(1)
|
||||
.SH AUTHOR
|
||||
Emmanuel Engelhart <kelson@kiwix.org>
|
||||
.br
|
||||
Vasudev Kamath <kamathvasudev@gmail.com> (Manual)
|
||||
Vasudev Kamath <kamathvasudev@gmail.com> (Manual)
|
||||
|
||||
@@ -52,10 +52,10 @@ Chemin vers le fichier bibliothèque de Kiwix.
|
||||
Le fichier bibliothèque est un fichier XML créé avec \fBkiwix-manage\fB.
|
||||
|
||||
.SH SEE ALSO
|
||||
kiwix(1) kiwix\-index(1) kiwix\-manage(1)
|
||||
kiwix(1) kiwix\-manage(1)
|
||||
.br
|
||||
kiwix\-install(1) kiwix\-compact(1)
|
||||
kiwix\-install(1)
|
||||
.SH AUTHOR
|
||||
Emmanuel Engelhart <kelson@kiwix.org>
|
||||
.br
|
||||
Vasudev Kamath <kamathvasudev@gmail.com> (Manual)
|
||||
Vasudev Kamath <kamathvasudev@gmail.com> (Manual)
|
||||
|
||||
@@ -34,9 +34,9 @@ Fonctionnalités:
|
||||
* Bibliothèque de contenus intégrée
|
||||
|
||||
.SH SEE ALSO
|
||||
kiwix-index(1) kiwix-install(1) kiwix-serve(1)
|
||||
kiwix\-install(1) kiwix\-serve(1)
|
||||
.br
|
||||
kiwix-compact(1) kiwix-manage(1)
|
||||
kiwix\-manage(1)
|
||||
|
||||
|
||||
.SH TROUBLESHOOTING
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
install_man('kiwix-index.1',
|
||||
'kiwix-install.1',
|
||||
install_man('kiwix-install.1',
|
||||
'kiwix-manage.1',
|
||||
'kiwix-serve.1',
|
||||
install_dir:get_option('mandir')+'/fr/man1')
|
||||
@@ -1,23 +0,0 @@
|
||||
.TH KIWIX 1 "21 May 2012"
|
||||
.SH NAME
|
||||
kiwix-compact \- ZIM index compacter
|
||||
.SH SYNOPSIS
|
||||
.IX Header SYNOPSIS
|
||||
kiwix-compact INDEX_PATH
|
||||
.SH DESCRIPTION
|
||||
.PP
|
||||
Compacts ZIM index files used by Kiwix using xapian-compact
|
||||
|
||||
.TP
|
||||
\fBINDEX_PATH\fR
|
||||
Full-text index path to compact.
|
||||
|
||||
.SH SEE ALSO
|
||||
kiwix(1) kiwix-index(1) kiwix-manage(1) xapian-compact(1)
|
||||
.br
|
||||
kiwix-install(1) kiwix-serve(1)
|
||||
|
||||
.SH AUTHOR
|
||||
Emmanuel Engelhart <kelson@kiwix.org>
|
||||
.br
|
||||
Vasudev Kamath <kamathvasudev@gmail.com> (Manual)
|
||||
@@ -1,31 +0,0 @@
|
||||
.TH KIWIX 1 "21 May 2012"
|
||||
.SH NAME
|
||||
kiwix\-index \- Kiwix ZIM Indexer
|
||||
.SH SYNOPSIS
|
||||
.IX Header SYNOPSIS
|
||||
kiwix\-index [\-\-backend|\-b=xapian|clucene] ZIM_PATH INDEX_PATH
|
||||
.SH DESCRIPTION
|
||||
.PP
|
||||
Generates a xapian or clucene full-text index for a ZIM file.
|
||||
|
||||
.TP
|
||||
\fB\-\-backend=xapian|clucene\fR
|
||||
Select an indexing backend.
|
||||
|
||||
.TP
|
||||
\fBZIM_PATH\fR
|
||||
ZIM file path to index.
|
||||
|
||||
.TP
|
||||
\fBINDEX_PATH\fR
|
||||
Path to save the full\-text index to.
|
||||
|
||||
.SH SEE ALSO
|
||||
kiwix\-compact(1) kiwix\-manage(1) kiwix\-serve(1)
|
||||
.br
|
||||
kiwix(1) kiwix\-install(1)
|
||||
|
||||
.SH AUTHOR
|
||||
Emmanuel Engelhart <kelson@kiwix.org>
|
||||
.br
|
||||
Vasudev Kamath <kamathvasudev@gmail.com> (Manual)
|
||||
@@ -33,8 +33,8 @@ Path to the kiwix/ folder.
|
||||
The path must contain the kiwix standalone binary.
|
||||
|
||||
.SH SEE ALSO
|
||||
kiwix(1) kiwix\-index(1) kiwix\-serve(1) kiwix\-compact(1) kiwix\-manage(1)
|
||||
kiwix(1) kiwix\-serve(1) kiwix\-manage(1)
|
||||
.SH AUTHOR
|
||||
Emmanuel Engelhart <kelson@kiwix.org>
|
||||
.br
|
||||
Vasudev Kamath <kamathvasudev@gmail.com> (Manual)
|
||||
Vasudev Kamath <kamathvasudev@gmail.com> (Manual)
|
||||
|
||||
@@ -55,8 +55,8 @@ Path to full-text index for that ZIM file.
|
||||
Set the content location of the ZIM file over the network for in\-kiwix download.
|
||||
|
||||
.SH SEE ALSO
|
||||
kiwix(1) kiwix\-index(1) kiwix\-install(1) kiwix\-serve(1) kiwix\-compact(1)
|
||||
kiwix(1) kiwix\-install(1) kiwix\-serve(1)
|
||||
.SH AUTHOR
|
||||
Emmanuel Engelhart <kelson@kiwix.org>
|
||||
.br
|
||||
Vasudev Kamath <kamathvasudev@gmail.com> (Manual)
|
||||
Vasudev Kamath <kamathvasudev@gmail.com> (Manual)
|
||||
|
||||
@@ -13,7 +13,7 @@ Stand\-alone HTTP server for serving ZIM content over the network.
|
||||
|
||||
.TP
|
||||
\fB\-\-index=INDEX_PATH\fR
|
||||
Path to index folder created using \fBkiwix-index\fB.
|
||||
Path to index folder created using \fBkiwix-install\fB.
|
||||
|
||||
.TP
|
||||
\fB\-\-port=PORT\fR
|
||||
@@ -52,10 +52,10 @@ Kiwix library file path.
|
||||
Library is XML file created using \fBkiwix-manage\fB.
|
||||
|
||||
.SH SEE ALSO
|
||||
kiwix(1) kiwix\-index(1) kiwix\-manage(1)
|
||||
kiwix(1) kiwix\-manage(1)
|
||||
.br
|
||||
kiwix\-install(1) kiwix\-compact(1)
|
||||
kiwix\-install(1)
|
||||
.SH AUTHOR
|
||||
Emmanuel Engelhart <kelson@kiwix.org>
|
||||
.br
|
||||
Vasudev Kamath <kamathvasudev@gmail.com> (Manual)
|
||||
Vasudev Kamath <kamathvasudev@gmail.com> (Manual)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
install_man('kiwix-index.1',
|
||||
'kiwix-install.1',
|
||||
install_man('kiwix-install.1',
|
||||
'kiwix-manage.1',
|
||||
'kiwix-serve.1')
|
||||
subdir('fr')
|
||||
|
||||
@@ -67,14 +67,128 @@ void usage()
|
||||
cerr << "\tkiwix-manage LIBRARY_PATH remove CONTENTID1 [CONTENTID2]" << endl;
|
||||
}
|
||||
|
||||
|
||||
void handle_show(kiwix::Manager* libraryManager, const std::string& libraryPath,
|
||||
int argc, char* argv[])
|
||||
{
|
||||
show(libraryManager->cloneLibrary());
|
||||
}
|
||||
|
||||
void handle_add(kiwix::Manager* libraryManager, const std::string& libraryPath,
|
||||
int argc, char* argv[])
|
||||
{
|
||||
string zimPath;
|
||||
string zimPathToSave = ".";
|
||||
string indexPath;
|
||||
kiwix::supportedIndexType indexBackend = kiwix::XAPIAN;
|
||||
string url;
|
||||
string origID = "";
|
||||
bool setCurrent = false;
|
||||
int option_index = 0;
|
||||
int c = 0;
|
||||
|
||||
if (argc > 3) {
|
||||
zimPath = argv[3];
|
||||
}
|
||||
|
||||
/* Options parsing */
|
||||
optind = 2;
|
||||
while (42) {
|
||||
static struct option long_options[]
|
||||
= {{"url", required_argument, 0, 'u'},
|
||||
{"origId", required_argument, 0, 'o'},
|
||||
{"indexPath", required_argument, 0, 'i'},
|
||||
{"indexBackend", required_argument, 0, 'b'},
|
||||
{"zimPathToSave", required_argument, 0, 'z'},
|
||||
{"current", no_argument, 0, 'c'},
|
||||
{0, 0, 0, 0}};
|
||||
|
||||
c = getopt_long(argc, argv, "cz:u:i:b:", long_options, &option_index);
|
||||
|
||||
if (c != -1) {
|
||||
switch (c) {
|
||||
case 'u':
|
||||
url = optarg;
|
||||
break;
|
||||
|
||||
case 'o':
|
||||
origID = optarg;
|
||||
break;
|
||||
|
||||
case 'c':
|
||||
setCurrent = true;
|
||||
break;
|
||||
|
||||
case 'i':
|
||||
indexPath = optarg;
|
||||
break;
|
||||
|
||||
case 'b':
|
||||
if (!strcmp(optarg, "xapian")) {
|
||||
indexBackend = kiwix::XAPIAN;
|
||||
} else {
|
||||
usage();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'z':
|
||||
zimPathToSave = optarg;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!zimPath.empty()) {
|
||||
zimPathToSave = zimPathToSave == "." ? zimPath : zimPathToSave;
|
||||
string bookId = libraryManager->addBookFromPathAndGetId(
|
||||
zimPath, zimPathToSave, url, false);
|
||||
if (!bookId.empty()) {
|
||||
if (setCurrent)
|
||||
libraryManager->setCurrentBookId(bookId);
|
||||
/* Save the index infos if necessary */
|
||||
if (!indexPath.empty())
|
||||
libraryManager->setBookIndex(bookId, indexPath, indexBackend);
|
||||
} else {
|
||||
cerr << "Unable to build or save library file '" << libraryPath << "'"
|
||||
<< endl;
|
||||
}
|
||||
} else {
|
||||
std::cerr << "Invalid zim file path" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void handle_remove(kiwix::Manager* libraryManager, const std::string& libraryPath,
|
||||
int argc, char* argv[])
|
||||
{
|
||||
unsigned int bookIndex = 0;
|
||||
const unsigned int totalBookCount = libraryManager->getBookCount(true, true);
|
||||
|
||||
if (argc > 3) {
|
||||
bookIndex = atoi(argv[3]);
|
||||
}
|
||||
|
||||
if (bookIndex > 0 && bookIndex <= totalBookCount) {
|
||||
libraryManager->removeBookByIndex(bookIndex - 1);
|
||||
} else {
|
||||
if (totalBookCount > 0) {
|
||||
std::cerr
|
||||
<< "Invalid book index number. Please give a number between 1 and "
|
||||
<< totalBookCount << std::endl;
|
||||
} else {
|
||||
std::cerr
|
||||
<< "Invalid book index number. Library is empty, no book to delete."
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
string libraryPath = "";
|
||||
supportedAction action = NONE;
|
||||
string zimPath = "";
|
||||
kiwix::Manager libraryManager;
|
||||
int option_index = 0;
|
||||
int c = 0;
|
||||
|
||||
/* Argument parsing */
|
||||
if (argc > 2) {
|
||||
@@ -103,111 +217,11 @@ int main(int argc, char** argv)
|
||||
|
||||
/* SHOW */
|
||||
if (action == SHOW) {
|
||||
show(libraryManager.cloneLibrary());
|
||||
handle_show(&libraryManager, libraryPath, argc, argv);
|
||||
} else if (action == ADD) {
|
||||
string zimPath;
|
||||
string zimPathToSave = ".";
|
||||
string indexPath;
|
||||
kiwix::supportedIndexType indexBackend = kiwix::XAPIAN;
|
||||
string url;
|
||||
string origID = "";
|
||||
bool setCurrent = false;
|
||||
|
||||
if (argc > 3) {
|
||||
zimPath = argv[3];
|
||||
}
|
||||
|
||||
/* Options parsing */
|
||||
optind = 2;
|
||||
while (42) {
|
||||
static struct option long_options[]
|
||||
= {{"url", required_argument, 0, 'u'},
|
||||
{"origId", required_argument, 0, 'o'},
|
||||
{"indexPath", required_argument, 0, 'i'},
|
||||
{"indexBackend", required_argument, 0, 'b'},
|
||||
{"zimPathToSave", required_argument, 0, 'z'},
|
||||
{"current", no_argument, 0, 'c'},
|
||||
{0, 0, 0, 0}};
|
||||
|
||||
c = getopt_long(argc, argv, "cz:u:i:b:", long_options, &option_index);
|
||||
|
||||
if (c != -1) {
|
||||
switch (c) {
|
||||
case 'u':
|
||||
url = optarg;
|
||||
break;
|
||||
|
||||
case 'o':
|
||||
origID = optarg;
|
||||
break;
|
||||
|
||||
case 'c':
|
||||
setCurrent = true;
|
||||
break;
|
||||
|
||||
case 'i':
|
||||
indexPath = optarg;
|
||||
break;
|
||||
|
||||
case 'b':
|
||||
if (!strcmp(optarg, "xapian")) {
|
||||
indexBackend = kiwix::XAPIAN;
|
||||
} else {
|
||||
usage();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'z':
|
||||
zimPathToSave = optarg;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!zimPath.empty()) {
|
||||
zimPathToSave = zimPathToSave == "." ? zimPath : zimPathToSave;
|
||||
string bookId = libraryManager.addBookFromPathAndGetId(
|
||||
zimPath, zimPathToSave, url, false);
|
||||
|
||||
if (!bookId.empty()) {
|
||||
if (setCurrent)
|
||||
libraryManager.setCurrentBookId(bookId);
|
||||
|
||||
/* Save the index infos if necessary */
|
||||
if (!indexPath.empty())
|
||||
libraryManager.setBookIndex(bookId, indexPath, indexBackend);
|
||||
|
||||
} else {
|
||||
cerr << "Unable to build or save library file '" << libraryPath << "'"
|
||||
<< endl;
|
||||
}
|
||||
} else {
|
||||
std::cerr << "Invalid zim file path" << std::endl;
|
||||
}
|
||||
|
||||
handle_add(&libraryManager, libraryPath, argc, argv);
|
||||
} else if (action == REMOVE) {
|
||||
unsigned int bookIndex = 0;
|
||||
const unsigned int totalBookCount = libraryManager.getBookCount(true, true);
|
||||
|
||||
if (argc > 3) {
|
||||
bookIndex = atoi(argv[3]);
|
||||
}
|
||||
|
||||
if (bookIndex > 0 && bookIndex <= totalBookCount) {
|
||||
libraryManager.removeBookByIndex(bookIndex - 1);
|
||||
} else {
|
||||
if (totalBookCount > 0) {
|
||||
std::cerr
|
||||
<< "Invalid book index number. Please give a number between 1 and "
|
||||
<< totalBookCount << std::endl;
|
||||
} else {
|
||||
std::cerr
|
||||
<< "Invalid book index number. Library is empty, no book to delete."
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
handle_remove(&libraryManager, libraryPath, argc, argv);
|
||||
}
|
||||
|
||||
/* Rewrite the library file */
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
executable('kiwix-manage', ['kiwix-manage.cpp'],
|
||||
dependencies:all_deps,
|
||||
install:true)
|
||||
install:true,
|
||||
install_rpath: join_paths(get_option('prefix'), get_option('libdir')))
|
||||
|
||||
@@ -79,10 +79,8 @@ int main(int argc, char** argv)
|
||||
|
||||
/* Start to read an article */
|
||||
if (reader != NULL) {
|
||||
string mainPageUrl = reader->getMainPageUrl();
|
||||
string content;
|
||||
string contentType;
|
||||
unsigned int contentLength = 0;
|
||||
string suggestion;
|
||||
|
||||
if (pattern != NULL) {
|
||||
@@ -94,13 +92,6 @@ int main(int argc, char** argv)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
if (reader->getContentByUrl(mainPageUrl, content, contentLength,
|
||||
contentType)) {
|
||||
cout << content << endl;
|
||||
}
|
||||
*/
|
||||
|
||||
delete reader;
|
||||
} else {
|
||||
cerr << "Unable instanciate the Kiwix reader." << endl;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
executable('kiwix-read', ['kiwix-read.cpp'],
|
||||
dependencies:all_deps,
|
||||
install:true)
|
||||
install:true,
|
||||
install_rpath: join_paths(get_option('prefix'), get_option('libdir')))
|
||||
|
||||
@@ -22,9 +22,16 @@
|
||||
#include <kiwix/searcher.h>
|
||||
#include <unistd.h>
|
||||
|
||||
|
||||
void usage()
|
||||
{
|
||||
cout << "Usage: kiwix-search [--verbose|-v] ZIM_PATH SEARCH" << endl;
|
||||
cout << "Usage: kiwix-search [OPTIONS] ZIM PATTERN" << endl << endl
|
||||
<< " kiwix-search allows to find articles based on the a fulltext search pattern." << endl << endl
|
||||
<< " ZIM is the full path of the ZIM file. This can also be a disctinct fulltext" << endl
|
||||
<< " index directory (usually distributed with the *.idx extension)." << endl
|
||||
<< " PATTERN is/are word(s) - or part of - to search in the ZIM." << endl << endl
|
||||
<< " -s, --suggestion\tSuggest article titles based on the few letters of the PATTERN instead of making a fulltext search. Work a bit like a completion solution." << endl
|
||||
<< " -v, --verbose\t\tGive details about the search process" << endl;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
@@ -37,6 +44,7 @@ int main(int argc, char** argv)
|
||||
const char* zimPath = NULL;
|
||||
const char* search = NULL;
|
||||
bool verboseFlag = false;
|
||||
bool suggestionFlag = false;
|
||||
int option_index = 0;
|
||||
int c = 0;
|
||||
|
||||
@@ -46,15 +54,20 @@ int main(int argc, char** argv)
|
||||
/* Argument parsing */
|
||||
while (42) {
|
||||
static struct option long_options[]
|
||||
= {{"verbose", no_argument, 0, 'v'}, {0, 0, 0, 0}};
|
||||
= {{"verbose", no_argument, 0, 'v'},
|
||||
{"suggestion", no_argument, 0, 's'},
|
||||
{0, 0, 0, 0}};
|
||||
|
||||
if (c != -1) {
|
||||
c = getopt_long(argc, argv, "vb:", long_options, &option_index);
|
||||
c = getopt_long(argc, argv, "vsb:", long_options, &option_index);
|
||||
|
||||
switch (c) {
|
||||
case 'v':
|
||||
verboseFlag = true;
|
||||
break;
|
||||
case 's':
|
||||
suggestionFlag = true;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (optind < argc) {
|
||||
@@ -100,7 +113,11 @@ int main(int argc, char** argv)
|
||||
/* Start the indexing */
|
||||
if (searcher != NULL) {
|
||||
string searchString(search);
|
||||
searcher->search(searchString, 0, 10);
|
||||
if (suggestionFlag) {
|
||||
searcher->suggestions(searchString, verboseFlag);
|
||||
} else {
|
||||
searcher->search(searchString, 0, 10, verboseFlag);
|
||||
}
|
||||
kiwix::Result* p_result;
|
||||
while ((p_result = searcher->getNextResult())) {
|
||||
cout << p_result->get_title() << endl;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
executable('kiwix-search', ['kiwix-search.cpp'],
|
||||
dependencies:all_deps,
|
||||
install:true)
|
||||
install:true,
|
||||
install_rpath: join_paths(get_option('prefix'), get_option('libdir')))
|
||||
|
||||
@@ -51,6 +51,7 @@ extern "C" {
|
||||
#include <kiwix/manager.h>
|
||||
#include <kiwix/reader.h>
|
||||
#include <kiwix/searcher.h>
|
||||
#include <kiwix/opds_dumper.h>
|
||||
#include <pthread.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -81,6 +82,8 @@ extern "C" {
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include "request_context.h"
|
||||
|
||||
#ifdef interface
|
||||
#undef interface
|
||||
#endif
|
||||
@@ -90,12 +93,14 @@ using namespace std;
|
||||
static bool noLibraryButtonFlag = false;
|
||||
static bool noSearchBarFlag = false;
|
||||
static string welcomeHTML;
|
||||
static string catalogOpenSearchDescription;
|
||||
static std::atomic_bool isVerbose(false);
|
||||
static std::string rootLocation = "";
|
||||
static std::map<std::string, std::string> extMimeTypes;
|
||||
static std::map<std::string, kiwix::Reader*> readers;
|
||||
static std::map<std::string, kiwix::Searcher*> searchers;
|
||||
static kiwix::Searcher* globalSearcher = nullptr;
|
||||
static kiwix::Manager libraryManager;
|
||||
static pthread_mutex_t searchLock = PTHREAD_MUTEX_INITIALIZER;
|
||||
static pthread_mutex_t compressorLock = PTHREAD_MUTEX_INITIALIZER;
|
||||
static pthread_mutex_t regexLock = PTHREAD_MUTEX_INITIALIZER;
|
||||
@@ -124,35 +129,11 @@ static std::string getMimeTypeForFile(const std::string& filename)
|
||||
}
|
||||
|
||||
|
||||
struct RequestContext {
|
||||
struct MHD_Connection* connection;
|
||||
int httpResponseCode;
|
||||
kiwix::Reader* reader;
|
||||
kiwix::Searcher* searcher;
|
||||
const std::string urlStr;
|
||||
const std::string humanReadableBookId;
|
||||
bool acceptEncodingDeflate;
|
||||
bool acceptRange;
|
||||
int range_start;
|
||||
int range_end;
|
||||
|
||||
RequestContext(struct MHD_Connection* connection, int httpResponseCode,
|
||||
kiwix::Reader* reader, kiwix::Searcher* searcher,
|
||||
const std::string& urlStr, const std::string& humanReadableBookId,
|
||||
bool acceptEncodingDeflate,
|
||||
bool acceptRange, int range_start, int range_end) :
|
||||
connection(connection),
|
||||
httpResponseCode(httpResponseCode),
|
||||
reader(reader),
|
||||
searcher(searcher),
|
||||
urlStr(urlStr),
|
||||
humanReadableBookId(humanReadableBookId),
|
||||
acceptEncodingDeflate(acceptEncodingDeflate),
|
||||
acceptRange(acceptRange),
|
||||
range_start(range_start),
|
||||
range_end(range_end)
|
||||
{}
|
||||
};
|
||||
static bool startswith(const std::string& base, const std::string& start)
|
||||
{
|
||||
return start.length() <= base.length()
|
||||
&& std::equal(start.begin(), start.end(), base.begin());
|
||||
}
|
||||
|
||||
|
||||
void introduceTaskbar(string& content, const string& humanReadableBookId)
|
||||
@@ -162,9 +143,7 @@ void introduceTaskbar(string& content, const string& humanReadableBookId)
|
||||
content = appendToFirstOccurence(
|
||||
content,
|
||||
"<head>",
|
||||
replaceRegex(
|
||||
RESOURCE::include_html_part, humanReadableBookId, "__CONTENT__")
|
||||
+ (noLibraryButtonFlag
|
||||
RESOURCE::include_html_part + (noLibraryButtonFlag
|
||||
? "<style>#kiwix_serve_taskbar_library_button { display: none }</style>"
|
||||
: "")
|
||||
);
|
||||
@@ -183,6 +162,7 @@ void introduceTaskbar(string& content, const string& humanReadableBookId)
|
||||
"__CONTENT__"));
|
||||
}
|
||||
content = replaceRegex(content, rootLocation, "__ROOT_LOCATION__");
|
||||
content = replaceRegex(content, replaceRegex(humanReadableBookId, "%26", "&"), "__CONTENT_ESCAPED__");
|
||||
}
|
||||
pthread_mutex_unlock(®exLock);
|
||||
}
|
||||
@@ -241,8 +221,8 @@ static struct MHD_Response* build_response(const void* data,
|
||||
bool cacheEnabled)
|
||||
{
|
||||
/* Create the response */
|
||||
struct MHD_Response* response = MHD_create_response_from_data(
|
||||
length, const_cast<void*>(data), MHD_NO, MHD_YES);
|
||||
struct MHD_Response* response = MHD_create_response_from_buffer(
|
||||
length, const_cast<void*>(data), MHD_RESPMEM_MUST_COPY);
|
||||
|
||||
/* Make a redirection if necessary otherwise send the content */
|
||||
if (!httpRedirection.empty()) {
|
||||
@@ -286,39 +266,47 @@ static struct MHD_Response* build_response(const void* data,
|
||||
}
|
||||
|
||||
|
||||
static struct MHD_Response* build_404(RequestContext* request_context) {
|
||||
static struct MHD_Response* build_404(RequestContext* request,
|
||||
const std::string& humanReadableBookId) {
|
||||
std::string content
|
||||
= "<!DOCTYPE html>\n<html><head><meta "
|
||||
"content=\"text/html;charset=UTF-8\" http-equiv=\"content-type\" "
|
||||
"/><title>Content not found</title></head><body><h1>Not "
|
||||
"Found</h1><p>The requested URL \""
|
||||
+ request_context->urlStr + "\" was not found on this server.</p></body></html>";
|
||||
+ request->get_full_url() + "\" was not found on this server.</p></body></html>";
|
||||
auto mimeType = "text/html";
|
||||
request_context->httpResponseCode = MHD_HTTP_NOT_FOUND;
|
||||
introduceTaskbar(content, request_context->humanReadableBookId);
|
||||
request->httpResponseCode = MHD_HTTP_NOT_FOUND;
|
||||
introduceTaskbar(content, humanReadableBookId);
|
||||
bool deflated
|
||||
= request_context->acceptEncodingDeflate && compress_content(content, mimeType);
|
||||
= request->can_compress() && compress_content(content, mimeType);
|
||||
return build_response(
|
||||
content.data(), content.size(), "", mimeType, deflated, false);
|
||||
}
|
||||
|
||||
static struct MHD_Response* build_homepage(RequestContext* request)
|
||||
{
|
||||
std::string content = welcomeHTML;
|
||||
|
||||
std::string mimeType = "text/html; charset=utf-8";
|
||||
|
||||
bool deflated = request->can_compress() && compress_content(content, mimeType);
|
||||
return build_response(
|
||||
content.data(), content.size(), "", mimeType, deflated, false);
|
||||
}
|
||||
|
||||
struct RunningResponse {
|
||||
zim::Article* article;
|
||||
kiwix::Entry entry;
|
||||
int range_start;
|
||||
|
||||
RunningResponse(zim::Article* article,
|
||||
RunningResponse(kiwix::Entry entry,
|
||||
int range_start) :
|
||||
article(article),
|
||||
entry(entry),
|
||||
range_start(range_start)
|
||||
{}
|
||||
|
||||
~RunningResponse() {
|
||||
delete article;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
ssize_t callback_reader_from_article(void* cls,
|
||||
ssize_t callback_reader_from_entry(void* cls,
|
||||
uint64_t pos,
|
||||
char* buf,
|
||||
size_t max)
|
||||
@@ -327,39 +315,39 @@ ssize_t callback_reader_from_article(void* cls,
|
||||
|
||||
size_t max_size_to_set = min<size_t>(
|
||||
max,
|
||||
response->article->getArticleSize() - pos - response->range_start);
|
||||
response->entry.getSize() - pos - response->range_start);
|
||||
|
||||
if (max_size_to_set <= 0) {
|
||||
return MHD_CONTENT_READER_END_WITH_ERROR;
|
||||
}
|
||||
|
||||
zim::Blob blob = response->article->getData(response->range_start+pos, max_size_to_set);
|
||||
zim::Blob blob = response->entry.getBlob(response->range_start+pos, max_size_to_set);
|
||||
memcpy(buf, blob.data(), max_size_to_set);
|
||||
return max_size_to_set;
|
||||
}
|
||||
|
||||
void callback_free_article(void* cls)
|
||||
void callback_free_response(void* cls)
|
||||
{
|
||||
RunningResponse* response = static_cast<RunningResponse*>(cls);
|
||||
delete response;
|
||||
}
|
||||
|
||||
static struct MHD_Response* build_callback_response_from_article(
|
||||
zim::Article& article, int range_start, int range_len, const std::string& mimeType)
|
||||
static struct MHD_Response* build_callback_response_from_entry(
|
||||
kiwix::Entry entry, int range_start, int range_len, const std::string& mimeType)
|
||||
{
|
||||
RunningResponse* run_response =
|
||||
new RunningResponse(new zim::Article(article), range_start);
|
||||
new RunningResponse(entry, range_start);
|
||||
|
||||
struct MHD_Response* response
|
||||
= MHD_create_response_from_callback(article.getArticleSize(),
|
||||
= MHD_create_response_from_callback(entry.getSize(),
|
||||
16384,
|
||||
callback_reader_from_article,
|
||||
callback_reader_from_entry,
|
||||
run_response,
|
||||
callback_free_article);
|
||||
callback_free_response);
|
||||
/* Tell the client that byte ranges are accepted */
|
||||
MHD_add_response_header(response, MHD_HTTP_HEADER_ACCEPT_RANGES, "bytes");
|
||||
std::ostringstream oss;
|
||||
oss << "bytes " << range_start << "-" << range_start + range_len - 1 << "/" << article.getArticleSize();
|
||||
oss << "bytes " << range_start << "-" << range_start + range_len - 1 << "/" << entry.getSize();
|
||||
|
||||
MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_RANGE, oss.str().c_str());
|
||||
|
||||
@@ -381,29 +369,98 @@ static struct MHD_Response* build_callback_response_from_article(
|
||||
return response;
|
||||
}
|
||||
|
||||
static struct MHD_Response* handle_suggest(RequestContext* request_context)
|
||||
std::pair<kiwix::Reader*, kiwix::Searcher*>
|
||||
get_from_humanReadableBookId(const std::string& humanReadableBookId) {
|
||||
kiwix::Searcher* searcher
|
||||
= searchers.find(humanReadableBookId) != searchers.end()
|
||||
? searchers.find(humanReadableBookId)->second
|
||||
: globalSearcher;
|
||||
kiwix::Reader* reader = readers.find(humanReadableBookId) != readers.end()
|
||||
? readers.find(humanReadableBookId)->second
|
||||
: NULL;
|
||||
return std::pair<kiwix::Reader*, kiwix::Searcher*>(reader, searcher);
|
||||
}
|
||||
|
||||
static struct MHD_Response* handle_meta(RequestContext* request)
|
||||
{
|
||||
std::string humanReadableBookId;
|
||||
std::string meta_name;
|
||||
try {
|
||||
humanReadableBookId = request->get_argument("content");
|
||||
meta_name = request->get_argument("name");
|
||||
} catch (const std::out_of_range& e) {
|
||||
return build_404(request, humanReadableBookId);
|
||||
}
|
||||
|
||||
auto reader = get_from_humanReadableBookId(humanReadableBookId).first;
|
||||
if (reader == nullptr) {
|
||||
return build_404(request, humanReadableBookId);
|
||||
}
|
||||
|
||||
std::string content;
|
||||
std::string mimeType = "text";
|
||||
|
||||
if (meta_name == "title") {
|
||||
content = reader->getTitle();
|
||||
} else if (meta_name == "description") {
|
||||
content = reader->getDescription();
|
||||
} else if (meta_name == "language") {
|
||||
content = reader->getLanguage();
|
||||
} else if (meta_name == "name") {
|
||||
content = reader->getName();
|
||||
} else if (meta_name == "tags") {
|
||||
content = reader->getTags();
|
||||
} else if (meta_name == "date") {
|
||||
content = reader->getDate();
|
||||
} else if (meta_name == "creator") {
|
||||
content = reader->getCreator();
|
||||
} else if (meta_name == "publisher") {
|
||||
content = reader->getPublisher();
|
||||
} else if (meta_name == "favicon") {
|
||||
reader->getFavicon(content, mimeType);
|
||||
} else {
|
||||
return build_404(request, humanReadableBookId);
|
||||
}
|
||||
|
||||
return build_response(content.data(), content.size(), "", mimeType, false, true);
|
||||
}
|
||||
|
||||
static struct MHD_Response* handle_suggest(RequestContext* request)
|
||||
{
|
||||
if (isVerbose.load()) {
|
||||
printf("** running handle_suggest\n");
|
||||
}
|
||||
|
||||
std::string content;
|
||||
std::string mimeType;
|
||||
unsigned int maxSuggestionCount = 10;
|
||||
unsigned int suggestionCount = 0;
|
||||
std::string suggestion;
|
||||
|
||||
/* Get the suggestion pattern from the HTTP request */
|
||||
const char* cTerm = MHD_lookup_connection_value(
|
||||
request_context->connection, MHD_GET_ARGUMENT_KIND, "term");
|
||||
std::string term = cTerm == NULL ? "" : cTerm;
|
||||
std::string humanReadableBookId;
|
||||
std::string term;
|
||||
try {
|
||||
humanReadableBookId = request->get_argument("content");
|
||||
term = request->get_argument("term");
|
||||
} catch (const std::out_of_range&) {
|
||||
return build_homepage(request);
|
||||
}
|
||||
|
||||
if (isVerbose.load()) {
|
||||
printf("Searching suggestions for: \"%s\"\n", term.c_str());
|
||||
}
|
||||
|
||||
auto reader_searcher = get_from_humanReadableBookId(humanReadableBookId);
|
||||
auto reader = reader_searcher.first;
|
||||
auto searcher = reader_searcher.second;
|
||||
|
||||
pthread_mutex_lock(&searchLock);
|
||||
/* Get the suggestions */
|
||||
content = "[";
|
||||
if (request_context->reader != NULL) {
|
||||
if (reader != nullptr) {
|
||||
/* Get the suggestions */
|
||||
request_context->reader->searchSuggestionsSmart(term, maxSuggestionCount);
|
||||
while (request_context->reader->getNextSuggestion(suggestion)) {
|
||||
reader->searchSuggestionsSmart(term, maxSuggestionCount);
|
||||
while (reader->getNextSuggestion(suggestion)) {
|
||||
kiwix::stringReplacement(suggestion, "\"", "\\\"");
|
||||
content += (content == "[" ? "" : ",");
|
||||
content += "{\"value\":\"" + suggestion + "\",\"label\":\"" + suggestion
|
||||
@@ -414,225 +471,343 @@ static struct MHD_Response* handle_suggest(RequestContext* request_context)
|
||||
pthread_mutex_unlock(&searchLock);
|
||||
|
||||
/* Propose the fulltext search if possible */
|
||||
if (request_context->searcher != NULL) {
|
||||
if (searcher != NULL) {
|
||||
content += (suggestionCount == 0 ? "" : ",");
|
||||
content += "{\"value\":\"" + std::string(term)
|
||||
+ " \", \"label\":\"containing '" + std::string(term)
|
||||
content += "{\"value\":\"" + term
|
||||
+ " \", \"label\":\"containing '" + term
|
||||
+ "'...\"}";
|
||||
}
|
||||
|
||||
content += "]";
|
||||
mimeType = "application/json; charset=utf-8";
|
||||
bool deflated = request_context->acceptEncodingDeflate && compress_content(content, mimeType);
|
||||
bool deflated = request->can_compress() && compress_content(content, mimeType);
|
||||
return build_response(
|
||||
content.data(), content.size(), "", mimeType, deflated, true);
|
||||
}
|
||||
|
||||
static struct MHD_Response* handle_skin(RequestContext* request_context)
|
||||
static struct MHD_Response* handle_skin(RequestContext* request)
|
||||
{
|
||||
std::string content;
|
||||
try {
|
||||
content = getResource(request_context->urlStr.substr(rootLocation.size() + 6));
|
||||
} catch (const ResourceNotFound& e) {
|
||||
return build_404(request_context);
|
||||
if (isVerbose.load()) {
|
||||
printf("** running handle_skin\n");
|
||||
}
|
||||
std::string mimeType = getMimeTypeForFile(request_context->urlStr);
|
||||
bool deflated = request_context->acceptEncodingDeflate && compress_content(content, mimeType);
|
||||
|
||||
std::string content;
|
||||
auto resourceName = request->get_url().substr(6);
|
||||
try {
|
||||
content = getResource(resourceName);
|
||||
} catch (const ResourceNotFound& e) {
|
||||
return build_404(request, "");
|
||||
}
|
||||
std::string mimeType = getMimeTypeForFile(resourceName);
|
||||
bool deflated = request->can_compress() && compress_content(content, mimeType);
|
||||
return build_response(
|
||||
content.data(), content.size(), "", mimeType, deflated, true);
|
||||
}
|
||||
|
||||
static struct MHD_Response* handle_search(RequestContext* request_context)
|
||||
static struct MHD_Response* handle_search(RequestContext* request)
|
||||
{
|
||||
if (isVerbose.load()) {
|
||||
printf("** running handle_search\n");
|
||||
}
|
||||
|
||||
std::string content;
|
||||
std::string mimeType;
|
||||
std::string httpRedirection;
|
||||
|
||||
/* Retrieve the pattern to search */
|
||||
const char* pattern = MHD_lookup_connection_value(
|
||||
request_context->connection, MHD_GET_ARGUMENT_KIND, "pattern");
|
||||
std::string patternString
|
||||
= kiwix::urlDecode(pattern == NULL ? "" : string(pattern));
|
||||
std::string patternCorrespondingUrl;
|
||||
std::string humanReadableBookId;
|
||||
std::string patternString;
|
||||
try {
|
||||
humanReadableBookId = request->get_argument("content");
|
||||
} catch (const std::out_of_range&) {}
|
||||
|
||||
try {
|
||||
patternString = request->get_argument("pattern");
|
||||
} catch (const std::out_of_range&) {}
|
||||
|
||||
/* Retrive geo search */
|
||||
bool has_geo_query = false;
|
||||
float latitude;
|
||||
float longitude;
|
||||
float distance;
|
||||
try {
|
||||
latitude = request->get_argument<float>("latitude");
|
||||
longitude = request->get_argument<float>("longitude");
|
||||
distance = request->get_argument<float>("distance");
|
||||
has_geo_query = true;
|
||||
} catch(const std::out_of_range&) {}
|
||||
catch(const std::invalid_argument&) {}
|
||||
|
||||
/* Search results for searches from the welcome page should not
|
||||
be cached
|
||||
*/
|
||||
auto reader_searcher = get_from_humanReadableBookId(humanReadableBookId);
|
||||
auto reader = reader_searcher.first;
|
||||
auto searcher = reader_searcher.second;
|
||||
bool cacheEnabled = !(searcher == globalSearcher);
|
||||
|
||||
/* Try first to load directly the article */
|
||||
if (request_context->reader != NULL) {
|
||||
std::vector<std::string> variants = request_context->reader->getTitleVariants(patternString);
|
||||
std::vector<std::string>::iterator variantsItr = variants.begin();
|
||||
if (reader != nullptr && !patternString.empty()) {
|
||||
std::string patternCorrespondingUrl;
|
||||
auto variants = reader->getTitleVariants(patternString);
|
||||
auto variantsItr = variants.begin();
|
||||
|
||||
while (patternCorrespondingUrl.empty() && variantsItr != variants.end()) {
|
||||
request_context->reader->getPageUrlFromTitle(*variantsItr, patternCorrespondingUrl);
|
||||
variantsItr++;
|
||||
try {
|
||||
auto entry = reader->getEntryFromTitle(*variantsItr);
|
||||
entry = entry.getFinalEntry();
|
||||
patternCorrespondingUrl = entry.getPath();
|
||||
break;
|
||||
} catch(kiwix::NoEntry& e) {
|
||||
variantsItr++;
|
||||
}
|
||||
}
|
||||
|
||||
/* If article found then redirect directly to it */
|
||||
if (!patternCorrespondingUrl.empty()) {
|
||||
httpRedirection
|
||||
= rootLocation + "/" + request_context->humanReadableBookId + "/" + patternCorrespondingUrl;
|
||||
request_context->httpResponseCode = MHD_HTTP_FOUND;
|
||||
= rootLocation + "/" + humanReadableBookId + "/" + patternCorrespondingUrl;
|
||||
request->httpResponseCode = MHD_HTTP_FOUND;
|
||||
return build_response("", 0, httpRedirection, "", false, true);
|
||||
}
|
||||
}
|
||||
|
||||
/* Make the search */
|
||||
if (request_context->searcher != NULL) {
|
||||
const char* start = MHD_lookup_connection_value(
|
||||
request_context->connection, MHD_GET_ARGUMENT_KIND, "start");
|
||||
const char* end = MHD_lookup_connection_value(
|
||||
request_context->connection, MHD_GET_ARGUMENT_KIND, "end");
|
||||
unsigned int startNumber = start != NULL ? atoi(start) : 0;
|
||||
unsigned int endNumber = end != NULL ? atoi(end) : 25;
|
||||
if (searcher != nullptr &&
|
||||
(!patternString.empty() || has_geo_query)) {
|
||||
auto start = 0;
|
||||
try {
|
||||
start = request->get_argument<unsigned int>("start");
|
||||
} catch (const std::exception&) {}
|
||||
auto end = 25;
|
||||
try {
|
||||
end = request->get_argument<unsigned int>("end");
|
||||
} catch (const std::exception&) {}
|
||||
|
||||
/* Get the results */
|
||||
pthread_mutex_lock(&searchLock);
|
||||
try {
|
||||
request_context->searcher->search(patternString, startNumber, endNumber, isVerbose.load());
|
||||
content = request_context->searcher->getHtml();
|
||||
if (patternString.empty()) {
|
||||
searcher->geo_search(latitude, longitude, distance,
|
||||
start, end, isVerbose.load());
|
||||
} else {
|
||||
searcher->search(patternString,
|
||||
start, end, isVerbose.load());
|
||||
}
|
||||
content = searcher->getHtml();
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
}
|
||||
pthread_mutex_unlock(&searchLock);
|
||||
} else {
|
||||
content = "<!DOCTYPE html>\n<html><head><meta content=\"text/html;charset=UTF-8\" http-equiv=\"content-type\" /><title>Fulltext search unavailable</title></head><body><h1>Not Found</h1><p>There is no article with the title <b>\"" + kiwix::encodeDiples(patternString) + "\"</b> and the fulltext search engine is not available for this content.</p></body></html>";
|
||||
request_context->httpResponseCode = MHD_HTTP_NOT_FOUND;
|
||||
request->httpResponseCode = MHD_HTTP_NOT_FOUND;
|
||||
}
|
||||
|
||||
mimeType = "text/html; charset=utf-8";
|
||||
|
||||
introduceTaskbar(content, request_context->humanReadableBookId);
|
||||
introduceTaskbar(content, humanReadableBookId);
|
||||
|
||||
bool deflated = request_context->acceptEncodingDeflate && compress_content(content, mimeType);
|
||||
bool deflated = request->can_compress() && compress_content(content, mimeType);
|
||||
return build_response(content.data(),
|
||||
content.size(),
|
||||
httpRedirection,
|
||||
mimeType,
|
||||
deflated,
|
||||
true);
|
||||
cacheEnabled);
|
||||
}
|
||||
|
||||
static struct MHD_Response* handle_random(RequestContext* request_context)
|
||||
static struct MHD_Response* handle_random(RequestContext* request)
|
||||
{
|
||||
std::string httpRedirection;
|
||||
request_context->httpResponseCode = MHD_HTTP_FOUND;
|
||||
if (request_context->reader != NULL) {
|
||||
std::string randomUrl = request_context->reader->getRandomPageUrl();
|
||||
httpRedirection
|
||||
= rootLocation + "/" + request_context->humanReadableBookId + "/" + kiwix::urlEncode(randomUrl);
|
||||
if (isVerbose.load()) {
|
||||
printf("** running handle_random\n");
|
||||
}
|
||||
|
||||
std::string httpRedirection;
|
||||
request->httpResponseCode = MHD_HTTP_FOUND;
|
||||
std::string humanReadableBookId;
|
||||
try {
|
||||
humanReadableBookId = request->get_argument("content");
|
||||
} catch (const std::out_of_range&) {
|
||||
return build_homepage(request);
|
||||
}
|
||||
|
||||
auto reader = get_from_humanReadableBookId(humanReadableBookId).first;
|
||||
if (reader == nullptr) {
|
||||
return build_homepage(request);
|
||||
}
|
||||
|
||||
try {
|
||||
auto entry = reader->getRandomPage();
|
||||
entry = entry.getFinalEntry();
|
||||
httpRedirection
|
||||
= rootLocation + "/" + humanReadableBookId + "/" + kiwix::urlEncode(entry.getPath());
|
||||
return build_response("", 0, httpRedirection, "", false, false);
|
||||
} catch(kiwix::NoEntry& e) {
|
||||
return build_404(request, humanReadableBookId);
|
||||
}
|
||||
return build_response("", 0, httpRedirection, "", false, false);
|
||||
}
|
||||
|
||||
static struct MHD_Response* handle_content(RequestContext* request_context)
|
||||
static struct MHD_Response* handle_catalog(RequestContext* request)
|
||||
{
|
||||
if (isVerbose.load()) {
|
||||
printf("** running handle_catalog");
|
||||
}
|
||||
|
||||
std::string host;
|
||||
std::string url;
|
||||
try {
|
||||
host = request->get_header("Host");
|
||||
url = request->get_url_part(1);
|
||||
} catch (const std::out_of_range&) {
|
||||
return build_404(request, "");
|
||||
}
|
||||
|
||||
std::string content;
|
||||
std::string mimeType;
|
||||
|
||||
if (url == "searchdescription.xml") {
|
||||
content = catalogOpenSearchDescription;
|
||||
mimeType = "application/opensearchdescription+xml";
|
||||
} else {
|
||||
zim::Uuid uuid;
|
||||
kiwix::OPDSDumper opdsDumper;
|
||||
opdsDumper.setRootLocation(rootLocation);
|
||||
opdsDumper.setSearchDescriptionUrl("catalog/searchdescription.xml");
|
||||
mimeType = "application/atom+xml;profile=opds-catalog;kind=acquisition; charset=utf-8";
|
||||
kiwix::Library library_to_dump;
|
||||
if (url == "root.xml") {
|
||||
opdsDumper.setTitle("All zims");
|
||||
uuid = zim::Uuid::generate(host);
|
||||
library_to_dump = libraryManager.cloneLibrary();
|
||||
} else if (url == "search") {
|
||||
std::string query;
|
||||
try {
|
||||
query = request->get_argument("q");
|
||||
} catch (const std::out_of_range&) {
|
||||
return build_404(request, "");
|
||||
}
|
||||
opdsDumper.setTitle("Search result for " + query);
|
||||
uuid = zim::Uuid::generate();
|
||||
library_to_dump = libraryManager.filter(query);
|
||||
} else {
|
||||
return build_404(request, "");
|
||||
}
|
||||
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << uuid;
|
||||
opdsDumper.setId(ss.str());
|
||||
}
|
||||
opdsDumper.setLibrary(library_to_dump);
|
||||
content = opdsDumper.dumpOPDSFeed();
|
||||
}
|
||||
|
||||
bool deflated = request->can_compress() && compress_content(content, mimeType);
|
||||
return build_response(
|
||||
content.data(), content.size(), "", mimeType, deflated, false);
|
||||
}
|
||||
|
||||
static struct MHD_Response* handle_content(RequestContext* request)
|
||||
{
|
||||
if (isVerbose.load()) {
|
||||
printf("** running handle_content\n");
|
||||
}
|
||||
|
||||
std::string baseUrl;
|
||||
std::string content;
|
||||
std::string mimeType;
|
||||
|
||||
bool found = false;
|
||||
zim::Article article;
|
||||
kiwix::Entry entry;
|
||||
|
||||
std::string humanReadableBookId;
|
||||
try {
|
||||
found = request_context->reader->getArticleObjectByDecodedUrl(request_context->urlStr, article);
|
||||
|
||||
if (found) {
|
||||
/* If redirect */
|
||||
unsigned int loopCounter = 0;
|
||||
while (article.isRedirect() && loopCounter++ < 42) {
|
||||
article = article.getRedirectArticle();
|
||||
}
|
||||
|
||||
/* To many loop */
|
||||
if (loopCounter == 42)
|
||||
found = false;
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
found = false;
|
||||
humanReadableBookId = request->get_url_part(0);
|
||||
} catch (const std::out_of_range& e) {
|
||||
return build_homepage(request);
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
auto reader = get_from_humanReadableBookId(humanReadableBookId).first;
|
||||
if (reader == nullptr) {
|
||||
return build_homepage(request);
|
||||
}
|
||||
|
||||
auto urlStr = request->get_url().substr(humanReadableBookId.size()+1);
|
||||
|
||||
try {
|
||||
entry = reader->getEntryFromPath(urlStr);
|
||||
entry = entry.getFinalEntry();
|
||||
} catch(kiwix::NoEntry& e) {
|
||||
if (isVerbose.load())
|
||||
printf("Failed to find %s\n", request_context->urlStr.c_str());
|
||||
printf("Failed to find %s\n", urlStr.c_str());
|
||||
|
||||
return build_404(request_context);
|
||||
return build_404(request, humanReadableBookId);
|
||||
}
|
||||
|
||||
try {
|
||||
mimeType = article.getMimeType();
|
||||
mimeType = entry.getMimetype();
|
||||
} catch (exception& e) {
|
||||
mimeType = "application/octet-stream";
|
||||
}
|
||||
|
||||
if (isVerbose.load()) {
|
||||
printf("Found %s\n", request_context->urlStr.c_str());
|
||||
printf("Found %s\n", urlStr.c_str());
|
||||
printf("mimeType: %s\n", mimeType.c_str());
|
||||
}
|
||||
|
||||
if (mimeType.find("text/") != string::npos
|
||||
|| mimeType.find("application/javascript") != string::npos
|
||||
|| mimeType.find("application/json") != string::npos) {
|
||||
zim::Blob raw_content = article.getData();
|
||||
zim::Blob raw_content = entry.getBlob();
|
||||
content = string(raw_content.data(), raw_content.size());
|
||||
|
||||
/* Special rewrite URL in case of ZIM file use intern *asbolute* url like
|
||||
* /A/Kiwix */
|
||||
if (mimeType.find("text/html") != string::npos) {
|
||||
baseUrl = "/" + std::string(1, article.getNamespace()) + "/"
|
||||
+ article.getUrl();
|
||||
baseUrl = "/" + entry.getPath();
|
||||
pthread_mutex_lock(®exLock);
|
||||
content = replaceRegex(content,
|
||||
"$1$2" + rootLocation + "/" + request_context->humanReadableBookId + "/$3/",
|
||||
"$1$2" + rootLocation + "/" + humanReadableBookId + "/$3/",
|
||||
"(href|src)(=[\"|\']{0,1})/([A-Z|\\-])/");
|
||||
content = replaceRegex(content,
|
||||
"$1$2" + rootLocation + "/" + request_context->humanReadableBookId + "/$3/",
|
||||
"$1$2" + rootLocation + "/" + humanReadableBookId + "/$3/",
|
||||
"(@import[ ]+)([\"|\']{0,1})/([A-Z|\\-])/");
|
||||
content = replaceRegex(
|
||||
content,
|
||||
"<head><base href=\"" + rootLocation + "/" + request_context->humanReadableBookId + baseUrl + "\" />",
|
||||
"<head><base href=\"" + rootLocation + "/" + humanReadableBookId + baseUrl + "\" />",
|
||||
"<head>");
|
||||
pthread_mutex_unlock(®exLock);
|
||||
introduceTaskbar(content, request_context->humanReadableBookId);
|
||||
introduceTaskbar(content, humanReadableBookId);
|
||||
} else if (mimeType.find("text/css") != string::npos) {
|
||||
pthread_mutex_lock(®exLock);
|
||||
content = replaceRegex(content,
|
||||
"$1$2" + rootLocation + "/" + request_context->humanReadableBookId + "/$3/",
|
||||
"$1$2" + rootLocation + "/" + humanReadableBookId + "/$3/",
|
||||
"(url|URL)(\\([\"|\']{0,1})/([A-Z|\\-])/");
|
||||
pthread_mutex_unlock(®exLock);
|
||||
}
|
||||
|
||||
bool deflated
|
||||
= request_context->acceptEncodingDeflate && compress_content(content, mimeType);
|
||||
= request->can_compress() && compress_content(content, mimeType);
|
||||
return build_response(
|
||||
content.data(), content.size(), "", mimeType, deflated, true);
|
||||
} else {
|
||||
int range_len;
|
||||
if (request_context->range_end == -1) {
|
||||
range_len = article.getArticleSize() - request_context->range_start;
|
||||
if (request->get_range().second == -1) {
|
||||
range_len = entry.getSize() - request->get_range().first;
|
||||
} else {
|
||||
range_len = request_context->range_end - request_context->range_start;
|
||||
range_len = request->get_range().second - request->get_range().first;
|
||||
}
|
||||
return build_callback_response_from_article(
|
||||
article,
|
||||
request_context->range_start,
|
||||
return build_callback_response_from_entry(
|
||||
entry,
|
||||
request->get_range().first,
|
||||
range_len,
|
||||
mimeType);
|
||||
}
|
||||
}
|
||||
|
||||
static struct MHD_Response* handle_default(RequestContext* request_context)
|
||||
{
|
||||
std::string content = welcomeHTML;
|
||||
|
||||
std::string mimeType = "text/html; charset=utf-8";
|
||||
|
||||
bool deflated = request_context->acceptEncodingDeflate && compress_content(content, mimeType);
|
||||
return build_response(
|
||||
content.data(), content.size(), "", mimeType, deflated, false);
|
||||
}
|
||||
|
||||
int print_out_key (void *cls, enum MHD_ValueKind kind,
|
||||
int print_key_value (void *cls, enum MHD_ValueKind kind,
|
||||
const char *key, const char *value)
|
||||
{
|
||||
printf ("%s: %s\n", key, value);
|
||||
printf (" - %s: '%s'\n", key, value);
|
||||
return MHD_YES;
|
||||
}
|
||||
|
||||
@@ -645,135 +820,56 @@ static int accessHandlerCallback(void* cls,
|
||||
size_t* upload_data_size,
|
||||
void** ptr)
|
||||
{
|
||||
/* Unexpected method */
|
||||
if (0 != strcmp(method, "GET") && 0 != strcmp(method, "POST"))
|
||||
return MHD_NO;
|
||||
|
||||
if (isVerbose.load()) {
|
||||
if (isVerbose.load() ) {
|
||||
printf("======================\n");
|
||||
printf("Requesting : \n");
|
||||
printf("u : %s\n", url);
|
||||
printf("m : %s\n", method);
|
||||
printf("v : %s\n", version);
|
||||
MHD_get_connection_values (connection, MHD_HEADER_KIND, &print_out_key, NULL);
|
||||
printf("full_url : %s\n", url);
|
||||
}
|
||||
RequestContext request(connection, rootLocation, url, method, version);
|
||||
|
||||
/* Check if the response can be compressed */
|
||||
const char* acceptEncodingHeaderValue = MHD_lookup_connection_value(
|
||||
connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_ACCEPT_ENCODING);
|
||||
const bool acceptEncodingDeflate
|
||||
= acceptEncodingHeaderValue
|
||||
&& string(acceptEncodingHeaderValue).find("deflate") != string::npos;
|
||||
|
||||
/* Check if range is requested. */
|
||||
const char* acceptRangeHeaderValue = MHD_lookup_connection_value(
|
||||
connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_RANGE);
|
||||
bool acceptRange = false;
|
||||
int range_start = 0;
|
||||
int range_end = -1;
|
||||
if (acceptRangeHeaderValue != NULL) {
|
||||
auto range = std::string(acceptRangeHeaderValue);
|
||||
if (range.substr(0, 6) == "bytes=")
|
||||
{
|
||||
range = range.substr(6);
|
||||
std::istringstream iss(range);
|
||||
iss >> range_start;
|
||||
range = range.substr(iss.tellg());
|
||||
if (range[0] == '-') {
|
||||
range = range.substr(1);
|
||||
if (! range.empty()) {
|
||||
std::istringstream iss(range);
|
||||
iss >> range_end;
|
||||
range = range.substr(iss.tellg());
|
||||
}
|
||||
if (range.empty()) {
|
||||
// Nothing left to read. We are OK.
|
||||
acceptRange = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isVerbose.load() ) {
|
||||
request.print_debug_info();
|
||||
}
|
||||
/* Unexpected method */
|
||||
if (request.get_method() != RequestMethod::GET && request.get_method() != RequestMethod::POST) {
|
||||
printf("Reject request because of unhandled request method.\n");
|
||||
printf("----------------------\n");
|
||||
return MHD_NO;
|
||||
}
|
||||
|
||||
/* Prepare the variables */
|
||||
struct MHD_Response* response;
|
||||
int httpResponseCode = acceptRange ? MHD_HTTP_PARTIAL_CONTENT : MHD_HTTP_OK;
|
||||
std::string urlStr = string(url);
|
||||
request.httpResponseCode = request.has_range() ? MHD_HTTP_PARTIAL_CONTENT : MHD_HTTP_OK;
|
||||
|
||||
/* Get searcher and reader */
|
||||
std::string humanReadableBookId = "";
|
||||
|
||||
if (!rootLocation.empty() && urlStr.substr(0, rootLocation.size() + 1) != rootLocation + "/"){
|
||||
humanReadableBookId = "";
|
||||
}
|
||||
|
||||
else if (!(urlStr.size() > rootLocation.size() + 5 && urlStr.substr(rootLocation.size() , 6) == "/skin/")) {
|
||||
if ((urlStr == rootLocation + "/" + "search") || (urlStr == rootLocation + "/" + "suggest")
|
||||
|| (urlStr == rootLocation + "/" + "random")) {
|
||||
const char* tmpGetValue = MHD_lookup_connection_value(
|
||||
connection, MHD_GET_ARGUMENT_KIND, "content");
|
||||
humanReadableBookId = (tmpGetValue != NULL ? string(tmpGetValue) : "");
|
||||
if (! request.is_valid_url()) {
|
||||
response = build_homepage(&request);
|
||||
} else {
|
||||
if (startswith(request.get_url(), "/skin/")) {
|
||||
response = handle_skin(&request);
|
||||
} else if (startswith(request.get_url(), "/catalog")) {
|
||||
response = handle_catalog(&request);
|
||||
} else if (request.get_url() == "/meta") {
|
||||
response = handle_meta(&request);
|
||||
} else if (request.get_url() == "/search") {
|
||||
response = handle_search(&request);
|
||||
} else if (request.get_url() == "/suggest") {
|
||||
response = handle_suggest(&request);
|
||||
} else if (request.get_url() == "/random") {
|
||||
response = handle_random(&request);
|
||||
} else {
|
||||
humanReadableBookId = urlStr.substr(rootLocation.size() + 1,
|
||||
urlStr.find("/", rootLocation.size() + 1) != string::npos
|
||||
? urlStr.find("/", rootLocation.size() + 1) - (rootLocation.size() + 1)
|
||||
: urlStr.size() - (rootLocation.size() + 2));
|
||||
if (!humanReadableBookId.empty()) {
|
||||
urlStr = urlStr.substr(urlStr.find("/", rootLocation.size() + 1) != string::npos
|
||||
? urlStr.find("/", rootLocation.size() + 1)
|
||||
: humanReadableBookId.size());
|
||||
}
|
||||
response = handle_content(&request);
|
||||
}
|
||||
}
|
||||
|
||||
kiwix::Searcher* searcher
|
||||
= searchers.find(humanReadableBookId) != searchers.end()
|
||||
? searchers.find(humanReadableBookId)->second
|
||||
: globalSearcher;
|
||||
kiwix::Reader* reader = readers.find(humanReadableBookId) != readers.end()
|
||||
? readers.find(humanReadableBookId)->second
|
||||
: NULL;
|
||||
if (reader == NULL) {
|
||||
humanReadableBookId = "";
|
||||
}
|
||||
|
||||
RequestContext request_context(connection, httpResponseCode,
|
||||
reader, searcher,
|
||||
urlStr, humanReadableBookId,
|
||||
acceptEncodingDeflate,
|
||||
acceptRange, range_start, range_end);
|
||||
|
||||
|
||||
/* Get suggestions */
|
||||
if ((urlStr == (rootLocation + "/" + "suggest")) && reader != NULL) {
|
||||
response = handle_suggest(&request_context);
|
||||
}
|
||||
|
||||
/* Get static skin stuff */
|
||||
else if (urlStr.size() > rootLocation.size() + 5 && urlStr.substr(rootLocation.size() , 6) == "/skin/") {
|
||||
response = handle_skin(&request_context);
|
||||
}
|
||||
|
||||
/* Display the search restults */
|
||||
else if (urlStr == (rootLocation + "/" + "search")) {
|
||||
response = handle_search(&request_context);
|
||||
}
|
||||
|
||||
/* Display a random article */
|
||||
else if (urlStr == (rootLocation + "/" + "random")) {
|
||||
response = handle_random(&request_context);
|
||||
}
|
||||
|
||||
/* Display the content of a ZIM content (article, image, ...) */
|
||||
else if (reader != NULL) {
|
||||
response = handle_content(&request_context);
|
||||
}
|
||||
|
||||
/* Display the global Welcome page */
|
||||
else {
|
||||
response = handle_default(&request_context);
|
||||
}
|
||||
|
||||
/* Queue the response */
|
||||
int ret = MHD_queue_response(connection, request_context.httpResponseCode, response);
|
||||
if (isVerbose.load()) {
|
||||
printf("Response :\n");
|
||||
printf("httpResponseCode : %d\n", request.httpResponseCode);
|
||||
printf("headers :\n");
|
||||
MHD_get_response_headers(response, print_key_value, nullptr);
|
||||
printf("----------------------\n");
|
||||
}
|
||||
int ret = MHD_queue_response(connection, request.httpResponseCode, response);
|
||||
MHD_destroy_response(response);
|
||||
|
||||
return ret;
|
||||
@@ -788,12 +884,11 @@ int main(int argc, char** argv)
|
||||
string rootPath;
|
||||
string interface;
|
||||
int serverPort = 80;
|
||||
int daemonFlag = false;
|
||||
int daemonFlag [[gnu::unused]] = false;
|
||||
int libraryFlag = false;
|
||||
string PPIDString;
|
||||
unsigned int PPID = 0;
|
||||
unsigned int nb_threads = std::thread::hardware_concurrency();
|
||||
kiwix::Manager libraryManager;
|
||||
|
||||
static struct option long_options[]
|
||||
= {{"daemon", no_argument, 0, 'd'},
|
||||
@@ -912,9 +1007,7 @@ int main(int argc, char** argv)
|
||||
try {
|
||||
string libraryPath
|
||||
= isRelativePath(*itr)
|
||||
? computeAbsolutePath(removeLastPathElement(
|
||||
getExecutablePath(), true, false),
|
||||
*itr)
|
||||
? computeAbsolutePath(getCurrentDirectory(), *itr)
|
||||
: *itr;
|
||||
retVal = libraryManager.readFile(libraryPath, true);
|
||||
} catch (...) {
|
||||
@@ -977,7 +1070,7 @@ int main(int argc, char** argv)
|
||||
readers[humanReadableId] = reader;
|
||||
|
||||
if ( reader->hasFulltextIndex()) {
|
||||
kiwix::Searcher* searcher = new kiwix::Searcher();
|
||||
kiwix::Searcher* searcher = new kiwix::Searcher(humanReadableId);
|
||||
searcher->setProtocolPrefix(rootLocation + "/");
|
||||
searcher->setSearchProtocolPrefix(rootLocation + "/" + "search?");
|
||||
searcher->add_reader(reader, humanReadableId);
|
||||
@@ -1035,6 +1128,11 @@ int main(int argc, char** argv)
|
||||
|
||||
introduceTaskbar(welcomeHTML, "");
|
||||
|
||||
/* Compute the OpenSearch description */
|
||||
catalogOpenSearchDescription = RESOURCE::opensearchdescription_xml;
|
||||
catalogOpenSearchDescription = replaceRegex(catalogOpenSearchDescription, rootLocation, "__ROOT_LOCATION__");
|
||||
|
||||
|
||||
#ifndef _WIN32
|
||||
/* Fork if necessary */
|
||||
if (daemonFlag) {
|
||||
@@ -1154,7 +1252,7 @@ int main(int argc, char** argv)
|
||||
}
|
||||
|
||||
if (daemon == NULL) {
|
||||
cerr << "Unable to instanciate the HTTP daemon. The port " << serverPort
|
||||
cerr << "Unable to instantiate the HTTP daemon. The port " << serverPort
|
||||
<< " is maybe already occupied or need more permissions to be open. "
|
||||
"Please try as root or with a port number higher or equal to 1024."
|
||||
<< endl;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
|
||||
sources = ['kiwix-serve.cpp']
|
||||
sources = ['kiwix-serve.cpp', 'request_context.cpp']
|
||||
sources += server_resources
|
||||
|
||||
executable('kiwix-serve', sources,
|
||||
dependencies:all_deps,
|
||||
install:true)
|
||||
install:true,
|
||||
install_rpath: join_paths(get_option('prefix'), get_option('libdir')))
|
||||
|
||||
211
src/server/request_context.cpp
Normal file
211
src/server/request_context.cpp
Normal file
@@ -0,0 +1,211 @@
|
||||
/*
|
||||
* Copyright 2009-2016 Emmanuel Engelhart <kelson@kiwix.org>
|
||||
* Copyright 2017 Matthieu Gautier<mgautier@kymeria.fr>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
|
||||
#include "request_context.h"
|
||||
#include <string.h>
|
||||
#include <stdexcept>
|
||||
#include <sstream>
|
||||
|
||||
RequestContext::RequestContext(struct MHD_Connection* connection,
|
||||
std::string rootLocation,
|
||||
const std::string& _url,
|
||||
const std::string& method,
|
||||
const std::string& version) :
|
||||
connection(connection),
|
||||
full_url(_url),
|
||||
url(_url),
|
||||
valid_url(true),
|
||||
version(version),
|
||||
acceptEncodingDeflate(false),
|
||||
accept_range(false),
|
||||
range_pair(0, -1)
|
||||
{
|
||||
if (method == "GET") {
|
||||
this->method = RequestMethod::GET;
|
||||
} else if (method == "HEAD") {
|
||||
this->method = RequestMethod::HEAD;
|
||||
} else if (method == "POST") {
|
||||
this->method = RequestMethod::POST;
|
||||
} else if (method == "PUT") {
|
||||
this->method = RequestMethod::PUT;
|
||||
} else if (method == "DELETE") {
|
||||
this->method = RequestMethod::DELETE_;
|
||||
} else if (method == "CONNECT") {
|
||||
this->method = RequestMethod::CONNECT;
|
||||
} else if (method == "OPTIONS") {
|
||||
this->method = RequestMethod::OPTIONS;
|
||||
} else if (method == "TRACE") {
|
||||
this->method = RequestMethod::TRACE;
|
||||
} else if (method == "PATCH") {
|
||||
this->method = RequestMethod::PATCH;
|
||||
} else {
|
||||
this->method = RequestMethod::OTHER;
|
||||
}
|
||||
|
||||
MHD_get_connection_values(connection, MHD_HEADER_KIND, &RequestContext::fill_header, this);
|
||||
MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, &RequestContext::fill_argument, this);
|
||||
|
||||
valid_url = true;
|
||||
if (rootLocation.empty()) {
|
||||
// nothing special to handle.
|
||||
url = full_url;
|
||||
} else {
|
||||
if (full_url.size() > rootLocation.size() &&
|
||||
full_url.substr(0, rootLocation.size()+1) == rootLocation + "/") {
|
||||
url = full_url.substr(rootLocation.size());
|
||||
} else {
|
||||
valid_url = false;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
acceptEncodingDeflate =
|
||||
(get_header(MHD_HTTP_HEADER_ACCEPT_ENCODING).find("deflate") != std::string::npos);
|
||||
} catch (const std::out_of_range&) {}
|
||||
|
||||
/*Check if range is requested. */
|
||||
try {
|
||||
auto range = get_header(MHD_HTTP_HEADER_RANGE);
|
||||
int start = 0;
|
||||
int end = -1;
|
||||
std::istringstream iss(range);
|
||||
char c;
|
||||
|
||||
iss >> start >> c;
|
||||
if (iss.good() && c=='-') {
|
||||
iss >> end;
|
||||
if (iss.fail()) {
|
||||
// Something went wrong will extracting.
|
||||
end = -1;
|
||||
}
|
||||
if (iss.eof()) {
|
||||
accept_range = true;
|
||||
range_pair = std::pair<int, int>(start, end);
|
||||
}
|
||||
}
|
||||
} catch (const std::out_of_range&) {}
|
||||
|
||||
|
||||
}
|
||||
|
||||
RequestContext::~RequestContext()
|
||||
{}
|
||||
|
||||
|
||||
int RequestContext::fill_header(void *__this, enum MHD_ValueKind kind,
|
||||
const char *key, const char *value)
|
||||
{
|
||||
RequestContext *_this = static_cast<RequestContext*>(__this);
|
||||
_this->headers[key] = value;
|
||||
return MHD_YES;
|
||||
}
|
||||
|
||||
int RequestContext::fill_argument(void *__this, enum MHD_ValueKind kind,
|
||||
const char *key, const char* value)
|
||||
{
|
||||
RequestContext *_this = static_cast<RequestContext*>(__this);
|
||||
_this->arguments[key] = value == nullptr ? "" : value;
|
||||
return MHD_YES;
|
||||
}
|
||||
|
||||
void RequestContext::print_debug_info() {
|
||||
printf("method : %s (%d)\n", method==RequestMethod::GET ? "GET" :
|
||||
method==RequestMethod::POST ? "POST" :
|
||||
"OTHER", (int)method);
|
||||
printf("version : %s\n", version.c_str());
|
||||
printf("headers :\n");
|
||||
for (auto it=headers.begin(); it!=headers.end(); it++) {
|
||||
printf(" - %s : '%s'\n", it->first.c_str(), it->second.c_str());
|
||||
}
|
||||
printf("arguments :\n");
|
||||
for (auto it=arguments.begin(); it!=arguments.end(); it++) {
|
||||
printf(" - %s : '%s'\n", it->first.c_str(), it->second.c_str());
|
||||
}
|
||||
printf("Parsed : \n");
|
||||
printf("url : %s\n", url.c_str());
|
||||
printf("acceptEncodingDeflate : %d\n", acceptEncodingDeflate);
|
||||
printf("has_range : %d\n", accept_range);
|
||||
printf(".............\n");
|
||||
}
|
||||
|
||||
|
||||
RequestMethod RequestContext::get_method() {
|
||||
return method;
|
||||
}
|
||||
|
||||
std::string RequestContext::get_url() {
|
||||
return url;
|
||||
}
|
||||
|
||||
std::string RequestContext::get_url_part(int number) {
|
||||
size_t start = 1;
|
||||
while(true) {
|
||||
auto found = url.find('/', start);
|
||||
if (number == 0) {
|
||||
if (found == std::string::npos) {
|
||||
return url.substr(start);
|
||||
} else {
|
||||
return url.substr(start, found-start);
|
||||
}
|
||||
} else {
|
||||
if (found == std::string::npos) {
|
||||
throw std::out_of_range("No parts");
|
||||
}
|
||||
start = found + 1;
|
||||
number -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string RequestContext::get_full_url() {
|
||||
return full_url;
|
||||
}
|
||||
|
||||
bool RequestContext::is_valid_url() {
|
||||
return valid_url;
|
||||
}
|
||||
|
||||
bool RequestContext::has_range() {
|
||||
return accept_range;
|
||||
}
|
||||
|
||||
std::pair<int, int> RequestContext::get_range() {
|
||||
return range_pair;
|
||||
}
|
||||
|
||||
template<>
|
||||
std::string RequestContext::get_argument(const std::string& name) {
|
||||
return arguments.at(name);
|
||||
}
|
||||
|
||||
template<>
|
||||
unsigned int RequestContext::get_argument(const std::string& name) {
|
||||
return std::stoi(arguments.at(name).c_str());
|
||||
}
|
||||
|
||||
template<>
|
||||
float RequestContext::get_argument(const std::string& name) {
|
||||
return std::stof(arguments.at(name).c_str());
|
||||
}
|
||||
|
||||
std::string RequestContext::get_header(const std::string& name) {
|
||||
return headers.at(name);
|
||||
}
|
||||
100
src/server/request_context.h
Normal file
100
src/server/request_context.h
Normal file
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright 2009-2016 Emmanuel Engelhart <kelson@kiwix.org>
|
||||
* Copyright 2017 Matthieu Gautier<mgautier@kymeria.fr>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef REQUEST_CONTEXT_H
|
||||
#define REQUEST_CONTEXT_H
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <stdexcept>
|
||||
|
||||
extern "C" {
|
||||
#include <microhttpd.h>
|
||||
}
|
||||
|
||||
enum class RequestMethod {
|
||||
GET,
|
||||
HEAD,
|
||||
POST,
|
||||
PUT,
|
||||
DELETE_,
|
||||
CONNECT,
|
||||
OPTIONS,
|
||||
TRACE,
|
||||
PATCH,
|
||||
OTHER
|
||||
};
|
||||
|
||||
class KeyError : public std::runtime_error {};
|
||||
class IndexError: public std::runtime_error {};
|
||||
|
||||
|
||||
class RequestContext {
|
||||
public:
|
||||
RequestContext(struct MHD_Connection* connection,
|
||||
std::string rootLocation,
|
||||
const std::string& url,
|
||||
const std::string& method,
|
||||
const std::string& version);
|
||||
~RequestContext();
|
||||
|
||||
void print_debug_info();
|
||||
|
||||
bool is_valid_url();
|
||||
|
||||
std::string get_header(const std::string& name);
|
||||
template<typename T=std::string>
|
||||
T get_argument(const std::string& name);
|
||||
|
||||
RequestMethod get_method();
|
||||
std::string get_url();
|
||||
std::string get_url_part(int part);
|
||||
std::string get_full_url();
|
||||
|
||||
bool has_range();
|
||||
std::pair<int, int> get_range();
|
||||
|
||||
bool can_compress() { return acceptEncodingDeflate; }
|
||||
|
||||
// [TODO] Move this to the response builder
|
||||
int httpResponseCode;
|
||||
|
||||
private:
|
||||
struct MHD_Connection* connection;
|
||||
std::string full_url;
|
||||
std::string url;
|
||||
bool valid_url;
|
||||
RequestMethod method;
|
||||
std::string version;
|
||||
|
||||
bool acceptEncodingDeflate;
|
||||
|
||||
bool accept_range;
|
||||
std::pair<int, int> range_pair;
|
||||
std::map<std::string, std::string> headers;
|
||||
std::map<std::string, std::string> arguments;
|
||||
|
||||
static int fill_header(void *, enum MHD_ValueKind, const char*, const char*);
|
||||
static int fill_argument(void *, enum MHD_ValueKind, const char*, const char*);
|
||||
};
|
||||
|
||||
|
||||
#endif //REQUEST_CONTEXT_H
|
||||
@@ -8,7 +8,7 @@
|
||||
jk(function() {
|
||||
jk( "#kiwixsearchbox" ).autocomplete({
|
||||
|
||||
source: "__ROOT_LOCATION__/suggest?content=__CONTENT__",
|
||||
source: "__ROOT_LOCATION__/suggest?content=__CONTENT_ESCAPED__",
|
||||
dataType: "json",
|
||||
cache: false,
|
||||
|
||||
|
||||
8
static/server/opensearchdescription.xml
Normal file
8
static/server/opensearchdescription.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
|
||||
<ShortName>Zim catalog search</ShortName>
|
||||
<Description>Search zim files in the catalog.</Description>
|
||||
<Url type="application/atom+xml;profile=opds-catalog"
|
||||
xmlns:atom="http://www.w3.org/2005/Atom"
|
||||
template="/__ROOT_LOCATION__/catalog/search?q={searchTerms}"/>
|
||||
</OpenSearchDescription>
|
||||
@@ -22,3 +22,4 @@ include.html.part
|
||||
taskbar.css
|
||||
taskbar.html.part
|
||||
global_taskbar.html.part
|
||||
opensearchdescription.xml
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<div class="kiwix_button_wrapper">
|
||||
<a id="kiwix_serve_taskbar_library_button" href="__ROOT_LOCATION__/"><button>Library</button></a>
|
||||
<a id="kiwix_serve_taskbar_home_button" href="__ROOT_LOCATION__/__CONTENT__/"><button>Home</button></a>
|
||||
<a id="kiwix_serve_taskbar_random_button" href="__ROOT_LOCATION__/random?content=__CONTENT__"><button>Random</button></a>
|
||||
<a id="kiwix_serve_taskbar_random_button" href="__ROOT_LOCATION__/random?content=__CONTENT_ESCAPED__"><button>Random</button></a>
|
||||
</div>
|
||||
<div class="kiwix_searchform">
|
||||
<form class="kiwixsearch" method="GET" action="__ROOT_LOCATION__/search" id="kiwixsearchform">
|
||||
|
||||
@@ -8,24 +8,17 @@ INSTALL_DIR=${BUILD_DIR}/INSTALL
|
||||
|
||||
case ${PLATFORM} in
|
||||
"native_static")
|
||||
MESON_OPTION="--default-library=static"
|
||||
MESON_OPTION="-Dstatic-linkage=true"
|
||||
;;
|
||||
"native_dyn")
|
||||
MESON_OPTION="--default-library=shared"
|
||||
MESON_OPTION=""
|
||||
;;
|
||||
"win32_static")
|
||||
MESON_OPTION="--default-library=static --cross-file ${BUILD_DIR}/meson_cross_file.txt"
|
||||
MESON_OPTION="-Dstatic-linkage=true --cross-file ${BUILD_DIR}/meson_cross_file.txt"
|
||||
;;
|
||||
"win32_dyn")
|
||||
MESON_OPTION="--default-library=shared --cross-file ${BUILD_DIR}/meson_cross_file.txt"
|
||||
MESON_OPTION="--cross-file ${BUILD_DIR}/meson_cross_file.txt"
|
||||
;;
|
||||
"android_arm")
|
||||
MESON_OPTION="-Dandroid=true --default-library=shared --cross-file ${BUILD_DIR}/meson_cross_file.txt"
|
||||
;;
|
||||
"android_arm64")
|
||||
MESON_OPTION="-Dandroid=true --default-library=shared --cross-file ${BUILD_DIR}/meson_cross_file.txt"
|
||||
;;
|
||||
|
||||
esac
|
||||
|
||||
cd ${TRAVIS_BUILD_DIR}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
set -e
|
||||
|
||||
REPO_NAME=${TRAVIS_REPO_SLUG#*/}
|
||||
ARCHIVE_NAME=deps_${PLATFORM}_${REPO_NAME}.tar.gz
|
||||
ARCHIVE_NAME=deps_${TRAVIS_OS_NAME}_${PLATFORM}_${REPO_NAME}.tar.gz
|
||||
|
||||
# Packages.
|
||||
case ${PLATFORM} in
|
||||
@@ -29,7 +29,7 @@ esac
|
||||
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -qq python3-pip ${PACKAGES}
|
||||
sudo pip3 install meson
|
||||
sudo pip3 install meson==0.43.0
|
||||
|
||||
# Ninja
|
||||
cd $HOME
|
||||
|
||||
Reference in New Issue
Block a user