Compare commits

..

67 Commits

Author SHA1 Message Date
sgourdas
142bf834f0 Introduce ipAvailable network function 2024-09-21 21:09:38 +03:00
sgourdas
f32ab92e85 Cleanup line 2024-09-21 21:09:10 +03:00
sgourdas
97ad8232e4 Refactor getBestPublicIp for both protocols 2024-09-19 11:47:13 +03:00
sgourdas
766816b3c4 Line cleanup 2024-09-19 11:21:18 +03:00
sgourdas
e32e9e7722 Make server getters const 2024-09-18 18:44:57 +03:00
Kelson
0b14fda94d Merge pull request #1135 from kiwix/fix-deb-package-deps
Fix deb package dependencies
2024-09-18 12:39:34 +00:00
Emmanuel Engelhart
fe965faf1b Fix deb package dependencies 2024-09-18 14:21:42 +02:00
Kelson
6ad1776242 Merge pull request #1133 from kiwix/pkgconfig
Generation of libkiwix.pc via meson's pkgconfig module
2024-09-17 10:35:29 +02:00
Veloman Yunkan
cbfd3ec7c4 Dropped generation of kiwix.pc
Below are the contents of libkiwix.pc and kiwix.pc files generated using
the new and old approaches, respectively, for native_dyn and
native_static configurations:

```
$ cat BUILD_native_dyn/INSTALL/lib/x86_64-linux-gnu/pkgconfig/libkiwix.pc
prefix=<REDACTED>
includedir=${prefix}/include
libdir=${prefix}/lib/x86_64-linux-gnu

Name: libkiwix
Description: A library that contains useful primitives that Kiwix readers have in common
Version: 14.0.0
Requires.private: icu-i18n, libzim < 10.0.0, libzim >= 9.0.0, pugixml, libcurl, libmicrohttpd, zlib, xapian-core
Libs: -L${prefix}/lib/x86_64-linux-gnu -lkiwix
Libs.private: -pthread
Cflags: -I${includedir}

$ cat BUILD_native_dyn/INSTALL/lib/x86_64-linux-gnu/pkgconfig/kiwix.pc
prefix=<REDACTED>
libdir=${prefix}/lib64
includedir=${prefix}/include

Name: libkiwix
Description: A library that contains a lot of things used by used by other kiwix programs
Version: 14.0.0
Requires: libzim icu-i18n pugixml libcurl libmicrohttpd xapian-core
Libs: -L${libdir} -lkiwix
Cflags: -I${includedir}/

$ cat BUILD_native_static/INSTALL/lib/x86_64-linux-gnu/pkgconfig/libkiwix.pc
prefix=<REDACTED>
includedir=${prefix}/include
libdir=${prefix}/lib/x86_64-linux-gnu

Name: libkiwix
Description: A library that contains useful primitives that Kiwix readers have in common
Version: 14.0.0
Requires: icu-i18n, libzim < 10.0.0, libzim >= 9.0.0, pugixml, libcurl, libmicrohttpd, zlib, xapian-core
Libs: -L${prefix}/lib/x86_64-linux-gnu -lkiwix -pthread
Cflags: -I${includedir} -pthread

$ cat BUILD_native_static/INSTALL/lib/x86_64-linux-gnu/pkgconfig/kiwix.pc
prefix=<REDACTED>
libdir=${prefix}/lib64
includedir=${prefix}/include

Name: libkiwix
Description: A library that contains a lot of things used by used by other kiwix programs
Version: 14.0.0
Requires: libzim icu-i18n pugixml libcurl libmicrohttpd xapian-core
Libs: -L${libdir} -lkiwix
Cflags: -I${includedir}/
```

The notable differences are:

- libdir changed from `${prefix}/lib64` to `${prefix}/lib/x86_64-linux-gnu`

- for native_dyn configuration Requires.private is used

- pthread has appeared in Libs/Libs.private and/or Cflags

- version information was added to the libzim requirement
2024-09-17 12:26:14 +04:00
Veloman Yunkan
f6765137e7 Fixed the libzim version requirement
Also fixed capitalization in an error message
2024-09-16 17:26:28 +04:00
Veloman Yunkan
c24e04c8da Generation of libkiwix.pc via meson's pkgconfig module
Generation of kiwix.pc using the previous approach still stays in place.
2024-09-16 17:25:42 +04:00
Kelson
327fec1877 Merge pull request #1131 from kiwix/ungarbled_binary_resources
Ungarbled binary resources
2024-09-14 14:43:23 +02:00
Veloman Yunkan
c8524b95bc Protection against adding resources of new types
Now if a static resource of a new type is added the build will
fail unless the list of known file type extensions is updated.
2024-09-12 17:39:49 +04:00
Veloman Yunkan
0ac3130b0d Added an extension to an extensionless resource
... so that all resources have extensions and can be automatically
categorized as binary or text based on extension (coming next).
2024-09-12 17:18:16 +04:00
Veloman Yunkan
425ae1efae Disabled dos2unix conversion for binary resources 2024-09-11 17:52:11 +04:00
Veloman Yunkan
920d603a89 Validation of resource content against cacheid
Added a test that checks that the static resources returned by the
server have content that matches their cacheid. This test currently
fails because some binary resources (e.g. png images) are garbled by the
dos2unix conversion.
2024-09-11 17:46:40 +04:00
Kelson
f5c91cc272 Merge pull request #1094 from kiwix/downloadBtn
More conspicuous download button
2024-09-11 11:42:59 +02:00
Veloman Yunkan
3cdc036858 Updated nojs library page
... to keep it in sync with the JSful library page that has been
modified in the previous commit.
2024-09-11 13:17:27 +04:00
Nikhil Tanwar
29bfaa5c5b Move download button to end of tile
- Changed the position of download button to the end of tile and
  added a proper download icon to it. When the button is hovered it
  becomes darker.

- Also internationalized the "Download" text on the modal download widget
  and added download size information to it.
2024-09-11 11:02:08 +04:00
Kelson
bec80e8091 Merge pull request #1123 from kiwix/handling_of_external_app_links
Handling of external app links in the viewer
2024-09-10 13:45:50 +00:00
Veloman Yunkan
2da9801bac Fixed ctrl-clicking of links for Zimit2 ZIMs
Zimit2 ZIMs employ Wombat for client-side rewriting of URLs. The latter
interferes with our approach of handling in the viewer CTRL/SHIFT-clicks
on links inside articles. This commit disables Wombat temporarily while
changing the href attribute of the clicked link.
2024-09-10 17:03:04 +04:00
Veloman Yunkan
16ebc6611b Handling of external app links in the viewer
Links that should be handled/opened by external applications - such as email
addresses (mailto:), phone numbers (tel:), etc - are opened by the
viewer in a new tab/window, thus avoiding any issues with content
security policy.
2024-09-10 16:28:28 +04:00
Kelson
d5a44b913e Merge pull request #1127 from kiwix/crisp_illustration_on_book_tile
Illustration size on book tile is fixed to 48x48
2024-09-09 18:03:57 +00:00
Veloman Yunkan
a63a162c58 Illustration size on book tile is fixed to 48x48 2024-09-09 17:47:55 +00:00
Kelson
c29cd8cf3b Merge pull request #1128 from kiwix/mul_as_mul
Deprived pseudolang mul of a self-name
2024-09-09 15:03:47 +00:00
Veloman Yunkan
04bf1be9d6 Deprived pseudolang mul of a self-name
ICU package contains a special code "mul" with a self-name of "multiple
languages". libkiwix now suppresses that. As a result the self-name of
"mul" (like for any other unknown language code) is the code itself
(i.e. "mul").

The most prominent user-visible effect of this change is that the
language filter in the library page no longer contains a "Multiple
languages" entry if there is a legacy ZIM file with the language set to
"mul" - that entry now shows up as "Mul".
2024-09-09 16:57:07 +04:00
Kelson
59054aa5ad Merge pull request #1109 from kiwix/translatewiki
Localisation updates from https://translatewiki.net.
2024-09-08 20:49:31 +00:00
translatewiki.net
1b8dde0115 Localisation updates from https://translatewiki.net. 2024-09-08 19:34:42 +00:00
Kelson
9d0f6a3170 Merge pull request #1125 from kiwix/remove-windows-cross-compile
Remove Windows cross-compilation from CI
2024-09-08 19:34:27 +00:00
Emmanuel Engelhart
c16ed0aa4c Remove Windows cross-compilation from CI 2024-09-08 21:18:16 +02:00
Kelson
3d95b386c6 Merge pull request #1124 from kiwix/better-libzim-version-check
Better check libzim version
2024-09-07 13:43:37 +00:00
Emmanuel Engelhart
a3f5a654f2 Better check libzim version 2024-09-07 15:35:30 +02:00
Kelson
801b1df304 Merge pull request #1121 from kiwix/safe_tags_without_makeup
Untransformed (but HTML-safe) tags in the kiwix-serve frontend
2024-09-05 04:43:29 +00:00
Veloman Yunkan
2b8a071c6f Fixed cacheids in unit-tests 2024-09-04 19:13:41 +04:00
Veloman Yunkan
00fae37f2d Fixed vertical alignment of the tag filter indicator 2024-09-04 19:09:35 +04:00
Veloman Yunkan
846404e959 Proper HTML encoding/decoding of tags in the frontend
- Tags in the OPDS feed are HTML encoded and must be decoded.

- Tag values must be HTML encoded when injected into the DOM:

  * When the injection is done by setting the innerHTML attribute of a
    DOM element, HTML encoding must be done explicitly (since that text
    is going to be parsed as HTML).

  * When the tag value is expanded into a string that is then set as an
    attribute of a DOM element via the setAttribute() method, no HTML
    encoding must be done (since Element.setAttribute() directly sets
    that value and no HTML decoding is involved in that operation).
2024-09-04 18:43:24 +04:00
Veloman Yunkan
fbcd160efd Disabled beautification of tags in the frontend 2024-09-04 18:15:01 +04:00
Kelson
196185dd73 Merge pull request #1119 from kiwix/fix-debian-control-file-for-v14
Fix deb control file for 14.0.0
2024-09-02 18:14:23 +00:00
Emmanuel Engelhart
affb996769 Fix deb control file for 14.0.0 2024-09-02 19:59:27 +02:00
Kelson
418abbcefa Merge pull request #1116 from kiwix/bump-up-to-version-14.0.0
Bump-up version to 14.0.0
2024-09-01 12:47:13 +00:00
Emmanuel Engelhart
00867a13f6 Rename deb .install file 2024-09-01 14:39:45 +02:00
Emmanuel Engelhart
e096c7e2fd Bump-up version to 14.0.0 2024-09-01 10:48:49 +02:00
Kelson
69341eab47 Merge pull request #1114 from kiwix/update-deb-packages-ci-cd
Update deb packages CI/CD
2024-09-01 08:41:30 +00:00
Emmanuel Engelhart
082727ebb6 Comment out Debian related CI/CD 2024-09-01 10:31:19 +02:00
Emmanuel Engelhart
75a4f8b806 Requires libzim9 2024-09-01 10:14:03 +02:00
Emmanuel Engelhart
2eaa1c4649 Refresh deb packages CI/CD 2024-09-01 10:06:31 +02:00
Matthieu Gautier
199a10d093 Merge pull request #1113 from kiwix/ci_windows 2024-08-27 13:25:40 +02:00
Matthieu Gautier
4812fb18f6 Ensure resources use \n as newline and not \r\n
It appears that git on Windows replace `\n` with `\r\n` in the working
tree.

So compiled resources contain a extra `\r` and our tests are failing.
2024-08-27 13:17:53 +02:00
Matthieu Gautier
940818d801 Do not do iterator underflow in urlDecode
`value.end() - 3` may be "before start of string" if string length is < 3.
On Windows debug, is throw an exception.
On other platform it is probably an undefined behavior.

Rewrite the test to avoid such invalid substraction.
2024-08-27 13:17:53 +02:00
Matthieu Gautier
5182a66b19 Update test to have Windows paths on Windows 2024-08-27 13:17:53 +02:00
Matthieu Gautier
b688aa294a [CI] Build libkiwix on Windows CI
As kiwix-build now publish deps archive for libkiwix on Windows (github actions),
we can now start compiling (and test !!) libkiwix on Windows CI.
2024-08-22 17:04:54 +02:00
Kelson
27ad77c566 Merge pull request #1111 from kiwix/revert-1110-revert-1107-feature/data-directory
"Removed getDataDirectory()" again
2024-08-21 23:50:24 +02:00
Matthieu Gautier
7677f76854 Revert "Revert "Removed getDataDirectory()"" 2024-08-21 22:58:42 +02:00
Kelson
513a8d1383 Merge pull request #1105 from kiwix/string_slugification
Add String Slugification for Generating File Name
2024-08-14 20:24:08 +00:00
ShaopengLin
be464a5986 Introduce getSlugifiedFileName in tools.h
The function sanitizes file names depending on OS.
2024-08-14 20:11:11 +00:00
Kelson
c2042c3be8 Merge pull request #1112 from kiwix/public_i18n_api
Public i18n API
2024-08-14 11:18:29 +00:00
Veloman Yunkan
8d480c8b6d Introduced translateBookCategory() 2024-08-14 12:45:37 +04:00
Veloman Yunkan
82ff88f5d8 Made some i18n API public 2024-08-14 12:45:37 +04:00
Veloman Yunkan
2535f210b3 Renamed src/server/{i18n -> i18n_utils}.h
... so that i18n.h can be introduced in include/
2024-08-14 12:44:20 +04:00
Matthieu Gautier
cb0a2c234a Merge pull request #1110 from kiwix/revert-1107-feature/data-directory 2024-08-12 16:32:21 +02:00
Matthieu Gautier
5a73a75798 Revert "Removed getDataDirectory()" 2024-08-12 16:22:01 +02:00
Matthieu Gautier
0ea756c42a Merge pull request #1107 from kiwix/feature/data-directory 2024-08-12 14:41:33 +02:00
sgourdas
7108dfa9c2 Remove makeDirectory 2024-08-11 23:46:48 +03:00
sgourdas
9fd8e81de2 Remove getDataDirectory 2024-08-11 23:46:34 +03:00
sgourdas
566b40a2f8 Pass download directory directly to startDownload 2024-08-11 23:45:27 +03:00
Kelson
ff6d8a4b30 Merge pull request #1108 from kiwix/pkgconf
Replace pkg-config by pkgconf package in deb
2024-08-07 05:03:38 +00:00
Emmanuel Engelhart
f456ce3e93 Replace pkg-config by pkgconf 2024-08-07 06:53:25 +02:00
64 changed files with 1029 additions and 429 deletions

View File

