We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
FTS doesn't perform OCR on attachments for keyword extraction
If one send a PDF composed of images, no keywords will be extracted/indexed.

To fix that, we could do some OCR:
- https://github.com/tesseract-ocr/tesseract

or if we want to get fancy, something like
- https://github.com/JaidedAI/EasyOCR

Or the built-in integration with
- https://tika.apache.org/
There is a security argument going for that option: it's written in java (memory  safe language) and would remove the need for decode2text and friends.

This is a follow-up to #2184 and #2972

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `core/admin/mailu/configuration.py`
Content:
```
1 import os
2 
3 from datetime import timedelta
4 import ipaddress
5 
6 DEFAULT_CONFIG = {
7     # Specific to the admin UI
8     'DOCKER_SOCKET': 'unix:///var/run/docker.sock',
9     'BABEL_DEFAULT_LOCALE': 'en',
10     'BABEL_DEFAULT_TIMEZONE': 'UTC',
11     'BOOTSTRAP_SERVE_LOCAL': True,
12     'RATELIMIT_STORAGE_URL': '',
13     'DEBUG': False,
14     'DEBUG_PROFILER': False,
15     'DEBUG_TB_INTERCEPT_REDIRECTS': False,
16     'DEBUG_ASSETS': '',
17     'DOMAIN_REGISTRATION': False,
18     'TEMPLATES_AUTO_RELOAD': True,
19     'MEMORY_SESSIONS': False,
20     'FETCHMAIL_ENABLED': True,
21     'MAILU_VERSION': 'unknown',
22     # Database settings
23     'DB_FLAVOR': None,
24     'DB_USER': 'mailu',
25     'DB_PW': None,
26     'DB_HOST': 'database',
27     'DB_NAME': 'mailu',
28     'SQLITE_DATABASE_FILE': 'data/main.db',
29     'SQLALCHEMY_DATABASE_URI': 'sqlite:////data/main.db',
30     'SQLALCHEMY_DATABASE_URI_ROUNDCUBE': 'sqlite:////data/roundcube.db',
31     'SQLALCHEMY_TRACK_MODIFICATIONS': False,
32     # Statistics management
33     'INSTANCE_ID_PATH': '/data/instance',
34     'STATS_ENDPOINT': '20.{}.stats.mailu.io',
35     # Common configuration variables
36     'SECRET_KEY': 'changeMe',
37     'DOMAIN': 'mailu.io',
38     'HOSTNAMES': 'mail.mailu.io,alternative.mailu.io,yetanother.mailu.io',
39     'POSTMASTER': 'postmaster',
40     'WILDCARD_SENDERS': '',
41     'TLS_FLAVOR': 'cert',
42     'INBOUND_TLS_ENFORCE': False,
43     'DEFER_ON_TLS_ERROR': True,
44     'AUTH_RATELIMIT_IP': '5/hour',
45     'AUTH_RATELIMIT_IP_V4_MASK': 24,
46     'AUTH_RATELIMIT_IP_V6_MASK': 48,
47     'AUTH_RATELIMIT_USER': '50/day',
48     'AUTH_RATELIMIT_EXEMPTION': '',
49     'AUTH_RATELIMIT_EXEMPTION_LENGTH': 86400,
50     'DISABLE_STATISTICS': False,
51     # Mail settings
52     'DMARC_RUA': None,
53     'DMARC_RUF': None,
54     'WELCOME': False,
55     'WELCOME_SUBJECT': 'Dummy welcome topic',
56     'WELCOME_BODY': 'Dummy welcome body',
57     'DKIM_SELECTOR': 'dkim',
58     'DKIM_PATH': '/dkim/{domain}.{selector}.key',
59     'DEFAULT_QUOTA': 1000000000,
60     'MESSAGE_RATELIMIT': '200/day',
61     'MESSAGE_RATELIMIT_EXEMPTION': '',
62     'RECIPIENT_DELIMITER': '',
63     # Web settings
64     'SITENAME': 'Mailu',
65     'WEBSITE': 'https://mailu.io',
66     'ADMIN': 'none',
67     'WEB_ADMIN': '/admin',
68     'WEB_WEBMAIL': '/webmail',
69     'WEBMAIL': 'none',
70     'RECAPTCHA_PUBLIC_KEY': '',
71     'RECAPTCHA_PRIVATE_KEY': '',
72     'LOGO_URL': None,
73     'LOGO_BACKGROUND': None,
74     # Advanced settings
75     'API': False,
76     'WEB_API': '/api',
77     'API_TOKEN': None,
78     'LOG_LEVEL': 'INFO',
79     'SESSION_KEY_BITS': 128,
80     'SESSION_TIMEOUT': 3600,
81     'PERMANENT_SESSION_LIFETIME': 30*24*3600,
82     'SESSION_COOKIE_SECURE': None,
83     'CREDENTIAL_ROUNDS': 12,
84     'TLS_PERMISSIVE': True,
85     'TZ': 'Etc/UTC',
86     'DEFAULT_SPAM_THRESHOLD': 80,
87     'PROXY_AUTH_WHITELIST': '',
88     'PROXY_AUTH_HEADER': 'X-Auth-Email',
89     'PROXY_AUTH_CREATE': False,
90     'PROXY_AUTH_LOGOUT_URL': None,
91     'SUBNET': '192.168.203.0/24',
92     'SUBNET6': None,
93 }
94 
95 class ConfigManager:
96     """ Naive configuration manager that uses environment only
97     """
98 
99     DB_TEMPLATES = {
100         'sqlite': 'sqlite:////{SQLITE_DATABASE_FILE}',
101         'postgresql': 'postgresql://{DB_USER}:{DB_PW}@{DB_HOST}/{DB_NAME}',
102         'mysql': 'mysql+mysqlconnector://{DB_USER}:{DB_PW}@{DB_HOST}/{DB_NAME}',
103     }
104 
105     def __init__(self):
106         self.config = dict()
107 
108     def __get_env(self, key, value):
109         key_file = key + "_FILE"
110         if key_file in os.environ:
111             with open(os.environ.get(key_file)) as file:
112                 value_from_file = file.read()
113             return value_from_file.strip()
114         else:
115             return os.environ.get(key, value)
116 
117     def __coerce_value(self, value):
118         if isinstance(value, str) and value.lower() in ('true','yes'):
119             return True
120         elif isinstance(value, str) and value.lower() in ('false', 'no'):
121             return False
122         return value
123 
124     def init_app(self, app):
125         # get current app config
126         self.config.update(app.config)
127         # get environment variables
128         for key in os.environ:
129             if key.endswith('_ADDRESS'):
130                 self.config[key] = os.environ[key]
131 
132         self.config.update({
133             key: self.__coerce_value(self.__get_env(key, value))
134             for key, value in DEFAULT_CONFIG.items()
135         })
136 
137         # automatically set the sqlalchemy string
138         if self.config['DB_FLAVOR']:
139             template = self.DB_TEMPLATES[self.config['DB_FLAVOR']]
140             self.config['SQLALCHEMY_DATABASE_URI'] = template.format(**self.config)
141 
142         if not self.config.get('RATELIMIT_STORAGE_URL'):
143             self.config['RATELIMIT_STORAGE_URL'] = f'redis://{self.config["REDIS_ADDRESS"]}/2'
144 
145         self.config['SESSION_STORAGE_URL'] = f'redis://{self.config["REDIS_ADDRESS"]}/3'
146         self.config['SESSION_COOKIE_SAMESITE'] = 'Strict'
147         self.config['SESSION_COOKIE_HTTPONLY'] = True
148         if self.config['SESSION_COOKIE_SECURE'] is None:
149             self.config['SESSION_COOKIE_SECURE'] = self.config['TLS_FLAVOR'] != 'notls'
150         self.config['SESSION_PERMANENT'] = True
151         self.config['SESSION_TIMEOUT'] = int(self.config['SESSION_TIMEOUT'])
152         self.config['SESSION_KEY_BITS'] = int(self.config['SESSION_KEY_BITS'])
153         self.config['PERMANENT_SESSION_LIFETIME'] = int(self.config['PERMANENT_SESSION_LIFETIME'])
154         self.config['AUTH_RATELIMIT_IP_V4_MASK'] = int(self.config['AUTH_RATELIMIT_IP_V4_MASK'])
155         self.config['AUTH_RATELIMIT_IP_V6_MASK'] = int(self.config['AUTH_RATELIMIT_IP_V6_MASK'])
156         self.config['AUTH_RATELIMIT_EXEMPTION'] = set(ipaddress.ip_network(cidr, False) for cidr in (cidr.strip() for cidr in self.config['AUTH_RATELIMIT_EXEMPTION'].split(',')) if cidr)
157         self.config['MESSAGE_RATELIMIT_EXEMPTION'] = set([s for s in self.config['MESSAGE_RATELIMIT_EXEMPTION'].lower().replace(' ', '').split(',') if s])
158         hostnames = [host.strip() for host in self.config['HOSTNAMES'].split(',')]
159         self.config['HOSTNAMES'] = ','.join(hostnames)
160         self.config['HOSTNAME'] = hostnames[0]
161         self.config['DEFAULT_SPAM_THRESHOLD'] = int(self.config['DEFAULT_SPAM_THRESHOLD'])
162         self.config['PROXY_AUTH_WHITELIST'] = set(ipaddress.ip_network(cidr, False) for cidr in (cidr.strip() for cidr in self.config['PROXY_AUTH_WHITELIST'].split(',')) if cidr)
163         try:
164             self.config['MAILU_VERSION'] = open('/version', 'r').read()
165         except FileNotFoundError:
166             pass
167 
168         # update the app config
169         app.config.update(self.config)
170
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/core/admin/mailu/configuration.py b/core/admin/mailu/configuration.py
--- a/core/admin/mailu/configuration.py
+++ b/core/admin/mailu/configuration.py
@@ -75,6 +75,8 @@
     'API': False,
     'WEB_API': '/api',
     'API_TOKEN': None,
+    'FULL_TEXT_SEARCH': 'en',
+    'FULL_TEXT_SEARCH_ATTACHMENTS': False,
     'LOG_LEVEL': 'INFO',
     'SESSION_KEY_BITS': 128,
     'SESSION_TIMEOUT': 3600,

{"golden_diff": "diff --git a/core/admin/mailu/configuration.py b/core/admin/mailu/configuration.py\n--- a/core/admin/mailu/configuration.py\n+++ b/core/admin/mailu/configuration.py\n@@ -75,6 +75,8 @@\n     'API': False,\n     'WEB_API': '/api',\n     'API_TOKEN': None,\n+    'FULL_TEXT_SEARCH': 'en',\n+    'FULL_TEXT_SEARCH_ATTACHMENTS': False,\n     'LOG_LEVEL': 'INFO',\n     'SESSION_KEY_BITS': 128,\n     'SESSION_TIMEOUT': 3600,\n", "issue": "FTS doesn't perform OCR on attachments for keyword extraction\nIf one send a PDF composed of images, no keywords will be extracted/indexed.\r\n\r\nTo fix that, we could do some OCR:\r\n- https://github.com/tesseract-ocr/tesseract\r\n\r\nor if we want to get fancy, something like\r\n- https://github.com/JaidedAI/EasyOCR\r\n\r\nOr the built-in integration with\r\n- https://tika.apache.org/\r\nThere is a security argument going for that option: it's written in java (memory  safe language) and would remove the need for decode2text and friends.\r\n\r\nThis is a follow-up to #2184 and #2972\n", "before_files": [{"content": "import os\n\nfrom datetime import timedelta\nimport ipaddress\n\nDEFAULT_CONFIG = {\n    # Specific to the admin UI\n    'DOCKER_SOCKET': 'unix:///var/run/docker.sock',\n    'BABEL_DEFAULT_LOCALE': 'en',\n    'BABEL_DEFAULT_TIMEZONE': 'UTC',\n    'BOOTSTRAP_SERVE_LOCAL': True,\n    'RATELIMIT_STORAGE_URL': '',\n    'DEBUG': False,\n    'DEBUG_PROFILER': False,\n    'DEBUG_TB_INTERCEPT_REDIRECTS': False,\n    'DEBUG_ASSETS': '',\n    'DOMAIN_REGISTRATION': False,\n    'TEMPLATES_AUTO_RELOAD': True,\n    'MEMORY_SESSIONS': False,\n    'FETCHMAIL_ENABLED': True,\n    'MAILU_VERSION': 'unknown',\n    # Database settings\n    'DB_FLAVOR': None,\n    'DB_USER': 'mailu',\n    'DB_PW': None,\n    'DB_HOST': 'database',\n    'DB_NAME': 'mailu',\n    'SQLITE_DATABASE_FILE': 'data/main.db',\n    'SQLALCHEMY_DATABASE_URI': 'sqlite:////data/main.db',\n    'SQLALCHEMY_DATABASE_URI_ROUNDCUBE': 'sqlite:////data/roundcube.db',\n    'SQLALCHEMY_TRACK_MODIFICATIONS': False,\n    # Statistics management\n    'INSTANCE_ID_PATH': '/data/instance',\n    'STATS_ENDPOINT': '20.{}.stats.mailu.io',\n    # Common configuration variables\n    'SECRET_KEY': 'changeMe',\n    'DOMAIN': 'mailu.io',\n    'HOSTNAMES': 'mail.mailu.io,alternative.mailu.io,yetanother.mailu.io',\n    'POSTMASTER': 'postmaster',\n    'WILDCARD_SENDERS': '',\n    'TLS_FLAVOR': 'cert',\n    'INBOUND_TLS_ENFORCE': False,\n    'DEFER_ON_TLS_ERROR': True,\n    'AUTH_RATELIMIT_IP': '5/hour',\n    'AUTH_RATELIMIT_IP_V4_MASK': 24,\n    'AUTH_RATELIMIT_IP_V6_MASK': 48,\n    'AUTH_RATELIMIT_USER': '50/day',\n    'AUTH_RATELIMIT_EXEMPTION': '',\n    'AUTH_RATELIMIT_EXEMPTION_LENGTH': 86400,\n    'DISABLE_STATISTICS': False,\n    # Mail settings\n    'DMARC_RUA': None,\n    'DMARC_RUF': None,\n    'WELCOME': False,\n    'WELCOME_SUBJECT': 'Dummy welcome topic',\n    'WELCOME_BODY': 'Dummy welcome body',\n    'DKIM_SELECTOR': 'dkim',\n    'DKIM_PATH': '/dkim/{domain}.{selector}.key',\n    'DEFAULT_QUOTA': 1000000000,\n    'MESSAGE_RATELIMIT': '200/day',\n    'MESSAGE_RATELIMIT_EXEMPTION': '',\n    'RECIPIENT_DELIMITER': '',\n    # Web settings\n    'SITENAME': 'Mailu',\n    'WEBSITE': 'https://mailu.io',\n    'ADMIN': 'none',\n    'WEB_ADMIN': '/admin',\n    'WEB_WEBMAIL': '/webmail',\n    'WEBMAIL': 'none',\n    'RECAPTCHA_PUBLIC_KEY': '',\n    'RECAPTCHA_PRIVATE_KEY': '',\n    'LOGO_URL': None,\n    'LOGO_BACKGROUND': None,\n    # Advanced settings\n    'API': False,\n    'WEB_API': '/api',\n    'API_TOKEN': None,\n    'LOG_LEVEL': 'INFO',\n    'SESSION_KEY_BITS': 128,\n    'SESSION_TIMEOUT': 3600,\n    'PERMANENT_SESSION_LIFETIME': 30*24*3600,\n    'SESSION_COOKIE_SECURE': None,\n    'CREDENTIAL_ROUNDS': 12,\n    'TLS_PERMISSIVE': True,\n    'TZ': 'Etc/UTC',\n    'DEFAULT_SPAM_THRESHOLD': 80,\n    'PROXY_AUTH_WHITELIST': '',\n    'PROXY_AUTH_HEADER': 'X-Auth-Email',\n    'PROXY_AUTH_CREATE': False,\n    'PROXY_AUTH_LOGOUT_URL': None,\n    'SUBNET': '192.168.203.0/24',\n    'SUBNET6': None,\n}\n\nclass ConfigManager:\n    \"\"\" Naive configuration manager that uses environment only\n    \"\"\"\n\n    DB_TEMPLATES = {\n        'sqlite': 'sqlite:////{SQLITE_DATABASE_FILE}',\n        'postgresql': 'postgresql://{DB_USER}:{DB_PW}@{DB_HOST}/{DB_NAME}',\n        'mysql': 'mysql+mysqlconnector://{DB_USER}:{DB_PW}@{DB_HOST}/{DB_NAME}',\n    }\n\n    def __init__(self):\n        self.config = dict()\n\n    def __get_env(self, key, value):\n        key_file = key + \"_FILE\"\n        if key_file in os.environ:\n            with open(os.environ.get(key_file)) as file:\n                value_from_file = file.read()\n            return value_from_file.strip()\n        else:\n            return os.environ.get(key, value)\n\n    def __coerce_value(self, value):\n        if isinstance(value, str) and value.lower() in ('true','yes'):\n            return True\n        elif isinstance(value, str) and value.lower() in ('false', 'no'):\n            return False\n        return value\n\n    def init_app(self, app):\n        # get current app config\n        self.config.update(app.config)\n        # get environment variables\n        for key in os.environ:\n            if key.endswith('_ADDRESS'):\n                self.config[key] = os.environ[key]\n\n        self.config.update({\n            key: self.__coerce_value(self.__get_env(key, value))\n            for key, value in DEFAULT_CONFIG.items()\n        })\n\n        # automatically set the sqlalchemy string\n        if self.config['DB_FLAVOR']:\n            template = self.DB_TEMPLATES[self.config['DB_FLAVOR']]\n            self.config['SQLALCHEMY_DATABASE_URI'] = template.format(**self.config)\n\n        if not self.config.get('RATELIMIT_STORAGE_URL'):\n            self.config['RATELIMIT_STORAGE_URL'] = f'redis://{self.config[\"REDIS_ADDRESS\"]}/2'\n\n        self.config['SESSION_STORAGE_URL'] = f'redis://{self.config[\"REDIS_ADDRESS\"]}/3'\n        self.config['SESSION_COOKIE_SAMESITE'] = 'Strict'\n        self.config['SESSION_COOKIE_HTTPONLY'] = True\n        if self.config['SESSION_COOKIE_SECURE'] is None:\n            self.config['SESSION_COOKIE_SECURE'] = self.config['TLS_FLAVOR'] != 'notls'\n        self.config['SESSION_PERMANENT'] = True\n        self.config['SESSION_TIMEOUT'] = int(self.config['SESSION_TIMEOUT'])\n        self.config['SESSION_KEY_BITS'] = int(self.config['SESSION_KEY_BITS'])\n        self.config['PERMANENT_SESSION_LIFETIME'] = int(self.config['PERMANENT_SESSION_LIFETIME'])\n        self.config['AUTH_RATELIMIT_IP_V4_MASK'] = int(self.config['AUTH_RATELIMIT_IP_V4_MASK'])\n        self.config['AUTH_RATELIMIT_IP_V6_MASK'] = int(self.config['AUTH_RATELIMIT_IP_V6_MASK'])\n        self.config['AUTH_RATELIMIT_EXEMPTION'] = set(ipaddress.ip_network(cidr, False) for cidr in (cidr.strip() for cidr in self.config['AUTH_RATELIMIT_EXEMPTION'].split(',')) if cidr)\n        self.config['MESSAGE_RATELIMIT_EXEMPTION'] = set([s for s in self.config['MESSAGE_RATELIMIT_EXEMPTION'].lower().replace(' ', '').split(',') if s])\n        hostnames = [host.strip() for host in self.config['HOSTNAMES'].split(',')]\n        self.config['HOSTNAMES'] = ','.join(hostnames)\n        self.config['HOSTNAME'] = hostnames[0]\n        self.config['DEFAULT_SPAM_THRESHOLD'] = int(self.config['DEFAULT_SPAM_THRESHOLD'])\n        self.config['PROXY_AUTH_WHITELIST'] = set(ipaddress.ip_network(cidr, False) for cidr in (cidr.strip() for cidr in self.config['PROXY_AUTH_WHITELIST'].split(',')) if cidr)\n        try:\n            self.config['MAILU_VERSION'] = open('/version', 'r').read()\n        except FileNotFoundError:\n            pass\n\n        # update the app config\n        app.config.update(self.config)\n", "path": "core/admin/mailu/configuration.py"}], "after_files": [{"content": "import os\n\nfrom datetime import timedelta\nimport ipaddress\n\nDEFAULT_CONFIG = {\n    # Specific to the admin UI\n    'DOCKER_SOCKET': 'unix:///var/run/docker.sock',\n    'BABEL_DEFAULT_LOCALE': 'en',\n    'BABEL_DEFAULT_TIMEZONE': 'UTC',\n    'BOOTSTRAP_SERVE_LOCAL': True,\n    'RATELIMIT_STORAGE_URL': '',\n    'DEBUG': False,\n    'DEBUG_PROFILER': False,\n    'DEBUG_TB_INTERCEPT_REDIRECTS': False,\n    'DEBUG_ASSETS': '',\n    'DOMAIN_REGISTRATION': False,\n    'TEMPLATES_AUTO_RELOAD': True,\n    'MEMORY_SESSIONS': False,\n    'FETCHMAIL_ENABLED': True,\n    'MAILU_VERSION': 'unknown',\n    # Database settings\n    'DB_FLAVOR': None,\n    'DB_USER': 'mailu',\n    'DB_PW': None,\n    'DB_HOST': 'database',\n    'DB_NAME': 'mailu',\n    'SQLITE_DATABASE_FILE': 'data/main.db',\n    'SQLALCHEMY_DATABASE_URI': 'sqlite:////data/main.db',\n    'SQLALCHEMY_DATABASE_URI_ROUNDCUBE': 'sqlite:////data/roundcube.db',\n    'SQLALCHEMY_TRACK_MODIFICATIONS': False,\n    # Statistics management\n    'INSTANCE_ID_PATH': '/data/instance',\n    'STATS_ENDPOINT': '20.{}.stats.mailu.io',\n    # Common configuration variables\n    'SECRET_KEY': 'changeMe',\n    'DOMAIN': 'mailu.io',\n    'HOSTNAMES': 'mail.mailu.io,alternative.mailu.io,yetanother.mailu.io',\n    'POSTMASTER': 'postmaster',\n    'WILDCARD_SENDERS': '',\n    'TLS_FLAVOR': 'cert',\n    'INBOUND_TLS_ENFORCE': False,\n    'DEFER_ON_TLS_ERROR': True,\n    'AUTH_RATELIMIT_IP': '5/hour',\n    'AUTH_RATELIMIT_IP_V4_MASK': 24,\n    'AUTH_RATELIMIT_IP_V6_MASK': 48,\n    'AUTH_RATELIMIT_USER': '50/day',\n    'AUTH_RATELIMIT_EXEMPTION': '',\n    'AUTH_RATELIMIT_EXEMPTION_LENGTH': 86400,\n    'DISABLE_STATISTICS': False,\n    # Mail settings\n    'DMARC_RUA': None,\n    'DMARC_RUF': None,\n    'WELCOME': False,\n    'WELCOME_SUBJECT': 'Dummy welcome topic',\n    'WELCOME_BODY': 'Dummy welcome body',\n    'DKIM_SELECTOR': 'dkim',\n    'DKIM_PATH': '/dkim/{domain}.{selector}.key',\n    'DEFAULT_QUOTA': 1000000000,\n    'MESSAGE_RATELIMIT': '200/day',\n    'MESSAGE_RATELIMIT_EXEMPTION': '',\n    'RECIPIENT_DELIMITER': '',\n    # Web settings\n    'SITENAME': 'Mailu',\n    'WEBSITE': 'https://mailu.io',\n    'ADMIN': 'none',\n    'WEB_ADMIN': '/admin',\n    'WEB_WEBMAIL': '/webmail',\n    'WEBMAIL': 'none',\n    'RECAPTCHA_PUBLIC_KEY': '',\n    'RECAPTCHA_PRIVATE_KEY': '',\n    'LOGO_URL': None,\n    'LOGO_BACKGROUND': None,\n    # Advanced settings\n    'API': False,\n    'WEB_API': '/api',\n    'API_TOKEN': None,\n    'FULL_TEXT_SEARCH': 'en',\n    'FULL_TEXT_SEARCH_ATTACHMENTS': False,\n    'LOG_LEVEL': 'INFO',\n    'SESSION_KEY_BITS': 128,\n    'SESSION_TIMEOUT': 3600,\n    'PERMANENT_SESSION_LIFETIME': 30*24*3600,\n    'SESSION_COOKIE_SECURE': None,\n    'CREDENTIAL_ROUNDS': 12,\n    'TLS_PERMISSIVE': True,\n    'TZ': 'Etc/UTC',\n    'DEFAULT_SPAM_THRESHOLD': 80,\n    'PROXY_AUTH_WHITELIST': '',\n    'PROXY_AUTH_HEADER': 'X-Auth-Email',\n    'PROXY_AUTH_CREATE': False,\n    'PROXY_AUTH_LOGOUT_URL': None,\n    'SUBNET': '192.168.203.0/24',\n    'SUBNET6': None,\n}\n\nclass ConfigManager:\n    \"\"\" Naive configuration manager that uses environment only\n    \"\"\"\n\n    DB_TEMPLATES = {\n        'sqlite': 'sqlite:////{SQLITE_DATABASE_FILE}',\n        'postgresql': 'postgresql://{DB_USER}:{DB_PW}@{DB_HOST}/{DB_NAME}',\n        'mysql': 'mysql+mysqlconnector://{DB_USER}:{DB_PW}@{DB_HOST}/{DB_NAME}',\n    }\n\n    def __init__(self):\n        self.config = dict()\n\n    def __get_env(self, key, value):\n        key_file = key + \"_FILE\"\n        if key_file in os.environ:\n            with open(os.environ.get(key_file)) as file:\n                value_from_file = file.read()\n            return value_from_file.strip()\n        else:\n            return os.environ.get(key, value)\n\n    def __coerce_value(self, value):\n        if isinstance(value, str) and value.lower() in ('true','yes'):\n            return True\n        elif isinstance(value, str) and value.lower() in ('false', 'no'):\n            return False\n        return value\n\n    def init_app(self, app):\n        # get current app config\n        self.config.update(app.config)\n        # get environment variables\n        for key in os.environ:\n            if key.endswith('_ADDRESS'):\n                self.config[key] = os.environ[key]\n\n        self.config.update({\n            key: self.__coerce_value(self.__get_env(key, value))\n            for key, value in DEFAULT_CONFIG.items()\n        })\n\n        # automatically set the sqlalchemy string\n        if self.config['DB_FLAVOR']:\n            template = self.DB_TEMPLATES[self.config['DB_FLAVOR']]\n            self.config['SQLALCHEMY_DATABASE_URI'] = template.format(**self.config)\n\n        if not self.config.get('RATELIMIT_STORAGE_URL'):\n            self.config['RATELIMIT_STORAGE_URL'] = f'redis://{self.config[\"REDIS_ADDRESS\"]}/2'\n\n        self.config['SESSION_STORAGE_URL'] = f'redis://{self.config[\"REDIS_ADDRESS\"]}/3'\n        self.config['SESSION_COOKIE_SAMESITE'] = 'Strict'\n        self.config['SESSION_COOKIE_HTTPONLY'] = True\n        if self.config['SESSION_COOKIE_SECURE'] is None:\n            self.config['SESSION_COOKIE_SECURE'] = self.config['TLS_FLAVOR'] != 'notls'\n        self.config['SESSION_PERMANENT'] = True\n        self.config['SESSION_TIMEOUT'] = int(self.config['SESSION_TIMEOUT'])\n        self.config['SESSION_KEY_BITS'] = int(self.config['SESSION_KEY_BITS'])\n        self.config['PERMANENT_SESSION_LIFETIME'] = int(self.config['PERMANENT_SESSION_LIFETIME'])\n        self.config['AUTH_RATELIMIT_IP_V4_MASK'] = int(self.config['AUTH_RATELIMIT_IP_V4_MASK'])\n        self.config['AUTH_RATELIMIT_IP_V6_MASK'] = int(self.config['AUTH_RATELIMIT_IP_V6_MASK'])\n        self.config['AUTH_RATELIMIT_EXEMPTION'] = set(ipaddress.ip_network(cidr, False) for cidr in (cidr.strip() for cidr in self.config['AUTH_RATELIMIT_EXEMPTION'].split(',')) if cidr)\n        self.config['MESSAGE_RATELIMIT_EXEMPTION'] = set([s for s in self.config['MESSAGE_RATELIMIT_EXEMPTION'].lower().replace(' ', '').split(',') if s])\n        hostnames = [host.strip() for host in self.config['HOSTNAMES'].split(',')]\n        self.config['HOSTNAMES'] = ','.join(hostnames)\n        self.config['HOSTNAME'] = hostnames[0]\n        self.config['DEFAULT_SPAM_THRESHOLD'] = int(self.config['DEFAULT_SPAM_THRESHOLD'])\n        self.config['PROXY_AUTH_WHITELIST'] = set(ipaddress.ip_network(cidr, False) for cidr in (cidr.strip() for cidr in self.config['PROXY_AUTH_WHITELIST'].split(',')) if cidr)\n        try:\n            self.config['MAILU_VERSION'] = open('/version', 'r').read()\n        except FileNotFoundError:\n            pass\n\n        # update the app config\n        app.config.update(self.config)\n", "path": "core/admin/mailu/configuration.py"}]}