WebUI: Fix reloading page after login
[qBittorrent.git] / src / webui / api / appcontroller.cpp
blobc56ab79fad6d3e90afaec4411e6c85132a588895
1 /*
2 * Bittorrent Client using Qt and libtorrent.
3 * Copyright (C) 2024 Jonathan Ketchker
4 * Copyright (C) 2018 Vladimir Golovnev <glassez@yandex.ru>
5 * Copyright (C) 2006-2012 Christophe Dumez <chris@qbittorrent.org>
6 * Copyright (C) 2006-2012 Ishan Arora <ishan@qbittorrent.org>
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License
10 * as published by the Free Software Foundation; either version 2
11 * of the License, or (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22 * In addition, as a special exception, the copyright holders give permission to
23 * link this program with the OpenSSL project's "OpenSSL" library (or with
24 * modified versions of it that use the same license as the "OpenSSL" library),
25 * and distribute the linked executables. You must obey the GNU General Public
26 * License in all respects for all of the code used other than "OpenSSL". If you
27 * modify file(s), you may extend this exception to your version of the file(s),
28 * but you are not obligated to do so. If you do not wish to do so, delete this
29 * exception statement from your version.
32 #include "appcontroller.h"
34 #include <algorithm>
35 #include <chrono>
37 #include <QCoreApplication>
38 #include <QDebug>
39 #include <QDir>
40 #include <QDirIterator>
41 #include <QJsonArray>
42 #include <QJsonDocument>
43 #include <QJsonObject>
44 #include <QNetworkCookie>
45 #include <QNetworkInterface>
46 #include <QRegularExpression>
47 #include <QStringList>
48 #include <QTimer>
49 #include <QTranslator>
51 #include "base/bittorrent/session.h"
52 #include "base/global.h"
53 #include "base/interfaces/iapplication.h"
54 #include "base/net/downloadmanager.h"
55 #include "base/net/portforwarder.h"
56 #include "base/net/proxyconfigurationmanager.h"
57 #include "base/path.h"
58 #include "base/preferences.h"
59 #include "base/rss/rss_autodownloader.h"
60 #include "base/rss/rss_session.h"
61 #include "base/torrentfileguard.h"
62 #include "base/torrentfileswatcher.h"
63 #include "base/utils/datetime.h"
64 #include "base/utils/fs.h"
65 #include "base/utils/misc.h"
66 #include "base/utils/net.h"
67 #include "base/utils/password.h"
68 #include "base/utils/string.h"
69 #include "base/version.h"
70 #include "apierror.h"
71 #include "../webapplication.h"
73 using namespace std::chrono_literals;
75 const QString KEY_COOKIE_NAME = u"name"_s;
76 const QString KEY_COOKIE_DOMAIN = u"domain"_s;
77 const QString KEY_COOKIE_PATH = u"path"_s;
78 const QString KEY_COOKIE_VALUE = u"value"_s;
79 const QString KEY_COOKIE_EXPIRATION_DATE = u"expirationDate"_s;
81 void AppController::webapiVersionAction()
83 setResult(API_VERSION.toString());
86 void AppController::versionAction()
88 setResult(QStringLiteral(QBT_VERSION));
91 void AppController::buildInfoAction()
93 const QString platformName =
94 #ifdef Q_OS_LINUX
95 u"linux"_s;
96 #elif defined(Q_OS_MACOS)
97 u"macos"_s;
98 #elif defined(Q_OS_WIN)
99 u"windows"_s;
100 #else
101 u"unknown"_s;
102 #endif
104 const QJsonObject versions =
106 {u"qt"_s, QStringLiteral(QT_VERSION_STR)},
107 {u"libtorrent"_s, Utils::Misc::libtorrentVersionString()},
108 {u"boost"_s, Utils::Misc::boostVersionString()},
109 {u"openssl"_s, Utils::Misc::opensslVersionString()},
110 {u"zlib"_s, Utils::Misc::zlibVersionString()},
111 {u"bitness"_s, (QT_POINTER_SIZE * 8)},
112 {u"platform"_s, platformName}
114 setResult(versions);
117 void AppController::shutdownAction()
119 // Special handling for shutdown, we
120 // need to reply to the WebUI before
121 // actually shutting down.
122 QTimer::singleShot(100ms, Qt::CoarseTimer, qApp, []
124 QCoreApplication::exit();
128 void AppController::preferencesAction()
130 const auto *pref = Preferences::instance();
131 const auto *session = BitTorrent::Session::instance();
133 QJsonObject data;
135 // Behavior
136 // Language
137 data[u"locale"_s] = pref->getLocale();
138 data[u"performance_warning"_s] = session->isPerformanceWarningEnabled();
139 data[u"status_bar_external_ip"_s] = pref->isStatusbarExternalIPDisplayed();
140 // Transfer List
141 data[u"confirm_torrent_deletion"_s] = pref->confirmTorrentDeletion();
142 // Log file
143 data[u"file_log_enabled"_s] = app()->isFileLoggerEnabled();
144 data[u"file_log_path"_s] = app()->fileLoggerPath().toString();
145 data[u"file_log_backup_enabled"_s] = app()->isFileLoggerBackup();
146 data[u"file_log_max_size"_s] = app()->fileLoggerMaxSize() / 1024;
147 data[u"file_log_delete_old"_s] = app()->isFileLoggerDeleteOld();
148 data[u"file_log_age"_s] = app()->fileLoggerAge();
149 data[u"file_log_age_type"_s] = app()->fileLoggerAgeType();
150 // Delete torrent contents files on torrent removal
151 data[u"delete_torrent_content_files"_s] = pref->removeTorrentContent();
153 // Downloads
154 // When adding a torrent
155 data[u"torrent_content_layout"_s] = Utils::String::fromEnum(session->torrentContentLayout());
156 data[u"add_to_top_of_queue"_s] = session->isAddTorrentToQueueTop();
157 data[u"add_stopped_enabled"_s] = session->isAddTorrentStopped();
158 data[u"torrent_stop_condition"_s] = Utils::String::fromEnum(session->torrentStopCondition());
159 data[u"merge_trackers"_s] = session->isMergeTrackersEnabled();
160 data[u"auto_delete_mode"_s] = static_cast<int>(TorrentFileGuard::autoDeleteMode());
161 data[u"preallocate_all"_s] = session->isPreallocationEnabled();
162 data[u"incomplete_files_ext"_s] = session->isAppendExtensionEnabled();
163 data[u"use_unwanted_folder"_s] = session->isUnwantedFolderEnabled();
164 // Saving Management
165 data[u"auto_tmm_enabled"_s] = !session->isAutoTMMDisabledByDefault();
166 data[u"torrent_changed_tmm_enabled"_s] = !session->isDisableAutoTMMWhenCategoryChanged();
167 data[u"save_path_changed_tmm_enabled"_s] = !session->isDisableAutoTMMWhenDefaultSavePathChanged();
168 data[u"category_changed_tmm_enabled"_s] = !session->isDisableAutoTMMWhenCategorySavePathChanged();
169 data[u"use_subcategories"] = session->isSubcategoriesEnabled();
170 data[u"save_path"_s] = session->savePath().toString();
171 data[u"temp_path_enabled"_s] = session->isDownloadPathEnabled();
172 data[u"temp_path"_s] = session->downloadPath().toString();
173 data[u"use_category_paths_in_manual_mode"_s] = session->useCategoryPathsInManualMode();
174 data[u"export_dir"_s] = session->torrentExportDirectory().toString();
175 data[u"export_dir_fin"_s] = session->finishedTorrentExportDirectory().toString();
177 // TODO: The following code is deprecated. Delete it once replaced by updated API method.
178 // === BEGIN DEPRECATED CODE === //
179 TorrentFilesWatcher *fsWatcher = TorrentFilesWatcher::instance();
180 const QHash<Path, TorrentFilesWatcher::WatchedFolderOptions> watchedFolders = fsWatcher->folders();
181 QJsonObject nativeDirs;
182 for (auto i = watchedFolders.cbegin(); i != watchedFolders.cend(); ++i)
184 const Path &watchedFolder = i.key();
185 const BitTorrent::AddTorrentParams params = i.value().addTorrentParams;
186 if (params.savePath.isEmpty())
187 nativeDirs.insert(watchedFolder.toString(), 1);
188 else if (params.savePath == watchedFolder)
189 nativeDirs.insert(watchedFolder.toString(), 0);
190 else
191 nativeDirs.insert(watchedFolder.toString(), params.savePath.toString());
193 data[u"scan_dirs"_s] = nativeDirs;
194 // === END DEPRECATED CODE === //
196 // Excluded file names
197 data[u"excluded_file_names_enabled"_s] = session->isExcludedFileNamesEnabled();
198 data[u"excluded_file_names"_s] = session->excludedFileNames().join(u'\n');
200 // Email notification upon download completion
201 data[u"mail_notification_enabled"_s] = pref->isMailNotificationEnabled();
202 data[u"mail_notification_sender"_s] = pref->getMailNotificationSender();
203 data[u"mail_notification_email"_s] = pref->getMailNotificationEmail();
204 data[u"mail_notification_smtp"_s] = pref->getMailNotificationSMTP();
205 data[u"mail_notification_ssl_enabled"_s] = pref->getMailNotificationSMTPSSL();
206 data[u"mail_notification_auth_enabled"_s] = pref->getMailNotificationSMTPAuth();
207 data[u"mail_notification_username"_s] = pref->getMailNotificationSMTPUsername();
208 data[u"mail_notification_password"_s] = pref->getMailNotificationSMTPPassword();
209 // Run an external program on torrent added
210 data[u"autorun_on_torrent_added_enabled"_s] = pref->isAutoRunOnTorrentAddedEnabled();
211 data[u"autorun_on_torrent_added_program"_s] = pref->getAutoRunOnTorrentAddedProgram();
212 // Run an external program on torrent finished
213 data[u"autorun_enabled"_s] = pref->isAutoRunOnTorrentFinishedEnabled();
214 data[u"autorun_program"_s] = pref->getAutoRunOnTorrentFinishedProgram();
216 // Connection
217 // Listening Port
218 data[u"listen_port"_s] = session->port();
219 data[u"ssl_enabled"_s] = session->isSSLEnabled();
220 data[u"ssl_listen_port"_s] = session->sslPort();
221 data[u"random_port"_s] = (session->port() == 0); // deprecated
222 data[u"upnp"_s] = Net::PortForwarder::instance()->isEnabled();
223 // Connections Limits
224 data[u"max_connec"_s] = session->maxConnections();
225 data[u"max_connec_per_torrent"_s] = session->maxConnectionsPerTorrent();
226 data[u"max_uploads"_s] = session->maxUploads();
227 data[u"max_uploads_per_torrent"_s] = session->maxUploadsPerTorrent();
229 // I2P
230 data[u"i2p_enabled"_s] = session->isI2PEnabled();
231 data[u"i2p_address"_s] = session->I2PAddress();
232 data[u"i2p_port"_s] = session->I2PPort();
233 data[u"i2p_mixed_mode"_s] = session->I2PMixedMode();
234 data[u"i2p_inbound_quantity"_s] = session->I2PInboundQuantity();
235 data[u"i2p_outbound_quantity"_s] = session->I2POutboundQuantity();
236 data[u"i2p_inbound_length"_s] = session->I2PInboundLength();
237 data[u"i2p_outbound_length"_s] = session->I2POutboundLength();
239 // Proxy Server
240 const auto *proxyManager = Net::ProxyConfigurationManager::instance();
241 Net::ProxyConfiguration proxyConf = proxyManager->proxyConfiguration();
242 data[u"proxy_type"_s] = Utils::String::fromEnum(proxyConf.type);
243 data[u"proxy_ip"_s] = proxyConf.ip;
244 data[u"proxy_port"_s] = proxyConf.port;
245 data[u"proxy_auth_enabled"_s] = proxyConf.authEnabled;
246 data[u"proxy_username"_s] = proxyConf.username;
247 data[u"proxy_password"_s] = proxyConf.password;
248 data[u"proxy_hostname_lookup"_s] = proxyConf.hostnameLookupEnabled;
250 data[u"proxy_bittorrent"_s] = pref->useProxyForBT();
251 data[u"proxy_peer_connections"_s] = session->isProxyPeerConnectionsEnabled();
252 data[u"proxy_rss"_s] = pref->useProxyForRSS();
253 data[u"proxy_misc"_s] = pref->useProxyForGeneralPurposes();
255 // IP Filtering
256 data[u"ip_filter_enabled"_s] = session->isIPFilteringEnabled();
257 data[u"ip_filter_path"_s] = session->IPFilterFile().toString();
258 data[u"ip_filter_trackers"_s] = session->isTrackerFilteringEnabled();
259 data[u"banned_IPs"_s] = session->bannedIPs().join(u'\n');
261 // Speed
262 // Global Rate Limits
263 data[u"dl_limit"_s] = session->globalDownloadSpeedLimit();
264 data[u"up_limit"_s] = session->globalUploadSpeedLimit();
265 data[u"alt_dl_limit"_s] = session->altGlobalDownloadSpeedLimit();
266 data[u"alt_up_limit"_s] = session->altGlobalUploadSpeedLimit();
267 data[u"bittorrent_protocol"_s] = static_cast<int>(session->btProtocol());
268 data[u"limit_utp_rate"_s] = session->isUTPRateLimited();
269 data[u"limit_tcp_overhead"_s] = session->includeOverheadInLimits();
270 data[u"limit_lan_peers"_s] = !session->ignoreLimitsOnLAN();
271 // Scheduling
272 data[u"scheduler_enabled"_s] = session->isBandwidthSchedulerEnabled();
273 const QTime start_time = pref->getSchedulerStartTime();
274 data[u"schedule_from_hour"_s] = start_time.hour();
275 data[u"schedule_from_min"_s] = start_time.minute();
276 const QTime end_time = pref->getSchedulerEndTime();
277 data[u"schedule_to_hour"_s] = end_time.hour();
278 data[u"schedule_to_min"_s] = end_time.minute();
279 data[u"scheduler_days"_s] = static_cast<int>(pref->getSchedulerDays());
281 // Bittorrent
282 // Privacy
283 data[u"dht"_s] = session->isDHTEnabled();
284 data[u"pex"_s] = session->isPeXEnabled();
285 data[u"lsd"_s] = session->isLSDEnabled();
286 data[u"encryption"_s] = session->encryption();
287 data[u"anonymous_mode"_s] = session->isAnonymousModeEnabled();
288 // Max active checking torrents
289 data[u"max_active_checking_torrents"_s] = session->maxActiveCheckingTorrents();
290 // Torrent Queueing
291 data[u"queueing_enabled"_s] = session->isQueueingSystemEnabled();
292 data[u"max_active_downloads"_s] = session->maxActiveDownloads();
293 data[u"max_active_torrents"_s] = session->maxActiveTorrents();
294 data[u"max_active_uploads"_s] = session->maxActiveUploads();
295 data[u"dont_count_slow_torrents"_s] = session->ignoreSlowTorrentsForQueueing();
296 data[u"slow_torrent_dl_rate_threshold"_s] = session->downloadRateForSlowTorrents();
297 data[u"slow_torrent_ul_rate_threshold"_s] = session->uploadRateForSlowTorrents();
298 data[u"slow_torrent_inactive_timer"_s] = session->slowTorrentsInactivityTimer();
299 // Share Ratio Limiting
300 data[u"max_ratio_enabled"_s] = (session->globalMaxRatio() >= 0.);
301 data[u"max_ratio"_s] = session->globalMaxRatio();
302 data[u"max_seeding_time_enabled"_s] = (session->globalMaxSeedingMinutes() >= 0.);
303 data[u"max_seeding_time"_s] = session->globalMaxSeedingMinutes();
304 data[u"max_inactive_seeding_time_enabled"_s] = (session->globalMaxInactiveSeedingMinutes() >= 0.);
305 data[u"max_inactive_seeding_time"_s] = session->globalMaxInactiveSeedingMinutes();
306 data[u"max_ratio_act"_s] = static_cast<int>(session->shareLimitAction());
307 // Add trackers
308 data[u"add_trackers_enabled"_s] = session->isAddTrackersEnabled();
309 data[u"add_trackers"_s] = session->additionalTrackers();
311 // WebUI
312 // HTTP Server
313 data[u"web_ui_domain_list"_s] = pref->getServerDomains();
314 data[u"web_ui_address"_s] = pref->getWebUIAddress();
315 data[u"web_ui_port"_s] = pref->getWebUIPort();
316 data[u"web_ui_upnp"_s] = pref->useUPnPForWebUIPort();
317 data[u"use_https"_s] = pref->isWebUIHttpsEnabled();
318 data[u"web_ui_https_cert_path"_s] = pref->getWebUIHttpsCertificatePath().toString();
319 data[u"web_ui_https_key_path"_s] = pref->getWebUIHttpsKeyPath().toString();
320 // Authentication
321 data[u"web_ui_username"_s] = pref->getWebUIUsername();
322 data[u"bypass_local_auth"_s] = !pref->isWebUILocalAuthEnabled();
323 data[u"bypass_auth_subnet_whitelist_enabled"_s] = pref->isWebUIAuthSubnetWhitelistEnabled();
324 QStringList authSubnetWhitelistStringList;
325 for (const Utils::Net::Subnet &subnet : asConst(pref->getWebUIAuthSubnetWhitelist()))
326 authSubnetWhitelistStringList << Utils::Net::subnetToString(subnet);
327 data[u"bypass_auth_subnet_whitelist"_s] = authSubnetWhitelistStringList.join(u'\n');
328 data[u"web_ui_max_auth_fail_count"_s] = pref->getWebUIMaxAuthFailCount();
329 data[u"web_ui_ban_duration"_s] = static_cast<int>(pref->getWebUIBanDuration().count());
330 data[u"web_ui_session_timeout"_s] = pref->getWebUISessionTimeout();
331 // Use alternative WebUI
332 data[u"alternative_webui_enabled"_s] = pref->isAltWebUIEnabled();
333 data[u"alternative_webui_path"_s] = pref->getWebUIRootFolder().toString();
334 // Security
335 data[u"web_ui_clickjacking_protection_enabled"_s] = pref->isWebUIClickjackingProtectionEnabled();
336 data[u"web_ui_csrf_protection_enabled"_s] = pref->isWebUICSRFProtectionEnabled();
337 data[u"web_ui_secure_cookie_enabled"_s] = pref->isWebUISecureCookieEnabled();
338 data[u"web_ui_host_header_validation_enabled"_s] = pref->isWebUIHostHeaderValidationEnabled();
339 // Custom HTTP headers
340 data[u"web_ui_use_custom_http_headers_enabled"_s] = pref->isWebUICustomHTTPHeadersEnabled();
341 data[u"web_ui_custom_http_headers"_s] = pref->getWebUICustomHTTPHeaders();
342 // Reverse proxy
343 data[u"web_ui_reverse_proxy_enabled"_s] = pref->isWebUIReverseProxySupportEnabled();
344 data[u"web_ui_reverse_proxies_list"_s] = pref->getWebUITrustedReverseProxiesList();
345 // Update my dynamic domain name
346 data[u"dyndns_enabled"_s] = pref->isDynDNSEnabled();
347 data[u"dyndns_service"_s] = static_cast<int>(pref->getDynDNSService());
348 data[u"dyndns_username"_s] = pref->getDynDNSUsername();
349 data[u"dyndns_password"_s] = pref->getDynDNSPassword();
350 data[u"dyndns_domain"_s] = pref->getDynDomainName();
352 // RSS settings
353 data[u"rss_refresh_interval"_s] = RSS::Session::instance()->refreshInterval();
354 data[u"rss_fetch_delay"_s] = static_cast<qlonglong>(RSS::Session::instance()->fetchDelay().count());
355 data[u"rss_max_articles_per_feed"_s] = RSS::Session::instance()->maxArticlesPerFeed();
356 data[u"rss_processing_enabled"_s] = RSS::Session::instance()->isProcessingEnabled();
357 data[u"rss_auto_downloading_enabled"_s] = RSS::AutoDownloader::instance()->isProcessingEnabled();
358 data[u"rss_download_repack_proper_episodes"_s] = RSS::AutoDownloader::instance()->downloadRepacks();
359 data[u"rss_smart_episode_filters"_s] = RSS::AutoDownloader::instance()->smartEpisodeFilters().join(u'\n');
361 // Advanced settings
362 // qBitorrent preferences
363 // Resume data storage type
364 data[u"resume_data_storage_type"_s] = Utils::String::fromEnum(session->resumeDataStorageType());
365 // Torrent content removing mode
366 data[u"torrent_content_remove_option"_s] = Utils::String::fromEnum(session->torrentContentRemoveOption());
367 // Physical memory (RAM) usage limit
368 data[u"memory_working_set_limit"_s] = app()->memoryWorkingSetLimit();
369 // Current network interface
370 data[u"current_network_interface"_s] = session->networkInterface();
371 // Current network interface name
372 data[u"current_interface_name"_s] = session->networkInterfaceName();
373 // Current network interface address
374 data[u"current_interface_address"_s] = session->networkInterfaceAddress();
375 // Save resume data interval
376 data[u"save_resume_data_interval"_s] = session->saveResumeDataInterval();
377 // Save statistics interval
378 data[u"save_statistics_interval"_s] = static_cast<int>(session->saveStatisticsInterval().count());
379 // .torrent file size limit
380 data[u"torrent_file_size_limit"_s] = pref->getTorrentFileSizeLimit();
381 // Confirm torrent recheck
382 data[u"confirm_torrent_recheck"_s] = pref->confirmTorrentRecheck();
383 // Recheck completed torrents
384 data[u"recheck_completed_torrents"_s] = pref->recheckTorrentsOnCompletion();
385 // Customize application instance name
386 data[u"app_instance_name"_s] = app()->instanceName();
387 // Refresh interval
388 data[u"refresh_interval"_s] = session->refreshInterval();
389 // Resolve peer countries
390 data[u"resolve_peer_countries"_s] = pref->resolvePeerCountries();
391 // Reannounce to all trackers when ip/port changed
392 data[u"reannounce_when_address_changed"_s] = session->isReannounceWhenAddressChangedEnabled();
393 // Embedded tracker
394 data[u"enable_embedded_tracker"_s] = session->isTrackerEnabled();
395 data[u"embedded_tracker_port"_s] = pref->getTrackerPort();
396 data[u"embedded_tracker_port_forwarding"_s] = pref->isTrackerPortForwardingEnabled();
397 // Mark-of-the-Web
398 data[u"mark_of_the_web"_s] = pref->isMarkOfTheWebEnabled();
399 // Ignore SSL errors
400 data[u"ignore_ssl_errors"_s] = pref->isIgnoreSSLErrors();
401 // Python executable path
402 data[u"python_executable_path"_s] = pref->getPythonExecutablePath().toString();
404 // libtorrent preferences
405 // Bdecode depth limit
406 data[u"bdecode_depth_limit"_s] = pref->getBdecodeDepthLimit();
407 // Bdecode token limit
408 data[u"bdecode_token_limit"_s] = pref->getBdecodeTokenLimit();
409 // Async IO threads
410 data[u"async_io_threads"_s] = session->asyncIOThreads();
411 // Hashing threads
412 data[u"hashing_threads"_s] = session->hashingThreads();
413 // File pool size
414 data[u"file_pool_size"_s] = session->filePoolSize();
415 // Checking memory usage
416 data[u"checking_memory_use"_s] = session->checkingMemUsage();
417 // Disk write cache
418 data[u"disk_cache"_s] = session->diskCacheSize();
419 data[u"disk_cache_ttl"_s] = session->diskCacheTTL();
420 // Disk queue size
421 data[u"disk_queue_size"_s] = session->diskQueueSize();
422 // Disk IO Type
423 data[u"disk_io_type"_s] = static_cast<int>(session->diskIOType());
424 // Disk IO read mode
425 data[u"disk_io_read_mode"_s] = static_cast<int>(session->diskIOReadMode());
426 // Disk IO write mode
427 data[u"disk_io_write_mode"_s] = static_cast<int>(session->diskIOWriteMode());
428 // Coalesce reads & writes
429 data[u"enable_coalesce_read_write"_s] = session->isCoalesceReadWriteEnabled();
430 // Piece Extent Affinity
431 data[u"enable_piece_extent_affinity"_s] = session->usePieceExtentAffinity();
432 // Suggest mode
433 data[u"enable_upload_suggestions"_s] = session->isSuggestModeEnabled();
434 // Send buffer watermark
435 data[u"send_buffer_watermark"_s] = session->sendBufferWatermark();
436 data[u"send_buffer_low_watermark"_s] = session->sendBufferLowWatermark();
437 data[u"send_buffer_watermark_factor"_s] = session->sendBufferWatermarkFactor();
438 // Outgoing connections per second
439 data[u"connection_speed"_s] = session->connectionSpeed();
440 // Socket send buffer size
441 data[u"socket_send_buffer_size"_s] = session->socketSendBufferSize();
442 // Socket receive buffer size
443 data[u"socket_receive_buffer_size"_s] = session->socketReceiveBufferSize();
444 // Socket listen backlog size
445 data[u"socket_backlog_size"_s] = session->socketBacklogSize();
446 // Outgoing ports
447 data[u"outgoing_ports_min"_s] = session->outgoingPortsMin();
448 data[u"outgoing_ports_max"_s] = session->outgoingPortsMax();
449 // UPnP lease duration
450 data[u"upnp_lease_duration"_s] = session->UPnPLeaseDuration();
451 // Type of service
452 data[u"peer_tos"_s] = session->peerToS();
453 // uTP-TCP mixed mode
454 data[u"utp_tcp_mixed_mode"_s] = static_cast<int>(session->utpMixedMode());
455 // Support internationalized domain name (IDN)
456 data[u"idn_support_enabled"_s] = session->isIDNSupportEnabled();
457 // Multiple connections per IP
458 data[u"enable_multi_connections_from_same_ip"_s] = session->multiConnectionsPerIpEnabled();
459 // Validate HTTPS tracker certificate
460 data[u"validate_https_tracker_certificate"_s] = session->validateHTTPSTrackerCertificate();
461 // SSRF mitigation
462 data[u"ssrf_mitigation"_s] = session->isSSRFMitigationEnabled();
463 // Disallow connection to peers on privileged ports
464 data[u"block_peers_on_privileged_ports"_s] = session->blockPeersOnPrivilegedPorts();
465 // Choking algorithm
466 data[u"upload_slots_behavior"_s] = static_cast<int>(session->chokingAlgorithm());
467 // Seed choking algorithm
468 data[u"upload_choking_algorithm"_s] = static_cast<int>(session->seedChokingAlgorithm());
469 // Announce
470 data[u"announce_to_all_trackers"_s] = session->announceToAllTrackers();
471 data[u"announce_to_all_tiers"_s] = session->announceToAllTiers();
472 data[u"announce_ip"_s] = session->announceIP();
473 data[u"max_concurrent_http_announces"_s] = session->maxConcurrentHTTPAnnounces();
474 data[u"stop_tracker_timeout"_s] = session->stopTrackerTimeout();
475 // Peer Turnover
476 data[u"peer_turnover"_s] = session->peerTurnover();
477 data[u"peer_turnover_cutoff"_s] = session->peerTurnoverCutoff();
478 data[u"peer_turnover_interval"_s] = session->peerTurnoverInterval();
479 // Maximum outstanding requests to a single peer
480 data[u"request_queue_size"_s] = session->requestQueueSize();
481 // DHT bootstrap nodes
482 data[u"dht_bootstrap_nodes"_s] = session->getDHTBootstrapNodes();
484 setResult(data);
487 void AppController::setPreferencesAction()
489 requireParams({u"json"_s});
491 auto *pref = Preferences::instance();
492 auto *session = BitTorrent::Session::instance();
493 const QVariantHash m = QJsonDocument::fromJson(params()[u"json"_s].toUtf8()).toVariant().toHash();
495 QVariantHash::ConstIterator it;
496 const auto hasKey = [&it, &m](const QString &key) -> bool
498 it = m.find(key);
499 return (it != m.constEnd());
502 // Behavior
503 // Language
504 if (hasKey(u"locale"_s))
506 QString locale = it.value().toString();
507 if (pref->getLocale() != locale)
509 auto *translator = new QTranslator;
510 if (translator->load(u":/lang/qbittorrent_"_s + locale))
512 qDebug("%s locale recognized, using translation.", qUtf8Printable(locale));
514 else
516 qDebug("%s locale unrecognized, using default (en).", qUtf8Printable(locale));
518 qApp->installTranslator(translator);
520 pref->setLocale(locale);
523 if (hasKey(u"status_bar_external_ip"_s))
524 pref->setStatusbarExternalIPDisplayed(it.value().toBool());
525 if (hasKey(u"performance_warning"_s))
526 session->setPerformanceWarningEnabled(it.value().toBool());
527 // Transfer List
528 if (hasKey(u"confirm_torrent_deletion"_s))
529 pref->setConfirmTorrentDeletion(it.value().toBool());
530 // Log file
531 if (hasKey(u"file_log_enabled"_s))
532 app()->setFileLoggerEnabled(it.value().toBool());
533 if (hasKey(u"file_log_path"_s))
534 app()->setFileLoggerPath(Path(it.value().toString()));
535 if (hasKey(u"file_log_backup_enabled"_s))
536 app()->setFileLoggerBackup(it.value().toBool());
537 if (hasKey(u"file_log_max_size"_s))
538 app()->setFileLoggerMaxSize(it.value().toInt() * 1024);
539 if (hasKey(u"file_log_delete_old"_s))
540 app()->setFileLoggerDeleteOld(it.value().toBool());
541 if (hasKey(u"file_log_age"_s))
542 app()->setFileLoggerAge(it.value().toInt());
543 if (hasKey(u"file_log_age_type"_s))
544 app()->setFileLoggerAgeType(it.value().toInt());
545 // Delete torrent content files on torrent removal
546 if (hasKey(u"delete_torrent_content_files"_s))
547 pref->setRemoveTorrentContent(it.value().toBool());
549 // Downloads
550 // When adding a torrent
551 if (hasKey(u"torrent_content_layout"_s))
552 session->setTorrentContentLayout(Utils::String::toEnum(it.value().toString(), BitTorrent::TorrentContentLayout::Original));
553 if (hasKey(u"add_to_top_of_queue"_s))
554 session->setAddTorrentToQueueTop(it.value().toBool());
555 if (hasKey(u"add_stopped_enabled"_s))
556 session->setAddTorrentStopped(it.value().toBool());
557 if (hasKey(u"torrent_stop_condition"_s))
558 session->setTorrentStopCondition(Utils::String::toEnum(it.value().toString(), BitTorrent::Torrent::StopCondition::None));
559 if (hasKey(u"merge_trackers"_s))
560 session->setMergeTrackersEnabled(it.value().toBool());
561 if (hasKey(u"auto_delete_mode"_s))
562 TorrentFileGuard::setAutoDeleteMode(static_cast<TorrentFileGuard::AutoDeleteMode>(it.value().toInt()));
564 if (hasKey(u"preallocate_all"_s))
565 session->setPreallocationEnabled(it.value().toBool());
566 if (hasKey(u"incomplete_files_ext"_s))
567 session->setAppendExtensionEnabled(it.value().toBool());
568 if (hasKey(u"use_unwanted_folder"_s))
569 session->setUnwantedFolderEnabled(it.value().toBool());
571 // Saving Management
572 if (hasKey(u"auto_tmm_enabled"_s))
573 session->setAutoTMMDisabledByDefault(!it.value().toBool());
574 if (hasKey(u"torrent_changed_tmm_enabled"_s))
575 session->setDisableAutoTMMWhenCategoryChanged(!it.value().toBool());
576 if (hasKey(u"save_path_changed_tmm_enabled"_s))
577 session->setDisableAutoTMMWhenDefaultSavePathChanged(!it.value().toBool());
578 if (hasKey(u"category_changed_tmm_enabled"_s))
579 session->setDisableAutoTMMWhenCategorySavePathChanged(!it.value().toBool());
580 if (hasKey(u"use_subcategories"_s))
581 session->setSubcategoriesEnabled(it.value().toBool());
582 if (hasKey(u"save_path"_s))
583 session->setSavePath(Path(it.value().toString()));
584 if (hasKey(u"temp_path_enabled"_s))
585 session->setDownloadPathEnabled(it.value().toBool());
586 if (hasKey(u"temp_path"_s))
587 session->setDownloadPath(Path(it.value().toString()));
588 if (hasKey(u"use_category_paths_in_manual_mode"_s))
589 session->setUseCategoryPathsInManualMode(it.value().toBool());
590 if (hasKey(u"export_dir"_s))
591 session->setTorrentExportDirectory(Path(it.value().toString()));
592 if (hasKey(u"export_dir_fin"_s))
593 session->setFinishedTorrentExportDirectory(Path(it.value().toString()));
595 // TODO: The following code is deprecated. Delete it once replaced by updated API method.
596 // === BEGIN DEPRECATED CODE === //
597 if (hasKey(u"scan_dirs"_s))
599 PathList scanDirs;
600 TorrentFilesWatcher *fsWatcher = TorrentFilesWatcher::instance();
601 const PathList oldScanDirs = fsWatcher->folders().keys();
602 const QVariantHash nativeDirs = it.value().toHash();
603 for (auto i = nativeDirs.cbegin(); i != nativeDirs.cend(); ++i)
607 const Path watchedFolder {i.key()};
608 TorrentFilesWatcher::WatchedFolderOptions options = fsWatcher->folders().value(watchedFolder);
609 BitTorrent::AddTorrentParams &params = options.addTorrentParams;
611 bool isInt = false;
612 const int intVal = i.value().toInt(&isInt);
613 if (isInt)
615 if (intVal == 0)
617 params.savePath = watchedFolder;
618 params.useAutoTMM = false;
621 else
623 const Path customSavePath {i.value().toString()};
624 params.savePath = customSavePath;
625 params.useAutoTMM = false;
628 fsWatcher->setWatchedFolder(watchedFolder, options);
629 scanDirs.append(watchedFolder);
631 catch (...)
636 // Update deleted folders
637 for (const Path &path : oldScanDirs)
639 if (!scanDirs.contains(path))
640 fsWatcher->removeWatchedFolder(path);
643 // === END DEPRECATED CODE === //
645 // Excluded file names
646 if (hasKey(u"excluded_file_names_enabled"_s))
647 session->setExcludedFileNamesEnabled(it.value().toBool());
648 if (hasKey(u"excluded_file_names"_s))
649 session->setExcludedFileNames(it.value().toString().split(u'\n'));
651 // Email notification upon download completion
652 if (hasKey(u"mail_notification_enabled"_s))
653 pref->setMailNotificationEnabled(it.value().toBool());
654 if (hasKey(u"mail_notification_sender"_s))
655 pref->setMailNotificationSender(it.value().toString());
656 if (hasKey(u"mail_notification_email"_s))
657 pref->setMailNotificationEmail(it.value().toString());
658 if (hasKey(u"mail_notification_smtp"_s))
659 pref->setMailNotificationSMTP(it.value().toString());
660 if (hasKey(u"mail_notification_ssl_enabled"_s))
661 pref->setMailNotificationSMTPSSL(it.value().toBool());
662 if (hasKey(u"mail_notification_auth_enabled"_s))
663 pref->setMailNotificationSMTPAuth(it.value().toBool());
664 if (hasKey(u"mail_notification_username"_s))
665 pref->setMailNotificationSMTPUsername(it.value().toString());
666 if (hasKey(u"mail_notification_password"_s))
667 pref->setMailNotificationSMTPPassword(it.value().toString());
668 // Run an external program on torrent added
669 if (hasKey(u"autorun_on_torrent_added_enabled"_s))
670 pref->setAutoRunOnTorrentAddedEnabled(it.value().toBool());
671 if (hasKey(u"autorun_on_torrent_added_program"_s))
672 pref->setAutoRunOnTorrentAddedProgram(it.value().toString());
673 // Run an external program on torrent finished
674 if (hasKey(u"autorun_enabled"_s))
675 pref->setAutoRunOnTorrentFinishedEnabled(it.value().toBool());
676 if (hasKey(u"autorun_program"_s))
677 pref->setAutoRunOnTorrentFinishedProgram(it.value().toString());
679 // Connection
680 // Listening Port
681 if (hasKey(u"random_port"_s) && it.value().toBool()) // deprecated
683 session->setPort(0);
685 else if (hasKey(u"listen_port"_s))
687 session->setPort(it.value().toInt());
689 // SSL Torrents
690 if (hasKey(u"ssl_enabled"_s))
691 session->setSSLEnabled(it.value().toBool());
692 if (hasKey(u"ssl_listen_port"_s))
693 session->setSSLPort(it.value().toInt());
694 if (hasKey(u"upnp"_s))
695 Net::PortForwarder::instance()->setEnabled(it.value().toBool());
696 // Connections Limits
697 if (hasKey(u"max_connec"_s))
698 session->setMaxConnections(it.value().toInt());
699 if (hasKey(u"max_connec_per_torrent"_s))
700 session->setMaxConnectionsPerTorrent(it.value().toInt());
701 if (hasKey(u"max_uploads"_s))
702 session->setMaxUploads(it.value().toInt());
703 if (hasKey(u"max_uploads_per_torrent"_s))
704 session->setMaxUploadsPerTorrent(it.value().toInt());
706 // I2P
707 if (hasKey(u"i2p_enabled"_s))
708 session->setI2PEnabled(it.value().toBool());
709 if (hasKey(u"i2p_address"_s))
710 session->setI2PAddress(it.value().toString());
711 if (hasKey(u"i2p_port"_s))
712 session->setI2PPort(it.value().toInt());
713 if (hasKey(u"i2p_mixed_mode"_s))
714 session->setI2PMixedMode(it.value().toBool());
715 if (hasKey(u"i2p_inbound_quantity"_s))
716 session->setI2PInboundQuantity(it.value().toInt());
717 if (hasKey(u"i2p_outbound_quantity"_s))
718 session->setI2POutboundQuantity(it.value().toInt());
719 if (hasKey(u"i2p_inbound_length"_s))
720 session->setI2PInboundLength(it.value().toInt());
721 if (hasKey(u"i2p_outbound_length"_s))
722 session->setI2POutboundLength(it.value().toInt());
724 // Proxy Server
725 auto *proxyManager = Net::ProxyConfigurationManager::instance();
726 Net::ProxyConfiguration proxyConf = proxyManager->proxyConfiguration();
727 if (hasKey(u"proxy_type"_s))
728 proxyConf.type = Utils::String::toEnum(it.value().toString(), Net::ProxyType::None);
729 if (hasKey(u"proxy_ip"_s))
730 proxyConf.ip = it.value().toString();
731 if (hasKey(u"proxy_port"_s))
732 proxyConf.port = it.value().toUInt();
733 if (hasKey(u"proxy_auth_enabled"_s))
734 proxyConf.authEnabled = it.value().toBool();
735 if (hasKey(u"proxy_username"_s))
736 proxyConf.username = it.value().toString();
737 if (hasKey(u"proxy_password"_s))
738 proxyConf.password = it.value().toString();
739 if (hasKey(u"proxy_hostname_lookup"_s))
740 proxyConf.hostnameLookupEnabled = it.value().toBool();
741 proxyManager->setProxyConfiguration(proxyConf);
743 if (hasKey(u"proxy_bittorrent"_s))
744 pref->setUseProxyForBT(it.value().toBool());
745 if (hasKey(u"proxy_peer_connections"_s))
746 session->setProxyPeerConnectionsEnabled(it.value().toBool());
747 if (hasKey(u"proxy_rss"_s))
748 pref->setUseProxyForRSS(it.value().toBool());
749 if (hasKey(u"proxy_misc"_s))
750 pref->setUseProxyForGeneralPurposes(it.value().toBool());
752 // IP Filtering
753 if (hasKey(u"ip_filter_enabled"_s))
754 session->setIPFilteringEnabled(it.value().toBool());
755 if (hasKey(u"ip_filter_path"_s))
756 session->setIPFilterFile(Path(it.value().toString()));
757 if (hasKey(u"ip_filter_trackers"_s))
758 session->setTrackerFilteringEnabled(it.value().toBool());
759 if (hasKey(u"banned_IPs"_s))
760 session->setBannedIPs(it.value().toString().split(u'\n', Qt::SkipEmptyParts));
762 // Speed
763 // Global Rate Limits
764 if (hasKey(u"dl_limit"_s))
765 session->setGlobalDownloadSpeedLimit(it.value().toInt());
766 if (hasKey(u"up_limit"_s))
767 session->setGlobalUploadSpeedLimit(it.value().toInt());
768 if (hasKey(u"alt_dl_limit"_s))
769 session->setAltGlobalDownloadSpeedLimit(it.value().toInt());
770 if (hasKey(u"alt_up_limit"_s))
771 session->setAltGlobalUploadSpeedLimit(it.value().toInt());
772 if (hasKey(u"bittorrent_protocol"_s))
773 session->setBTProtocol(static_cast<BitTorrent::BTProtocol>(it.value().toInt()));
774 if (hasKey(u"limit_utp_rate"_s))
775 session->setUTPRateLimited(it.value().toBool());
776 if (hasKey(u"limit_tcp_overhead"_s))
777 session->setIncludeOverheadInLimits(it.value().toBool());
778 if (hasKey(u"limit_lan_peers"_s))
779 session->setIgnoreLimitsOnLAN(!it.value().toBool());
780 // Scheduling
781 if (hasKey(u"scheduler_enabled"_s))
782 session->setBandwidthSchedulerEnabled(it.value().toBool());
783 if (m.contains(u"schedule_from_hour"_s) && m.contains(u"schedule_from_min"_s))
784 pref->setSchedulerStartTime(QTime(m[u"schedule_from_hour"_s].toInt(), m[u"schedule_from_min"_s].toInt()));
785 if (m.contains(u"schedule_to_hour"_s) && m.contains(u"schedule_to_min"_s))
786 pref->setSchedulerEndTime(QTime(m[u"schedule_to_hour"_s].toInt(), m[u"schedule_to_min"_s].toInt()));
787 if (hasKey(u"scheduler_days"_s))
788 pref->setSchedulerDays(static_cast<Scheduler::Days>(it.value().toInt()));
790 // Bittorrent
791 // Privacy
792 if (hasKey(u"dht"_s))
793 session->setDHTEnabled(it.value().toBool());
794 if (hasKey(u"pex"_s))
795 session->setPeXEnabled(it.value().toBool());
796 if (hasKey(u"lsd"_s))
797 session->setLSDEnabled(it.value().toBool());
798 if (hasKey(u"encryption"_s))
799 session->setEncryption(it.value().toInt());
800 if (hasKey(u"anonymous_mode"_s))
801 session->setAnonymousModeEnabled(it.value().toBool());
802 // Max active checking torrents
803 if (hasKey(u"max_active_checking_torrents"_s))
804 session->setMaxActiveCheckingTorrents(it.value().toInt());
805 // Torrent Queueing
806 if (hasKey(u"queueing_enabled"_s))
807 session->setQueueingSystemEnabled(it.value().toBool());
808 if (hasKey(u"max_active_downloads"_s))
809 session->setMaxActiveDownloads(it.value().toInt());
810 if (hasKey(u"max_active_torrents"_s))
811 session->setMaxActiveTorrents(it.value().toInt());
812 if (hasKey(u"max_active_uploads"_s))
813 session->setMaxActiveUploads(it.value().toInt());
814 if (hasKey(u"dont_count_slow_torrents"_s))
815 session->setIgnoreSlowTorrentsForQueueing(it.value().toBool());
816 if (hasKey(u"slow_torrent_dl_rate_threshold"_s))
817 session->setDownloadRateForSlowTorrents(it.value().toInt());
818 if (hasKey(u"slow_torrent_ul_rate_threshold"_s))
819 session->setUploadRateForSlowTorrents(it.value().toInt());
820 if (hasKey(u"slow_torrent_inactive_timer"_s))
821 session->setSlowTorrentsInactivityTimer(it.value().toInt());
822 // Share Ratio Limiting
823 if (hasKey(u"max_ratio_enabled"_s))
825 if (it.value().toBool())
826 session->setGlobalMaxRatio(m[u"max_ratio"_s].toReal());
827 else
828 session->setGlobalMaxRatio(-1);
830 if (hasKey(u"max_seeding_time_enabled"_s))
832 if (it.value().toBool())
833 session->setGlobalMaxSeedingMinutes(m[u"max_seeding_time"_s].toInt());
834 else
835 session->setGlobalMaxSeedingMinutes(-1);
837 if (hasKey(u"max_inactive_seeding_time_enabled"_s))
839 session->setGlobalMaxInactiveSeedingMinutes(it.value().toBool()
840 ? m[u"max_inactive_seeding_time"_s].toInt() : -1);
842 if (hasKey(u"max_ratio_act"_s))
844 switch (it.value().toInt())
846 default:
847 case 0:
848 session->setShareLimitAction(BitTorrent::ShareLimitAction::Stop);
849 break;
850 case 1:
851 session->setShareLimitAction(BitTorrent::ShareLimitAction::Remove);
852 break;
853 case 2:
854 session->setShareLimitAction(BitTorrent::ShareLimitAction::EnableSuperSeeding);
855 break;
856 case 3:
857 session->setShareLimitAction(BitTorrent::ShareLimitAction::RemoveWithContent);
858 break;
861 // Add trackers
862 if (hasKey(u"add_trackers_enabled"_s))
863 session->setAddTrackersEnabled(it.value().toBool());
864 if (hasKey(u"add_trackers"_s))
865 session->setAdditionalTrackers(it.value().toString());
867 // WebUI
868 // HTTP Server
869 if (hasKey(u"web_ui_domain_list"_s))
870 pref->setServerDomains(it.value().toString());
871 if (hasKey(u"web_ui_address"_s))
872 pref->setWebUIAddress(it.value().toString());
873 if (hasKey(u"web_ui_port"_s))
874 pref->setWebUIPort(it.value().value<quint16>());
875 if (hasKey(u"web_ui_upnp"_s))
876 pref->setUPnPForWebUIPort(it.value().toBool());
877 if (hasKey(u"use_https"_s))
878 pref->setWebUIHttpsEnabled(it.value().toBool());
879 if (hasKey(u"web_ui_https_cert_path"_s))
880 pref->setWebUIHttpsCertificatePath(Path(it.value().toString()));
881 if (hasKey(u"web_ui_https_key_path"_s))
882 pref->setWebUIHttpsKeyPath(Path(it.value().toString()));
883 // Authentication
884 if (hasKey(u"web_ui_username"_s))
885 pref->setWebUIUsername(it.value().toString());
886 if (hasKey(u"web_ui_password"_s))
887 pref->setWebUIPassword(Utils::Password::PBKDF2::generate(it.value().toByteArray()));
888 if (hasKey(u"bypass_local_auth"_s))
889 pref->setWebUILocalAuthEnabled(!it.value().toBool());
890 if (hasKey(u"bypass_auth_subnet_whitelist_enabled"_s))
891 pref->setWebUIAuthSubnetWhitelistEnabled(it.value().toBool());
892 if (hasKey(u"bypass_auth_subnet_whitelist"_s))
894 // recognize new lines and commas as delimiters
895 pref->setWebUIAuthSubnetWhitelist(it.value().toString().split(QRegularExpression(u"\n|,"_s), Qt::SkipEmptyParts));
897 if (hasKey(u"web_ui_max_auth_fail_count"_s))
898 pref->setWebUIMaxAuthFailCount(it.value().toInt());
899 if (hasKey(u"web_ui_ban_duration"_s))
900 pref->setWebUIBanDuration(std::chrono::seconds {it.value().toInt()});
901 if (hasKey(u"web_ui_session_timeout"_s))
902 pref->setWebUISessionTimeout(it.value().toInt());
903 // Use alternative WebUI
904 if (hasKey(u"alternative_webui_enabled"_s))
905 pref->setAltWebUIEnabled(it.value().toBool());
906 if (hasKey(u"alternative_webui_path"_s))
907 pref->setWebUIRootFolder(Path(it.value().toString()));
908 // Security
909 if (hasKey(u"web_ui_clickjacking_protection_enabled"_s))
910 pref->setWebUIClickjackingProtectionEnabled(it.value().toBool());
911 if (hasKey(u"web_ui_csrf_protection_enabled"_s))
912 pref->setWebUICSRFProtectionEnabled(it.value().toBool());
913 if (hasKey(u"web_ui_secure_cookie_enabled"_s))
914 pref->setWebUISecureCookieEnabled(it.value().toBool());
915 if (hasKey(u"web_ui_host_header_validation_enabled"_s))
916 pref->setWebUIHostHeaderValidationEnabled(it.value().toBool());
917 // Custom HTTP headers
918 if (hasKey(u"web_ui_use_custom_http_headers_enabled"_s))
919 pref->setWebUICustomHTTPHeadersEnabled(it.value().toBool());
920 if (hasKey(u"web_ui_custom_http_headers"_s))
921 pref->setWebUICustomHTTPHeaders(it.value().toString());
922 // Reverse proxy
923 if (hasKey(u"web_ui_reverse_proxy_enabled"_s))
924 pref->setWebUIReverseProxySupportEnabled(it.value().toBool());
925 if (hasKey(u"web_ui_reverse_proxies_list"_s))
926 pref->setWebUITrustedReverseProxiesList(it.value().toString());
927 // Update my dynamic domain name
928 if (hasKey(u"dyndns_enabled"_s))
929 pref->setDynDNSEnabled(it.value().toBool());
930 if (hasKey(u"dyndns_service"_s))
931 pref->setDynDNSService(static_cast<DNS::Service>(it.value().toInt()));
932 if (hasKey(u"dyndns_username"_s))
933 pref->setDynDNSUsername(it.value().toString());
934 if (hasKey(u"dyndns_password"_s))
935 pref->setDynDNSPassword(it.value().toString());
936 if (hasKey(u"dyndns_domain"_s))
937 pref->setDynDomainName(it.value().toString());
939 if (hasKey(u"rss_refresh_interval"_s))
940 RSS::Session::instance()->setRefreshInterval(it.value().toInt());
941 if (hasKey(u"rss_fetch_delay"_s))
942 RSS::Session::instance()->setFetchDelay(std::chrono::seconds(it.value().toLongLong()));
943 if (hasKey(u"rss_max_articles_per_feed"_s))
944 RSS::Session::instance()->setMaxArticlesPerFeed(it.value().toInt());
945 if (hasKey(u"rss_processing_enabled"_s))
946 RSS::Session::instance()->setProcessingEnabled(it.value().toBool());
947 if (hasKey(u"rss_auto_downloading_enabled"_s))
948 RSS::AutoDownloader::instance()->setProcessingEnabled(it.value().toBool());
949 if (hasKey(u"rss_download_repack_proper_episodes"_s))
950 RSS::AutoDownloader::instance()->setDownloadRepacks(it.value().toBool());
951 if (hasKey(u"rss_smart_episode_filters"_s))
952 RSS::AutoDownloader::instance()->setSmartEpisodeFilters(it.value().toString().split(u'\n'));
954 // Advanced settings
955 // qBittorrent preferences
956 // Resume data storage type
957 if (hasKey(u"resume_data_storage_type"_s))
958 session->setResumeDataStorageType(Utils::String::toEnum(it.value().toString(), BitTorrent::ResumeDataStorageType::Legacy));
959 // Torrent content removing mode
960 if (hasKey(u"torrent_content_remove_option"_s))
961 session->setTorrentContentRemoveOption(Utils::String::toEnum(it.value().toString(), BitTorrent::TorrentContentRemoveOption::MoveToTrash));
962 // Physical memory (RAM) usage limit
963 if (hasKey(u"memory_working_set_limit"_s))
964 app()->setMemoryWorkingSetLimit(it.value().toInt());
965 // Current network interface
966 if (hasKey(u"current_network_interface"_s))
968 const QString ifaceValue {it.value().toString()};
970 const QList<QNetworkInterface> ifaces = QNetworkInterface::allInterfaces();
971 const auto ifacesIter = std::find_if(ifaces.cbegin(), ifaces.cend(), [&ifaceValue](const QNetworkInterface &iface)
973 return (!iface.addressEntries().isEmpty()) && (iface.name() == ifaceValue);
975 const QString ifaceName = (ifacesIter != ifaces.cend()) ? ifacesIter->humanReadableName() : QString {};
977 session->setNetworkInterface(ifaceValue);
978 if (!ifaceName.isEmpty() || ifaceValue.isEmpty())
979 session->setNetworkInterfaceName(ifaceName);
981 // Current network interface address
982 if (hasKey(u"current_interface_address"_s))
984 const QHostAddress ifaceAddress {it.value().toString().trimmed()};
985 session->setNetworkInterfaceAddress(ifaceAddress.isNull() ? QString {} : ifaceAddress.toString());
987 // Save resume data interval
988 if (hasKey(u"save_resume_data_interval"_s))
989 session->setSaveResumeDataInterval(it.value().toInt());
990 // Save statistics interval
991 if (hasKey(u"save_statistics_interval"_s))
992 session->setSaveStatisticsInterval(std::chrono::minutes(it.value().toInt()));
993 // .torrent file size limit
994 if (hasKey(u"torrent_file_size_limit"_s))
995 pref->setTorrentFileSizeLimit(it.value().toLongLong());
996 // Confirm torrent recheck
997 if (hasKey(u"confirm_torrent_recheck"_s))
998 pref->setConfirmTorrentRecheck(it.value().toBool());
999 // Recheck completed torrents
1000 if (hasKey(u"recheck_completed_torrents"_s))
1001 pref->recheckTorrentsOnCompletion(it.value().toBool());
1002 // Customize application instance name
1003 if (hasKey(u"app_instance_name"_s))
1004 app()->setInstanceName(it.value().toString());
1005 // Refresh interval
1006 if (hasKey(u"refresh_interval"_s))
1007 session->setRefreshInterval(it.value().toInt());
1008 // Resolve peer countries
1009 if (hasKey(u"resolve_peer_countries"_s))
1010 pref->resolvePeerCountries(it.value().toBool());
1011 // Reannounce to all trackers when ip/port changed
1012 if (hasKey(u"reannounce_when_address_changed"_s))
1013 session->setReannounceWhenAddressChangedEnabled(it.value().toBool());
1014 // Embedded tracker
1015 if (hasKey(u"embedded_tracker_port"_s))
1016 pref->setTrackerPort(it.value().toInt());
1017 if (hasKey(u"embedded_tracker_port_forwarding"_s))
1018 pref->setTrackerPortForwardingEnabled(it.value().toBool());
1019 if (hasKey(u"enable_embedded_tracker"_s))
1020 session->setTrackerEnabled(it.value().toBool());
1021 // Mark-of-the-Web
1022 if (hasKey(u"mark_of_the_web"_s))
1023 pref->setMarkOfTheWebEnabled(it.value().toBool());
1024 // Ignore SLL errors
1025 if (hasKey(u"ignore_ssl_errors"_s))
1026 pref->setIgnoreSSLErrors(it.value().toBool());
1027 // Python executable path
1028 if (hasKey(u"python_executable_path"_s))
1029 pref->setPythonExecutablePath(Path(it.value().toString()));
1031 // libtorrent preferences
1032 // Bdecode depth limit
1033 if (hasKey(u"bdecode_depth_limit"_s))
1034 pref->setBdecodeDepthLimit(it.value().toInt());
1035 // Bdecode token limit
1036 if (hasKey(u"bdecode_token_limit"_s))
1037 pref->setBdecodeTokenLimit(it.value().toInt());
1038 // Async IO threads
1039 if (hasKey(u"async_io_threads"_s))
1040 session->setAsyncIOThreads(it.value().toInt());
1041 // Hashing threads
1042 if (hasKey(u"hashing_threads"_s))
1043 session->setHashingThreads(it.value().toInt());
1044 // File pool size
1045 if (hasKey(u"file_pool_size"_s))
1046 session->setFilePoolSize(it.value().toInt());
1047 // Checking Memory Usage
1048 if (hasKey(u"checking_memory_use"_s))
1049 session->setCheckingMemUsage(it.value().toInt());
1050 // Disk write cache
1051 if (hasKey(u"disk_cache"_s))
1052 session->setDiskCacheSize(it.value().toInt());
1053 if (hasKey(u"disk_cache_ttl"_s))
1054 session->setDiskCacheTTL(it.value().toInt());
1055 // Disk queue size
1056 if (hasKey(u"disk_queue_size"_s))
1057 session->setDiskQueueSize(it.value().toLongLong());
1058 // Disk IO Type
1059 if (hasKey(u"disk_io_type"_s))
1060 session->setDiskIOType(static_cast<BitTorrent::DiskIOType>(it.value().toInt()));
1061 // Disk IO read mode
1062 if (hasKey(u"disk_io_read_mode"_s))
1063 session->setDiskIOReadMode(static_cast<BitTorrent::DiskIOReadMode>(it.value().toInt()));
1064 // Disk IO write mode
1065 if (hasKey(u"disk_io_write_mode"_s))
1066 session->setDiskIOWriteMode(static_cast<BitTorrent::DiskIOWriteMode>(it.value().toInt()));
1067 // Coalesce reads & writes
1068 if (hasKey(u"enable_coalesce_read_write"_s))
1069 session->setCoalesceReadWriteEnabled(it.value().toBool());
1070 // Piece extent affinity
1071 if (hasKey(u"enable_piece_extent_affinity"_s))
1072 session->setPieceExtentAffinity(it.value().toBool());
1073 // Suggest mode
1074 if (hasKey(u"enable_upload_suggestions"_s))
1075 session->setSuggestMode(it.value().toBool());
1076 // Send buffer watermark
1077 if (hasKey(u"send_buffer_watermark"_s))
1078 session->setSendBufferWatermark(it.value().toInt());
1079 if (hasKey(u"send_buffer_low_watermark"_s))
1080 session->setSendBufferLowWatermark(it.value().toInt());
1081 if (hasKey(u"send_buffer_watermark_factor"_s))
1082 session->setSendBufferWatermarkFactor(it.value().toInt());
1083 // Outgoing connections per second
1084 if (hasKey(u"connection_speed"_s))
1085 session->setConnectionSpeed(it.value().toInt());
1086 // Socket send buffer size
1087 if (hasKey(u"socket_send_buffer_size"_s))
1088 session->setSocketSendBufferSize(it.value().toInt());
1089 // Socket receive buffer size
1090 if (hasKey(u"socket_receive_buffer_size"_s))
1091 session->setSocketReceiveBufferSize(it.value().toInt());
1092 // Socket listen backlog size
1093 if (hasKey(u"socket_backlog_size"_s))
1094 session->setSocketBacklogSize(it.value().toInt());
1095 // Outgoing ports
1096 if (hasKey(u"outgoing_ports_min"_s))
1097 session->setOutgoingPortsMin(it.value().toInt());
1098 if (hasKey(u"outgoing_ports_max"_s))
1099 session->setOutgoingPortsMax(it.value().toInt());
1100 // UPnP lease duration
1101 if (hasKey(u"upnp_lease_duration"_s))
1102 session->setUPnPLeaseDuration(it.value().toInt());
1103 // Type of service
1104 if (hasKey(u"peer_tos"_s))
1105 session->setPeerToS(it.value().toInt());
1106 // uTP-TCP mixed mode
1107 if (hasKey(u"utp_tcp_mixed_mode"_s))
1108 session->setUtpMixedMode(static_cast<BitTorrent::MixedModeAlgorithm>(it.value().toInt()));
1109 // Support internationalized domain name (IDN)
1110 if (hasKey(u"idn_support_enabled"_s))
1111 session->setIDNSupportEnabled(it.value().toBool());
1112 // Multiple connections per IP
1113 if (hasKey(u"enable_multi_connections_from_same_ip"_s))
1114 session->setMultiConnectionsPerIpEnabled(it.value().toBool());
1115 // Validate HTTPS tracker certificate
1116 if (hasKey(u"validate_https_tracker_certificate"_s))
1117 session->setValidateHTTPSTrackerCertificate(it.value().toBool());
1118 // SSRF mitigation
1119 if (hasKey(u"ssrf_mitigation"_s))
1120 session->setSSRFMitigationEnabled(it.value().toBool());
1121 // Disallow connection to peers on privileged ports
1122 if (hasKey(u"block_peers_on_privileged_ports"_s))
1123 session->setBlockPeersOnPrivilegedPorts(it.value().toBool());
1124 // Choking algorithm
1125 if (hasKey(u"upload_slots_behavior"_s))
1126 session->setChokingAlgorithm(static_cast<BitTorrent::ChokingAlgorithm>(it.value().toInt()));
1127 // Seed choking algorithm
1128 if (hasKey(u"upload_choking_algorithm"_s))
1129 session->setSeedChokingAlgorithm(static_cast<BitTorrent::SeedChokingAlgorithm>(it.value().toInt()));
1130 // Announce
1131 if (hasKey(u"announce_to_all_trackers"_s))
1132 session->setAnnounceToAllTrackers(it.value().toBool());
1133 if (hasKey(u"announce_to_all_tiers"_s))
1134 session->setAnnounceToAllTiers(it.value().toBool());
1135 if (hasKey(u"announce_ip"_s))
1137 const QHostAddress announceAddr {it.value().toString().trimmed()};
1138 session->setAnnounceIP(announceAddr.isNull() ? QString {} : announceAddr.toString());
1140 if (hasKey(u"max_concurrent_http_announces"_s))
1141 session->setMaxConcurrentHTTPAnnounces(it.value().toInt());
1142 if (hasKey(u"stop_tracker_timeout"_s))
1143 session->setStopTrackerTimeout(it.value().toInt());
1144 // Peer Turnover
1145 if (hasKey(u"peer_turnover"_s))
1146 session->setPeerTurnover(it.value().toInt());
1147 if (hasKey(u"peer_turnover_cutoff"_s))
1148 session->setPeerTurnoverCutoff(it.value().toInt());
1149 if (hasKey(u"peer_turnover_interval"_s))
1150 session->setPeerTurnoverInterval(it.value().toInt());
1151 // Maximum outstanding requests to a single peer
1152 if (hasKey(u"request_queue_size"_s))
1153 session->setRequestQueueSize(it.value().toInt());
1154 // DHT bootstrap nodes
1155 if (hasKey(u"dht_bootstrap_nodes"_s))
1156 session->setDHTBootstrapNodes(it.value().toString());
1158 // Save preferences
1159 pref->apply();
1162 void AppController::defaultSavePathAction()
1164 setResult(BitTorrent::Session::instance()->savePath().toString());
1167 void AppController::sendTestEmailAction()
1169 app()->sendTestEmail();
1173 void AppController::getDirectoryContentAction()
1175 requireParams({u"dirPath"_s});
1177 const QString dirPath = params().value(u"dirPath"_s);
1178 if (dirPath.isEmpty() || dirPath.startsWith(u':'))
1179 throw APIError(APIErrorType::BadParams, tr("Invalid directory path"));
1181 const QDir dir {dirPath};
1182 if (!dir.isAbsolute())
1183 throw APIError(APIErrorType::BadParams, tr("Invalid directory path"));
1184 if (!dir.exists())
1185 throw APIError(APIErrorType::NotFound, tr("Directory does not exist"));
1187 const QString visibility = params().value(u"mode"_s, u"all"_s);
1189 const auto parseDirectoryContentMode = [](const QString &visibility) -> QDir::Filters
1191 if (visibility == u"dirs")
1192 return QDir::Dirs;
1193 if (visibility == u"files")
1194 return QDir::Files;
1195 if (visibility == u"all")
1196 return (QDir::Dirs | QDir::Files);
1197 throw APIError(APIErrorType::BadParams, tr("Invalid mode, allowed values: %1").arg(u"all, dirs, files"_s));
1200 QJsonArray ret;
1201 QDirIterator it {dirPath, (QDir::NoDotAndDotDot | parseDirectoryContentMode(visibility))};
1202 while (it.hasNext())
1203 ret.append(it.next());
1204 setResult(ret);
1207 void AppController::cookiesAction()
1209 const QList<QNetworkCookie> cookies = Net::DownloadManager::instance()->allCookies();
1210 QJsonArray ret;
1211 for (const QNetworkCookie &cookie : cookies)
1213 ret << QJsonObject {
1214 {KEY_COOKIE_NAME, QString::fromLatin1(cookie.name())},
1215 {KEY_COOKIE_DOMAIN, cookie.domain()},
1216 {KEY_COOKIE_PATH, cookie.path()},
1217 {KEY_COOKIE_VALUE, QString::fromLatin1(cookie.value())},
1218 {KEY_COOKIE_EXPIRATION_DATE, Utils::DateTime::toSecsSinceEpoch(cookie.expirationDate())},
1222 setResult(ret);
1225 void AppController::setCookiesAction()
1227 requireParams({u"cookies"_s});
1228 const QString cookiesParam {params()[u"cookies"_s].trimmed()};
1230 QJsonParseError jsonError;
1231 const auto cookiesJsonDocument = QJsonDocument::fromJson(cookiesParam.toUtf8(), &jsonError);
1232 if (jsonError.error != QJsonParseError::NoError)
1233 throw APIError(APIErrorType::BadParams, jsonError.errorString());
1234 if (!cookiesJsonDocument.isArray())
1235 throw APIError(APIErrorType::BadParams, tr("cookies must be array"));
1237 const QJsonArray cookiesJsonArr = cookiesJsonDocument.array();
1238 QList<QNetworkCookie> cookies;
1239 cookies.reserve(cookiesJsonArr.size());
1240 for (const QJsonValue &jsonVal : cookiesJsonArr)
1242 if (!jsonVal.isObject())
1243 throw APIError(APIErrorType::BadParams);
1245 QNetworkCookie cookie;
1246 const QJsonObject jsonObj = jsonVal.toObject();
1247 if (jsonObj.contains(KEY_COOKIE_NAME))
1248 cookie.setName(jsonObj.value(KEY_COOKIE_NAME).toString().toLatin1());
1249 if (jsonObj.contains(KEY_COOKIE_DOMAIN))
1250 cookie.setDomain(jsonObj.value(KEY_COOKIE_DOMAIN).toString());
1251 if (jsonObj.contains(KEY_COOKIE_PATH))
1252 cookie.setPath(jsonObj.value(KEY_COOKIE_PATH).toString());
1253 if (jsonObj.contains(KEY_COOKIE_VALUE))
1254 cookie.setValue(jsonObj.value(KEY_COOKIE_VALUE).toString().toUtf8());
1255 if (jsonObj.contains(KEY_COOKIE_EXPIRATION_DATE))
1256 cookie.setExpirationDate(QDateTime::fromSecsSinceEpoch(jsonObj.value(KEY_COOKIE_EXPIRATION_DATE).toInteger()));
1258 cookies << cookie;
1261 Net::DownloadManager::instance()->setAllCookies(cookies);
1264 void AppController::networkInterfaceListAction()
1266 QJsonArray ifaceList;
1267 for (const QNetworkInterface &iface : asConst(QNetworkInterface::allInterfaces()))
1269 if (!iface.addressEntries().isEmpty())
1271 ifaceList.append(QJsonObject
1273 {u"name"_s, iface.humanReadableName()},
1274 {u"value"_s, iface.name()}
1279 setResult(ifaceList);
1282 void AppController::networkInterfaceAddressListAction()
1284 requireParams({u"iface"_s});
1286 const QString ifaceName = params().value(u"iface"_s);
1287 QJsonArray addressList;
1289 const auto appendAddress = [&addressList](const QHostAddress &addr)
1291 if (addr.protocol() == QAbstractSocket::IPv6Protocol)
1292 addressList.append(Utils::Net::canonicalIPv6Addr(addr).toString());
1293 else
1294 addressList.append(addr.toString());
1297 if (ifaceName.isEmpty())
1299 for (const QHostAddress &addr : asConst(QNetworkInterface::allAddresses()))
1300 appendAddress(addr);
1302 else
1304 const QNetworkInterface iface = QNetworkInterface::interfaceFromName(ifaceName);
1305 for (const QNetworkAddressEntry &entry : asConst(iface.addressEntries()))
1306 appendAddress(entry.ip());
1309 setResult(addressList);