@@ -71,6 +71,52 @@ jobs:
LD_LIBRARY_PATH: ${{env.HOME}}/BUILD_${{matrix.arch_name}}/INSTALL/lib:${{env.HOME}}/BUILD_${{matrix.arch_name}}/INSTALL/lib64
run: meson test -C build --verbose
Windows:
runs-on: windows-2022
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup python 3.10
uses: actions/setup-python@v5
with:
python-version: '3.10'
- name: Install packages
run:
choco install pkgconfiglite ninja
- name: Install python modules
run: pip3 install meson
- name: Setup MSVC compiler
uses: bus1/cabuild/action/msdevshell@v1
with:
architecture: x64
- name: Install dependencies
uses: kiwix/kiwix-build/actions/dl_deps_archive@main
with:
target_platform: win-x86_64-static
- name: Compile
shell: cmd
run: |
set PKG_CONFIG_PATH=%cd%\BUILD_win-amd64\INSTALL\lib\pkgconfig
set CPPFLAGS=-I%cd%\BUILD_win-amd64\INSTALL\include
meson.exe setup . build -Dwerror=false --default-library=static --buildtype=debug
cd build
ninja.exe
- name: Test
shell: cmd
run: |
cd build
meson.exe test --verbose
env:
WAIT_TIME_FACTOR_TEST: 10
Linux:
strategy:
fail-fast: false
@@ -80,8 +126,6 @@ jobs:
- linux-x86_64-dyn
- android-arm
- android-arm64
- win32-static
- win32-dyn
include:
- target: linux-x86_64-static
image_variant: focal
@@ -107,18 +151,6 @@ jobs:
arch_name: aarch64-linux-android
run_test: false
coverage: false
- target: win32-static
image_variant: f35
lib_postfix: '64'
arch_name: i686-w64-mingw32
run_test: false
coverage: false
- target: win32-dyn
image_variant: f35
lib_postfix: '64'
arch_name: i686-w64-mingw32
run_test: false
coverage: false
env:
HOME: /home/runner
runs-on: ubuntu-20.04

View File

@@ -15,11 +15,16 @@ jobs:
fail-fast: false
matrix:
distro:
- debian-unstable
# - debian-unstable
# - debian-trixie
# - debian-bookworm
# - debian-bullseye
- ubuntu-noble
- ubuntu-jammy
- ubuntu-focal
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
# Determine which PPA we should upload to
- name: PPA
@@ -34,18 +39,47 @@ jobs:
env:
REF: ${{ github.ref }}
- uses: legoktm/gh-action-auto-dch@master
- uses: legoktm/gh-action-auto-dch@main
with:
fullname: Kiwix builder
email: release+launchpad@kiwix.org
distro: ${{ matrix.distro }}
- uses: legoktm/gh-action-build-deb@debian-unstable
if: matrix.distro == 'debian-unstable'
name: Build package for debian-unstable
id: build-debian-unstable
# - uses: legoktm/gh-action-build-deb@debian-unstable
# if: matrix.distro == 'debian-unstable'
# name: Build package for debian-unstable
# id: build-debian-unstable
# with:
# args: --no-sign
#
# - uses: legoktm/gh-action-build-deb@b47978ba8498dc8b8153cc3b5f99a5fc1afa5de1 # pin@debian-trixie
# if: matrix.distro == 'debian-trixie'
# name: Build package for debian-trixie
# id: build-debian-trixie
# with:
# args: --no-sign
#
# - uses: legoktm/gh-action-build-deb@1f4e86a6bb34aaad388167eaf5eb85d553935336 # pin@debian-bookworm
# if: matrix.distro == 'debian-bookworm'
# name: Build package for debian-bookworm
# id: build-debian-bookworm
# with:
# args: --no-sign
#
# - uses: legoktm/gh-action-build-deb@084b4263209252ec80a75d2c78a586192c17f18d # pin@debian-bullseye
# if: matrix.distro == 'debian-bullseye'
# name: Build package for debian-bullseye
# id: build-debian-bullseye
# with:
# args: --no-sign
- uses: legoktm/gh-action-build-deb@9114a536498b65c40b932209b9833aa942bf108d # pin@ubuntu-noble
if: matrix.distro == 'ubuntu-noble'
name: Build package for ubuntu-noble
id: build-ubuntu-noble
with:
args: --no-sign
ppa: ${{ steps.ppa.outputs.ppa }}
- uses: legoktm/gh-action-build-deb@ubuntu-jammy
if: matrix.distro == 'ubuntu-jammy'
@@ -68,7 +102,7 @@ jobs:
name: Packages for ${{ matrix.distro }}
path: output
- uses: legoktm/gh-action-dput@master
- uses: legoktm/gh-action-dput@main
name: Upload dev package
# Only upload on pushes to main
if: github.event_name == 'push' && github.event.ref == 'refs/heads/main' && startswith(matrix.distro, 'ubuntu-')
@@ -77,7 +111,7 @@ jobs:
repository: ppa:kiwixteam/dev
packages: output/*_source.changes
- uses: legoktm/gh-action-dput@master
- uses: legoktm/gh-action-dput@main
name: Upload release package
if: github.event_name == 'release' && startswith(matrix.distro, 'ubuntu-')
with:

17
debian/control vendored
View File

@@ -3,13 +3,12 @@ Priority: optional
Maintainer: Kiwix team <kiwix@kiwix.org>
Build-Depends: debhelper-compat (= 13),
meson,
pkg-config,
libzim-dev (>= 7.2.0~),
pkgconf,
libzim-dev (>= 9.0.0), libzim-dev (<< 10.0.0),
libcurl4-gnutls-dev,
libicu-dev,
libgtest-dev,
libkainjow-mustache-dev,
liblzma-dev,
libmicrohttpd-dev,
libpugixml-dev,
zlib1g-dev
@@ -22,12 +21,13 @@ Package: libkiwix-dev
Section: libdevel
Architecture: any
Multi-Arch: same
Depends: libkiwix10 (= ${binary:Version}), ${misc:Depends}, python3,
libzim-dev (>= 7.2.0~),
Depends: libkiwix14 (= ${binary:Version}), ${misc:Depends}, python3,
libzim-dev (>= 9.0.0), libzim-dev (<< 10.0.0),
libicu-dev,
libpugixml-dev,
libcurl4-gnutls-dev,
libmicrohttpd-dev
libmicrohttpd-dev,
zlib1g-dev
Description: library of common code for Kiwix (development)
Kiwix is an offline Wikipedia reader. libkiwix provides the
software core for Kiwix, and contains the code shared by all
@@ -35,11 +35,12 @@ Description: library of common code for Kiwix (development)
.
This package contains development files.
Package: libkiwix10
Package: libkiwix14
Architecture: any
Multi-Arch: same
Depends: ${shlibs:Depends}, ${misc:Depends}, aria2
Conflicts: libkiwix0, libkiwix3, libkiwix9
Conflicts: libkiwix0, libkiwix3, libkiwix9, libkiwix10, libkiwix11, libkiwix12, libkiwix13
Replaces: libkiwix0, libkiwix3, libkiwix9, libkiwix10, libkiwix11, libkiwix12, libkiwix13
Description: library of common code for Kiwix
Kiwix is an offline Wikipedia reader. libkiwix provides the
software core for Kiwix, and contains the code shared by all

View File

View File

@@ -172,7 +172,12 @@ class Downloader
typedef std::vector<std::pair<std::string, std::string>> Options;
public: // functions
Downloader();
/*
* Create a new Downloader object.
*
* @param sessionFileDir: The directory where aria2 will store its session file.
*/
explicit Downloader(std::string sessionFileDir);
virtual ~Downloader();
void close();
@@ -191,10 +196,11 @@ class Downloader
* User should call `update` on the returned `Download` to have an accurate status.
*
* @param uri: The uri of the thing to download.
* @param downloadDir: The download directory where the thing should be stored (takes precedence over any "dir" in `options`).
* @param options: A series of pair <option_name, option_value> to pass to aria.
* @return: The newly created Download.
*/
std::shared_ptr<Download> startDownload(const std::string& uri, const Options& options = {});
std::shared_ptr<Download> startDownload(const std::string& uri, const std::string& downloadDir, Options options = {});
/**
* Get a download corrsponding to a download id (did)

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2022 Veloman Yunkan <veloman.yunkan@gmail.com>
* Copyright 2024 Veloman Yunkan <veloman.yunkan@gmail.com>
*
* 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
@@ -17,29 +17,16 @@
* MA 02110-1301, USA.
*/
#ifndef KIWIX_SERVER_I18N
#define KIWIX_SERVER_I18N
#ifndef KIWIX_I18N
#define KIWIX_I18N
#include <tools.h>
#include <map>
#include <string>
#include <mustache.hpp>
namespace kiwix
{
struct I18nString {
const char* const key;
const char* const value;
};
struct I18nStringTable {
const char* const lang;
const size_t entryCount;
const I18nString* const entries;
const char* get(const std::string& key) const;
};
std::string getTranslatedString(const std::string& lang, const std::string& key);
namespace i18n
{
@@ -69,28 +56,6 @@ private:
const std::string m_lang;
};
class GetTranslatedStringWithMsgId
{
typedef kainjow::mustache::basic_data<std::string> MustacheString;
typedef std::pair<std::string, MustacheString> MsgIdAndTranslation;
public:
explicit GetTranslatedStringWithMsgId(const std::string& lang) : m_lang(lang) {}
MsgIdAndTranslation operator()(const std::string& key) const
{
return {key, getTranslatedString(m_lang, key)};
}
MsgIdAndTranslation operator()(const std::string& key, const Parameters& params) const
{
return {key, expandParameterizedString(m_lang, key, params)};
}
private:
const std::string m_lang;
};
} // namespace i18n
class ParameterizedMessage
@@ -120,18 +85,8 @@ inline ParameterizedMessage nonParameterizedMessage(const std::string& msgId)
return ParameterizedMessage(msgId, noParams);
}
struct LangPreference
{
const std::string lang;
const float preference;
};
typedef std::vector<LangPreference> UserLangPreferences;
UserLangPreferences parseUserLanguagePreferences(const std::string& s);
std::string selectMostSuitableLanguage(const UserLangPreferences& prefs);
std::string translateBookCategory(const std::string& lang, const std::string& category);
} // namespace kiwix
#endif // KIWIX_SERVER_I18N
#endif // KIWIX_I18N

View File

@@ -10,7 +10,8 @@ headers = [
'kiwixserve.h',
'name_mapper.h',
'tools.h',
'version.h'
'version.h',
'i18n.h'
]
install_headers(headers, subdir:'kiwix')

View File

