1 /*
2 * Copyright 2009-2019 Emmanuel Engelhart <kelson@kiwix.org>
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 <getopt.h>
21 #include <kiwix/manager.h>
22 #include <kiwix/server.h>
23 #include <kiwix/name_mapper.h>
24 #include <kiwix/tools/otherTools.h>
25
26 #ifdef _WIN32
27 # include <windows.h>
28 #else
29 # include <unistd.h>
30 #endif
31
32 #ifdef __APPLE__
33 # import <sys/sysctl.h>
34 # import <sys/types.h>
35 # define MIBSIZE 4
36 #endif
37
38 #include "../version.h"
39
40 #define DEFAULT_THREADS 4
41
usage()42 void usage()
43 {
44 std::cout << "Usage:" << std::endl
45 << "\tkiwix-serve [OPTIONS] ZIM_PATH+" << std::endl
46 << "\tkiwix-serve --library [OPTIONS] LIBRARY_PATH" << std::endl
47 << std::endl
48
49 << "Purpose:" << std::endl
50 << "\tDeliver ZIM file articles via HTTP"
51 << std::endl << std::endl
52
53 << "Mandatory arguments:" << std::endl
54 << "\tLIBRARY_PATH\t\tis the XML library file path listing ZIM file to serve. To be used only with the --library argument."
55 << std::endl
56 << "\tZIM_PATH\t\tis the path of a ZIM file."
57 << std::endl << std::endl
58
59 << "Optional arguments:" << std::endl
60 << "\t-a, --attachToProcess\texit if given process id is not running anymore" << std::endl
61 << "\t-d, --daemon\t\tdetach the HTTP server daemon from the main process" << std::endl
62 << "\t-i, --address\t\tlisten only on this ip address, all available ones otherwise" << std::endl
63 << "\t-m, --nolibrarybutton\tdo not print the builtin home button in the builtin top bar overlay" << std::endl
64 << "\t-n, --nosearchbar\tdo not print the builtin bar overlay on the top of each served page" << std::endl
65 << "\t-b, --blockexternal\tprevent users from directly accessing external links" << std::endl
66 << "\t-p, --port\t\tTCP port on which to listen to HTTP requests (default: 80)" << std::endl
67 << "\t-r, --urlRootLocation\tURL prefix on which the content should be made available (default: /)" << std::endl
68 << "\t-t, --threads\t\tnumber of threads to run in parallel (default: " << DEFAULT_THREADS << ")" << std::endl
69 << "\t-v, --verbose\t\tprint debug log to STDOUT" << std::endl
70 << "\t-V, --version\t\tprint software version" << std::endl
71 << "\t-z, --nodatealiases\tcreate URL aliases for each content by removing the date" << std::endl
72 << "\t--donottrustlibrary\tRead the metadata from the zim file instead of trusting the library." << std::endl
73 << std::endl
74
75 << "Documentation:" << std::endl
76 << "\tSource code\t\thttps://github.com/kiwix/kiwix-tools" << std::endl
77 << "\tMore info\t\thttps://wiki.kiwix.org/wiki/Kiwix-serve" << std::endl
78 << std::endl;
79 }
80
main(int argc,char ** argv)81 int main(int argc, char** argv)
82 {
83 std::string rootLocation = "";
84 kiwix::Library library;
85 unsigned int nb_threads = DEFAULT_THREADS;
86 vector<string> zimPathes;
87 string libraryPath;
88 string rootPath;
89 string address;
90 int serverPort = 80;
91 int daemonFlag [[gnu::unused]] = false;
92 int libraryFlag = false;
93 bool noLibraryButtonFlag = false;
94 bool noSearchBarFlag = false;
95 bool noDateAliasesFlag = false;
96 bool blockExternalLinks = false;
97 bool isVerboseFlag = false;
98 bool trustlibrary = true;
99 string PPIDString;
100 unsigned int PPID = 0;
101
102 static struct option long_options[]
103 = {{"daemon", no_argument, 0, 'd'},
104 {"verbose", no_argument, 0, 'v'},
105 {"version", no_argument, 0, 'V'},
106 {"library", no_argument, 0, 'l'},
107 {"nolibrarybutton", no_argument, 0, 'm'},
108 {"nodatealiases", no_argument, 0, 'z'},
109 {"nosearchbar", no_argument, 0, 'n'},
110 {"blockexternallinks", no_argument, 0, 'b'},
111 {"attachToProcess", required_argument, 0, 'a'},
112 {"port", required_argument, 0, 'p'},
113 {"address", required_argument, 0, 'i'},
114 {"threads", required_argument, 0, 't'},
115 {"urlRootLocation", required_argument, 0, 'r'},
116 {"donottrustlibrary", no_argument, 0, 'T'},
117 {0, 0, 0, 0}};
118
119 /* Argument parsing */
120 while (true) {
121 int option_index = 0;
122 int c
123 = getopt_long(argc, argv, "zmnbdvVla:p:f:t:r:i:", long_options, &option_index);
124
125 if (c != -1) {
126 switch (c) {
127 case 'd':
128 daemonFlag = true;
129 break;
130 case 'v':
131 isVerboseFlag = true;
132 break;
133 case 'V':
134 version();
135 return 0;
136 case 'l':
137 libraryFlag = true;
138 break;
139 case 'n':
140 noSearchBarFlag = true;
141 break;
142 case 'b':
143 blockExternalLinks = true;
144 break;
145 case 'z':
146 noDateAliasesFlag = true;
147 break;
148 case 'm':
149 noLibraryButtonFlag = true;
150 break;
151 case 'T':
152 trustlibrary = false;
153 case 'p':
154 serverPort = atoi(optarg);
155 break;
156 case 'a':
157 PPIDString = string(optarg);
158 PPID = atoi(optarg);
159 break;
160 case 'i':
161 address = string(optarg);
162 break;
163 case 't':
164 nb_threads = atoi(optarg);
165 break;
166 case 'r':
167 rootLocation = string(optarg);
168 break;
169 }
170 } else {
171 if (optind < argc) {
172 if (libraryFlag) {
173 libraryPath = argv[optind++];
174 } else {
175 while (optind < argc)
176 zimPathes.push_back(std::string(argv[optind++]));
177 }
178 }
179 break;
180 }
181 }
182
183 /* Print usage)) if necessary */
184 if (zimPathes.empty() && libraryPath.empty()) {
185 usage();
186 exit(1);
187 }
188
189 /* Setup the library manager and get the list of books */
190 kiwix::Manager manager(&library);
191 if (libraryFlag) {
192 vector<string> libraryPaths = kiwix::split(libraryPath, ";");
193 vector<string>::iterator itr;
194
195 for (itr = libraryPaths.begin(); itr != libraryPaths.end(); ++itr) {
196 if (!itr->empty()) {
197 bool retVal = false;
198
199 try {
200 string libraryPath
201 = isRelativePath(*itr)
202 ? computeAbsolutePath(getCurrentDirectory(), *itr)
203 : *itr;
204 retVal = manager.readFile(libraryPath, true, trustlibrary);
205 } catch (...) {
206 retVal = false;
207 }
208
209 if (!retVal) {
210 cerr << "Unable to open the XML library file '" << *itr << "'."
211 << endl;
212 exit(1);
213 }
214 }
215 }
216
217 /* Check if the library is not empty (or only remote books)*/
218 if (library.getBookCount(true, false) == 0) {
219 cerr << "The XML library file '" << libraryPath
220 << "' is empty (or has only remote books)." << endl;
221 }
222 } else {
223 std::vector<std::string>::iterator it;
224 for (it = zimPathes.begin(); it != zimPathes.end(); it++) {
225 if (!manager.addBookFromPath(*it, *it, "", false)) {
226 cerr << "Unable to add the ZIM file '" << *it
227 << "' to the internal library." << endl;
228 exit(1);
229 }
230 }
231 }
232
233 #ifndef _WIN32
234 /* Fork if necessary */
235 if (daemonFlag) {
236 pid_t pid;
237
238 /* Fork off the parent process */
239 pid = fork();
240 if (pid < 0) {
241 exit(1);
242 }
243
244 /* If we got a good PID, then
245 we can exit the parent process. */
246 if (pid > 0) {
247 exit(0);
248 }
249 }
250 #endif
251
252 kiwix::HumanReadableNameMapper nameMapper(library, noDateAliasesFlag);
253 kiwix::Server server(&library, &nameMapper);
254 server.setAddress(address);
255 server.setRoot(rootLocation);
256 server.setPort(serverPort);
257 server.setNbThreads(nb_threads);
258 server.setVerbose(isVerboseFlag);
259 server.setTaskbar(!noSearchBarFlag, !noLibraryButtonFlag);
260 server.setBlockExternalLinks(blockExternalLinks);
261
262 if (! server.start()) {
263 cerr << "Unable to instantiate the HTTP daemon. The port " << serverPort
264 << " is maybe already occupied or need more permissions to be open. "
265 "Please try as root or with a port number higher or equal to 1024."
266 << endl;
267 exit(1);
268 }
269
270 /* Run endless (until PPID dies) */
271 bool waiting = true;
272 do {
273 if (PPID > 0) {
274 #ifdef _WIN32
275 HANDLE process = OpenProcess(SYNCHRONIZE, FALSE, PPID);
276 DWORD ret = WaitForSingleObject(process, 0);
277 CloseHandle(process);
278 if (ret == WAIT_TIMEOUT) {
279 #elif __APPLE__
280 int mib[MIBSIZE];
281 struct kinfo_proc kp;
282 size_t len = sizeof(kp);
283
284 mib[0] = CTL_KERN;
285 mib[1] = KERN_PROC;
286 mib[2] = KERN_PROC_PID;
287 mib[3] = PPID;
288
289 int ret = sysctl(mib, MIBSIZE, &kp, &len, NULL, 0);
290 if (ret != -1 && len > 0) {
291 #else /* Linux & co */
292 string procPath = "/proc/" + string(PPIDString);
293 if (access(procPath.c_str(), F_OK) != -1) {
294 #endif
295 } else {
296 waiting = false;
297 }
298 }
299
300 kiwix::sleep(1000);
301 } while (waiting);
302
303 /* Stop the daemon */
304 server.stop();
305 }
306