@@ -22,7 +22,7 @@
#include <string>
#include <memory>
#include "common.h"
#include "tools.h"
namespace kiwix
{
@@ -52,7 +52,7 @@ namespace kiwix
void stop();
void setRoot(const std::string& root);
void setAddress(const std::string& addr) { m_addr = addr; }
void setAddress(const std::string& addr);
void setPort(int port) { m_port = port; }
void setNbThreads(int threads) { m_nbThreads = threads; }
void setMultiZimSearchLimit(unsigned int limit) { m_multizimSearchLimit = limit; }
@@ -64,15 +64,15 @@ namespace kiwix
void setBlockExternalLinks(bool blockExternalLinks)
{ m_blockExternalLinks = blockExternalLinks; }
void setIpMode(IpMode mode) { m_ipMode = mode; }
int getPort();
std::string getAddress();
int getPort() const;
IpAddress getAddress() const;
IpMode getIpMode() const;
protected:
std::shared_ptr<Library> mp_library;
std::shared_ptr<NameMapper> mp_nameMapper;
std::string m_root = "";
std::string m_addr = "";
IpAddress m_addr;
std::string m_indexTemplateString = "";
int m_port = 80;
int m_nbThreads = 1;

View File

@@ -24,6 +24,7 @@
#include <vector>
#include <map>
#include <cstdint>
#include "common.h"
namespace kiwix
{
@@ -32,6 +33,12 @@ struct IpAddress
{
std::string addr; // IPv4 address
std::string addr6; // IPv6 address
bool empty() const { return addr.empty() && addr6.empty(); }
bool empty4() const { return addr.empty(); }
bool empty6() const { return addr6.empty(); }
bool valid4(IpMode mode) const { return (mode == IpMode::ipv4 || mode == IpMode::all) && !this->empty4(); }
bool valid6(IpMode mode) const { return (mode == IpMode::ipv6 || mode == IpMode::all) && !this->empty6(); }
};
typedef std::pair<std::string, std::string> LangNameCodePair;
@@ -45,32 +52,6 @@ typedef std::vector<std::string> FeedCategories;
*/
std::string getCurrentDirectory();
/**
* Return the data directory
*
* The data directory is the default directory where downloaded files
* should be saved (it can be overriden via the options parameter of
* `kiwix::Downloader::startDownload()`).
*
* Its path can vary and is determined as follows:
*
* * `$KIWIX_DATA_DIR` if `$KIWIX_DATA_DIR` environment variable set, *otherwise...*
* * On Windows:
*
* * `$APPDATA/kiwix` if environment variable `$APPDATA` set, *otherwise...*
* * `$USERPROFILE/kiwix` if environment variable `$USERPROFILE` set, *otherwise...*
*
* * On other Operating Systems:
*
* * `$XDG_DATA_HOME/kiwix` if environment variable `$XDG_DATA_HOME` set, *otherwise...*
* * `$HOME/.local/share/kiwx` if environment variable `$HOME` set, *otherwise...*
*
* * Current working directory.
*
* @return the path of the data directory (UTF-8 encoded)
*/
std::string getDataDirectory();
/** Return the path of the executable
*
* Some application may be packaged in auto extractible archive (Appimage) and the
@@ -242,7 +223,7 @@ std::map<std::string, std::string> getNetworkInterfaces();
/** Provides the best IP address
* This function provides the best IP address from the list given by getNetworkInterfacesIPv4Or6()
*/
std::string getBestPublicIp(bool ipv6);
IpAddress getBestPublicIps(IpMode mode);
/** Provides the best IPv4 adddress
* Equivalent to getBestPublicIp(false). Provided for backward compatibility
@@ -250,6 +231,15 @@ std::string getBestPublicIp(bool ipv6);
*/
std::string getBestPublicIp();
/** Checks if IP address is available
*
* Check if the given IP address is configured on any network interface of the system
*
* @param addr the IP address to check.
* @return true if the IP address is available on the system.
*/
bool ipAvailable(const IpAddress& addr);
/** Converts file size to human readable format.
*
* This function will convert a number to its equivalent size using units.
@@ -284,12 +274,12 @@ FeedCategories readCategoriesFromFeed(const std::string& content);
std::string getLanguageSelfName(const std::string& lang);
/**
* Retrieve the translation corresponding to key in language lang
* Slugifies the filename by converting any characters reserved by the operating
* system to '_'. Note filename is only the file name and not a path.
*
* @param lang ISO 639-3 language code.
* @param key translation key string
* @return translated key
* @param filename Valid UTF-8 encoded file name string.
* @return slugified string.
*/
std::string getTranslatedString(const std::string& lang, const std::string& key);
std::string getSlugifiedFileName(const std::string& filename);
}
#endif // KIWIX_TOOLS_H

View File

@@ -1,10 +0,0 @@
prefix=@prefix@
libdir=${prefix}/lib64
includedir=${prefix}/include
Name: libkiwix
Description: A library that contains a lot of things used by used by other kiwix programs
Version: @version@
Requires: @requires@
Libs: -L${libdir} -lkiwix @extra_libs@
Cflags: -I${includedir}/ @extra_cflags@

View File

@@ -1,5 +1,5 @@
project('libkiwix', 'cpp',
version : '13.1.0',
version : '14.0.0',
license : 'GPLv3+',
default_options : ['c_std=c11', 'cpp_std=c++17', 'werror=true'])
@@ -35,9 +35,10 @@ else
error('Cannot found header mustache.hpp')
endif
libzim_dep = dependency('libzim', version : '>=8.1.0', static:static_deps)
libzim_dep = dependency('libzim', version:['>=9.0.0', '<10.0.0'], static:static_deps)
if not compiler.has_header_symbol('zim/zim.h', 'LIBZIM_WITH_XAPIAN', dependencies: libzim_dep)
error('Libzim seems to be compiled without xapian. Xapian support is mandatory.')
error('Libzim seems to be compiled without Xapian. Xapian support is mandatory.')
endif
@@ -73,17 +74,10 @@ if get_option('doc')
subdir('docs')
endif
pkg_requires = ['libzim', 'icu-i18n', 'pugixml', 'libcurl', 'libmicrohttpd', 'xapian-core']
pkg_conf = configuration_data()
pkg_conf.set('prefix', get_option('prefix'))
pkg_conf.set('requires', ' '.join(pkg_requires))
pkg_conf.set('extra_libs', ' '.join(extra_libs))
pkg_conf.set('extra_cflags', extra_cflags)
pkg_conf.set('version', meson.project_version())
configure_file(output : 'kiwix.pc',
configuration : pkg_conf,
input : 'kiwix.pc.in',
install_dir: get_option('libdir')+'/pkgconfig'
)
pkg_mod = import('pkgconfig')
pkg_mod.generate(libraries : [libkiwix] + extra_libs,
version : meson.project_version(),
name : 'libkiwix',
filebase : 'libkiwix',
description : 'A library that contains useful primitives that Kiwix readers have in common',
extra_cflags: extra_cflags)

View File

@@ -61,7 +61,7 @@ lang_table_entry_cxx_template = '''
cxxfile_template = '''// This file is automatically generated. Do not modify it.
#include "server/i18n.h"
#include "server/i18n_utils.h"
namespace kiwix {
namespace i18n {

View File

@@ -61,6 +61,32 @@ resource_decl_template = """{namespaces_open}
extern const std::string {identifier};
{namespaces_close}"""
BINARY_RESOURCE_EXTENSIONS = {'.ico', '.png', '.ttf'}
TEXT_RESOURCE_EXTENSIONS = {
'.css',
'.html',
'.js',
'.json',
'.svg',
'.tmpl',
'.webmanifest',
'.xml',
}
if not BINARY_RESOURCE_EXTENSIONS.isdisjoint(TEXT_RESOURCE_EXTENSIONS):
raise RuntimeError(f"The following file type extensions are declared to be both binary and text: {BINARY_RESOURCE_EXTENSIONS.intersection(TEXT_RESOURCE_EXTENSIONS)}")
def is_binary_resource(filename):
_, extension = os.path.splitext(filename)
is_binary = extension in BINARY_RESOURCE_EXTENSIONS
is_text = extension in TEXT_RESOURCE_EXTENSIONS
if not is_binary and not is_text:
# all file type extensions of static resources must be listed
# in either BINARY_RESOURCE_EXTENSIONS or TEXT_RESOURCE_EXTENSIONS
raise RuntimeError(f"Unknown file type extension: {extension}")
return is_binary
class Resource:
def __init__(self, base_dirs, filename, cacheid=None):
filename = filename
@@ -72,6 +98,8 @@ class Resource:
try:
with open(os.path.join(base_dir, filename), 'rb') as f:
self.data = f.read()
if not is_binary_resource(filename):
self.data = self.data.replace(b"\r\n", b"\n")
found = True
break
except FileNotFoundError:

View File

@@ -55,18 +55,15 @@ void pauseAnyActiveDownloads(const std::string& ariaSessionFilePath)
} // unnamed namespace
Aria2::Aria2():
Aria2::Aria2(std::string sessionFileDir):
mp_aria(nullptr),
m_port(42042),
m_secret(getNewRpcSecret())
{
m_downloadDir = getDataDirectory();
makeDirectory(m_downloadDir);
std::vector<const char*> callCmd;
std::string rpc_port = "--rpc-listen-port=" + to_string(m_port);
std::string download_dir = "--dir=" + getDataDirectory();
std::string session_file = appendToDirectory(getDataDirectory(), "kiwix.session");
std::string session_file = appendToDirectory(sessionFileDir, "kiwix.session");
pauseAnyActiveDownloads(session_file);
std::string session = "--save-session=" + session_file;
std::string inputFile = "--input-file=" + session_file;
@@ -94,7 +91,6 @@ Aria2::Aria2():
callCmd.push_back("--enable-rpc");
callCmd.push_back(rpc_secret.c_str());
callCmd.push_back(rpc_port.c_str());
callCmd.push_back(download_dir.c_str());
if (fileReadable(session_file)) {
callCmd.push_back(inputFile.c_str());
}

View File

@@ -22,11 +22,10 @@ class Aria2
std::unique_ptr<Subprocess> mp_aria;
int m_port;
std::string m_secret;
std::string m_downloadDir;
std::string doRequest(const MethodCall& methodCall);
public:
Aria2();
explicit Aria2(std::string sessionFileDir);
virtual ~Aria2() = default;
void close();

View File

@@ -125,8 +125,8 @@ void Download::cancelDownload()
}
/* Constructor */
Downloader::Downloader() :
mp_aria(new Aria2())
Downloader::Downloader(std::string sessionFileDir) :
mp_aria(new Aria2(sessionFileDir))
{
try {
for (auto gid : mp_aria->tellWaiting()) {
@@ -209,9 +209,13 @@ bool downloadCanBeReused(const Download& d,
} // unnamed namespace
std::shared_ptr<Download> Downloader::startDownload(const std::string& uri, const Options& options)
std::shared_ptr<Download> Downloader::startDownload(const std::string& uri, const std::string& downloadDir, Options options)
{
std::unique_lock<std::mutex> lock(m_lock);
options.erase(std::remove_if(options.begin(), options.end(), [](const auto& option) {
return option.first == "dir";
}), options.end());
options.push_back({"dir", downloadDir});
for (auto& p: m_knownDownloads) {
auto& d = p.second;
if ( downloadCanBeReused(*d, uri, options) )

View File

@@ -3,7 +3,7 @@
#include "tools/otherTools.h"
#include "tools.h"
#include "tools/regexTools.h"
#include "server/i18n.h"
#include "server/i18n_utils.h"
namespace kiwix
{
@@ -77,7 +77,7 @@ std::string HTMLDumper::dumpPlainHTML(kiwix::Filter filter) const
const auto tags = bookObj.getTags();
const auto downloadAvailable = (bookObj.getUrl() != "");
std::string faviconAttr = "style=background-image:url(" + bookIconUrl + ")";
booksData.push_back(kainjow::mustache::object{
{"id", contentId},
{"title", bookTitle},

View File

@@ -32,7 +32,7 @@
#include "libkiwix-resources.h"
#include "tools/stringTools.h"
#include "server/i18n.h"
#include "server/i18n_utils.h"
namespace kiwix
{

View File

@@ -75,12 +75,25 @@ void Server::setRoot(const std::string& root)
}
}
int Server::getPort()
void Server::setAddress(const std::string& addr)
{
if (addr.empty())
return;
if (addr.find(':') != std::string::npos) { // IPv6
for (char ch : addr) if (ch == '[' or ch == ']') ch = '\0'; // Clean possible brackets
m_addr.addr6 = addr;
} else {
m_addr.addr = addr;
}
}
int Server::getPort() const
{
return mp_server->getPort();
}
std::string Server::getAddress()
IpAddress Server::getAddress() const
{
return mp_server->getAddress();
}

View File

@@ -17,7 +17,7 @@
* MA 02110-1301, USA.
*/
#include "i18n.h"
#include "i18n_utils.h"
#include "tools/otherTools.h"
@@ -193,4 +193,13 @@ std::string selectMostSuitableLanguage(const UserLangPreferences& prefs)
return bestLangSoFar;
}
std::string translateBookCategory(const std::string& lang, const std::string& category)
{
try {
return getTranslatedString(lang, "book-category." + category);
} catch (...) {
return category;
}
}
} // namespace kiwix

83
src/server/i18n_utils.h Normal file
View File

@@ -0,0 +1,83 @@
/*
* Copyright 2022 Veloman Yunkan <veloman.yunkan@gmail.com>
*
* 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 KIWIX_SERVER_I18N_UTILS
#define KIWIX_SERVER_I18N_UTILS
#include "i18n.h"
#include <mustache.hpp>
namespace kiwix
{
struct I18nString {
const char* const key;
const char* const value;
};
struct I18nStringTable {
const char* const lang;
const size_t entryCount;
const I18nString* const entries;
const char* get(const std::string& key) const;
};
namespace i18n
{
class GetTranslatedStringWithMsgId
{
typedef kainjow::mustache::basic_data<std::string> MustacheString;
typedef std::pair<std::string, MustacheString> MsgIdAndTranslation;
public:
explicit GetTranslatedStringWithMsgId(const std::string& lang) : m_lang(lang) {}
MsgIdAndTranslation operator()(const std::string& key) const
{
return {key, getTranslatedString(m_lang, key)};
}
MsgIdAndTranslation operator()(const std::string& key, const Parameters& params) const
{
return {key, expandParameterizedString(m_lang, key, params)};
}
private:
const std::string m_lang;
};
} // namespace i18n
struct LangPreference
{
const std::string lang;
const float preference;
};
typedef std::vector<LangPreference> UserLangPreferences;
UserLangPreferences parseUserLanguagePreferences(const std::string& s);
std::string selectMostSuitableLanguage(const UserLangPreferences& prefs);
} // namespace kiwix
#endif // KIWIX_SERVER_I18N_UTILS

View File

@@ -54,7 +54,7 @@ extern "C" {
#include "search_renderer.h"
#include "opds_dumper.h"
#include "html_dumper.h"
#include "i18n.h"
#include "i18n_utils.h"
#include <zim/uuid.h>
#include <zim/error.h>
@@ -407,7 +407,7 @@ public:
InternalServer::InternalServer(LibraryPtr library,
std::shared_ptr<NameMapper> nameMapper,
std::string addr,
IpAddress addr,
int port,
std::string root,
int nbThreads,
@@ -462,18 +462,23 @@ bool InternalServer::start() {
sockAddr6.sin6_port = htons(m_port);
if (m_addr.empty()) {
if (0 != INADDR_ANY) {
sockAddr6.sin6_addr = in6addr_any;
sockAddr4.sin_addr.s_addr = htonl(INADDR_ANY);
}
m_addr = kiwix::getBestPublicIp(m_ipMode == IpMode::ipv6 || m_ipMode == IpMode::all);
sockAddr6.sin6_addr = in6addr_any;
sockAddr4.sin_addr.s_addr = htonl(INADDR_ANY);
m_addr = kiwix::getBestPublicIps(m_ipMode);
} else {
bool ipv6 = inet_pton(AF_INET6, m_addr.c_str(), &(sockAddr6.sin6_addr.s6_addr)) == 1;
bool ipv4 = inet_pton(AF_INET, m_addr.c_str(), &(sockAddr4.sin_addr.s_addr)) == 1;
bool ipv6 = inet_pton(AF_INET6, m_addr.addr6.c_str(), &(sockAddr6.sin6_addr.s6_addr)) == 1;
bool ipv4 = inet_pton(AF_INET, m_addr.addr.c_str(), &(sockAddr4.sin_addr.s_addr)) == 1;
if (ipv6){
m_ipMode = IpMode::all;
m_ipMode = IpMode::ipv6;
} else if (!ipv4) {
std::cerr << "Ip address " << m_addr << " is not a valid ip address" << std::endl;
const std::string& address = m_addr.addr.empty() ? m_addr.addr6 : m_addr.addr;
std::cerr << "IP address " << address << " is not a valid IP address" << std::endl;
return false;
}
if (!ipAvailable(m_addr)) {
std::cerr << "IP address " << (m_addr.addr.empty() ? m_addr.addr6 : m_addr.addr) << " is not available on this system" << std::endl;
return false;
}
}
@@ -959,7 +964,7 @@ std::unique_ptr<Response> InternalServer::handle_search_request(const RequestCon
} catch(std::runtime_error& e) {
// Searcher->search will throw a runtime error if there is no valid xapian database to do the search.
// (in case of zim file not containing a index)
const auto cssUrl = renderUrl(m_root, RESOURCE::templates::url_of_search_results_css);
const auto cssUrl = renderUrl(m_root, RESOURCE::templates::url_of_search_results_css_tmpl);
HTTPErrorResponse response(request, MHD_HTTP_NOT_FOUND,
"fulltext-search-unavailable",
"404-page-heading",

View File

@@ -27,6 +27,7 @@ extern "C" {
#include "library.h"
#include "name_mapper.h"
#include "tools.h"
#include <zim/search.h>
#include <zim/suggestion.h>
@@ -94,7 +95,7 @@ class InternalServer {
public:
InternalServer(LibraryPtr library,
std::shared_ptr<NameMapper> nameMapper,
std::string addr,
IpAddress addr,
int port,
std::string root,
int nbThreads,
@@ -117,8 +118,8 @@ class InternalServer {
void** cont_cls);
bool start();
void stop();
std::string getAddress() { return m_addr; }
int getPort() { return m_port; }
IpAddress getAddress() const { return m_addr; }
int getPort() const { return m_port; }
IpMode getIpMode() const { return m_ipMode; }
private: // functions
@@ -166,7 +167,7 @@ class InternalServer {
typedef ConcurrentCache<std::string, std::shared_ptr<LockableSuggestionSearcher>> SuggestionSearcherCache;
private: // data
std::string m_addr;
IpAddress m_addr;
int m_port;
std::string m_root; // URI-encoded
std::string m_rootPrefixOfDecodedURL; // URI-decoded

View File

@@ -28,7 +28,7 @@
#include <cctype>
#include "tools/stringTools.h"
#include "i18n.h"
#include "i18n_utils.h"
namespace kiwix {

View File

@@ -27,7 +27,7 @@
#include <mustache.hpp>
#include "byte_range.h"
#include "etag.h"
#include "i18n.h"
#include "i18n_utils.h"
#include <zim/item.h>

View File

@@ -68,6 +68,7 @@ void fillLanguagesMap()
const kiwix::ICULanguageInfo lang(*icuLangPtr);
iso639_3.insert({lang.iso3Code(), lang.selfName()});
}
iso639_3.erase("mul");
}
} // unnamed namespace

View File

@@ -211,40 +211,48 @@ std::map<std::string, std::string> getNetworkInterfaces() {
return result;
}
std::string getBestPublicIp(bool ipv6) {
IpAddress bestPublicIp = IpAddress{"127.0.0.1","::1"};
IpAddress getBestPublicIps(IpMode mode) {
IpAddress bestPublicIps = IpAddress{"127.0.0.1","::1"};
std::map<std::string, IpAddress> interfaces = getNetworkInterfacesIPv4Or6();
#ifndef _WIN32
const char* const prioritizedNames[] =
{ "eth0", "eth1", "wlan0", "wlan1", "en0", "en1" };
const char* const prioritizedNames[] = { "eth0", "eth1", "wlan0", "wlan1", "en0", "en1" };
for(auto name: prioritizedNames) {
auto it=interfaces.find(name);
if(it != interfaces.end() && !(ipv6 && (*it).second.addr6.empty())) {
bestPublicIp = (*it).second;
break;
}
const auto it = interfaces.find(name);
if(it == interfaces.end()) continue;
const IpAddress& interfaceIps = (*it).second;
if(!bestPublicIps.empty4() && interfaceIps.valid4(mode)) bestPublicIps.addr = interfaceIps.addr;
if(!bestPublicIps.empty6() && interfaceIps.valid6(mode)) bestPublicIps.addr6 = interfaceIps.addr6;
}
#endif
const char* const prefixes[] = { "192.168", "172.16.", "10.0" };
for(auto prefix : prefixes){
for(auto& itr : interfaces) {
std::string interfaceIp(itr.second.addr);
if (interfaceIp.find(prefix) == 0 && !(ipv6 && itr.second.addr6.empty())) {
bestPublicIp = itr.second;
break;
}
const IpAddress& interfaceIps = itr.second;
if(!bestPublicIps.empty4() && interfaceIps.valid4(mode) && interfaceIps.addr.find(prefix) == 0) bestPublicIps.addr = interfaceIps.addr;
if(!bestPublicIps.empty6() && interfaceIps.valid6(mode)) bestPublicIps.addr6 = interfaceIps.addr6;
}
}
return ipv6 ? bestPublicIp.addr6 : bestPublicIp.addr;
}
return bestPublicIps;
}
std::string getBestPublicIp()
{
return getBestPublicIp(false);
return getBestPublicIps(IpMode::ipv4).addr;
}
bool ipAvailable(const IpAddress& addr)
{
auto interfaces = kiwix::getNetworkInterfacesIPv4Or6();
for (const auto& interface : interfaces) {
IpAddress interfaceIps = interface.second;
if (!interfaceIps.empty4() && interfaceIps.addr == addr.addr) return true;
if (!interfaceIps.empty6() && interfaceIps.addr6 == addr.addr6) return true;
}
return false;
}
} // namespace kiwix

View File

@@ -32,7 +32,7 @@
#endif
#include "tools/stringTools.h"
#include "server/i18n.h"
#include "server/i18n_utils.h"
#include "libkiwix-resources.h"
#include <map>

View File

@@ -320,16 +320,6 @@ bool kiwix::fileReadable(const std::string& path)
#endif
}
bool makeDirectory(const std::string& path)
{
#ifdef _WIN32
int status = _wmkdir(Utf8ToWide(path).c_str());
#else
int status = mkdir(path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
#endif
return status == 0;
}
std::string makeTmpDirectory()
{
#ifdef _WIN32
@@ -438,52 +428,6 @@ std::string kiwix::getCurrentDirectory()
return ret;
}
std::string kiwix::getDataDirectory()
{
// Try to get the dataDir from the `KIWIX_DATA_DIR` env var
#ifdef _WIN32
wchar_t* cDataDir = ::_wgetenv(L"KIWIX_DATA_DIR");
if (cDataDir != nullptr) {
return WideToUtf8(cDataDir);
}
#else
char* cDataDir = ::getenv("KIWIX_DATA_DIR");
if (cDataDir != nullptr) {
return cDataDir;
}
#endif
// Compute the dataDir from the user directory.
std::string dataDir;
#ifdef _WIN32
cDataDir = ::_wgetenv(L"APPDATA");
if (cDataDir == nullptr)
cDataDir = ::_wgetenv(L"USERPROFILE");
if (cDataDir != nullptr)
dataDir = WideToUtf8(cDataDir);
#else
cDataDir = ::getenv("XDG_DATA_HOME");
if (cDataDir != nullptr) {
dataDir = cDataDir;
} else {
cDataDir = ::getenv("HOME");
if (cDataDir != nullptr) {
dataDir = cDataDir;
dataDir = appendToDirectory(dataDir, ".local");
dataDir = appendToDirectory(dataDir, "share");
}
}
#endif
if (!dataDir.empty()) {
dataDir = appendToDirectory(dataDir, "kiwix");
makeDirectory(dataDir);
return dataDir;
}
// Let's use the currentDirectory
return getCurrentDirectory();
}
static std::map<std::string, std::string> extMimeTypes = {
{ "html", "text/html"},
{ "htm", "text/html"},

View File

@@ -29,7 +29,6 @@ std::wstring Utf8ToWide(const std::string& str);
unsigned int getFileSize(const std::string& path);
std::string getFileSizeAsString(const std::string& path);
bool makeDirectory(const std::string& path);
std::string makeTmpDirectory();
bool copyFile(const std::string& sourcePath, const std::string& destPath);
bool writeTextFile(const std::string& path, const std::string& content);

View File

@@ -32,6 +32,7 @@
#include <iostream>
#include <iomanip>
#include <regex>
/* tell ICU where to find its dat file (tables) */
void kiwix::loadICUExternalTables()
@@ -256,7 +257,7 @@ std::string kiwix::urlDecode(const std::string& value, bool component)
// If there aren't enough characters left for this to be a
// valid escape code, just use the character and move on
if (it > value.end() - 3) {
if (value.end() - it < 3) {
os << *it;
continue;
}
@@ -439,3 +440,13 @@ template<>
std::string kiwix::extractFromString(const std::string& str) {
return str;
}
std::string kiwix::getSlugifiedFileName(const std::string& filename)
{
#ifdef _WIN32
const std::regex reservedCharsReg(R"([<>:"/\\|?*])");
#else
const std::regex reservedCharsReg("/");
#endif
return std::regex_replace(filename, reservedCharsReg, "_");
}

View File

@@ -4,6 +4,7 @@ skin/magnet.png
skin/feed.svg
skin/langSelector.svg
skin/download.png
skin/download-white.svg
skin/hash.png
skin/search-icon.svg
skin/iso6391To3.js
@@ -37,7 +38,7 @@ templates/catalog_v2_entry.xml
templates/catalog_v2_partial_entry.xml
templates/catalog_v2_categories.xml
templates/catalog_v2_languages.xml
templates/url_of_search_results_css
templates/url_of_search_results_css.tmpl
templates/viewer_settings.js
templates/no_js_library_page.html
templates/no_js_download.html

View File

@@ -0,0 +1,6 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="SVGRepo_bgCarrier" stroke-width="0"/>
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/>
<g id="SVGRepo_iconCarrier"> <g id="Interface / Download"> <path id="Vector" d="M6 21H18M12 3V17M12 17L17 12M12 17L7 12" stroke="#f6f5f4" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </g> </g>
</svg>

After

Width:  |  Height:  |  Size: 546 B

View File

@@ -16,5 +16,15 @@
"search": "অনুসন্ধান",
"welcome-to-kiwix-server": "কিউইক্স সার্ভারে স্বাগতম",
"download-links-title": "বই ডাউনলোড করুন",
"preview-book": "প্রাকদর্শন"
"preview-book": "প্রাকদর্শন",
"book-category.wikibooks": "উইকিবই",
"book-category.wikinews": "উইকিসংবাদ",
"book-category.wikipedia": "উইকিপিডিয়া",
"book-category.wikiquote": "উইকিউক্তি",
"book-category.wikisource": "উইকিসংকলন",
"book-category.wikispecies": "উইকিপ্রজাতি",
"book-category.wikiversity": "উইকিবিশ্ববিদ্যালয়",
"book-category.wikivoyage": "উইকিভ্রমণ",
"book-category.wiktionary": "উইকিঅভিধান",
"book-category.other": "অন্যান্য"
}

View File

@@ -51,8 +51,8 @@
, "torrent-download-link-text": "BitTorrent"
, "torrent-download-alt-text": "Download via BitTorrent"
, "library-opds-feed-all-entries": "Library OPDS Feed - All entries"
, "filter-by-tag": "Filter by tag \"{{TAG}}\""
, "stop-filtering-by-tag": "Stop filtering by tag \"{{TAG}}\""
, "filter-by-tag": "Filter by tag \"{{{TAG}}}\""
, "stop-filtering-by-tag": "Stop filtering by tag \"{{{TAG}}}\""
, "library-opds-feed-parameterised": "Library OPDS Feed - entries matching {{#LANG}}\nLanguage: {{LANG}} {{/LANG}}{{#CATEGORY}}\nCategory: {{CATEGORY}} {{/CATEGORY}}{{#TAG}}\nTag: {{TAG}} {{/TAG}}{{#Q}}\nQuery: {{Q}} {{/Q}}"
, "welcome-to-kiwix-server": "Welcome to Kiwix Server"
, "download-links-heading": "Download links for <b><i>{{BOOK_TITLE}}</i></b>"
@@ -60,4 +60,22 @@
, "preview-book": "Preview"
, "non-translated-text": "{{MSG}}"
, "unknown-error": "Unknown error"
, "book-category.gutenberg": "Gutenberg"
, "book-category.iFixit": "iFixit"
, "book-category.mooc": "MOOC"
, "book-category.phet": "Phet"
, "book-category.stack_exchange": "Stack Exchange"
, "book-category.ted": "Ted"
, "book-category.vikidia": "Vikidia"
, "book-category.wikibooks": "Wikibooks"
, "book-category.wikihow": "wikiHow"
, "book-category.wikinews": "Wikinews"
, "book-category.wikipedia": "Wikipedia"
, "book-category.wikiquote": "Wikiquote"
, "book-category.wikisource": "Wikisource"
, "book-category.wikispecies": "Wikispecies"
, "book-category.wikiversity": "Wikiversity"
, "book-category.wikivoyage": "Wikivoyage"
, "book-category.wiktionary": "Wiktionary"
, "book-category.other": "Other"
}

View File

@@ -3,6 +3,7 @@
"authors": [
"Adriendelucca",
"Gomoko",
"Goombiis",
"Melimeli",
"Stephane",
"Thibaut120094",
@@ -67,5 +68,15 @@
"download-links-heading": "Liens de téléchargement pour <b><i>{{BOOK_TITLE}}</i></b>",
"download-links-title": "Télécharger le livre",
"preview-book": "Aperçu",
"unknown-error": "Erreur inconnue"
"unknown-error": "Erreur inconnue",
"book-category.wikibooks": "Wikilivres",
"book-category.wikinews": "Wikinews",
"book-category.wikipedia": "Wikipédia",
"book-category.wikiquote": "Wikiquote",
"book-category.wikisource": "Wikisource",
"book-category.wikispecies": "Wikispecies",
"book-category.wikiversity": "Wikiversité",
"book-category.wikivoyage": "Wikivoyage",
"book-category.wiktionary": "Wiktionnaire",
"book-category.other": "Autre"
}

View File

@@ -60,5 +60,17 @@
"download-links-heading": "הורדת קישורים עבור <b><i>{{BOOK_TITLE}}</i></b>",
"download-links-title": "הורדת ספר",
"preview-book": "תצוגה מקדימה",
"unknown-error": "שגיאה בלתי־ידועה"
"unknown-error": "שגיאה בלתי־ידועה",
"book-category.gutenberg": "גוטנברג",
"book-category.vikidia": "ויקידיה",
"book-category.wikibooks": "ויקיספר",
"book-category.wikinews": "ויקיחדשות",
"book-category.wikipedia": "ויקיפדיה",
"book-category.wikiquote": "ויקיציטוט",
"book-category.wikisource": "ויקיטקסט (Wikisource)",
"book-category.wikispecies": "ויקימינים",
"book-category.wikiversity": "ויקיברסיטה",
"book-category.wikivoyage": "ויקימסע",
"book-category.wiktionary": "ויקימילון",
"book-category.other": "אחר"
}

View File

@@ -52,5 +52,15 @@
"welcome-to-kiwix-server": "कीविक्स सर्वर में आपका स्वागत है",
"download-links-heading": "<b><i>{{BOOK_TITLE}}</i></b> के लिए डाउनलोड लिंक",
"download-links-title": "पुस्तक डाउनलोड करें",
"preview-book": "पूर्वावलोकन"
"preview-book": "पूर्वावलोकन",
"book-category.wikibooks": "विकिपुस्तक",
"book-category.wikinews": "विकिसमाचार",
"book-category.wikipedia": "विकिपीडिया",
"book-category.wikiquote": "विकिसूक्ति",
"book-category.wikisource": "विकिस्रोत",
"book-category.wikispecies": "विकिप्रजाति",
"book-category.wikiversity": "विकिविश्वविद्यालय",
"book-category.wikivoyage": "विकियात्रा",
"book-category.wiktionary": "विकिकोश",
"book-category.other": "अन्य"
}

View File

@@ -17,5 +17,15 @@
"home-button-text": "Դեպի '{{BOOK_TITLE}}'֊ի գլխավոր էջը",
"random-page-button-text": "Բացել պատահական էջ",
"searchbox-tooltip": "Որոնել '{{BOOK_TITLE}}'֊ում",
"book-filtering-all-categories": "Բոլոր կատեգորիաներ"
"book-filtering-all-categories": "Բոլոր կատեգորիաներ",
"book-category.wikibooks": "Վիքիգրքեր",
"book-category.wikinews": "Վիքիլուրեր",
"book-category.wikipedia": "Վիքիպեդիա",
"book-category.wikiquote": "Վիքիքաղվածք",
"book-category.wikisource": "Վիքիդարան",
"book-category.wikispecies": "Վիքիցեղեր",
"book-category.wikiversity": "Վիքիլսարան",
"book-category.wikivoyage": "Վիքիճամփորդ",
"book-category.wiktionary": "Վիքիբառարան",
"book-category.other": "Այլ"
}

View File

@@ -36,5 +36,16 @@
"direct-download-link-text": "Direkt",
"torrent-download-link-text": "BitTorrent",
"download-links-title": "Buch eroflueden",
"unknown-error": "Onbekannte Feeler"
"unknown-error": "Onbekannte Feeler",
"book-category.stack_exchange": "Stack Exchange",
"book-category.wikibooks": "Wikibooks",
"book-category.wikinews": "Wikinews",
"book-category.wikipedia": "Wikipedia",
"book-category.wikiquote": "Wikiquote",
"book-category.wikisource": "Wikisource",
"book-category.wikispecies": "Wikispecies",
"book-category.wikiversity": "Wikiversity",
"book-category.wikivoyage": "Wikivoyage",
"book-category.wiktionary": "Wiktionnaire",
"book-category.other": "Anerer"
}

View File

@@ -60,5 +60,23 @@
"download-links-heading": "Врски за преземање на <b><i>{{BOOK_TITLE}}</i></b>",
"download-links-title": "Преземи книга",
"preview-book": "Преглед",
"unknown-error": "Непозната грешка"
"unknown-error": "Непозната грешка",
"book-category.gutenberg": "Гутенберг",
"book-category.iFixit": "iFixit",
"book-category.mooc": "MOOC",
"book-category.phet": "Phet",
"book-category.stack_exchange": "Stack Exchange",
"book-category.ted": "Ted",
"book-category.vikidia": "Викидија",
"book-category.wikibooks": "Викикниги",
"book-category.wikihow": "wikiHow",
"book-category.wikinews": "Викивести",
"book-category.wikipedia": "Википедија",
"book-category.wikiquote": "Викицитат",
"book-category.wikisource": "Викиизвор",
"book-category.wikispecies": "Викивидови",
"book-category.wikiversity": "Викиуниверзитет",
"book-category.wikivoyage": "Википатување",
"book-category.wiktionary": "Викиречник",
"book-category.other": "друго"
}

View File

@@ -3,6 +3,7 @@
"authors": [
"Kelson",
"McDutchie",
"Siebrand",
"Vistaus"
]
},
@@ -24,8 +25,12 @@
"404-page-heading": "Niet gevonden",
"500-page-title": "Interne serverfout",
"500-page-heading": "Interne serverfout",
"500-page-text": "Er is een interne serverfout opgetreden. Dat spijt ons",
"fulltext-search-unavailable": "Zoeken in volledige tekst is niet beschikbaar",
"no-search-results": "De zoekmachine voor volledige tekst is niet beschikbaar voor deze inhoud.",
"search-results-page-title": "Zoeken: {{SEARCH_PATTERN}}",
"search-result-book-info": "uit {{BOOK_TITLE}}",
"word-count": "{{COUNT}} woorden",
"library-button-text": "Naar de welkomstpagina",
"home-button-text": "Naar de hoofdpagina van {{BOOK_TITLE}}",
"random-page-button-text": "Naar een willekeurig geselecteerde pagina gaan",
@@ -53,5 +58,6 @@
"welcome-to-kiwix-server": "Welkom bij de Kiwix-server",
"download-links-heading": "Downloadkoppelingen voor <b><i>{{BOOK_TITLE}}</i></b>",
"download-links-title": "Boek downloaden",
"preview-book": "Voorvertoning"
"preview-book": "Voorvertoning",
"unknown-error": "Onbekende fout"
}

View File

@@ -34,5 +34,6 @@
"torrent-download-link-text": "Plik torrent",
"welcome-to-kiwix-server": "Witamy na serwerze Kiwix",
"download-links-title": "Pobierz książkę",
"preview-book": "Podgląd"
"preview-book": "Podgląd",
"book-category.other": "Inne"
}

View File

@@ -3,7 +3,8 @@
"authors": [
"Eduardoaddad",
"Obiru",
"Re demz"
"Re demz",
"YoReaper"
]
},
"name": "Português",
@@ -31,10 +32,10 @@
"book-filtering-all-languages": "Todos os idiomas",
"count-of-matching-books": "{{COUNT}} livro(s)",
"download": "Baixar",
"hash-download-link-text": "Hash sha256",
"hash-download-alt-text": "baixar hash",
"torrent-download-link-text": "Arquivo torrent",
"torrent-download-alt-text": "baixar torrent",
"hash-download-link-text": "Verificação SHA-256",
"hash-download-alt-text": "Exibir arquivo de verificação SHA-256",
"torrent-download-link-text": "BitTorrent",
"torrent-download-alt-text": "Baixar via BitTorrent",
"welcome-to-kiwix-server": "Bem vindo ao servidor Kiwix",
"download-links-heading": "Links para baixar <b><i>{{BOOK_TITLE}}</i></b>",
"download-links-title": "Download do livro",

73
static/skin/i18n/pt.json Normal file
View File

@@ -0,0 +1,73 @@
{
"@metadata": {
"authors": [
"B3rnas"
]
},
"name": "português",
"suggest-full-text-search": "contendo '{{{SEARCH_TERMS}}}'...",
"no-such-book": "Não existe o livro: {{BOOK_NAME}}",
"too-many-books": "Demasiadas solicitações de livros ({{NB_BOOKS}}) onde o limite é {{LIMIT}}",
"no-book-found": "Nenhum livro corresponde aos critérios de seleção",
"url-not-found": "A URL solicitada \"{{url}}\" não foi encontrada neste servidor.",
"suggest-search": "Faça uma pesquisa de texto completo para <a href=\"{{{SEARCH_URL}}}\">{{PATTERN}}</a>",
"random-article-failure": "Ups! Erro ao eleger um artigo aleatório :(",
"invalid-raw-data-type": "{{DATATYPE}} não é uma solicitação válida para conteúdo bruto.",
"invalid-request": "A URL solicitada \"{{{url}}}\" não é uma solicitação válida.",
"no-value-for-arg": "Nenhum valor fornecido para o argumento {{ARGUMENT}}",
"no-query": "Nenhuma consulta fornecida.",
"raw-entry-not-found": "Não é possível encontrar a entrada {{DATATYPE}} {{ENTRY}}",
"400-page-title": "Solicitação inválida",
"400-page-heading": "Solicitação inválida",
"404-page-title": "Conteúdo não encontrado",
"404-page-heading": "Não encontrado",
"500-page-title": "Erro interno do servidor",
"500-page-heading": "Erro interno do servidor",
"500-page-text": "Ocorreu um erro interno do servidor. Lamentamos por isso :/",
"fulltext-search-unavailable": "Pesquisa de texto completo indisponível",
"no-search-results": "O motor de busca de texto completo não está disponível para este conteúdo.",
"search-results-page-title": "Pesquisar: {{SEARCH_PATTERN}}",
"search-results-page-header": "Resultados <b>{{START}}-{{END}}</b> de <b>{{COUNT}}</b> para <b>\"{{{SEARCH_PATTERN}}}\"</b>",
"empty-search-results-page-header": "Nenhum resultado foi encontrado para <b>\"{{{SEARCH_PATTERN}}}\"</b>",
"search-result-book-info": "de {{BOOK_TITLE}}",
"word-count": "{{COUNT}} palavras",
"library-button-text": "Ir para página inicial",
"home-button-text": "Vá para a página principal de '{{BOOK_TITLE}}'",
"random-page-button-text": "Vá para uma página selecionada aleatoriamente",
"searchbox-tooltip": "Procurar '{{BOOK_TITLE}}'",
"confusion-of-tongues": "Dois ou mais livros em idiomas diferentes participariam da pesquisa, o que poderia levar a resultados confusos.",
"welcome-page-overzealous-filter": "Nenhum resultado. Gostaria de <a href=\"{{URL}}\">redefinir o filtro</a> ?",
"powered-by-kiwix-html": "Desenvolvido por <a href=\"https://kiwix.org\">Kiwix</a>",
"search": "Pesquisar",
"book-filtering-all-categories": "Todas as categorias",
"book-filtering-all-languages": "Todos os idiomas",
"count-of-matching-books": "{{COUNT}} livro(s)",
"download": "Transferir",
"direct-download-link-text": "Direto",
"direct-download-alt-text": "Descarregar diretamente através de HTTP (S)",
"hash-download-link-text": "Verificação SHA-256",
"hash-download-alt-text": "Exibir arquivo de verificação SHA-256",
"magnet-link-text": "Link magnético",
"magnet-alt-text": "Descarregar através do link Magnet",
"torrent-download-link-text": "BitTorrent",
"torrent-download-alt-text": "Descarregar através de BitTorrent",
"library-opds-feed-all-entries": "Feed OPDS da biblioteca - Todas as entradas",
"filter-by-tag": "Filtrar por tag \"{{TAG}}\"",
"stop-filtering-by-tag": "Pare de filtrar pela tag \"{{TAG}}\"",
"library-opds-feed-parameterised": "Feed OPDS da biblioteca - entradas que correspondem a {{#LANG}}\nIdioma: {{LANG}} {{/LANG}}{{#CATEGORY}}\nCategoria: {{CATEGORY}} {{/CATEGORY}}{{#TAG}}\nTag: {{TAG}} {{/TAG}}{{#Q}}\nConsulta: {{Q}} {{/Q}}",
"welcome-to-kiwix-server": "Bem-vindo ao Kiwix Server",
"download-links-heading": "Links para download de <b><i>{{BOOK_TITLE}}</i></b>",
"download-links-title": "Descarregar livros",
"preview-book": "Pré-visualização",
"unknown-error": "Erro desconhecido",
"book-category.wikibooks": "Wikilivros",
"book-category.wikinews": "Wikinotícias",
"book-category.wikipedia": "Wikipédia",
"book-category.wikiquote": "Wikiquote",
"book-category.wikisource": "Wikisource",
"book-category.wikispecies": "Wikispecies",
"book-category.wikiversity": "Wikiversidade",
"book-category.wikivoyage": "Wikivoyage",
"book-category.wiktionary": "Wikcionário",
"book-category.other": "Outro"
}

View File

@@ -62,5 +62,23 @@
"download-links-title": "Title for no-js download page",
"preview-book": "Tooltip of book-tile leading to the book",
"non-translated-text": "{{ignored}}\nUsed to display text that is generated at runtime and cannot be translated. Nothing to translate about this one.",
"unknown-error": "Unknown error"
"unknown-error": "Unknown error",
"book-category.gutenberg": "Name for the category of books from the Gutenberg project",
"book-category.iFixit": "Name for the category of iFixit books",
"book-category.mooc": "Name for the category of MOOC books",
"book-category.phet": "Name for the category of Phet books",
"book-category.stack_exchange": "Name for the category of books from the Stack Exchange network books",
"book-category.ted": "Name for the category of Ted books",
"book-category.vikidia": "Name for the category of Vikidia books",
"book-category.wikibooks": "Name for the category of Wikibooks books books",
"book-category.wikihow": "Name for the category of wikiHow books",
"book-category.wikinews": "Name for the category of Wikinews books",
"book-category.wikipedia": "Name for the category of Wikipedia books",
"book-category.wikiquote": "Name for the category of Wikiquote books",
"book-category.wikisource": "Name for the category of Wikisource books",
"book-category.wikispecies": "Name for the category of Wikispecies books",
"book-category.wikiversity": "Name for the category of Wikiversity books",
"book-category.wikivoyage": "Name for the category of Wikivoyage books",
"book-category.wiktionary": "Name for the category of Wiktionary books",
"book-category.other": "Books not belonging to any special category are listed under this one"
}

View File

@@ -22,5 +22,16 @@
"torrent-download-link-text": "ٹورنٹ فائل",
"torrent-download-alt-text": "ٹورںٹ ݙاؤن لوڈ کرو",
"download-links-title": "کتاب ڈاؤن لوڈ کرو",
"preview-book": "پیشگی ݙکھالا"
"preview-book": "پیشگی ݙکھالا",
"book-category.vikidia": "وکی ڈیا",
"book-category.wikibooks": "وکی کتاباں",
"book-category.wikinews": "وکی خبراں",
"book-category.wikipedia": "وکیپیڈیا",
"book-category.wikiquote": "وکی ٻول",
"book-category.wikisource": "وکی ماخذ",
"book-category.wikispecies": "وکی سپیشیز",
"book-category.wikiversity": "وکی ورسٹی",
"book-category.wikivoyage": "وکی سیرسپاٹا",
"book-category.wiktionary": "وکشنری",
"book-category.other": "ٻیا"
}

View File

@@ -47,4 +47,22 @@
, "empty-search-results-page-header": "[I18N TESTING] No results were found for <b>\"{{{SEARCH_PATTERN}}}\"</b>"
, "search-result-book-info": "from [I18N TESTING] {{BOOK_TITLE}}"
, "word-count": "{{COUNT}} [I18N TESTING] words"
, "book-category.gutenberg": "[I18N] Gutenberg [TESTING]"
, "book-category.iFixit": "[I18N] iFixit [TESTING]"
, "book-category.mooc": "[I18N] MOOC [TESTING]"
, "book-category.phet": "[I18N] Phet [TESTING]"
, "book-category.stack_exchange": "[I18N] Stack Exchange [TESTING]"
, "book-category.ted": "[I18N] Ted [TESTING]"
, "book-category.vikidia": "[I18N] Vikidia [TESTING]"
, "book-category.wikibooks": "[I18N] Wikibooks [TESTING]"
, "book-category.wikihow": "[I18N] wikiHow [TESTING]"
, "book-category.wikinews": "[I18N] Wikinews [TESTING]"
, "book-category.wikipedia": "[I18N] Wikipedia [TESTING]"
, "book-category.wikiquote": "[I18N] Wikiquote [TESTING]"
, "book-category.wikisource": "[I18N] Wikisource [TESTING]"
, "book-category.wikispecies": "[I18N] Wikispecies [TESTING]"
, "book-category.wikiversity": "[I18N] Wikiversity [TESTING]"
, "book-category.wikivoyage": "[I18N] Wikivoyage [TESTING]"
, "book-category.wiktionary": "[I18N] Wiktionary [TESTING]"
, "book-category.other": "[I18N] Other [TESTING]"
}

View File

@@ -65,5 +65,15 @@
"download-links-heading": "下载<b><i>{{BOOK_TITLE}}</i></b>的链接",
"download-links-title": "下载书籍",
"preview-book": "预览",
"unknown-error": "未知错误"
"unknown-error": "未知错误",
"book-category.wikibooks": "维基教科书",
"book-category.wikinews": "维基新闻",
"book-category.wikipedia": "维基百科",
"book-category.wikiquote": "维基语录",
"book-category.wikisource": "维基文库",
"book-category.wikispecies": "维基物种",
"book-category.wikiversity": "维基学院",
"book-category.wikivoyage": "维基导游",
"book-category.wiktionary": "维基词典",
"book-category.other": "其他"
}

View File

@@ -46,13 +46,13 @@
"count-of-matching-books": "{{COUNT}} 本書籍",
"download": "下載",
"direct-download-link-text": "直接",
"direct-download-alt-text": "直接下載",
"hash-download-link-text": "Sha256 雜湊",
"hash-download-alt-text": "下載雜湊",
"direct-download-alt-text": "直接透過 HTTPS下載",
"hash-download-link-text": "SHA-256 核對和",
"hash-download-alt-text": "顯示 SHA-256 檔案核對和",
"magnet-link-text": "Magnet 連結",
"magnet-alt-text": "下載 magnet",
"torrent-download-link-text": "Torrent 檔案",
"torrent-download-alt-text": "下載 torrent",
"magnet-alt-text": "透過磁力連結下載",
"torrent-download-link-text": "BitTorrent",
"torrent-download-alt-text": "透過 BitTorrent 下載",
"library-opds-feed-all-entries": "圖書館 OPDS 摘要 - 所有項目",
"filter-by-tag": "依標籤「{{TAG}}」篩選",
"stop-filtering-by-tag": "停止依標籤「{{TAG}}」篩選",
@@ -61,5 +61,23 @@
"download-links-heading": "下載<b><i>{{BOOK_TITLE}}</i></b>的連結",
"download-links-title": "下載書籍",
"preview-book": "預覽",
"unknown-error": "不明錯誤"
"unknown-error": "不明錯誤",
"book-category.gutenberg": "古騰堡計劃",
"book-category.iFixit": "iFixit",
"book-category.mooc": "MOOC",
"book-category.phet": "PhET",
"book-category.stack_exchange": "Stack Exchange",
"book-category.ted": "Ted",
"book-category.vikidia": "Vikidia",
"book-category.wikibooks": "維基教科書",
"book-category.wikihow": "wikiHow",
"book-category.wikinews": "維基新聞",
"book-category.wikipedia": "維基百科",
"book-category.wikiquote": "維基語錄",
"book-category.wikisource": "維基文庫",
"book-category.wikispecies": "維基物種",
"book-category.wikiversity": "維基學院",
"book-category.wikivoyage": "維基導遊",
"book-category.wiktionary": "維基詞典",
"book-category.other": "其他"
}

View File

@@ -121,7 +121,7 @@
.tagFilterLabel {
width: max-content;
padding: 10px;
padding: 7px;
font-family: roboto;
font-size: 12px;
margin: 0 0 0 17px;
@@ -152,23 +152,24 @@
.book__link {
text-decoration: none;
grid-column: 1 / 3;
grid-row: 1 / 3;
}
.book__wrapper {
--tile-width: 250px;
--tile-border-width: 1px;
--tile-border-radius: 3px;
color: #444343;
height: 248px;
width: 250px;
height: 258px;
width: var(--tile-width);
margin: 15px;
background-color: #f7f7f7;
border: 1px solid #ececec;
border-radius: 3px;
display: grid;
grid-template-columns: 65px 1fr;
grid-template-rows: 70px 120px 1fr 1fr;
grid-gap: 5px;
border: var(--tile-border-width) solid #ececec;
border-radius: var(--tile-border-radius);
display: flex;
flex-direction: column;
justify-content: space-between;
transition: transform 0.25s;
gap: 4px;
}
.book__wrapper:hover {
@@ -177,8 +178,12 @@
.book__link__wrapper {
display: grid;
grid-template-areas:
"bookIcon bookHeader"
"bookDesc bookDesc"
;
grid-template-columns: 65px 1fr;
grid-template-rows: 70px 120px 1fr 1fr;
grid-template-rows: 70px 120px;
}
.book__icon {
@@ -189,7 +194,10 @@
align-items: center;
align-content: center;
justify-content: center;
width: 48px;
height: 48px;
margin: 10px 0 0 10px;
grid-area: bookIcon;
}
.book__header {
@@ -201,6 +209,7 @@
height: 100%;
align-items: center;
align-content: center;
grid-area: bookHeader;
}
.book__title {
@@ -211,28 +220,36 @@
}
.book__download {
font-size: 1.1rem;
margin: 3px 0;
cursor: pointer;
font-size: 1.2rem;
background: #00b4e4;
padding: 8px;
border-radius: 0 0 var(--tile-border-radius) var(--tile-border-radius);
display: flex;
align-items: center;
justify-content: center;
position: relative;
width: var(--tile-width);
left: calc(0px - var(--tile-border-width));
text-decoration: none;
}
.book__download > img {
height: 16px;
width: 16px;
}
.book__download > span {
cursor: pointer;
text-decoration: none;
position: relative;
padding: 0 3px 1px;
font-family: roboto;
background: #00b4e4;
color: white;
box-shadow: 0 0 0 0;
border-radius: 2px;
}
.book__download > span:hover {
box-shadow: 0 5px 5px rgba(0, 0, 0, 0.1)
.book__download:hover {
background: royalblue;
}
.book__description {
grid-column: 1 / 3;
margin: 10px 10px 5px;
overflow: hidden;
display: -webkit-box;
@@ -243,6 +260,13 @@
font-size: 1.2rem;
color: #4d4d4d;
line-height: 1.25;
grid-area: bookDesc;
}
.book__meta {
display: flex;
justify-content: space-between;
padding: 0 10px 4px;
}
.book__languageTag {
@@ -255,7 +279,6 @@
color: black;
height: 25px;
width: 25px;
margin: 10px auto 0 10px;
border-radius: 5px;
font-size: 0.85rem;
}
@@ -266,8 +289,6 @@
font-size: 1.1rem;
justify-content: flex-end;
font-family: roboto;
margin-right: 10px;
margin-top: 10px;
overflow: hidden;
}

View File

@@ -105,6 +105,14 @@
return '';
}
// Borrowed from https://stackoverflow.com/a/1912522
function htmlDecode(input){
var e = document.createElement('textarea');
e.innerHTML = input;
// handle case of empty input
return e.childNodes.length === 0 ? "" : e.childNodes[0].nodeValue;
}
function htmlEncode(str) {
return str.replace(/[\u00A0-\u9999<>\&]/gim, (i) => `&#${i.charCodeAt(0)};`);
}
@@ -121,9 +129,27 @@
function generateTagLink(tagValue) {
tagValue = tagValue.toLowerCase();
const humanFriendlyTagValue = humanFriendlyTitle(tagValue);
const tagMessage = $t("filter-by-tag", {TAG: humanFriendlyTagValue});
return `<span class='tag__link' aria-label='${tagMessage}' title='${tagMessage}' data-tag=${tagValue}>${humanFriendlyTagValue}</span>`
const tagMessage = $t("filter-by-tag", {TAG: tagValue});
const spanElement = document.createElement("span");
spanElement.className = 'tag__link';
spanElement.setAttribute('aria-label', tagMessage);
spanElement.setAttribute('title', tagMessage);
spanElement.setAttribute('data-tag', tagValue);
spanElement.innerHTML = htmlEncode(tagValue);
return spanElement.outerHTML;
}
function downloadButtonText(zimSize) {
return $t("download") + (zimSize ? ` - ${zimSize}`: '');
}
function downloadButtonHtml(url, zimSize) {
return url
? `<div class="book__download" data-link="${url}" data-size="${zimSize}">
<img src="${root}/skin/download-white.svg?KIWIXCACHEID">
<span ">${downloadButtonText(zimSize)}</span>
</div>`
: '';
}
function generateBookHtml(book, sort = false) {
@@ -144,7 +170,7 @@
const mulLangList = langCodesList.filter(x => languages.hasOwnProperty(x)).map(x => languages[x]);
language = mulLangList.join(', ');
}
const tags = getInnerHtml(book, 'tags');
const tags = htmlDecode(getInnerHtml(book, 'tags'));
const tagList = tags.split(';').filter(tag => {return !(tag.startsWith('_'))});
const tagFilterLinks = tagList.map((tagValue) => generateTagLink(tagValue));
const tagHtml = tagFilterLinks.join(' | ');
@@ -170,6 +196,7 @@
}
const faviconAttr = iconUrl != undefined ? `style="background-image: url('${iconUrl}')"` : '';
const languageAttr = langCode != '' ? `title="${language}" aria-label="${language}"` : 'style="background-color: transparent"';
divTag.innerHTML = `
<div class="book__wrapper">
<a class="book__link" href="${viewerLink}" data-hover="Preview">
@@ -177,13 +204,15 @@
<div class="book__icon" ${faviconAttr}></div>
<div class="book__header">
<div id="book__title">${title}</div>
${downloadLink ? `<div class="book__download"><span data-link="${downloadLink}">${$t("download")} ${humanFriendlyZimSize ? ` - ${humanFriendlyZimSize}</span></div>`: ''}` : ''}
</div>
<div class="book__description" title="${description}">${description}</div>
</div>
</a>
<div class="book__meta">
<div class="book__languageTag" ${languageAttr}>${getLanguageCodeToDisplay(langCode)}</div>
<div class="book__tags"><div class="book__tags--wrapper">${tagHtml}</div></div>
</div>
${downloadButtonHtml(downloadLink, humanFriendlyZimSize)}
</div></div>`;
return divTag;
}
@@ -269,6 +298,7 @@
}
function insertModal(button) {
const downloadSize = button.getAttribute('data-size');
const downloadLink = button.getAttribute('data-link');
button.addEventListener('click', async (event) => {
event.preventDefault();
@@ -278,7 +308,7 @@
<div class="modal-heading">
<div class="modal-title">
<div>
Download
${downloadButtonText(downloadSize)}
</div>
</div>
<div onclick="closeModal()" class="modal-close-button">
@@ -438,7 +468,7 @@
booksToDelete.forEach(book => {iso.remove(book);});
books.forEach((book) => {
iso.insert(generateBookHtml(book, sort))
const downloadButton = document.querySelector(`[data-id="${getInnerHtml(book, 'id')}"] .book__download span`);
const downloadButton = document.querySelector(`[data-id="${getInnerHtml(book, 'id')}"] .book__download`);
if (downloadButton) {
insertModal(downloadButton);
}
@@ -492,7 +522,7 @@
function addTagElement(tagValue, resetFilter) {
const tagElement = document.getElementsByClassName('tagFilterLabel')[0];
tagElement.style.display = 'inline-block';
tagElement.innerHTML = `${tagValue}`;
tagElement.innerHTML = htmlEncode(tagValue);
const tagMessage = $t("stop-filtering-by-tag", {TAG: tagValue});
tagElement.setAttribute('aria-label', tagMessage);
tagElement.setAttribute('title', tagMessage);

View File

@@ -310,6 +310,12 @@ function blockLink(url) {
: url;
}
function urlMustBeHandledByAnExternalApp(url) {
const WHITELISTED_URL_SCHEMATA = ['http:', 'https:', 'about:', 'javascript:'];
return WHITELISTED_URL_SCHEMATA.indexOf(url.protocol) == -1;
}
function isExternalUrl(url) {
if ( url.startsWith(window.location.origin) )
return false;
@@ -329,20 +335,34 @@ function getRealHref(target) {
return target_href;
}
function setHrefAvoidingWombatRewriting(target, url) {
const old_no_rewrite = target._no_rewrite;
target._no_rewrite = true;
target.setAttribute("href", url);
target._no_rewrite = old_no_rewrite;
}
function onClickEvent(e) {
const iframeDocument = contentIframe.contentDocument;
const target = matchingAncestorElement(e.target, iframeDocument, "a");
if (target !== null && "href" in target) {
const target_href = getRealHref(target);
if (isExternalUrl(target_href)) {
const target_url = new URL(target_href, iframeDocument.location);
const isExternalAppUrl = urlMustBeHandledByAnExternalApp(target_url);
if ( isExternalAppUrl && !viewerSettings.linkBlockingEnabled ) {
target.setAttribute("target", "_blank");
}
if (isExternalAppUrl || isExternalUrl(target_href)) {
const possiblyBlockedLink = blockLink(target_href);
if ( e.ctrlKey || e.shiftKey ) {
// The link will be loaded in a new tab/window - update the link
// and let the browser handle the rest.
target.setAttribute("href", possiblyBlockedLink);
setHrefAvoidingWombatRewriting(target, possiblyBlockedLink);
} else {
// Load the external URL in the viewer window (rather than iframe)
contentIframe.contentWindow.parent.location = possiblyBlockedLink;
e.preventDefault();
}
}
}

View File

@@ -50,20 +50,11 @@
pointer-events: none;
}
.book__link__wrapper {
grid-column: 1 / 3;
grid-row: 1 / 3;
}
.book__link {
grid-row: 2 / 3;
}
.kiwixHomeBody__results {
flex-basis: 100%;
}
#book__title>a, .book__download a {
#book__title>a {
text-decoration: none;
all: unset;
}
@@ -117,25 +108,32 @@
<h3 class="kiwixHomeBody__results">{{translations.count-of-matching-books}}</h3>
{{#books}}
<div class="book__wrapper">
<div class="book__link__wrapper">
<div class="book__icon" {{faviconAttr}}></div>
<div class="book__header">
<div id="book__title"><a href="{{root}}/content/{{id}}">{{title}}</a></div>
{{#downloadAvailable}}
<div class="book__download"><span><a href="{{root}}/nojs/download/{{id}}">{{translations.download}}</a></span></div>
{{/downloadAvailable}}
</div>
<a class="book__link" href="{{root}}/content/{{id}}" title="{{translations.preview-book}}" aria-label="{{translations.preview-book}}">
<div class="book__description" title="{{description}}">{{description}}</div>
</a>
</div>
<div class="book__languageTag" {{languageAttr}}>{{langCode}}</div>
<div class="book__tags"><div class="book__tags--wrapper">
{{#tagList}}
<span class="tag__link" aria-label='{{tag}}' title='{{tag}}'>{{tag}}</span>
{{/tagList}}
<a class="book__link" href="{{root}}/content/{{id}}" title="{{translations.preview-book}}" aria-label="{{translations.preview-book}}">
<div class="book__link__wrapper">
<div class="book__icon" {{faviconAttr}}></div>
<div class="book__header">
<div id="book__title">{{title}}</div>
</div>
</div>
<div class="book__description" title="{{description}}">{{description}}</div>
</div>
</a>
<div class="book__meta">
<div class="book__languageTag" {{languageAttr}}>{{langCode}}</div>
<div class="book__tags"><div class="book__tags--wrapper">
{{#tagList}}
<span class="tag__link" aria-label='{{tag}}' title='{{tag}}'>{{tag}}</span>
{{/tagList}}
</div>
</div>
</div>
{{#downloadAvailable}}
<div>
<a class="book__download" href="{{root}}/nojs/download/{{id}}">
<img src="{{root}}/skin/download-white.svg?KIWIXCACHEID">
<span>{{translations.download}}</span>
</a>
</div>
{{/downloadAvailable}}
</div>
{{/books}}
</div>

View File

@@ -15,11 +15,19 @@ struct XMLDoc : pugi::xml_document
} // unnamed namespace
#if _WIN32
# define DATA_ABS_PATH "C:\\data\\zim"
# define ZARA_ABS_PATH "C:\\data\\zim\\zara.zim"
#else
# define DATA_ABS_PATH "/data/zim"
# define ZARA_ABS_PATH "/data/zim/zara.zim"
#endif
TEST(BookTest, updateFromXMLTest)
{
const XMLDoc xml(R"(
<book id="zara"
path="./zara.zim"
path="zara.zim"
url="https://who.org/zara.zim"
title="Catch an infection in 24 hours"
description="Complete guide to contagious diseases"
@@ -40,9 +48,9 @@ TEST(BookTest, updateFromXMLTest)
)");
kiwix::Book book;
book.updateFromXml(xml.child("book"), "/data/zim");
book.updateFromXml(xml.child("book"), DATA_ABS_PATH);
EXPECT_EQ(book.getPath(), "/data/zim/zara.zim");
EXPECT_EQ(book.getPath(), ZARA_ABS_PATH);
EXPECT_EQ(book.getUrl(), "https://who.org/zara.zim");
EXPECT_EQ(book.getTitle(), "Catch an infection in 24 hours");
EXPECT_EQ(book.getDescription(), "Complete guide to contagious diseases");

View File

@@ -1,4 +1,4 @@
#include "../src/server/i18n.h"
#include "../src/server/i18n_utils.h"
#include "gtest/gtest.h"
using namespace kiwix;
@@ -48,3 +48,17 @@ TEST(ParameterizedMessage, messagesWithParameters)
EXPECT_EQ(msg.getText("test"), "Filter [I18N] by [TESTING] tag \"\"");
}
}
TEST(I18n, translateBookCategory)
{
EXPECT_EQ(translateBookCategory("en", "ted"), "Ted");
EXPECT_EQ(translateBookCategory("test", "ted"), "[I18N] Ted [TESTING]");
EXPECT_EQ(translateBookCategory("en", "stack_exchange"), "Stack Exchange");
EXPECT_EQ(translateBookCategory("test", "stack_exchange"), "[I18N] Stack Exchange [TESTING]");
// unknown categories are simply not translated
EXPECT_EQ(translateBookCategory("en", "Qwerty"), "Qwerty");
EXPECT_EQ(translateBookCategory("test", "Qwerty"), "Qwerty");
}

View File

@@ -267,11 +267,21 @@ const char * sampleOpdsStream = R"(
)";
#ifdef _WIN32
# define ZIMFILE_PATH ".\\zimfile.zim"
# define EXAMPLE_PATH ".\\example.zim"
# define LIBRARY_PATH ".\\test\\library.xml"
#else
# define ZIMFILE_PATH "./zimfile.zim"
# define EXAMPLE_PATH "./example.zim"
# define LIBRARY_PATH "./test/library.xml"
#endif
const char sampleLibraryXML[] = R"(
<library version="1.0">
<book
id="raycharles"
path="./zimfile.zim"
path=")" ZIMFILE_PATH R"("
url="https://github.com/kiwix/libkiwix/raw/master/test/data/zimfile.zim"
title="Ray Charles"
description="Wikipedia articles about Ray Charles"
@@ -287,7 +297,7 @@ const char sampleLibraryXML[] = R"(
></book>
<book
id="example"
path="./example.zim"
path=")" EXAMPLE_PATH R"("
title="An example ZIM archive"
description="An eXaMpLe book added to the catalog via XML"
language="deu"
@@ -383,7 +393,7 @@ class LibraryTest : public ::testing::Test {
void SetUp() override {
kiwix::Manager manager(lib);
manager.readOpds(sampleOpdsStream, "foo.urlHost");
manager.readXml(sampleLibraryXML, false, "./test/library.xml", true);
manager.readXml(sampleLibraryXML, false, LIBRARY_PATH, true);
}
kiwix::Bookmark createBookmark(const std::string &id, const std::string& url="", const std::string& title="") {

View File

@@ -1033,7 +1033,7 @@ TEST_F(LibraryServerTest, no_name_mapper_catalog_v2_individual_entry_access)
" />\n" \
" <link\n" \
" type=\"text/css\"\n" \
" href=\"/ROOT%23%3F/skin/index.css?cacheid=1e78e7cf\"\n" \
" href=\"/ROOT%23%3F/skin/index.css?cacheid=ae79e41a\"\n" \
" rel=\"Stylesheet\"\n" \
" />\n" \
" <link rel=\"apple-touch-icon\" sizes=\"180x180\" href=\"/ROOT%23%3F/skin/favicon/apple-touch-icon.png?cacheid=f86f8df3\">\n" \
@@ -1066,17 +1066,10 @@ TEST_F(LibraryServerTest, no_name_mapper_catalog_v2_individual_entry_access)
" .tag__link {\n" \
" pointer-events: none;\n" \
" }\n\n" \
" .book__link__wrapper {\n" \
" grid-column: 1 / 3;\n" \
" grid-row: 1 / 3;\n" \
" }\n\n" \
" .book__link {\n" \
" grid-row: 2 / 3;\n" \
" }\n\n" \
" .kiwixHomeBody__results {\n" \
" flex-basis: 100%;\n" \
" }\n\n" \
" #book__title>a, .book__download a {\n" \
" #book__title>a {\n" \
" text-decoration: none;\n" \
" all: unset;\n" \
" }\n" \
@@ -1087,62 +1080,83 @@ TEST_F(LibraryServerTest, no_name_mapper_catalog_v2_individual_entry_access)
#define CHARLES_RAY_BOOK_HTML \
" <div class=\"book__wrapper\">\n" \
" <div class=\"book__link__wrapper\">\n" \
" <div class=\"book__icon\" style=background-image:url(/ROOT%23%3F/catalog/v2/illustration/charlesray/?size=48)></div>\n" \
" <div class=\"book__header\">\n" \
" <div id=\"book__title\"><a href=\"/ROOT%23%3F/content/zimfile%26other\">Charles, Ray</a></div>\n" \
" <div class=\"book__download\"><span><a href=\"/ROOT%23%3F/nojs/download/zimfile%26other\">Download</a></span></div>\n" \
" </div>\n" \
" <a class=\"book__link\" href=\"/ROOT%23%3F/content/zimfile%26other\" title=\"Preview\" aria-label=\"Preview\">\n" \
" <div class=\"book__description\" title=\"Wikipedia articles about Ray Charles\">Wikipedia articles about Ray Charles</div>\n" \
" </a>\n" \
" </div>\n" \
" <div class=\"book__languageTag\" >fra</div>\n" \
" <div class=\"book__tags\"><div class=\"book__tags--wrapper\">\n" \
" <span class=\"tag__link\" aria-label='unittest' title='unittest'>unittest</span>\n" \
" <span class=\"tag__link\" aria-label='wikipedia' title='wikipedia'>wikipedia</span>\n" \
" <a class=\"book__link\" href=\"/ROOT%23%3F/content/zimfile%26other\" title=\"Preview\" aria-label=\"Preview\">\n" \
" <div class=\"book__link__wrapper\">\n" \
" <div class=\"book__icon\" style=background-image:url(/ROOT%23%3F/catalog/v2/illustration/charlesray/?size=48)></div>\n" \
" <div class=\"book__header\">\n" \
" <div id=\"book__title\">Charles, Ray</div>\n" \
" </div>\n" \
" <div class=\"book__description\" title=\"Wikipedia articles about Ray Charles\">Wikipedia articles about Ray Charles</div>\n" \
" </div>\n" \
" </a>\n" \
" <div class=\"book__meta\">\n" \
" <div class=\"book__languageTag\" >fra</div>\n" \
" <div class=\"book__tags\"><div class=\"book__tags--wrapper\">\n" \
" <span class=\"tag__link\" aria-label='unittest' title='unittest'>unittest</span>\n" \
" <span class=\"tag__link\" aria-label='wikipedia' title='wikipedia'>wikipedia</span>\n" \
" </div>\n" \
" </div>\n" \
" </div>\n" \
" <div>\n" \
" <a class=\"book__download\" href=\"/ROOT%23%3F/nojs/download/zimfile%26other\">\n" \
" <img src=\"/ROOT%23%3F/skin/download-white.svg?cacheid=079ab989\">\n" \
" <span>Download</span>\n" \
" </a>\n" \
" </div>\n" \
" </div>\n" \
" </div>\n"
#define RAY_CHARLES_BOOK_HTML \
" <div class=\"book__wrapper\">\n" \
" <div class=\"book__link__wrapper\">\n" \
" <div class=\"book__icon\" style=background-image:url(/ROOT%23%3F/catalog/v2/illustration/raycharles/?size=48)></div>\n" \
" <div class=\"book__header\">\n" \
" <div id=\"book__title\"><a href=\"/ROOT%23%3F/content/zimfile_raycharles\">Ray Charles</a></div>\n" \
" <div class=\"book__download\"><span><a href=\"/ROOT%23%3F/nojs/download/zimfile_raycharles\">Download</a></span></div>\n" \
" </div>\n" \
" <a class=\"book__link\" href=\"/ROOT%23%3F/content/zimfile_raycharles\" title=\"Preview\" aria-label=\"Preview\">\n" \
" <div class=\"book__description\" title=\"Wikipedia articles about Ray Charles\">Wikipedia articles about Ray Charles</div>\n" \
" </a>\n" \
" </div>\n" \
" <div class=\"book__languageTag\" >eng</div>\n" \
" <div class=\"book__tags\"><div class=\"book__tags--wrapper\">\n" \
" <span class=\"tag__link\" aria-label='public_tag_without_a_value' title='public_tag_without_a_value'>public_tag_without_a_value</span>\n" \
" <span class=\"tag__link\" aria-label='wikipedia' title='wikipedia'>wikipedia</span>\n" \
" <a class=\"book__link\" href=\"/ROOT%23%3F/content/zimfile_raycharles\" title=\"Preview\" aria-label=\"Preview\">\n" \
" <div class=\"book__link__wrapper\">\n" \
" <div class=\"book__icon\" style=background-image:url(/ROOT%23%3F/catalog/v2/illustration/raycharles/?size=48)></div>\n" \
" <div class=\"book__header\">\n" \
" <div id=\"book__title\">Ray Charles</div>\n" \
" </div>\n" \
" <div class=\"book__description\" title=\"Wikipedia articles about Ray Charles\">Wikipedia articles about Ray Charles</div>\n" \
" </div>\n" \
" </a>\n" \
" <div class=\"book__meta\">\n" \
" <div class=\"book__languageTag\" >eng</div>\n" \
" <div class=\"book__tags\"><div class=\"book__tags--wrapper\">\n" \
" <span class=\"tag__link\" aria-label='public_tag_without_a_value' title='public_tag_without_a_value'>public_tag_without_a_value</span>\n" \
" <span class=\"tag__link\" aria-label='wikipedia' title='wikipedia'>wikipedia</span>\n" \
" </div>\n" \
" </div>\n" \
" </div>\n" \
" <div>\n" \
" <a class=\"book__download\" href=\"/ROOT%23%3F/nojs/download/zimfile_raycharles\">\n" \
" <img src=\"/ROOT%23%3F/skin/download-white.svg?cacheid=079ab989\">\n" \
" <span>Download</span>\n" \
" </a>\n" \
" </div>\n" \
" </div>\n" \
" </div>\n"
#define RAY_CHARLES_UNCTZ_BOOK_HTML \
" <div class=\"book__wrapper\">\n" \
" <div class=\"book__link__wrapper\">\n" \
" <div class=\"book__icon\" style=background-image:url(/ROOT%23%3F/catalog/v2/illustration/raycharles_uncategorized/?size=48)></div>\n" \
" <div class=\"book__header\">\n" \
" <div id=\"book__title\"><a href=\"/ROOT%23%3F/content/zimfile_raycharles_uncategorized\">Ray (uncategorized) Charles</a></div>\n" \
" <div class=\"book__download\"><span><a href=\"/ROOT%23%3F/nojs/download/zimfile_raycharles_uncategorized\">Download</a></span></div>\n" \
" </div>\n" \
" <a class=\"book__link\" href=\"/ROOT%23%3F/content/zimfile_raycharles_uncategorized\" title=\"Preview\" aria-label=\"Preview\">\n" \
" <div class=\"book__description\" title=\"No category is assigned to this library entry.\">No category is assigned to this library entry.</div>\n" \
" </a>\n" \
" </div>\n" \
" <div class=\"book__languageTag\" >rus,eng</div>\n" \
" <div class=\"book__tags\"><div class=\"book__tags--wrapper\">\n" \
" <span class=\"tag__link\" aria-label='public_tag_with_a_value:value_of_a_public_tag' title='public_tag_with_a_value:value_of_a_public_tag'>public_tag_with_a_value:value_of_a_public_tag</span>\n" \
" <span class=\"tag__link\" aria-label='wikipedia' title='wikipedia'>wikipedia</span>\n" \
" <a class=\"book__link\" href=\"/ROOT%23%3F/content/zimfile_raycharles_uncategorized\" title=\"Preview\" aria-label=\"Preview\">\n" \
" <div class=\"book__link__wrapper\">\n" \
" <div class=\"book__icon\" style=background-image:url(/ROOT%23%3F/catalog/v2/illustration/raycharles_uncategorized/?size=48)></div>\n" \
" <div class=\"book__header\">\n" \
" <div id=\"book__title\">Ray (uncategorized) Charles</div>\n" \
" </div>\n" \
" <div class=\"book__description\" title=\"No category is assigned to this library entry.\">No category is assigned to this library entry.</div>\n" \
" </div>\n" \
" </a>\n" \
" <div class=\"book__meta\">\n" \
" <div class=\"book__languageTag\" >rus,eng</div>\n" \
" <div class=\"book__tags\"><div class=\"book__tags--wrapper\">\n" \
" <span class=\"tag__link\" aria-label='public_tag_with_a_value:value_of_a_public_tag' title='public_tag_with_a_value:value_of_a_public_tag'>public_tag_with_a_value:value_of_a_public_tag</span>\n" \
" <span class=\"tag__link\" aria-label='wikipedia' title='wikipedia'>wikipedia</span>\n" \
" </div>\n" \
" </div>\n" \
" </div>\n" \
" <div>\n" \
" <a class=\"book__download\" href=\"/ROOT%23%3F/nojs/download/zimfile_raycharles_uncategorized\">\n" \
" <img src=\"/ROOT%23%3F/skin/download-white.svg?cacheid=079ab989\">\n" \
" <span>Download</span>\n" \
" </a>\n" \
" </div>\n" \
" </div>\n" \
" </div>\n"
#define FINAL_HTML_TEXT \

View File

@@ -25,11 +25,23 @@ TEST(ManagerTest, addBookFromPathAndGetIdTest)
EXPECT_EQ(book.getUrl(), url);
}
#if _WIN32
# define UNITTEST_ZIM_PATH "zimfiles\\unittest.zim"
# define LIB_ABS_PATH "C:\\data\\lib.xml"
# define ZIM_ABS_PATH "C:\\data\\zimfiles\\unittest.zim"
#else
# define UNITTEST_ZIM_PATH "zimfiles/unittest.zim"
# define LIB_ABS_PATH "/data/lib.xml"
# define ZIM_ABS_PATH "/data/zimfiles/unittest.zim"
#endif
const char sampleLibraryXML[] = R"(
<library version="1.0">
<book
id="0d0bcd57-d3f6-cb22-44cc-a723ccb4e1b2"
path="zimfiles/unittest.zim"
path=")" UNITTEST_ZIM_PATH R"("
url="https://example.com/zimfiles/unittest.zim"
title="Unit Test"
description="Wikipedia articles about unit testing"
@@ -51,9 +63,9 @@ TEST(ManagerTest, readXml)
auto lib = kiwix::Library::create();
kiwix::Manager manager = kiwix::Manager(lib);
EXPECT_EQ(true, manager.readXml(sampleLibraryXML, true, "/data/lib.xml", true));
EXPECT_EQ(true, manager.readXml(sampleLibraryXML, true, LIB_ABS_PATH, true));
kiwix::Book book = lib->getBookById("0d0bcd57-d3f6-cb22-44cc-a723ccb4e1b2");
EXPECT_EQ("/data/zimfiles/unittest.zim", book.getPath());
EXPECT_EQ(ZIM_ABS_PATH, book.getPath());
EXPECT_EQ("https://example.com/zimfiles/unittest.zim", book.getUrl());
EXPECT_EQ("Unit Test", book.getTitle());
EXPECT_EQ("Wikipedia articles about unit testing", book.getDescription());

View File

@@ -7,6 +7,23 @@
namespace
{
#if _WIN32
const char libraryXML[] = R"(
<library version="1.0">
<book id="01" path="C:\data\zero_one.zim"> </book>
<book id="02" path="C:\data\zero two.zim"> </book>
<book id="03" path="C:\data\ZERO thrêë.zim"> </book>
<book id="04-2021-10" path="C:\data\zero_four_2021-10.zim"></book>
<book id="04-2021-11" path="C:\data\zero_four_2021-11.zim"></book>
<book id="05-a" path="C:\data\zero_five-a.zim" name="zero_five"></book>
<book id="05-b" path="C:\data\zero_five-b.zim" name="zero_five"></book>
<book id="06+" path="C:\data\zërô + SIX.zim"></book>
<book id="06plus" path="C:\data\zero_plus_six.zim"></book>
<book id="07-super" path="C:\data\zero_seven.zim"></book>
<book id="07-sub" path="C:\data\subdir\zero_seven.zim"></book>
</library>
)";
#else
const char libraryXML[] = R"(
<library version="1.0">
<book id="01" path="/data/zero_one.zim"> </book>
@@ -22,6 +39,7 @@ const char libraryXML[] = R"(
<book id="07-sub" path="/data/subdir/zero_seven.zim"></book>
</library>
)";
#endif
class NameMapperTest : public ::testing::Test {
public:
@@ -61,7 +79,22 @@ public:
operator std::string() const { return buffer.str(); }
};
#if _WIN32
const std::string ZERO_FOUR_NAME_CONFLICT_MSG =
"Path collision: 'C:\\data\\zero_four_2021-10.zim' and"
" 'C:\\data\\zero_four_2021-11.zim' can't share the same URL path 'zero_four'."
" Therefore, only 'C:\\data\\zero_four_2021-10.zim' will be served.\n";
const std::string ZERO_SIX_NAME_CONFLICT_MSG =
"Path collision: 'C:\\data\\zërô + SIX.zim' and "
"'C:\\data\\zero_plus_six.zim' can't share the same URL path 'zero_plus_six'."
" Therefore, only 'C:\\data\\zërô + SIX.zim' will be served.\n";
const std::string ZERO_SEVEN_NAME_CONFLICT_MSG =
"Path collision: 'C:\\data\\subdir\\zero_seven.zim' and"
" 'C:\\data\\zero_seven.zim' can't share the same URL path 'zero_seven'."
" Therefore, only 'C:\\data\\subdir\\zero_seven.zim' will be served.\n";
#else
const std::string ZERO_FOUR_NAME_CONFLICT_MSG =
"Path collision: '/data/zero_four_2021-10.zim' and"
" '/data/zero_four_2021-11.zim' can't share the same URL path 'zero_four'."
@@ -76,6 +109,7 @@ const std::string ZERO_SEVEN_NAME_CONFLICT_MSG =
"Path collision: '/data/subdir/zero_seven.zim' and"
" '/data/zero_seven.zim' can't share the same URL path 'zero_seven'."
" Therefore, only '/data/subdir/zero_seven.zim' will be served.\n";
#endif
// Name conflicts in the default mode (without the --nodatealiases is off
const std::string DEFAULT_NAME_CONFLICTS = ZERO_SIX_NAME_CONFLICT_MSG

View File

@@ -18,9 +18,10 @@
*/
#include "gtest/gtest.h"
#include "../include/tools.h"
#include "../src/tools/otherTools.h"
#include "zim/suggestion_iterator.h"
#include "../src/server/i18n.h"
#include "../src/server/i18n_utils.h"
#include <regex>
@@ -252,11 +253,14 @@ TEST(networkTools, getNetworkInterfaces)
}
}
TEST(networkTools, getBestPublicIp)
TEST(networkTools, getBestPublicIps)
{
using kiwix::getBestPublicIps;
using kiwix::getBestPublicIp;
using kiwix::IpMode;
std::cout << "getBestPublicIp(true) " << getBestPublicIp(true) << std::endl;
std::cout << "getBestPublicIp(false) " << getBestPublicIp(false) << std::endl;
std::cout << "getBestPublicIp() " << getBestPublicIp() << std::endl;
std::cout << "getBestPublicIps(true) : [" << getBestPublicIps(IpMode::ipv4).addr << ", " << getBestPublicIps(IpMode::ipv4).addr6 << "]" << std::endl;
std::cout << "getBestPublicIps(false) : [" << getBestPublicIps(IpMode::ipv6).addr << ", " << getBestPublicIps(IpMode::ipv6).addr6 << "]" << std::endl;
std::cout << "getBestPublicIps(false) : [" << getBestPublicIps(IpMode::all).addr << ", " << getBestPublicIps(IpMode::all).addr6 << "]" << std::endl;
std::cout << "getBestPublicIp() : " << getBestPublicIp() << std::endl;
}

View File

@@ -61,9 +61,9 @@ const ResourceCollection resources200Compressible{
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/i18n.js" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/i18n.js?cacheid=071abc9a" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/index.css" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/index.css?cacheid=1e78e7cf" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/index.css?cacheid=ae79e41a" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/index.js" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/index.js?cacheid=f43eb0b9" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/index.js?cacheid=8f4b6a1e" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/iso6391To3.js" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/iso6391To3.js?cacheid=ecde2bb3" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/isotope.pkgd.min.js" },
@@ -75,7 +75,7 @@ const ResourceCollection resources200Compressible{
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/taskbar.css" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/taskbar.css?cacheid=80d56607" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/viewer.js" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/viewer.js?cacheid=5fc4badf" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/viewer.js?cacheid=aca897b0" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/fonts/Poppins.ttf" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/fonts/Poppins.ttf?cacheid=af705837" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/fonts/Roboto.ttf" },
@@ -114,6 +114,8 @@ const ResourceCollection resources200Uncompressible{
{ STATIC_CONTENT, "/ROOT%23%3F/skin/caret.png?cacheid=22b942b4" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/download.png" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/download.png?cacheid=a39aa502" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/download-white.svg" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/download-white.svg?cacheid=079ab989"},
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/favicon/android-chrome-192x192.png" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/favicon/android-chrome-192x192.png?cacheid=bfac158b" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/favicon/android-chrome-512x512.png" },
@@ -279,7 +281,7 @@ TEST_F(ServerTest, CacheIdsOfStaticResources)
{
/* url */ "/ROOT%23%3F/",
R"EXPECTEDRESULT( href="/ROOT%23%3F/skin/kiwix.css?cacheid=2158fad9"
href="/ROOT%23%3F/skin/index.css?cacheid=1e78e7cf"
href="/ROOT%23%3F/skin/index.css?cacheid=ae79e41a"
<link rel="apple-touch-icon" sizes="180x180" href="/ROOT%23%3F/skin/favicon/apple-touch-icon.png?cacheid=f86f8df3">
<link rel="icon" type="image/png" sizes="32x32" href="/ROOT%23%3F/skin/favicon/favicon-32x32.png?cacheid=79ded625">
<link rel="icon" type="image/png" sizes="16x16" href="/ROOT%23%3F/skin/favicon/favicon-16x16.png?cacheid=a986fedc">
@@ -292,7 +294,7 @@ R"EXPECTEDRESULT( href="/ROOT%23%3F/skin/kiwix.css?cacheid=2158fad9"
<script type="text/javascript" src="/ROOT%23%3F/skin/languages.js?cacheid=ee7d95b5" defer></script>
<script src="/ROOT%23%3F/skin/isotope.pkgd.min.js?cacheid=2e48d392" defer></script>
<script src="/ROOT%23%3F/skin/iso6391To3.js?cacheid=ecde2bb3"></script>
<script type="text/javascript" src="/ROOT%23%3F/skin/index.js?cacheid=f43eb0b9" defer></script>
<script type="text/javascript" src="/ROOT%23%3F/skin/index.js?cacheid=8f4b6a1e" defer></script>
<img src="/ROOT%23%3F/skin/feed.svg?cacheid=055b333f"
<img src="/ROOT%23%3F/skin/langSelector.svg?cacheid=00b59961"
)EXPECTEDRESULT"
@@ -310,7 +312,8 @@ R"EXPECTEDRESULT( background-image: url('../skin/search-icon.svg?cacheid=b10a
},
{
/* url */ "/ROOT%23%3F/skin/index.js",
R"EXPECTEDRESULT( <img src="${root}/skin/download.png?cacheid=a39aa502" alt="${$t("direct-download-alt-text")}" />
R"EXPECTEDRESULT( <img src="${root}/skin/download-white.svg?cacheid=079ab989">
<img src="${root}/skin/download.png?cacheid=a39aa502" alt="${$t("direct-download-alt-text")}" />
<img src="${root}/skin/hash.png?cacheid=f836e872" alt="${$t("hash-download-alt-text")}" />
<img src="${root}/skin/magnet.png?cacheid=73b6bddf" alt="${$t("magnet-alt-text")}" />
<img src="${root}/skin/bittorrent.png?cacheid=4f5c6882" alt="${$t("torrent-download-alt-text")}" />
@@ -324,7 +327,7 @@ R"EXPECTEDRESULT( <link type="text/css" href="./skin/kiwix.css?cacheid=2158fa
<script type="text/javascript" src="./skin/polyfills.js?cacheid=a0e0343d"></script>
<script type="module" src="./skin/i18n.js?cacheid=071abc9a" defer></script>
<script type="text/javascript" src="./skin/languages.js?cacheid=ee7d95b5" defer></script>
<script type="text/javascript" src="./skin/viewer.js?cacheid=5fc4badf" defer></script>
<script type="text/javascript" src="./skin/viewer.js?cacheid=aca897b0" defer></script>
<script type="text/javascript" src="./skin/autoComplete/autoComplete.min.js?cacheid=1191aaaf"></script>
const blankPageUrl = root + "/skin/blank.html?cacheid=6b1fa032";
<label for="kiwix_button_show_toggle"><img src="./skin/caret.png?cacheid=22b942b4" alt=""></label>
@@ -356,6 +359,57 @@ R"EXPECTEDRESULT( <link type="text/css" href="/ROOT%23%3F/skin/search_results
}
}
std::string getCacheIdFromUrl(const std::string& url)
{
const std::string q("?cacheid=");
const auto i = url.find(q);
return i == std::string::npos ? "" : url.substr(i + q.size());
}
std::string runExternalCmdAndGetItsOutput(const std::string& cmd)
{
std::string cmdOutput;
#ifdef _WIN32
#define popen _popen
#define pclose _pclose
#endif
if (FILE* pPipe = popen(cmd.c_str(), "r"))
{
char buf[128];
while (fgets(buf, 128, pPipe)) {
cmdOutput += std::string(buf, buf+128);
}
pclose(pPipe);
}
return cmdOutput;
}
std::string getSha1OfResponseData(const std::string& url)
{
const std::string pythonScript =
"import urllib.request as req; "
"import hashlib; "
"print(hashlib.sha1(req.urlopen('" + url + "').read()).hexdigest())";
const std::string cmd = "python3 -c \"" + pythonScript + "\"";
return runExternalCmdAndGetItsOutput(cmd);
}
TEST_F(ServerTest, CacheIdsOfStaticResourcesMatchTheSha1HashOfResourceContent)
{
for ( const Resource& res : all200Resources() ) {
if ( res.kind == STATIC_CONTENT ) {
const TestContext ctx{ {"url", res.url} };
const std::string fullUrl = "http://localhost:" + std::to_string(SERVER_PORT) + res.url;
const std::string sha1 = getSha1OfResponseData(fullUrl);
ASSERT_EQ(sha1.substr(0, 8), getCacheIdFromUrl(res.url)) << ctx;
}
}
}
const char* urls400[] = {
"/ROOT%23%3F/search",
"/ROOT%23%3F/search?content=zimfile",

View File

@@ -19,6 +19,7 @@
#include "gtest/gtest.h"
#include "../src/tools/stringTools.h"
#include "../include/tools.h"
#include <string>
#include <vector>
@@ -170,4 +171,17 @@ TEST(stringTools, stripSuffix)
EXPECT_EQ(stripSuffix("abc123", "987"), "abc123");
}
TEST(stringTools, getSlugifiedFileName)
{
EXPECT_EQ(getSlugifiedFileName("abc123.png"), "abc123.png");
EXPECT_EQ(getSlugifiedFileName("/"), "_");
EXPECT_EQ(getSlugifiedFileName("abc/123.pdf"), "abc_123.pdf");
EXPECT_EQ(getSlugifiedFileName("abc//123.yaml"), "abc__123.yaml");
EXPECT_EQ(getSlugifiedFileName("//abc//123//"), "__abc__123__");
#ifdef _WIN32
EXPECT_EQ(getSlugifiedFileName(R"(<>:"/\\|?*)"), "__________");
EXPECT_EQ(getSlugifiedFileName(R"(<abc>:"/123\\|?*<.txt>)"), "_abc____123______.txt_");
#endif
}
};