Repository: mailcow/mailcow-dockerized Branch: master Commit: f399c07c8577 Files: 2768 Total size: 20.0 MB Directory structure: gitextract_rwp4yo6n/ ├── .editorconfig ├── .github/ │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── Bug_report.yml │ │ ├── Feature_request.yml │ │ ├── config.yml │ │ └── pr_to_nighty_template.yml │ ├── PULL_REQUEST_TEMPLATE.md │ ├── renovate.json │ └── workflows/ │ ├── check_if_support_labeled.yml │ ├── check_prs_if_on_staging.yml │ ├── close_old_issues_and_prs.yml │ ├── image_builds.yml │ ├── pr_to_nightly.yml │ ├── rebuild_backup_image.yml │ └── update_postscreen_access_list.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SECURITY.md ├── _modules/ │ └── scripts/ │ ├── core.sh │ ├── ipv6_controller.sh │ ├── migrate_options.sh │ └── new_options.sh ├── data/ │ ├── Dockerfiles/ │ │ ├── acme/ │ │ │ ├── Dockerfile │ │ │ ├── acme.sh │ │ │ ├── expand6.sh │ │ │ ├── functions.sh │ │ │ ├── load-dns-config.sh │ │ │ ├── obtain-certificate-dns.sh │ │ │ ├── obtain-certificate.sh │ │ │ └── reload-configurations.sh │ │ ├── backup/ │ │ │ └── Dockerfile │ │ ├── clamd/ │ │ │ ├── Dockerfile │ │ │ ├── clamd.sh │ │ │ ├── clamdcheck.sh │ │ │ └── healthcheck.sh │ │ ├── dockerapi/ │ │ │ ├── Dockerfile │ │ │ ├── docker-entrypoint.sh │ │ │ ├── main.py │ │ │ └── modules/ │ │ │ ├── DockerApi.py │ │ │ └── __init__.py │ │ ├── dovecot/ │ │ │ ├── Dockerfile │ │ │ ├── clean_q_aged.sh │ │ │ ├── docker-entrypoint.sh │ │ │ ├── imapsync │ │ │ ├── imapsync_runner.pl │ │ │ ├── maildir_gc.sh │ │ │ ├── optimize-fts.sh │ │ │ ├── quarantine_notify.py │ │ │ ├── quota_notify.py │ │ │ ├── repl_health.sh │ │ │ ├── report-ham.sieve │ │ │ ├── report-spam.sieve │ │ │ ├── rspamd-pipe-ham │ │ │ ├── rspamd-pipe-spam │ │ │ ├── sa-rules.sh │ │ │ ├── stop-supervisor.sh │ │ │ ├── supervisord.conf │ │ │ ├── syslog-ng-redis_slave.conf │ │ │ ├── syslog-ng.conf │ │ │ └── trim_logs.sh │ │ ├── netfilter/ │ │ │ ├── Dockerfile │ │ │ ├── docker-entrypoint.sh │ │ │ ├── main.py │ │ │ └── modules/ │ │ │ ├── IPTables.py │ │ │ ├── Logger.py │ │ │ ├── NFTables.py │ │ │ └── __init__.py │ │ ├── nginx/ │ │ │ ├── Dockerfile │ │ │ ├── bootstrap.py │ │ │ └── docker-entrypoint.sh │ │ ├── olefy/ │ │ │ ├── Dockerfile │ │ │ └── olefy.py │ │ ├── phpfpm/ │ │ │ ├── Dockerfile │ │ │ └── docker-entrypoint.sh │ │ ├── postfix/ │ │ │ ├── Dockerfile │ │ │ ├── docker-entrypoint.sh │ │ │ ├── postfix.sh │ │ │ ├── rspamd-pipe-ham │ │ │ ├── rspamd-pipe-spam │ │ │ ├── stop-supervisor.sh │ │ │ ├── supervisord.conf │ │ │ ├── syslog-ng-redis_slave.conf │ │ │ ├── syslog-ng.conf │ │ │ └── whitelist_forwardinghosts.sh │ │ ├── postfix-tlspol/ │ │ │ ├── Dockerfile │ │ │ ├── docker-entrypoint.sh │ │ │ ├── postfix-tlspol.sh │ │ │ ├── stop-supervisor.sh │ │ │ ├── supervisord.conf │ │ │ ├── syslog-ng-redis_slave.conf │ │ │ └── syslog-ng.conf │ │ ├── rspamd/ │ │ │ ├── Dockerfile │ │ │ ├── docker-entrypoint.sh │ │ │ ├── sa_trivial_convert.lua │ │ │ ├── set_worker_password.sh │ │ │ └── settings.conf │ │ ├── sogo/ │ │ │ ├── Dockerfile │ │ │ ├── acl.diff │ │ │ ├── bootstrap-sogo.sh │ │ │ ├── docker-entrypoint.sh │ │ │ ├── navMailcowBtns.diff │ │ │ ├── stop-supervisor.sh │ │ │ ├── supervisord.conf │ │ │ ├── syslog-ng-redis_slave.conf │ │ │ └── syslog-ng.conf │ │ ├── unbound/ │ │ │ ├── Dockerfile │ │ │ ├── docker-entrypoint.sh │ │ │ ├── healthcheck.sh │ │ │ ├── stop-supervisor.sh │ │ │ ├── supervisord.conf │ │ │ └── syslog-ng.conf │ │ └── watchdog/ │ │ ├── Dockerfile │ │ ├── check_dns.sh │ │ ├── check_mysql_slavestatus.sh │ │ ├── client.cnf │ │ └── watchdog.sh │ ├── assets/ │ │ ├── mysql/ │ │ │ └── docker-entrypoint.sh │ │ ├── passwd/ │ │ │ └── generate_passwords.sh │ │ └── templates/ │ │ ├── pw_reset_html.tpl │ │ ├── pw_reset_text.tpl │ │ ├── quarantine.tpl │ │ └── quota.tpl │ ├── conf/ │ │ ├── acme/ │ │ │ └── .gitkeep │ │ ├── clamav/ │ │ │ ├── clamd.conf │ │ │ └── freshclam.conf │ │ ├── dovecot/ │ │ │ ├── auth/ │ │ │ │ ├── mailcowauth.php │ │ │ │ └── passwd-verify.lua │ │ │ ├── dovecot.conf │ │ │ ├── dovecot.folders.conf │ │ │ └── ldap/ │ │ │ └── passdb.conf │ │ ├── mysql/ │ │ │ └── my.cnf │ │ ├── nginx/ │ │ │ └── templates/ │ │ │ ├── nginx.conf.j2 │ │ │ └── sites-default.conf.j2 │ │ ├── phpfpm/ │ │ │ ├── crons/ │ │ │ │ ├── keycloak-sync.php │ │ │ │ └── ldap-sync.php │ │ │ ├── php-conf.d/ │ │ │ │ ├── opcache-recommended.ini │ │ │ │ ├── other.ini │ │ │ │ └── upload.ini │ │ │ ├── php-fpm.d/ │ │ │ │ └── pools.conf │ │ │ └── sogo-sso/ │ │ │ └── .gitkeep │ │ ├── postfix/ │ │ │ ├── anonymize_headers.pcre │ │ │ ├── local_transport │ │ │ ├── main.cf │ │ │ ├── master.cf │ │ │ ├── postscreen_access.cidr │ │ │ └── smtp_dsn_filter │ │ ├── redis/ │ │ │ └── redis-conf.sh │ │ ├── rspamd/ │ │ │ ├── dynmaps/ │ │ │ │ ├── aliasexp.php │ │ │ │ ├── bcc.php │ │ │ │ ├── footer.php │ │ │ │ ├── forwardinghosts.php │ │ │ │ ├── index.html │ │ │ │ ├── sasl_logs.php │ │ │ │ ├── settings.php │ │ │ │ └── vars.inc.php │ │ │ ├── lua/ │ │ │ │ ├── ratelimit.lua │ │ │ │ └── rspamd.local.lua │ │ │ ├── meta_exporter/ │ │ │ │ ├── pipe.php │ │ │ │ ├── pipe_rl.php │ │ │ │ ├── pushover.php │ │ │ │ └── vars.inc.php │ │ │ ├── plugins.d/ │ │ │ │ └── README.md │ │ │ ├── rspamd.conf.local │ │ │ └── rspamd.conf.override │ │ ├── sogo/ │ │ │ ├── custom-sogo.js │ │ │ └── sogo.conf │ │ └── unbound/ │ │ └── unbound.conf │ ├── hooks/ │ │ └── README.md │ └── web/ │ ├── _rspamderror.php │ ├── _status.502.html │ ├── admin/ │ │ ├── dashboard.php │ │ ├── index.php │ │ ├── mailbox.php │ │ ├── queue.php │ │ └── system.php │ ├── api/ │ │ ├── index.css │ │ ├── index.html │ │ ├── oauth2-redirect.html │ │ ├── openapi.yaml │ │ ├── swagger-initializer.js │ │ ├── swagger-ui-bundle.js │ │ ├── swagger-ui-es-bundle-core.js │ │ ├── swagger-ui-es-bundle.js │ │ ├── swagger-ui-standalone-preset.js │ │ ├── swagger-ui.css │ │ └── swagger-ui.js │ ├── autoconfig.php │ ├── autodiscover-json.php │ ├── autodiscover.php │ ├── css/ │ │ ├── build/ │ │ │ ├── 003-bootstrap-select.css │ │ │ ├── 011-datatables.css │ │ │ ├── 012-bootstrap-icons.css │ │ │ ├── 013-datatables.css │ │ │ ├── 014-mailcow.css │ │ │ └── 015-responsive.css │ │ ├── site/ │ │ │ ├── admin.css │ │ │ ├── debug.css │ │ │ ├── edit.css │ │ │ ├── index.css │ │ │ ├── mailbox.css │ │ │ ├── quarantine.css │ │ │ └── user.css │ │ └── themes/ │ │ ├── lumen-bootstrap.css │ │ └── mailcow-darkmode.css │ ├── domainadmin/ │ │ ├── index.php │ │ ├── mailbox.php │ │ └── user.php │ ├── edit.php │ ├── f2b-banlist.php │ ├── inc/ │ │ ├── ajax/ │ │ │ ├── container_ctrl.php │ │ │ ├── destroy_tfa_auth.php │ │ │ ├── dns_diagnostics.php │ │ │ ├── qitem_details.php │ │ │ ├── qr_gen.php │ │ │ ├── show_rspamd_global_filters.php │ │ │ ├── sieve_validation.php │ │ │ ├── syncjob_logs.php │ │ │ └── transport_check.php │ │ ├── footer.inc.php │ │ ├── functions.acl.inc.php │ │ ├── functions.address_rewriting.inc.php │ │ ├── functions.admin.inc.php │ │ ├── functions.app_passwd.inc.php │ │ ├── functions.auth.inc.php │ │ ├── functions.customize.inc.php │ │ ├── functions.dkim.inc.php │ │ ├── functions.docker.inc.php │ │ ├── functions.domain_admin.inc.php │ │ ├── functions.fail2ban.inc.php │ │ ├── functions.fwdhost.inc.php │ │ ├── functions.inc.php │ │ ├── functions.mailbox.inc.php │ │ ├── functions.mailq.inc.php │ │ ├── functions.oauth2.inc.php │ │ ├── functions.policy.inc.php │ │ ├── functions.presets.inc.php │ │ ├── functions.pushover.inc.php │ │ ├── functions.quarantine.inc.php │ │ ├── functions.quota_notification.inc.php │ │ ├── functions.ratelimit.inc.php │ │ ├── functions.rspamd.inc.php │ │ ├── functions.tls_policy_maps.inc.php │ │ ├── functions.transports.inc.php │ │ ├── header.inc.php │ │ ├── init_db.inc.php │ │ ├── lib/ │ │ │ ├── CSSminifierExtended.php │ │ │ ├── JSminifierExtended.php │ │ │ ├── WebAuthn/ │ │ │ │ ├── Attestation/ │ │ │ │ │ ├── AttestationObject.php │ │ │ │ │ ├── AuthenticatorData.php │ │ │ │ │ └── Format/ │ │ │ │ │ ├── AndroidKey.php │ │ │ │ │ ├── AndroidSafetyNet.php │ │ │ │ │ ├── Apple.php │ │ │ │ │ ├── FormatBase.php │ │ │ │ │ ├── None.php │ │ │ │ │ ├── Packed.php │ │ │ │ │ ├── Tpm.php │ │ │ │ │ └── U2f.php │ │ │ │ ├── Binary/ │ │ │ │ │ └── ByteBuffer.php │ │ │ │ ├── CBOR/ │ │ │ │ │ └── CborDecoder.php │ │ │ │ ├── WebAuthn.php │ │ │ │ ├── WebAuthnException.php │ │ │ │ └── rootCertificates/ │ │ │ │ ├── apple.pem │ │ │ │ ├── bsi.pem │ │ │ │ ├── globalSign.pem │ │ │ │ ├── googleHardware.pem │ │ │ │ ├── huawei.pem │ │ │ │ ├── hypersecu.pem │ │ │ │ ├── microsoftTpmCollection.pem │ │ │ │ ├── nitro.pem │ │ │ │ ├── solo.pem │ │ │ │ ├── trustkey.pem │ │ │ │ └── yubico.pem │ │ │ ├── Yubico.php │ │ │ ├── array_merge_real.php │ │ │ ├── composer.json │ │ │ ├── sieve/ │ │ │ │ ├── SieveDumpable.php │ │ │ │ ├── SieveException.php │ │ │ │ ├── SieveKeywordRegistry.php │ │ │ │ ├── SieveParser.php │ │ │ │ ├── SieveScanner.php │ │ │ │ ├── SieveScript.php │ │ │ │ ├── SieveSemantics.php │ │ │ │ ├── SieveToken.php │ │ │ │ ├── SieveTree.php │ │ │ │ ├── extensions/ │ │ │ │ │ ├── body.xml │ │ │ │ │ ├── comparator-ascii-numeric.xml │ │ │ │ │ ├── copy.xml │ │ │ │ │ ├── date.xml │ │ │ │ │ ├── duplicate.xml │ │ │ │ │ ├── editheader.xml │ │ │ │ │ ├── enotify.xml │ │ │ │ │ ├── envelope.xml │ │ │ │ │ ├── environment.xml │ │ │ │ │ ├── ereject.xml │ │ │ │ │ ├── fileinto.xml │ │ │ │ │ ├── imap4flags.xml │ │ │ │ │ ├── imapflags.xml │ │ │ │ │ ├── index.xml │ │ │ │ │ ├── mailbox.xml │ │ │ │ │ ├── mime.xml │ │ │ │ │ ├── notify.xml │ │ │ │ │ ├── regex.xml │ │ │ │ │ ├── reject.xml │ │ │ │ │ ├── relational.xml │ │ │ │ │ ├── spamtest.xml │ │ │ │ │ ├── spamtestplus.xml │ │ │ │ │ ├── subaddress.xml │ │ │ │ │ ├── vacation-seconds.xml │ │ │ │ │ ├── vacation.xml │ │ │ │ │ ├── variables.xml │ │ │ │ │ ├── virustest.xml │ │ │ │ │ ├── vnd.dovecot.execute.xml │ │ │ │ │ ├── vnd.dovecot.filter.xml │ │ │ │ │ └── vnd.dovecot.pipe.xml │ │ │ │ └── keywords.xml │ │ │ ├── ssp.class.php │ │ │ └── vendor/ │ │ │ ├── adldap2/ │ │ │ │ └── adldap2/ │ │ │ │ ├── .gitattributes │ │ │ │ ├── .github/ │ │ │ │ │ └── issue_template.md │ │ │ │ ├── .gitignore │ │ │ │ ├── .scrutinizer.yml │ │ │ │ ├── .styleci.yml │ │ │ │ ├── .travis.yml │ │ │ │ ├── composer.json │ │ │ │ ├── docs/ │ │ │ │ │ ├── .nojekyll │ │ │ │ │ ├── _coverpage.md │ │ │ │ │ ├── _sidebar.md │ │ │ │ │ ├── distinguished-names.md │ │ │ │ │ ├── events.md │ │ │ │ │ ├── index.html │ │ │ │ │ ├── installation.md │ │ │ │ │ ├── logging.md │ │ │ │ │ ├── models/ │ │ │ │ │ │ ├── computer.md │ │ │ │ │ │ ├── contact.md │ │ │ │ │ │ ├── container.md │ │ │ │ │ │ ├── group.md │ │ │ │ │ │ ├── model.md │ │ │ │ │ │ ├── organization.md │ │ │ │ │ │ ├── ou.md │ │ │ │ │ │ ├── printer.md │ │ │ │ │ │ ├── root-dse.md │ │ │ │ │ │ ├── traits/ │ │ │ │ │ │ │ ├── has-critical-system-object.md │ │ │ │ │ │ │ ├── has-description.md │ │ │ │ │ │ │ ├── has-last-login-last-logoff.md │ │ │ │ │ │ │ └── has-member-of.md │ │ │ │ │ │ └── user.md │ │ │ │ │ ├── readme.md │ │ │ │ │ ├── searching.md │ │ │ │ │ ├── setup.md │ │ │ │ │ └── troubleshooting.md │ │ │ │ ├── license.md │ │ │ │ ├── phpunit.xml │ │ │ │ ├── readme.md │ │ │ │ └── src/ │ │ │ │ ├── Adldap.php │ │ │ │ ├── AdldapException.php │ │ │ │ ├── AdldapInterface.php │ │ │ │ ├── Auth/ │ │ │ │ │ ├── BindException.php │ │ │ │ │ ├── Events/ │ │ │ │ │ │ ├── Attempting.php │ │ │ │ │ │ ├── Binding.php │ │ │ │ │ │ ├── Bound.php │ │ │ │ │ │ ├── Event.php │ │ │ │ │ │ ├── Failed.php │ │ │ │ │ │ └── Passed.php │ │ │ │ │ ├── Guard.php │ │ │ │ │ ├── GuardInterface.php │ │ │ │ │ ├── PasswordRequiredException.php │ │ │ │ │ └── UsernameRequiredException.php │ │ │ │ ├── Configuration/ │ │ │ │ │ ├── ConfigurationException.php │ │ │ │ │ ├── DomainConfiguration.php │ │ │ │ │ └── Validators/ │ │ │ │ │ ├── ArrayValidator.php │ │ │ │ │ ├── BooleanValidator.php │ │ │ │ │ ├── ClassValidator.php │ │ │ │ │ ├── IntegerValidator.php │ │ │ │ │ ├── StringOrNullValidator.php │ │ │ │ │ └── Validator.php │ │ │ │ ├── Connections/ │ │ │ │ │ ├── ConnectionException.php │ │ │ │ │ ├── ConnectionInterface.php │ │ │ │ │ ├── DetailedError.php │ │ │ │ │ ├── Ldap.php │ │ │ │ │ ├── Provider.php │ │ │ │ │ └── ProviderInterface.php │ │ │ │ ├── Events/ │ │ │ │ │ ├── Dispatcher.php │ │ │ │ │ ├── DispatcherInterface.php │ │ │ │ │ └── DispatchesEvents.php │ │ │ │ ├── Log/ │ │ │ │ │ ├── EventLogger.php │ │ │ │ │ └── LogsInformation.php │ │ │ │ ├── Models/ │ │ │ │ │ ├── Attributes/ │ │ │ │ │ │ ├── AccountControl.php │ │ │ │ │ │ ├── DistinguishedName.php │ │ │ │ │ │ ├── Guid.php │ │ │ │ │ │ ├── MbString.php │ │ │ │ │ │ ├── Sid.php │ │ │ │ │ │ ├── TSProperty.php │ │ │ │ │ │ └── TSPropertyArray.php │ │ │ │ │ ├── BatchModification.php │ │ │ │ │ ├── Computer.php │ │ │ │ │ ├── Concerns/ │ │ │ │ │ │ ├── HasAttributes.php │ │ │ │ │ │ ├── HasCriticalSystemObject.php │ │ │ │ │ │ ├── HasDescription.php │ │ │ │ │ │ ├── HasEvents.php │ │ │ │ │ │ ├── HasLastLogonAndLogOff.php │ │ │ │ │ │ ├── HasMemberOf.php │ │ │ │ │ │ ├── HasUserAccountControl.php │ │ │ │ │ │ └── HasUserProperties.php │ │ │ │ │ ├── Contact.php │ │ │ │ │ ├── Container.php │ │ │ │ │ ├── Entry.php │ │ │ │ │ ├── Events/ │ │ │ │ │ │ ├── Created.php │ │ │ │ │ │ ├── Creating.php │ │ │ │ │ │ ├── Deleted.php │ │ │ │ │ │ ├── Deleting.php │ │ │ │ │ │ ├── Event.php │ │ │ │ │ │ ├── Saved.php │ │ │ │ │ │ ├── Saving.php │ │ │ │ │ │ ├── Updated.php │ │ │ │ │ │ └── Updating.php │ │ │ │ │ ├── Factory.php │ │ │ │ │ ├── ForeignSecurityPrincipal.php │ │ │ │ │ ├── Group.php │ │ │ │ │ ├── Model.php │ │ │ │ │ ├── ModelDoesNotExistException.php │ │ │ │ │ ├── ModelNotFoundException.php │ │ │ │ │ ├── Organization.php │ │ │ │ │ ├── OrganizationalUnit.php │ │ │ │ │ ├── Printer.php │ │ │ │ │ ├── RootDse.php │ │ │ │ │ ├── User.php │ │ │ │ │ ├── UserPasswordIncorrectException.php │ │ │ │ │ └── UserPasswordPolicyException.php │ │ │ │ ├── Query/ │ │ │ │ │ ├── Builder.php │ │ │ │ │ ├── Cache.php │ │ │ │ │ ├── Collection.php │ │ │ │ │ ├── Events/ │ │ │ │ │ │ ├── Listing.php │ │ │ │ │ │ ├── Paginate.php │ │ │ │ │ │ ├── QueryExecuted.php │ │ │ │ │ │ ├── Read.php │ │ │ │ │ │ └── Search.php │ │ │ │ │ ├── Factory.php │ │ │ │ │ ├── Grammar.php │ │ │ │ │ ├── Operator.php │ │ │ │ │ ├── Paginator.php │ │ │ │ │ └── Processor.php │ │ │ │ ├── Schemas/ │ │ │ │ │ ├── ActiveDirectory.php │ │ │ │ │ ├── Directory389.php │ │ │ │ │ ├── EDirectory.php │ │ │ │ │ ├── FreeIPA.php │ │ │ │ │ ├── OpenLDAP.php │ │ │ │ │ ├── Schema.php │ │ │ │ │ └── SchemaInterface.php │ │ │ │ └── Utilities.php │ │ │ ├── autoload.php │ │ │ ├── bacon/ │ │ │ │ └── bacon-qr-code/ │ │ │ │ ├── LICENSE │ │ │ │ ├── README.md │ │ │ │ ├── composer.json │ │ │ │ ├── phpunit.xml.dist │ │ │ │ ├── src/ │ │ │ │ │ ├── Common/ │ │ │ │ │ │ ├── BitArray.php │ │ │ │ │ │ ├── BitMatrix.php │ │ │ │ │ │ ├── BitUtils.php │ │ │ │ │ │ ├── CharacterSetEci.php │ │ │ │ │ │ ├── EcBlock.php │ │ │ │ │ │ ├── EcBlocks.php │ │ │ │ │ │ ├── ErrorCorrectionLevel.php │ │ │ │ │ │ ├── FormatInformation.php │ │ │ │ │ │ ├── Mode.php │ │ │ │ │ │ ├── ReedSolomonCodec.php │ │ │ │ │ │ └── Version.php │ │ │ │ │ ├── Encoder/ │ │ │ │ │ │ ├── BlockPair.php │ │ │ │ │ │ ├── ByteMatrix.php │ │ │ │ │ │ ├── Encoder.php │ │ │ │ │ │ ├── MaskUtil.php │ │ │ │ │ │ ├── MatrixUtil.php │ │ │ │ │ │ └── QrCode.php │ │ │ │ │ ├── Exception/ │ │ │ │ │ │ ├── ExceptionInterface.php │ │ │ │ │ │ ├── InvalidArgumentException.php │ │ │ │ │ │ ├── OutOfBoundsException.php │ │ │ │ │ │ ├── RuntimeException.php │ │ │ │ │ │ ├── UnexpectedValueException.php │ │ │ │ │ │ └── WriterException.php │ │ │ │ │ ├── Renderer/ │ │ │ │ │ │ ├── Color/ │ │ │ │ │ │ │ ├── Alpha.php │ │ │ │ │ │ │ ├── Cmyk.php │ │ │ │ │ │ │ ├── ColorInterface.php │ │ │ │ │ │ │ ├── Gray.php │ │ │ │ │ │ │ └── Rgb.php │ │ │ │ │ │ ├── Eye/ │ │ │ │ │ │ │ ├── CompositeEye.php │ │ │ │ │ │ │ ├── EyeInterface.php │ │ │ │ │ │ │ ├── ModuleEye.php │ │ │ │ │ │ │ ├── SimpleCircleEye.php │ │ │ │ │ │ │ └── SquareEye.php │ │ │ │ │ │ ├── Image/ │ │ │ │ │ │ │ ├── EpsImageBackEnd.php │ │ │ │ │ │ │ ├── ImageBackEndInterface.php │ │ │ │ │ │ │ ├── ImagickImageBackEnd.php │ │ │ │ │ │ │ ├── SvgImageBackEnd.php │ │ │ │ │ │ │ └── TransformationMatrix.php │ │ │ │ │ │ ├── ImageRenderer.php │ │ │ │ │ │ ├── Module/ │ │ │ │ │ │ │ ├── DotsModule.php │ │ │ │ │ │ │ ├── EdgeIterator/ │ │ │ │ │ │ │ │ ├── Edge.php │ │ │ │ │ │ │ │ └── EdgeIterator.php │ │ │ │ │ │ │ ├── ModuleInterface.php │ │ │ │ │ │ │ ├── RoundnessModule.php │ │ │ │ │ │ │ └── SquareModule.php │ │ │ │ │ │ ├── Path/ │ │ │ │ │ │ │ ├── Close.php │ │ │ │ │ │ │ ├── Curve.php │ │ │ │ │ │ │ ├── EllipticArc.php │ │ │ │ │ │ │ ├── Line.php │ │ │ │ │ │ │ ├── Move.php │ │ │ │ │ │ │ ├── OperationInterface.php │ │ │ │ │ │ │ └── Path.php │ │ │ │ │ │ ├── PlainTextRenderer.php │ │ │ │ │ │ ├── RendererInterface.php │ │ │ │ │ │ └── RendererStyle/ │ │ │ │ │ │ ├── EyeFill.php │ │ │ │ │ │ ├── Fill.php │ │ │ │ │ │ ├── Gradient.php │ │ │ │ │ │ ├── GradientType.php │ │ │ │ │ │ └── RendererStyle.php │ │ │ │ │ └── Writer.php │ │ │ │ └── test/ │ │ │ │ ├── Common/ │ │ │ │ │ ├── BitArrayTest.php │ │ │ │ │ ├── BitMatrixTest.php │ │ │ │ │ ├── BitUtilsTest.php │ │ │ │ │ ├── ErrorCorrectionLevelTest.php │ │ │ │ │ ├── FormatInformationTest.php │ │ │ │ │ ├── ModeTest.php │ │ │ │ │ ├── ReedSolomonCodecTest.php │ │ │ │ │ └── VersionTest.php │ │ │ │ ├── Encoder/ │ │ │ │ │ ├── EncoderTest.php │ │ │ │ │ ├── MaskUtilTest.php │ │ │ │ │ └── MatrixUtilTest.php │ │ │ │ └── Integration/ │ │ │ │ └── ImagickRenderingTest.php │ │ │ ├── bin/ │ │ │ │ ├── carbon │ │ │ │ ├── minifycss │ │ │ │ └── minifyjs │ │ │ ├── bshaffer/ │ │ │ │ └── oauth2-server-php/ │ │ │ │ ├── CHANGELOG.md │ │ │ │ ├── LICENSE │ │ │ │ ├── README.md │ │ │ │ ├── composer.json │ │ │ │ ├── src/ │ │ │ │ │ └── OAuth2/ │ │ │ │ │ ├── Autoloader.php │ │ │ │ │ ├── ClientAssertionType/ │ │ │ │ │ │ ├── ClientAssertionTypeInterface.php │ │ │ │ │ │ └── HttpBasic.php │ │ │ │ │ ├── Controller/ │ │ │ │ │ │ ├── AuthorizeController.php │ │ │ │ │ │ ├── AuthorizeControllerInterface.php │ │ │ │ │ │ ├── ResourceController.php │ │ │ │ │ │ ├── ResourceControllerInterface.php │ │ │ │ │ │ ├── TokenController.php │ │ │ │ │ │ └── TokenControllerInterface.php │ │ │ │ │ ├── Encryption/ │ │ │ │ │ │ ├── EncryptionInterface.php │ │ │ │ │ │ ├── FirebaseJwt.php │ │ │ │ │ │ └── Jwt.php │ │ │ │ │ ├── GrantType/ │ │ │ │ │ │ ├── AuthorizationCode.php │ │ │ │ │ │ ├── ClientCredentials.php │ │ │ │ │ │ ├── GrantTypeInterface.php │ │ │ │ │ │ ├── JwtBearer.php │ │ │ │ │ │ ├── RefreshToken.php │ │ │ │ │ │ └── UserCredentials.php │ │ │ │ │ ├── OpenID/ │ │ │ │ │ │ ├── Controller/ │ │ │ │ │ │ │ ├── AuthorizeController.php │ │ │ │ │ │ │ ├── AuthorizeControllerInterface.php │ │ │ │ │ │ │ ├── UserInfoController.php │ │ │ │ │ │ │ └── UserInfoControllerInterface.php │ │ │ │ │ │ ├── GrantType/ │ │ │ │ │ │ │ └── AuthorizationCode.php │ │ │ │ │ │ ├── ResponseType/ │ │ │ │ │ │ │ ├── AuthorizationCode.php │ │ │ │ │ │ │ ├── AuthorizationCodeInterface.php │ │ │ │ │ │ │ ├── CodeIdToken.php │ │ │ │ │ │ │ ├── CodeIdTokenInterface.php │ │ │ │ │ │ │ ├── IdToken.php │ │ │ │ │ │ │ ├── IdTokenInterface.php │ │ │ │ │ │ │ ├── IdTokenToken.php │ │ │ │ │ │ │ └── IdTokenTokenInterface.php │ │ │ │ │ │ └── Storage/ │ │ │ │ │ │ ├── AuthorizationCodeInterface.php │ │ │ │ │ │ └── UserClaimsInterface.php │ │ │ │ │ ├── Request.php │ │ │ │ │ ├── RequestInterface.php │ │ │ │ │ ├── Response.php │ │ │ │ │ ├── ResponseInterface.php │ │ │ │ │ ├── ResponseType/ │ │ │ │ │ │ ├── AccessToken.php │ │ │ │ │ │ ├── AccessTokenInterface.php │ │ │ │ │ │ ├── AuthorizationCode.php │ │ │ │ │ │ ├── AuthorizationCodeInterface.php │ │ │ │ │ │ ├── JwtAccessToken.php │ │ │ │ │ │ └── ResponseTypeInterface.php │ │ │ │ │ ├── Scope.php │ │ │ │ │ ├── ScopeInterface.php │ │ │ │ │ ├── Server.php │ │ │ │ │ ├── Storage/ │ │ │ │ │ │ ├── AccessTokenInterface.php │ │ │ │ │ │ ├── AuthorizationCodeInterface.php │ │ │ │ │ │ ├── Cassandra.php │ │ │ │ │ │ ├── ClientCredentialsInterface.php │ │ │ │ │ │ ├── ClientInterface.php │ │ │ │ │ │ ├── CouchbaseDB.php │ │ │ │ │ │ ├── DynamoDB.php │ │ │ │ │ │ ├── JwtAccessToken.php │ │ │ │ │ │ ├── JwtAccessTokenInterface.php │ │ │ │ │ │ ├── JwtBearerInterface.php │ │ │ │ │ │ ├── Memory.php │ │ │ │ │ │ ├── Mongo.php │ │ │ │ │ │ ├── MongoDB.php │ │ │ │ │ │ ├── Pdo.php │ │ │ │ │ │ ├── PublicKeyInterface.php │ │ │ │ │ │ ├── Redis.php │ │ │ │ │ │ ├── RefreshTokenInterface.php │ │ │ │ │ │ ├── ScopeInterface.php │ │ │ │ │ │ └── UserCredentialsInterface.php │ │ │ │ │ └── TokenType/ │ │ │ │ │ ├── Bearer.php │ │ │ │ │ ├── Mac.php │ │ │ │ │ └── TokenTypeInterface.php │ │ │ │ └── test/ │ │ │ │ ├── OAuth2/ │ │ │ │ │ ├── AutoloadTest.php │ │ │ │ │ ├── Controller/ │ │ │ │ │ │ ├── AuthorizeControllerTest.php │ │ │ │ │ │ ├── ResourceControllerTest.php │ │ │ │ │ │ └── TokenControllerTest.php │ │ │ │ │ ├── Encryption/ │ │ │ │ │ │ ├── FirebaseJwtTest.php │ │ │ │ │ │ └── JwtTest.php │ │ │ │ │ ├── GrantType/ │ │ │ │ │ │ ├── AuthorizationCodeTest.php │ │ │ │ │ │ ├── ClientCredentialsTest.php │ │ │ │ │ │ ├── ImplicitTest.php │ │ │ │ │ │ ├── JwtBearerTest.php │ │ │ │ │ │ ├── RefreshTokenTest.php │ │ │ │ │ │ └── UserCredentialsTest.php │ │ │ │ │ ├── OpenID/ │ │ │ │ │ │ ├── Controller/ │ │ │ │ │ │ │ ├── AuthorizeControllerTest.php │ │ │ │ │ │ │ └── UserInfoControllerTest.php │ │ │ │ │ │ ├── GrantType/ │ │ │ │ │ │ │ └── AuthorizationCodeTest.php │ │ │ │ │ │ ├── ResponseType/ │ │ │ │ │ │ │ ├── CodeIdTokenTest.php │ │ │ │ │ │ │ ├── IdTokenTest.php │ │ │ │ │ │ │ └── IdTokenTokenTest.php │ │ │ │ │ │ └── Storage/ │ │ │ │ │ │ ├── AuthorizationCodeTest.php │ │ │ │ │ │ └── UserClaimsTest.php │ │ │ │ │ ├── RequestTest.php │ │ │ │ │ ├── ResponseTest.php │ │ │ │ │ ├── ResponseType/ │ │ │ │ │ │ ├── AccessTokenTest.php │ │ │ │ │ │ └── JwtAccessTokenTest.php │ │ │ │ │ ├── ScopeTest.php │ │ │ │ │ ├── ServerTest.php │ │ │ │ │ ├── Storage/ │ │ │ │ │ │ ├── AccessTokenTest.php │ │ │ │ │ │ ├── AuthorizationCodeTest.php │ │ │ │ │ │ ├── ClientCredentialsTest.php │ │ │ │ │ │ ├── ClientTest.php │ │ │ │ │ │ ├── DynamoDBTest.php │ │ │ │ │ │ ├── JwtAccessTokenTest.php │ │ │ │ │ │ ├── JwtBearerTest.php │ │ │ │ │ │ ├── PdoTest.php │ │ │ │ │ │ ├── PublicKeyTest.php │ │ │ │ │ │ ├── RefreshTokenTest.php │ │ │ │ │ │ ├── ScopeTest.php │ │ │ │ │ │ └── UserCredentialsTest.php │ │ │ │ │ └── TokenType/ │ │ │ │ │ └── BearerTest.php │ │ │ │ ├── bootstrap.php │ │ │ │ ├── cleanup.php │ │ │ │ ├── config/ │ │ │ │ │ ├── keys/ │ │ │ │ │ │ ├── id_rsa │ │ │ │ │ │ └── id_rsa.pub │ │ │ │ │ └── storage.json │ │ │ │ └── lib/ │ │ │ │ └── OAuth2/ │ │ │ │ ├── Request/ │ │ │ │ │ └── TestRequest.php │ │ │ │ └── Storage/ │ │ │ │ ├── BaseTest.php │ │ │ │ ├── Bootstrap.php │ │ │ │ └── NullStorage.php │ │ │ ├── carbonphp/ │ │ │ │ └── carbon-doctrine-types/ │ │ │ │ ├── LICENSE │ │ │ │ ├── README.md │ │ │ │ ├── composer.json │ │ │ │ └── src/ │ │ │ │ └── Carbon/ │ │ │ │ └── Doctrine/ │ │ │ │ ├── CarbonDoctrineType.php │ │ │ │ ├── CarbonImmutableType.php │ │ │ │ ├── CarbonType.php │ │ │ │ ├── CarbonTypeConverter.php │ │ │ │ ├── DateTimeDefaultPrecision.php │ │ │ │ ├── DateTimeImmutableType.php │ │ │ │ └── DateTimeType.php │ │ │ ├── composer/ │ │ │ │ ├── ClassLoader.php │ │ │ │ ├── InstalledVersions.php │ │ │ │ ├── LICENSE │ │ │ │ ├── autoload_classmap.php │ │ │ │ ├── autoload_files.php │ │ │ │ ├── autoload_namespaces.php │ │ │ │ ├── autoload_psr4.php │ │ │ │ ├── autoload_real.php │ │ │ │ ├── autoload_static.php │ │ │ │ ├── installed.json │ │ │ │ ├── installed.php │ │ │ │ └── platform_check.php │ │ │ ├── dasprid/ │ │ │ │ └── enum/ │ │ │ │ ├── LICENSE │ │ │ │ ├── README.md │ │ │ │ ├── composer.json │ │ │ │ └── src/ │ │ │ │ ├── AbstractEnum.php │ │ │ │ ├── EnumMap.php │ │ │ │ ├── Exception/ │ │ │ │ │ ├── CloneNotSupportedException.php │ │ │ │ │ ├── ExceptionInterface.php │ │ │ │ │ ├── ExpectationException.php │ │ │ │ │ ├── IllegalArgumentException.php │ │ │ │ │ ├── MismatchException.php │ │ │ │ │ ├── SerializeNotSupportedException.php │ │ │ │ │ └── UnserializeNotSupportedException.php │ │ │ │ └── NullValue.php │ │ │ ├── ddeboer/ │ │ │ │ └── imap/ │ │ │ │ ├── .php-cs-fixer.php │ │ │ │ ├── LICENSE │ │ │ │ ├── README.md │ │ │ │ ├── composer.json │ │ │ │ └── src/ │ │ │ │ ├── Connection.php │ │ │ │ ├── ConnectionInterface.php │ │ │ │ ├── Exception/ │ │ │ │ │ ├── AbstractException.php │ │ │ │ │ ├── AuthenticationFailedException.php │ │ │ │ │ ├── CreateMailboxException.php │ │ │ │ │ ├── DeleteMailboxException.php │ │ │ │ │ ├── ImapFetchbodyException.php │ │ │ │ │ ├── ImapFetchheaderException.php │ │ │ │ │ ├── ImapGetmailboxesException.php │ │ │ │ │ ├── ImapMsgnoException.php │ │ │ │ │ ├── ImapNumMsgException.php │ │ │ │ │ ├── ImapQuotaException.php │ │ │ │ │ ├── ImapStatusException.php │ │ │ │ │ ├── InvalidDateHeaderException.php │ │ │ │ │ ├── InvalidHeadersException.php │ │ │ │ │ ├── InvalidResourceException.php │ │ │ │ │ ├── InvalidSearchCriteriaException.php │ │ │ │ │ ├── MailboxDoesNotExistException.php │ │ │ │ │ ├── MessageCopyException.php │ │ │ │ │ ├── MessageDeleteException.php │ │ │ │ │ ├── MessageDoesNotExistException.php │ │ │ │ │ ├── MessageMoveException.php │ │ │ │ │ ├── MessageStructureException.php │ │ │ │ │ ├── MessageUndeleteException.php │ │ │ │ │ ├── NotEmbeddedMessageException.php │ │ │ │ │ ├── OutOfBoundsException.php │ │ │ │ │ ├── ReopenMailboxException.php │ │ │ │ │ ├── ResourceCheckFailureException.php │ │ │ │ │ ├── UnexpectedEncodingException.php │ │ │ │ │ └── UnsupportedCharsetException.php │ │ │ │ ├── ImapResource.php │ │ │ │ ├── ImapResourceInterface.php │ │ │ │ ├── Mailbox.php │ │ │ │ ├── MailboxInterface.php │ │ │ │ ├── Message/ │ │ │ │ │ ├── AbstractMessage.php │ │ │ │ │ ├── AbstractPart.php │ │ │ │ │ ├── Attachment.php │ │ │ │ │ ├── AttachmentInterface.php │ │ │ │ │ ├── BasicMessageInterface.php │ │ │ │ │ ├── EmailAddress.php │ │ │ │ │ ├── EmbeddedMessage.php │ │ │ │ │ ├── EmbeddedMessageInterface.php │ │ │ │ │ ├── Headers.php │ │ │ │ │ ├── Parameters.php │ │ │ │ │ ├── PartInterface.php │ │ │ │ │ ├── SimplePart.php │ │ │ │ │ └── Transcoder.php │ │ │ │ ├── Message.php │ │ │ │ ├── MessageInterface.php │ │ │ │ ├── MessageIterator.php │ │ │ │ ├── MessageIteratorInterface.php │ │ │ │ ├── Search/ │ │ │ │ │ ├── AbstractDate.php │ │ │ │ │ ├── AbstractText.php │ │ │ │ │ ├── ConditionInterface.php │ │ │ │ │ ├── Date/ │ │ │ │ │ │ ├── Before.php │ │ │ │ │ │ ├── On.php │ │ │ │ │ │ └── Since.php │ │ │ │ │ ├── Email/ │ │ │ │ │ │ ├── Bcc.php │ │ │ │ │ │ ├── Cc.php │ │ │ │ │ │ ├── From.php │ │ │ │ │ │ └── To.php │ │ │ │ │ ├── Flag/ │ │ │ │ │ │ ├── Answered.php │ │ │ │ │ │ ├── Flagged.php │ │ │ │ │ │ ├── Recent.php │ │ │ │ │ │ ├── Seen.php │ │ │ │ │ │ ├── Unanswered.php │ │ │ │ │ │ ├── Unflagged.php │ │ │ │ │ │ └── Unseen.php │ │ │ │ │ ├── LogicalOperator/ │ │ │ │ │ │ ├── All.php │ │ │ │ │ │ └── OrConditions.php │ │ │ │ │ ├── RawExpression.php │ │ │ │ │ ├── State/ │ │ │ │ │ │ ├── Deleted.php │ │ │ │ │ │ ├── NewMessage.php │ │ │ │ │ │ ├── Old.php │ │ │ │ │ │ └── Undeleted.php │ │ │ │ │ └── Text/ │ │ │ │ │ ├── Body.php │ │ │ │ │ ├── Keyword.php │ │ │ │ │ ├── Subject.php │ │ │ │ │ ├── Text.php │ │ │ │ │ └── Unkeyword.php │ │ │ │ ├── SearchExpression.php │ │ │ │ ├── Server.php │ │ │ │ ├── ServerInterface.php │ │ │ │ └── Test/ │ │ │ │ └── RawMessageIterator.php │ │ │ ├── firebase/ │ │ │ │ └── php-jwt/ │ │ │ │ ├── CHANGELOG.md │ │ │ │ ├── LICENSE │ │ │ │ ├── README.md │ │ │ │ ├── composer.json │ │ │ │ └── src/ │ │ │ │ ├── BeforeValidException.php │ │ │ │ ├── CachedKeySet.php │ │ │ │ ├── ExpiredException.php │ │ │ │ ├── JWK.php │ │ │ │ ├── JWT.php │ │ │ │ ├── Key.php │ │ │ │ └── SignatureInvalidException.php │ │ │ ├── guzzlehttp/ │ │ │ │ ├── guzzle/ │ │ │ │ │ ├── CHANGELOG.md │ │ │ │ │ ├── LICENSE │ │ │ │ │ ├── README.md │ │ │ │ │ ├── UPGRADING.md │ │ │ │ │ ├── composer.json │ │ │ │ │ └── src/ │ │ │ │ │ ├── BodySummarizer.php │ │ │ │ │ ├── BodySummarizerInterface.php │ │ │ │ │ ├── Client.php │ │ │ │ │ ├── ClientInterface.php │ │ │ │ │ ├── ClientTrait.php │ │ │ │ │ ├── Cookie/ │ │ │ │ │ │ ├── CookieJar.php │ │ │ │ │ │ ├── CookieJarInterface.php │ │ │ │ │ │ ├── FileCookieJar.php │ │ │ │ │ │ ├── SessionCookieJar.php │ │ │ │ │ │ └── SetCookie.php │ │ │ │ │ ├── Exception/ │ │ │ │ │ │ ├── BadResponseException.php │ │ │ │ │ │ ├── ClientException.php │ │ │ │ │ │ ├── ConnectException.php │ │ │ │ │ │ ├── GuzzleException.php │ │ │ │ │ │ ├── InvalidArgumentException.php │ │ │ │ │ │ ├── RequestException.php │ │ │ │ │ │ ├── ServerException.php │ │ │ │ │ │ ├── TooManyRedirectsException.php │ │ │ │ │ │ └── TransferException.php │ │ │ │ │ ├── Handler/ │ │ │ │ │ │ ├── CurlFactory.php │ │ │ │ │ │ ├── CurlFactoryInterface.php │ │ │ │ │ │ ├── CurlHandler.php │ │ │ │ │ │ ├── CurlMultiHandler.php │ │ │ │ │ │ ├── EasyHandle.php │ │ │ │ │ │ ├── HeaderProcessor.php │ │ │ │ │ │ ├── MockHandler.php │ │ │ │ │ │ ├── Proxy.php │ │ │ │ │ │ └── StreamHandler.php │ │ │ │ │ ├── HandlerStack.php │ │ │ │ │ ├── MessageFormatter.php │ │ │ │ │ ├── MessageFormatterInterface.php │ │ │ │ │ ├── Middleware.php │ │ │ │ │ ├── Pool.php │ │ │ │ │ ├── PrepareBodyMiddleware.php │ │ │ │ │ ├── RedirectMiddleware.php │ │ │ │ │ ├── RequestOptions.php │ │ │ │ │ ├── RetryMiddleware.php │ │ │ │ │ ├── TransferStats.php │ │ │ │ │ ├── Utils.php │ │ │ │ │ ├── functions.php │ │ │ │ │ └── functions_include.php │ │ │ │ ├── promises/ │ │ │ │ │ ├── CHANGELOG.md │ │ │ │ │ ├── LICENSE │ │ │ │ │ ├── README.md │ │ │ │ │ ├── composer.json │ │ │ │ │ └── src/ │ │ │ │ │ ├── AggregateException.php │ │ │ │ │ ├── CancellationException.php │ │ │ │ │ ├── Coroutine.php │ │ │ │ │ ├── Create.php │ │ │ │ │ ├── Each.php │ │ │ │ │ ├── EachPromise.php │ │ │ │ │ ├── FulfilledPromise.php │ │ │ │ │ ├── Is.php │ │ │ │ │ ├── Promise.php │ │ │ │ │ ├── PromiseInterface.php │ │ │ │ │ ├── PromisorInterface.php │ │ │ │ │ ├── RejectedPromise.php │ │ │ │ │ ├── RejectionException.php │ │ │ │ │ ├── TaskQueue.php │ │ │ │ │ ├── TaskQueueInterface.php │ │ │ │ │ ├── Utils.php │ │ │ │ │ ├── functions.php │ │ │ │ │ └── functions_include.php │ │ │ │ └── psr7/ │ │ │ │ ├── CHANGELOG.md │ │ │ │ ├── LICENSE │ │ │ │ ├── README.md │ │ │ │ ├── composer.json │ │ │ │ └── src/ │ │ │ │ ├── AppendStream.php │ │ │ │ ├── BufferStream.php │ │ │ │ ├── CachingStream.php │ │ │ │ ├── DroppingStream.php │ │ │ │ ├── Exception/ │ │ │ │ │ └── MalformedUriException.php │ │ │ │ ├── FnStream.php │ │ │ │ ├── Header.php │ │ │ │ ├── HttpFactory.php │ │ │ │ ├── InflateStream.php │ │ │ │ ├── LazyOpenStream.php │ │ │ │ ├── LimitStream.php │ │ │ │ ├── Message.php │ │ │ │ ├── MessageTrait.php │ │ │ │ ├── MimeType.php │ │ │ │ ├── MultipartStream.php │ │ │ │ ├── NoSeekStream.php │ │ │ │ ├── PumpStream.php │ │ │ │ ├── Query.php │ │ │ │ ├── Request.php │ │ │ │ ├── Response.php │ │ │ │ ├── Rfc7230.php │ │ │ │ ├── ServerRequest.php │ │ │ │ ├── Stream.php │ │ │ │ ├── StreamDecoratorTrait.php │ │ │ │ ├── StreamWrapper.php │ │ │ │ ├── UploadedFile.php │ │ │ │ ├── Uri.php │ │ │ │ ├── UriComparator.php │ │ │ │ ├── UriNormalizer.php │ │ │ │ ├── UriResolver.php │ │ │ │ └── Utils.php │ │ │ ├── illuminate/ │ │ │ │ ├── collections/ │ │ │ │ │ ├── Arr.php │ │ │ │ │ ├── Collection.php │ │ │ │ │ ├── Enumerable.php │ │ │ │ │ ├── HigherOrderCollectionProxy.php │ │ │ │ │ ├── ItemNotFoundException.php │ │ │ │ │ ├── LICENSE.md │ │ │ │ │ ├── LazyCollection.php │ │ │ │ │ ├── MultipleItemsFoundException.php │ │ │ │ │ ├── Traits/ │ │ │ │ │ │ └── EnumeratesValues.php │ │ │ │ │ ├── composer.json │ │ │ │ │ ├── functions.php │ │ │ │ │ └── helpers.php │ │ │ │ ├── conditionable/ │ │ │ │ │ ├── HigherOrderWhenProxy.php │ │ │ │ │ ├── LICENSE.md │ │ │ │ │ ├── Traits/ │ │ │ │ │ │ └── Conditionable.php │ │ │ │ │ └── composer.json │ │ │ │ ├── contracts/ │ │ │ │ │ ├── Auth/ │ │ │ │ │ │ ├── Access/ │ │ │ │ │ │ │ ├── Authorizable.php │ │ │ │ │ │ │ └── Gate.php │ │ │ │ │ │ ├── Authenticatable.php │ │ │ │ │ │ ├── CanResetPassword.php │ │ │ │ │ │ ├── Factory.php │ │ │ │ │ │ ├── Guard.php │ │ │ │ │ │ ├── Middleware/ │ │ │ │ │ │ │ └── AuthenticatesRequests.php │ │ │ │ │ │ ├── MustVerifyEmail.php │ │ │ │ │ │ ├── PasswordBroker.php │ │ │ │ │ │ ├── PasswordBrokerFactory.php │ │ │ │ │ │ ├── StatefulGuard.php │ │ │ │ │ │ ├── SupportsBasicAuth.php │ │ │ │ │ │ └── UserProvider.php │ │ │ │ │ ├── Broadcasting/ │ │ │ │ │ │ ├── Broadcaster.php │ │ │ │ │ │ ├── Factory.php │ │ │ │ │ │ ├── HasBroadcastChannel.php │ │ │ │ │ │ ├── ShouldBeUnique.php │ │ │ │ │ │ ├── ShouldBroadcast.php │ │ │ │ │ │ └── ShouldBroadcastNow.php │ │ │ │ │ ├── Bus/ │ │ │ │ │ │ ├── Dispatcher.php │ │ │ │ │ │ └── QueueingDispatcher.php │ │ │ │ │ ├── Cache/ │ │ │ │ │ │ ├── Factory.php │ │ │ │ │ │ ├── Lock.php │ │ │ │ │ │ ├── LockProvider.php │ │ │ │ │ │ ├── LockTimeoutException.php │ │ │ │ │ │ ├── Repository.php │ │ │ │ │ │ └── Store.php │ │ │ │ │ ├── Config/ │ │ │ │ │ │ └── Repository.php │ │ │ │ │ ├── Console/ │ │ │ │ │ │ ├── Application.php │ │ │ │ │ │ ├── Isolatable.php │ │ │ │ │ │ ├── Kernel.php │ │ │ │ │ │ └── PromptsForMissingInput.php │ │ │ │ │ ├── Container/ │ │ │ │ │ │ ├── BindingResolutionException.php │ │ │ │ │ │ ├── CircularDependencyException.php │ │ │ │ │ │ ├── Container.php │ │ │ │ │ │ └── ContextualBindingBuilder.php │ │ │ │ │ ├── Cookie/ │ │ │ │ │ │ ├── Factory.php │ │ │ │ │ │ └── QueueingFactory.php │ │ │ │ │ ├── Database/ │ │ │ │ │ │ ├── Eloquent/ │ │ │ │ │ │ │ ├── Builder.php │ │ │ │ │ │ │ ├── Castable.php │ │ │ │ │ │ │ ├── CastsAttributes.php │ │ │ │ │ │ │ ├── CastsInboundAttributes.php │ │ │ │ │ │ │ ├── DeviatesCastableAttributes.php │ │ │ │ │ │ │ ├── SerializesCastableAttributes.php │ │ │ │ │ │ │ └── SupportsPartialRelations.php │ │ │ │ │ │ ├── Events/ │ │ │ │ │ │ │ └── MigrationEvent.php │ │ │ │ │ │ ├── ModelIdentifier.php │ │ │ │ │ │ └── Query/ │ │ │ │ │ │ ├── Builder.php │ │ │ │ │ │ ├── ConditionExpression.php │ │ │ │ │ │ └── Expression.php │ │ │ │ │ ├── Debug/ │ │ │ │ │ │ └── ExceptionHandler.php │ │ │ │ │ ├── Encryption/ │ │ │ │ │ │ ├── DecryptException.php │ │ │ │ │ │ ├── EncryptException.php │ │ │ │ │ │ ├── Encrypter.php │ │ │ │ │ │ └── StringEncrypter.php │ │ │ │ │ ├── Events/ │ │ │ │ │ │ ├── Dispatcher.php │ │ │ │ │ │ ├── ShouldDispatchAfterCommit.php │ │ │ │ │ │ └── ShouldHandleEventsAfterCommit.php │ │ │ │ │ ├── Filesystem/ │ │ │ │ │ │ ├── Cloud.php │ │ │ │ │ │ ├── Factory.php │ │ │ │ │ │ ├── FileNotFoundException.php │ │ │ │ │ │ ├── Filesystem.php │ │ │ │ │ │ └── LockTimeoutException.php │ │ │ │ │ ├── Foundation/ │ │ │ │ │ │ ├── Application.php │ │ │ │ │ │ ├── CachesConfiguration.php │ │ │ │ │ │ ├── CachesRoutes.php │ │ │ │ │ │ ├── ExceptionRenderer.php │ │ │ │ │ │ └── MaintenanceMode.php │ │ │ │ │ ├── Hashing/ │ │ │ │ │ │ └── Hasher.php │ │ │ │ │ ├── Http/ │ │ │ │ │ │ └── Kernel.php │ │ │ │ │ ├── LICENSE.md │ │ │ │ │ ├── Mail/ │ │ │ │ │ │ ├── Attachable.php │ │ │ │ │ │ ├── Factory.php │ │ │ │ │ │ ├── MailQueue.php │ │ │ │ │ │ ├── Mailable.php │ │ │ │ │ │ └── Mailer.php │ │ │ │ │ ├── Notifications/ │ │ │ │ │ │ ├── Dispatcher.php │ │ │ │ │ │ └── Factory.php │ │ │ │ │ ├── Pagination/ │ │ │ │ │ │ ├── CursorPaginator.php │ │ │ │ │ │ ├── LengthAwarePaginator.php │ │ │ │ │ │ └── Paginator.php │ │ │ │ │ ├── Pipeline/ │ │ │ │ │ │ ├── Hub.php │ │ │ │ │ │ └── Pipeline.php │ │ │ │ │ ├── Process/ │ │ │ │ │ │ ├── InvokedProcess.php │ │ │ │ │ │ └── ProcessResult.php │ │ │ │ │ ├── Queue/ │ │ │ │ │ │ ├── ClearableQueue.php │ │ │ │ │ │ ├── EntityNotFoundException.php │ │ │ │ │ │ ├── EntityResolver.php │ │ │ │ │ │ ├── Factory.php │ │ │ │ │ │ ├── Job.php │ │ │ │ │ │ ├── Monitor.php │ │ │ │ │ │ ├── Queue.php │ │ │ │ │ │ ├── QueueableCollection.php │ │ │ │ │ │ ├── QueueableEntity.php │ │ │ │ │ │ ├── ShouldBeEncrypted.php │ │ │ │ │ │ ├── ShouldBeUnique.php │ │ │ │ │ │ ├── ShouldBeUniqueUntilProcessing.php │ │ │ │ │ │ ├── ShouldQueue.php │ │ │ │ │ │ └── ShouldQueueAfterCommit.php │ │ │ │ │ ├── Redis/ │ │ │ │ │ │ ├── Connection.php │ │ │ │ │ │ ├── Connector.php │ │ │ │ │ │ ├── Factory.php │ │ │ │ │ │ └── LimiterTimeoutException.php │ │ │ │ │ ├── Routing/ │ │ │ │ │ │ ├── BindingRegistrar.php │ │ │ │ │ │ ├── Registrar.php │ │ │ │ │ │ ├── ResponseFactory.php │ │ │ │ │ │ ├── UrlGenerator.php │ │ │ │ │ │ └── UrlRoutable.php │ │ │ │ │ ├── Session/ │ │ │ │ │ │ ├── Middleware/ │ │ │ │ │ │ │ └── AuthenticatesSessions.php │ │ │ │ │ │ └── Session.php │ │ │ │ │ ├── Support/ │ │ │ │ │ │ ├── Arrayable.php │ │ │ │ │ │ ├── CanBeEscapedWhenCastToString.php │ │ │ │ │ │ ├── DeferrableProvider.php │ │ │ │ │ │ ├── DeferringDisplayableValue.php │ │ │ │ │ │ ├── Htmlable.php │ │ │ │ │ │ ├── Jsonable.php │ │ │ │ │ │ ├── MessageBag.php │ │ │ │ │ │ ├── MessageProvider.php │ │ │ │ │ │ ├── Renderable.php │ │ │ │ │ │ ├── Responsable.php │ │ │ │ │ │ └── ValidatedData.php │ │ │ │ │ ├── Translation/ │ │ │ │ │ │ ├── HasLocalePreference.php │ │ │ │ │ │ ├── Loader.php │ │ │ │ │ │ └── Translator.php │ │ │ │ │ ├── Validation/ │ │ │ │ │ │ ├── DataAwareRule.php │ │ │ │ │ │ ├── Factory.php │ │ │ │ │ │ ├── ImplicitRule.php │ │ │ │ │ │ ├── InvokableRule.php │ │ │ │ │ │ ├── Rule.php │ │ │ │ │ │ ├── UncompromisedVerifier.php │ │ │ │ │ │ ├── ValidatesWhenResolved.php │ │ │ │ │ │ ├── ValidationRule.php │ │ │ │ │ │ ├── Validator.php │ │ │ │ │ │ └── ValidatorAwareRule.php │ │ │ │ │ ├── View/ │ │ │ │ │ │ ├── Engine.php │ │ │ │ │ │ ├── Factory.php │ │ │ │ │ │ ├── View.php │ │ │ │ │ │ └── ViewCompilationException.php │ │ │ │ │ └── composer.json │ │ │ │ └── macroable/ │ │ │ │ ├── LICENSE.md │ │ │ │ ├── Traits/ │ │ │ │ │ └── Macroable.php │ │ │ │ └── composer.json │ │ │ ├── league/ │ │ │ │ └── oauth2-client/ │ │ │ │ ├── LICENSE │ │ │ │ ├── README.md │ │ │ │ ├── composer.json │ │ │ │ └── src/ │ │ │ │ ├── Grant/ │ │ │ │ │ ├── AbstractGrant.php │ │ │ │ │ ├── AuthorizationCode.php │ │ │ │ │ ├── ClientCredentials.php │ │ │ │ │ ├── Exception/ │ │ │ │ │ │ └── InvalidGrantException.php │ │ │ │ │ ├── GrantFactory.php │ │ │ │ │ ├── Password.php │ │ │ │ │ └── RefreshToken.php │ │ │ │ ├── OptionProvider/ │ │ │ │ │ ├── HttpBasicAuthOptionProvider.php │ │ │ │ │ ├── OptionProviderInterface.php │ │ │ │ │ └── PostAuthOptionProvider.php │ │ │ │ ├── Provider/ │ │ │ │ │ ├── AbstractProvider.php │ │ │ │ │ ├── Exception/ │ │ │ │ │ │ └── IdentityProviderException.php │ │ │ │ │ ├── GenericProvider.php │ │ │ │ │ ├── GenericResourceOwner.php │ │ │ │ │ └── ResourceOwnerInterface.php │ │ │ │ ├── Token/ │ │ │ │ │ ├── AccessToken.php │ │ │ │ │ ├── AccessTokenInterface.php │ │ │ │ │ └── ResourceOwnerAccessTokenInterface.php │ │ │ │ └── Tool/ │ │ │ │ ├── ArrayAccessorTrait.php │ │ │ │ ├── BearerAuthorizationTrait.php │ │ │ │ ├── GuardedPropertyTrait.php │ │ │ │ ├── MacAuthorizationTrait.php │ │ │ │ ├── ProviderRedirectTrait.php │ │ │ │ ├── QueryBuilderTrait.php │ │ │ │ ├── RequestFactory.php │ │ │ │ └── RequiredParameterTrait.php │ │ │ ├── matthiasmullie/ │ │ │ │ ├── minify/ │ │ │ │ │ ├── .github/ │ │ │ │ │ │ └── FUNDING.yml │ │ │ │ │ ├── CONTRIBUTING.md │ │ │ │ │ ├── Dockerfile │ │ │ │ │ ├── LICENSE │ │ │ │ │ ├── bin/ │ │ │ │ │ │ ├── minifycss │ │ │ │ │ │ └── minifyjs │ │ │ │ │ ├── composer.json │ │ │ │ │ ├── data/ │ │ │ │ │ │ └── js/ │ │ │ │ │ │ ├── keywords_after.txt │ │ │ │ │ │ ├── keywords_before.txt │ │ │ │ │ │ ├── keywords_reserved.txt │ │ │ │ │ │ ├── operators.txt │ │ │ │ │ │ ├── operators_after.txt │ │ │ │ │ │ └── operators_before.txt │ │ │ │ │ ├── docker-compose.yml │ │ │ │ │ └── src/ │ │ │ │ │ ├── CSS.php │ │ │ │ │ ├── Exception.php │ │ │ │ │ ├── Exceptions/ │ │ │ │ │ │ ├── BasicException.php │ │ │ │ │ │ ├── FileImportException.php │ │ │ │ │ │ └── IOException.php │ │ │ │ │ ├── JS.php │ │ │ │ │ └── Minify.php │ │ │ │ └── path-converter/ │ │ │ │ ├── LICENSE │ │ │ │ ├── composer.json │ │ │ │ └── src/ │ │ │ │ ├── Converter.php │ │ │ │ ├── ConverterInterface.php │ │ │ │ └── NoConverter.php │ │ │ ├── nesbot/ │ │ │ │ └── carbon/ │ │ │ │ ├── .phpstorm.meta.php │ │ │ │ ├── LICENSE │ │ │ │ ├── bin/ │ │ │ │ │ ├── carbon │ │ │ │ │ └── carbon.bat │ │ │ │ ├── composer.json │ │ │ │ ├── extension.neon │ │ │ │ ├── lazy/ │ │ │ │ │ └── Carbon/ │ │ │ │ │ ├── MessageFormatter/ │ │ │ │ │ │ ├── MessageFormatterMapperStrongType.php │ │ │ │ │ │ └── MessageFormatterMapperWeakType.php │ │ │ │ │ ├── PHPStan/ │ │ │ │ │ │ ├── AbstractMacroBuiltin.php │ │ │ │ │ │ ├── AbstractMacroStatic.php │ │ │ │ │ │ ├── MacroStrongType.php │ │ │ │ │ │ └── MacroWeakType.php │ │ │ │ │ ├── TranslatorStrongType.php │ │ │ │ │ └── TranslatorWeakType.php │ │ │ │ ├── readme.md │ │ │ │ ├── sponsors.php │ │ │ │ └── src/ │ │ │ │ └── Carbon/ │ │ │ │ ├── AbstractTranslator.php │ │ │ │ ├── Carbon.php │ │ │ │ ├── CarbonConverterInterface.php │ │ │ │ ├── CarbonImmutable.php │ │ │ │ ├── CarbonInterface.php │ │ │ │ ├── CarbonInterval.php │ │ │ │ ├── CarbonPeriod.php │ │ │ │ ├── CarbonPeriodImmutable.php │ │ │ │ ├── CarbonTimeZone.php │ │ │ │ ├── Cli/ │ │ │ │ │ └── Invoker.php │ │ │ │ ├── Exceptions/ │ │ │ │ │ ├── BadComparisonUnitException.php │ │ │ │ │ ├── BadFluentConstructorException.php │ │ │ │ │ ├── BadFluentSetterException.php │ │ │ │ │ ├── BadMethodCallException.php │ │ │ │ │ ├── EndLessPeriodException.php │ │ │ │ │ ├── Exception.php │ │ │ │ │ ├── ImmutableException.php │ │ │ │ │ ├── InvalidArgumentException.php │ │ │ │ │ ├── InvalidCastException.php │ │ │ │ │ ├── InvalidDateException.php │ │ │ │ │ ├── InvalidFormatException.php │ │ │ │ │ ├── InvalidIntervalException.php │ │ │ │ │ ├── InvalidPeriodDateException.php │ │ │ │ │ ├── InvalidPeriodParameterException.php │ │ │ │ │ ├── InvalidTimeZoneException.php │ │ │ │ │ ├── InvalidTypeException.php │ │ │ │ │ ├── NotACarbonClassException.php │ │ │ │ │ ├── NotAPeriodException.php │ │ │ │ │ ├── NotLocaleAwareException.php │ │ │ │ │ ├── OutOfRangeException.php │ │ │ │ │ ├── ParseErrorException.php │ │ │ │ │ ├── RuntimeException.php │ │ │ │ │ ├── UnitException.php │ │ │ │ │ ├── UnitNotConfiguredException.php │ │ │ │ │ ├── UnknownGetterException.php │ │ │ │ │ ├── UnknownMethodException.php │ │ │ │ │ ├── UnknownSetterException.php │ │ │ │ │ ├── UnknownUnitException.php │ │ │ │ │ └── UnreachableException.php │ │ │ │ ├── Factory.php │ │ │ │ ├── FactoryImmutable.php │ │ │ │ ├── Lang/ │ │ │ │ │ ├── aa.php │ │ │ │ │ ├── aa_DJ.php │ │ │ │ │ ├── aa_ER.php │ │ │ │ │ ├── aa_ER@saaho.php │ │ │ │ │ ├── aa_ET.php │ │ │ │ │ ├── af.php │ │ │ │ │ ├── af_NA.php │ │ │ │ │ ├── af_ZA.php │ │ │ │ │ ├── agq.php │ │ │ │ │ ├── agr.php │ │ │ │ │ ├── agr_PE.php │ │ │ │ │ ├── ak.php │ │ │ │ │ ├── ak_GH.php │ │ │ │ │ ├── am.php │ │ │ │ │ ├── am_ET.php │ │ │ │ │ ├── an.php │ │ │ │ │ ├── an_ES.php │ │ │ │ │ ├── anp.php │ │ │ │ │ ├── anp_IN.php │ │ │ │ │ ├── ar.php │ │ │ │ │ ├── ar_AE.php │ │ │ │ │ ├── ar_BH.php │ │ │ │ │ ├── ar_DJ.php │ │ │ │ │ ├── ar_DZ.php │ │ │ │ │ ├── ar_EG.php │ │ │ │ │ ├── ar_EH.php │ │ │ │ │ ├── ar_ER.php │ │ │ │ │ ├── ar_IL.php │ │ │ │ │ ├── ar_IN.php │ │ │ │ │ ├── ar_IQ.php │ │ │ │ │ ├── ar_JO.php │ │ │ │ │ ├── ar_KM.php │ │ │ │ │ ├── ar_KW.php │ │ │ │ │ ├── ar_LB.php │ │ │ │ │ ├── ar_LY.php │ │ │ │ │ ├── ar_MA.php │ │ │ │ │ ├── ar_MR.php │ │ │ │ │ ├── ar_OM.php │ │ │ │ │ ├── ar_PS.php │ │ │ │ │ ├── ar_QA.php │ │ │ │ │ ├── ar_SA.php │ │ │ │ │ ├── ar_SD.php │ │ │ │ │ ├── ar_SO.php │ │ │ │ │ ├── ar_SS.php │ │ │ │ │ ├── ar_SY.php │ │ │ │ │ ├── ar_Shakl.php │ │ │ │ │ ├── ar_TD.php │ │ │ │ │ ├── ar_TN.php │ │ │ │ │ ├── ar_YE.php │ │ │ │ │ ├── as.php │ │ │ │ │ ├── as_IN.php │ │ │ │ │ ├── asa.php │ │ │ │ │ ├── ast.php │ │ │ │ │ ├── ast_ES.php │ │ │ │ │ ├── ayc.php │ │ │ │ │ ├── ayc_PE.php │ │ │ │ │ ├── az.php │ │ │ │ │ ├── az_AZ.php │ │ │ │ │ ├── az_Cyrl.php │ │ │ │ │ ├── az_IR.php │ │ │ │ │ ├── az_Latn.php │ │ │ │ │ ├── bas.php │ │ │ │ │ ├── be.php │ │ │ │ │ ├── be_BY.php │ │ │ │ │ ├── be_BY@latin.php │ │ │ │ │ ├── bem.php │ │ │ │ │ ├── bem_ZM.php │ │ │ │ │ ├── ber.php │ │ │ │ │ ├── ber_DZ.php │ │ │ │ │ ├── ber_MA.php │ │ │ │ │ ├── bez.php │ │ │ │ │ ├── bg.php │ │ │ │ │ ├── bg_BG.php │ │ │ │ │ ├── bhb.php │ │ │ │ │ ├── bhb_IN.php │ │ │ │ │ ├── bho.php │ │ │ │ │ ├── bho_IN.php │ │ │ │ │ ├── bi.php │ │ │ │ │ ├── bi_VU.php │ │ │ │ │ ├── bm.php │ │ │ │ │ ├── bn.php │ │ │ │ │ ├── bn_BD.php │ │ │ │ │ ├── bn_IN.php │ │ │ │ │ ├── bo.php │ │ │ │ │ ├── bo_CN.php │ │ │ │ │ ├── bo_IN.php │ │ │ │ │ ├── br.php │ │ │ │ │ ├── br_FR.php │ │ │ │ │ ├── brx.php │ │ │ │ │ ├── brx_IN.php │ │ │ │ │ ├── bs.php │ │ │ │ │ ├── bs_BA.php │ │ │ │ │ ├── bs_Cyrl.php │ │ │ │ │ ├── bs_Latn.php │ │ │ │ │ ├── byn.php │ │ │ │ │ ├── byn_ER.php │ │ │ │ │ ├── ca.php │ │ │ │ │ ├── ca_AD.php │ │ │ │ │ ├── ca_ES.php │ │ │ │ │ ├── ca_ES_Valencia.php │ │ │ │ │ ├── ca_FR.php │ │ │ │ │ ├── ca_IT.php │ │ │ │ │ ├── ccp.php │ │ │ │ │ ├── ccp_IN.php │ │ │ │ │ ├── ce.php │ │ │ │ │ ├── ce_RU.php │ │ │ │ │ ├── cgg.php │ │ │ │ │ ├── chr.php │ │ │ │ │ ├── chr_US.php │ │ │ │ │ ├── ckb.php │ │ │ │ │ ├── cmn.php │ │ │ │ │ ├── cmn_TW.php │ │ │ │ │ ├── crh.php │ │ │ │ │ ├── crh_UA.php │ │ │ │ │ ├── cs.php │ │ │ │ │ ├── cs_CZ.php │ │ │ │ │ ├── csb.php │ │ │ │ │ ├── csb_PL.php │ │ │ │ │ ├── cu.php │ │ │ │ │ ├── cv.php │ │ │ │ │ ├── cv_RU.php │ │ │ │ │ ├── cy.php │ │ │ │ │ ├── cy_GB.php │ │ │ │ │ ├── da.php │ │ │ │ │ ├── da_DK.php │ │ │ │ │ ├── da_GL.php │ │ │ │ │ ├── dav.php │ │ │ │ │ ├── de.php │ │ │ │ │ ├── de_AT.php │ │ │ │ │ ├── de_BE.php │ │ │ │ │ ├── de_CH.php │ │ │ │ │ ├── de_DE.php │ │ │ │ │ ├── de_IT.php │ │ │ │ │ ├── de_LI.php │ │ │ │ │ ├── de_LU.php │ │ │ │ │ ├── dje.php │ │ │ │ │ ├── doi.php │ │ │ │ │ ├── doi_IN.php │ │ │ │ │ ├── dsb.php │ │ │ │ │ ├── dsb_DE.php │ │ │ │ │ ├── dua.php │ │ │ │ │ ├── dv.php │ │ │ │ │ ├── dv_MV.php │ │ │ │ │ ├── dyo.php │ │ │ │ │ ├── dz.php │ │ │ │ │ ├── dz_BT.php │ │ │ │ │ ├── ebu.php │ │ │ │ │ ├── ee.php │ │ │ │ │ ├── ee_TG.php │ │ │ │ │ ├── el.php │ │ │ │ │ ├── el_CY.php │ │ │ │ │ ├── el_GR.php │ │ │ │ │ ├── en.php │ │ │ │ │ ├── en_001.php │ │ │ │ │ ├── en_150.php │ │ │ │ │ ├── en_AG.php │ │ │ │ │ ├── en_AI.php │ │ │ │ │ ├── en_AS.php │ │ │ │ │ ├── en_AT.php │ │ │ │ │ ├── en_AU.php │ │ │ │ │ ├── en_BB.php │ │ │ │ │ ├── en_BE.php │ │ │ │ │ ├── en_BI.php │ │ │ │ │ ├── en_BM.php │ │ │ │ │ ├── en_BS.php │ │ │ │ │ ├── en_BW.php │ │ │ │ │ ├── en_BZ.php │ │ │ │ │ ├── en_CA.php │ │ │ │ │ ├── en_CC.php │ │ │ │ │ ├── en_CH.php │ │ │ │ │ ├── en_CK.php │ │ │ │ │ ├── en_CM.php │ │ │ │ │ ├── en_CX.php │ │ │ │ │ ├── en_CY.php │ │ │ │ │ ├── en_DE.php │ │ │ │ │ ├── en_DG.php │ │ │ │ │ ├── en_DK.php │ │ │ │ │ ├── en_DM.php │ │ │ │ │ ├── en_ER.php │ │ │ │ │ ├── en_FI.php │ │ │ │ │ ├── en_FJ.php │ │ │ │ │ ├── en_FK.php │ │ │ │ │ ├── en_FM.php │ │ │ │ │ ├── en_GB.php │ │ │ │ │ ├── en_GD.php │ │ │ │ │ ├── en_GG.php │ │ │ │ │ ├── en_GH.php │ │ │ │ │ ├── en_GI.php │ │ │ │ │ ├── en_GM.php │ │ │ │ │ ├── en_GU.php │ │ │ │ │ ├── en_GY.php │ │ │ │ │ ├── en_HK.php │ │ │ │ │ ├── en_IE.php │ │ │ │ │ ├── en_IL.php │ │ │ │ │ ├── en_IM.php │ │ │ │ │ ├── en_IN.php │ │ │ │ │ ├── en_IO.php │ │ │ │ │ ├── en_ISO.php │ │ │ │ │ ├── en_JE.php │ │ │ │ │ ├── en_JM.php │ │ │ │ │ ├── en_KE.php │ │ │ │ │ ├── en_KI.php │ │ │ │ │ ├── en_KN.php │ │ │ │ │ ├── en_KY.php │ │ │ │ │ ├── en_LC.php │ │ │ │ │ ├── en_LR.php │ │ │ │ │ ├── en_LS.php │ │ │ │ │ ├── en_MG.php │ │ │ │ │ ├── en_MH.php │ │ │ │ │ ├── en_MO.php │ │ │ │ │ ├── en_MP.php │ │ │ │ │ ├── en_MS.php │ │ │ │ │ ├── en_MT.php │ │ │ │ │ ├── en_MU.php │ │ │ │ │ ├── en_MW.php │ │ │ │ │ ├── en_MY.php │ │ │ │ │ ├── en_NA.php │ │ │ │ │ ├── en_NF.php │ │ │ │ │ ├── en_NG.php │ │ │ │ │ ├── en_NL.php │ │ │ │ │ ├── en_NR.php │ │ │ │ │ ├── en_NU.php │ │ │ │ │ ├── en_NZ.php │ │ │ │ │ ├── en_PG.php │ │ │ │ │ ├── en_PH.php │ │ │ │ │ ├── en_PK.php │ │ │ │ │ ├── en_PN.php │ │ │ │ │ ├── en_PR.php │ │ │ │ │ ├── en_PW.php │ │ │ │ │ ├── en_RW.php │ │ │ │ │ ├── en_SB.php │ │ │ │ │ ├── en_SC.php │ │ │ │ │ ├── en_SD.php │ │ │ │ │ ├── en_SE.php │ │ │ │ │ ├── en_SG.php │ │ │ │ │ ├── en_SH.php │ │ │ │ │ ├── en_SI.php │ │ │ │ │ ├── en_SL.php │ │ │ │ │ ├── en_SS.php │ │ │ │ │ ├── en_SX.php │ │ │ │ │ ├── en_SZ.php │ │ │ │ │ ├── en_TC.php │ │ │ │ │ ├── en_TK.php │ │ │ │ │ ├── en_TO.php │ │ │ │ │ ├── en_TT.php │ │ │ │ │ ├── en_TV.php │ │ │ │ │ ├── en_TZ.php │ │ │ │ │ ├── en_UG.php │ │ │ │ │ ├── en_UM.php │ │ │ │ │ ├── en_US.php │ │ │ │ │ ├── en_US_Posix.php │ │ │ │ │ ├── en_VC.php │ │ │ │ │ ├── en_VG.php │ │ │ │ │ ├── en_VI.php │ │ │ │ │ ├── en_VU.php │ │ │ │ │ ├── en_WS.php │ │ │ │ │ ├── en_ZA.php │ │ │ │ │ ├── en_ZM.php │ │ │ │ │ ├── en_ZW.php │ │ │ │ │ ├── eo.php │ │ │ │ │ ├── es.php │ │ │ │ │ ├── es_419.php │ │ │ │ │ ├── es_AR.php │ │ │ │ │ ├── es_BO.php │ │ │ │ │ ├── es_BR.php │ │ │ │ │ ├── es_BZ.php │ │ │ │ │ ├── es_CL.php │ │ │ │ │ ├── es_CO.php │ │ │ │ │ ├── es_CR.php │ │ │ │ │ ├── es_CU.php │ │ │ │ │ ├── es_DO.php │ │ │ │ │ ├── es_EA.php │ │ │ │ │ ├── es_EC.php │ │ │ │ │ ├── es_ES.php │ │ │ │ │ ├── es_GQ.php │ │ │ │ │ ├── es_GT.php │ │ │ │ │ ├── es_HN.php │ │ │ │ │ ├── es_IC.php │ │ │ │ │ ├── es_MX.php │ │ │ │ │ ├── es_NI.php │ │ │ │ │ ├── es_PA.php │ │ │ │ │ ├── es_PE.php │ │ │ │ │ ├── es_PH.php │ │ │ │ │ ├── es_PR.php │ │ │ │ │ ├── es_PY.php │ │ │ │ │ ├── es_SV.php │ │ │ │ │ ├── es_US.php │ │ │ │ │ ├── es_UY.php │ │ │ │ │ ├── es_VE.php │ │ │ │ │ ├── et.php │ │ │ │ │ ├── et_EE.php │ │ │ │ │ ├── eu.php │ │ │ │ │ ├── eu_ES.php │ │ │ │ │ ├── ewo.php │ │ │ │ │ ├── fa.php │ │ │ │ │ ├── fa_AF.php │ │ │ │ │ ├── fa_IR.php │ │ │ │ │ ├── ff.php │ │ │ │ │ ├── ff_CM.php │ │ │ │ │ ├── ff_GN.php │ │ │ │ │ ├── ff_MR.php │ │ │ │ │ ├── ff_SN.php │ │ │ │ │ ├── fi.php │ │ │ │ │ ├── fi_FI.php │ │ │ │ │ ├── fil.php │ │ │ │ │ ├── fil_PH.php │ │ │ │ │ ├── fo.php │ │ │ │ │ ├── fo_DK.php │ │ │ │ │ ├── fo_FO.php │ │ │ │ │ ├── fr.php │ │ │ │ │ ├── fr_BE.php │ │ │ │ │ ├── fr_BF.php │ │ │ │ │ ├── fr_BI.php │ │ │ │ │ ├── fr_BJ.php │ │ │ │ │ ├── fr_BL.php │ │ │ │ │ ├── fr_CA.php │ │ │ │ │ ├── fr_CD.php │ │ │ │ │ ├── fr_CF.php │ │ │ │ │ ├── fr_CG.php │ │ │ │ │ ├── fr_CH.php │ │ │ │ │ ├── fr_CI.php │ │ │ │ │ ├── fr_CM.php │ │ │ │ │ ├── fr_DJ.php │ │ │ │ │ ├── fr_DZ.php │ │ │ │ │ ├── fr_FR.php │ │ │ │ │ ├── fr_GA.php │ │ │ │ │ ├── fr_GF.php │ │ │ │ │ ├── fr_GN.php │ │ │ │ │ ├── fr_GP.php │ │ │ │ │ ├── fr_GQ.php │ │ │ │ │ ├── fr_HT.php │ │ │ │ │ ├── fr_KM.php │ │ │ │ │ ├── fr_LU.php │ │ │ │ │ ├── fr_MA.php │ │ │ │ │ ├── fr_MC.php │ │ │ │ │ ├── fr_MF.php │ │ │ │ │ ├── fr_MG.php │ │ │ │ │ ├── fr_ML.php │ │ │ │ │ ├── fr_MQ.php │ │ │ │ │ ├── fr_MR.php │ │ │ │ │ ├── fr_MU.php │ │ │ │ │ ├── fr_NC.php │ │ │ │ │ ├── fr_NE.php │ │ │ │ │ ├── fr_PF.php │ │ │ │ │ ├── fr_PM.php │ │ │ │ │ ├── fr_RE.php │ │ │ │ │ ├── fr_RW.php │ │ │ │ │ ├── fr_SC.php │ │ │ │ │ ├── fr_SN.php │ │ │ │ │ ├── fr_SY.php │ │ │ │ │ ├── fr_TD.php │ │ │ │ │ ├── fr_TG.php │ │ │ │ │ ├── fr_TN.php │ │ │ │ │ ├── fr_VU.php │ │ │ │ │ ├── fr_WF.php │ │ │ │ │ ├── fr_YT.php │ │ │ │ │ ├── fur.php │ │ │ │ │ ├── fur_IT.php │ │ │ │ │ ├── fy.php │ │ │ │ │ ├── fy_DE.php │ │ │ │ │ ├── fy_NL.php │ │ │ │ │ ├── ga.php │ │ │ │ │ ├── ga_IE.php │ │ │ │ │ ├── gd.php │ │ │ │ │ ├── gd_GB.php │ │ │ │ │ ├── gez.php │ │ │ │ │ ├── gez_ER.php │ │ │ │ │ ├── gez_ET.php │ │ │ │ │ ├── gl.php │ │ │ │ │ ├── gl_ES.php │ │ │ │ │ ├── gom.php │ │ │ │ │ ├── gom_Latn.php │ │ │ │ │ ├── gsw.php │ │ │ │ │ ├── gsw_CH.php │ │ │ │ │ ├── gsw_FR.php │ │ │ │ │ ├── gsw_LI.php │ │ │ │ │ ├── gu.php │ │ │ │ │ ├── gu_IN.php │ │ │ │ │ ├── guz.php │ │ │ │ │ ├── gv.php │ │ │ │ │ ├── gv_GB.php │ │ │ │ │ ├── ha.php │ │ │ │ │ ├── ha_GH.php │ │ │ │ │ ├── ha_NE.php │ │ │ │ │ ├── ha_NG.php │ │ │ │ │ ├── hak.php │ │ │ │ │ ├── hak_TW.php │ │ │ │ │ ├── haw.php │ │ │ │ │ ├── he.php │ │ │ │ │ ├── he_IL.php │ │ │ │ │ ├── hi.php │ │ │ │ │ ├── hi_IN.php │ │ │ │ │ ├── hif.php │ │ │ │ │ ├── hif_FJ.php │ │ │ │ │ ├── hne.php │ │ │ │ │ ├── hne_IN.php │ │ │ │ │ ├── hr.php │ │ │ │ │ ├── hr_BA.php │ │ │ │ │ ├── hr_HR.php │ │ │ │ │ ├── hsb.php │ │ │ │ │ ├── hsb_DE.php │ │ │ │ │ ├── ht.php │ │ │ │ │ ├── ht_HT.php │ │ │ │ │ ├── hu.php │ │ │ │ │ ├── hu_HU.php │ │ │ │ │ ├── hy.php │ │ │ │ │ ├── hy_AM.php │ │ │ │ │ ├── i18n.php │ │ │ │ │ ├── ia.php │ │ │ │ │ ├── ia_FR.php │ │ │ │ │ ├── id.php │ │ │ │ │ ├── id_ID.php │ │ │ │ │ ├── ig.php │ │ │ │ │ ├── ig_NG.php │ │ │ │ │ ├── ii.php │ │ │ │ │ ├── ik.php │ │ │ │ │ ├── ik_CA.php │ │ │ │ │ ├── in.php │ │ │ │ │ ├── is.php │ │ │ │ │ ├── is_IS.php │ │ │ │ │ ├── it.php │ │ │ │ │ ├── it_CH.php │ │ │ │ │ ├── it_IT.php │ │ │ │ │ ├── it_SM.php │ │ │ │ │ ├── it_VA.php │ │ │ │ │ ├── iu.php │ │ │ │ │ ├── iu_CA.php │ │ │ │ │ ├── iw.php │ │ │ │ │ ├── ja.php │ │ │ │ │ ├── ja_JP.php │ │ │ │ │ ├── jgo.php │ │ │ │ │ ├── jmc.php │ │ │ │ │ ├── jv.php │ │ │ │ │ ├── ka.php │ │ │ │ │ ├── ka_GE.php │ │ │ │ │ ├── kab.php │ │ │ │ │ ├── kab_DZ.php │ │ │ │ │ ├── kam.php │ │ │ │ │ ├── kde.php │ │ │ │ │ ├── kea.php │ │ │ │ │ ├── khq.php │ │ │ │ │ ├── ki.php │ │ │ │ │ ├── kk.php │ │ │ │ │ ├── kk_KZ.php │ │ │ │ │ ├── kkj.php │ │ │ │ │ ├── kl.php │ │ │ │ │ ├── kl_GL.php │ │ │ │ │ ├── kln.php │ │ │ │ │ ├── km.php │ │ │ │ │ ├── km_KH.php │ │ │ │ │ ├── kn.php │ │ │ │ │ ├── kn_IN.php │ │ │ │ │ ├── ko.php │ │ │ │ │ ├── ko_KP.php │ │ │ │ │ ├── ko_KR.php │ │ │ │ │ ├── kok.php │ │ │ │ │ ├── kok_IN.php │ │ │ │ │ ├── ks.php │ │ │ │ │ ├── ks_IN.php │ │ │ │ │ ├── ks_IN@devanagari.php │ │ │ │ │ ├── ksb.php │ │ │ │ │ ├── ksf.php │ │ │ │ │ ├── ksh.php │ │ │ │ │ ├── ku.php │ │ │ │ │ ├── ku_TR.php │ │ │ │ │ ├── kw.php │ │ │ │ │ ├── kw_GB.php │ │ │ │ │ ├── ky.php │ │ │ │ │ ├── ky_KG.php │ │ │ │ │ ├── lag.php │ │ │ │ │ ├── lb.php │ │ │ │ │ ├── lb_LU.php │ │ │ │ │ ├── lg.php │ │ │ │ │ ├── lg_UG.php │ │ │ │ │ ├── li.php │ │ │ │ │ ├── li_NL.php │ │ │ │ │ ├── lij.php │ │ │ │ │ ├── lij_IT.php │ │ │ │ │ ├── lkt.php │ │ │ │ │ ├── ln.php │ │ │ │ │ ├── ln_AO.php │ │ │ │ │ ├── ln_CD.php │ │ │ │ │ ├── ln_CF.php │ │ │ │ │ ├── ln_CG.php │ │ │ │ │ ├── lo.php │ │ │ │ │ ├── lo_LA.php │ │ │ │ │ ├── lrc.php │ │ │ │ │ ├── lrc_IQ.php │ │ │ │ │ ├── lt.php │ │ │ │ │ ├── lt_LT.php │ │ │ │ │ ├── lu.php │ │ │ │ │ ├── luo.php │ │ │ │ │ ├── luy.php │ │ │ │ │ ├── lv.php │ │ │ │ │ ├── lv_LV.php │ │ │ │ │ ├── lzh.php │ │ │ │ │ ├── lzh_TW.php │ │ │ │ │ ├── mag.php │ │ │ │ │ ├── mag_IN.php │ │ │ │ │ ├── mai.php │ │ │ │ │ ├── mai_IN.php │ │ │ │ │ ├── mas.php │ │ │ │ │ ├── mas_TZ.php │ │ │ │ │ ├── mer.php │ │ │ │ │ ├── mfe.php │ │ │ │ │ ├── mfe_MU.php │ │ │ │ │ ├── mg.php │ │ │ │ │ ├── mg_MG.php │ │ │ │ │ ├── mgh.php │ │ │ │ │ ├── mgo.php │ │ │ │ │ ├── mhr.php │ │ │ │ │ ├── mhr_RU.php │ │ │ │ │ ├── mi.php │ │ │ │ │ ├── mi_NZ.php │ │ │ │ │ ├── miq.php │ │ │ │ │ ├── miq_NI.php │ │ │ │ │ ├── mjw.php │ │ │ │ │ ├── mjw_IN.php │ │ │ │ │ ├── mk.php │ │ │ │ │ ├── mk_MK.php │ │ │ │ │ ├── ml.php │ │ │ │ │ ├── ml_IN.php │ │ │ │ │ ├── mn.php │ │ │ │ │ ├── mn_MN.php │ │ │ │ │ ├── mni.php │ │ │ │ │ ├── mni_IN.php │ │ │ │ │ ├── mo.php │ │ │ │ │ ├── mr.php │ │ │ │ │ ├── mr_IN.php │ │ │ │ │ ├── ms.php │ │ │ │ │ ├── ms_BN.php │ │ │ │ │ ├── ms_MY.php │ │ │ │ │ ├── ms_SG.php │ │ │ │ │ ├── mt.php │ │ │ │ │ ├── mt_MT.php │ │ │ │ │ ├── mua.php │ │ │ │ │ ├── my.php │ │ │ │ │ ├── my_MM.php │ │ │ │ │ ├── mzn.php │ │ │ │ │ ├── nan.php │ │ │ │ │ ├── nan_TW.php │ │ │ │ │ ├── nan_TW@latin.php │ │ │ │ │ ├── naq.php │ │ │ │ │ ├── nb.php │ │ │ │ │ ├── nb_NO.php │ │ │ │ │ ├── nb_SJ.php │ │ │ │ │ ├── nd.php │ │ │ │ │ ├── nds.php │ │ │ │ │ ├── nds_DE.php │ │ │ │ │ ├── nds_NL.php │ │ │ │ │ ├── ne.php │ │ │ │ │ ├── ne_IN.php │ │ │ │ │ ├── ne_NP.php │ │ │ │ │ ├── nhn.php │ │ │ │ │ ├── nhn_MX.php │ │ │ │ │ ├── niu.php │ │ │ │ │ ├── niu_NU.php │ │ │ │ │ ├── nl.php │ │ │ │ │ ├── nl_AW.php │ │ │ │ │ ├── nl_BE.php │ │ │ │ │ ├── nl_BQ.php │ │ │ │ │ ├── nl_CW.php │ │ │ │ │ ├── nl_NL.php │ │ │ │ │ ├── nl_SR.php │ │ │ │ │ ├── nl_SX.php │ │ │ │ │ ├── nmg.php │ │ │ │ │ ├── nn.php │ │ │ │ │ ├── nn_NO.php │ │ │ │ │ ├── nnh.php │ │ │ │ │ ├── no.php │ │ │ │ │ ├── nr.php │ │ │ │ │ ├── nr_ZA.php │ │ │ │ │ ├── nso.php │ │ │ │ │ ├── nso_ZA.php │ │ │ │ │ ├── nus.php │ │ │ │ │ ├── nyn.php │ │ │ │ │ ├── oc.php │ │ │ │ │ ├── oc_FR.php │ │ │ │ │ ├── om.php │ │ │ │ │ ├── om_ET.php │ │ │ │ │ ├── om_KE.php │ │ │ │ │ ├── or.php │ │ │ │ │ ├── or_IN.php │ │ │ │ │ ├── os.php │ │ │ │ │ ├── os_RU.php │ │ │ │ │ ├── pa.php │ │ │ │ │ ├── pa_Arab.php │ │ │ │ │ ├── pa_Guru.php │ │ │ │ │ ├── pa_IN.php │ │ │ │ │ ├── pa_PK.php │ │ │ │ │ ├── pap.php │ │ │ │ │ ├── pap_AW.php │ │ │ │ │ ├── pap_CW.php │ │ │ │ │ ├── pl.php │ │ │ │ │ ├── pl_PL.php │ │ │ │ │ ├── prg.php │ │ │ │ │ ├── ps.php │ │ │ │ │ ├── ps_AF.php │ │ │ │ │ ├── pt.php │ │ │ │ │ ├── pt_AO.php │ │ │ │ │ ├── pt_BR.php │ │ │ │ │ ├── pt_CH.php │ │ │ │ │ ├── pt_CV.php │ │ │ │ │ ├── pt_GQ.php │ │ │ │ │ ├── pt_GW.php │ │ │ │ │ ├── pt_LU.php │ │ │ │ │ ├── pt_MO.php │ │ │ │ │ ├── pt_MZ.php │ │ │ │ │ ├── pt_PT.php │ │ │ │ │ ├── pt_ST.php │ │ │ │ │ ├── pt_TL.php │ │ │ │ │ ├── qu.php │ │ │ │ │ ├── qu_BO.php │ │ │ │ │ ├── qu_EC.php │ │ │ │ │ ├── quz.php │ │ │ │ │ ├── quz_PE.php │ │ │ │ │ ├── raj.php │ │ │ │ │ ├── raj_IN.php │ │ │ │ │ ├── rm.php │ │ │ │ │ ├── rn.php │ │ │ │ │ ├── ro.php │ │ │ │ │ ├── ro_MD.php │ │ │ │ │ ├── ro_RO.php │ │ │ │ │ ├── rof.php │ │ │ │ │ ├── ru.php │ │ │ │ │ ├── ru_BY.php │ │ │ │ │ ├── ru_KG.php │ │ │ │ │ ├── ru_KZ.php │ │ │ │ │ ├── ru_MD.php │ │ │ │ │ ├── ru_RU.php │ │ │ │ │ ├── ru_UA.php │ │ │ │ │ ├── rw.php │ │ │ │ │ ├── rw_RW.php │ │ │ │ │ ├── rwk.php │ │ │ │ │ ├── sa.php │ │ │ │ │ ├── sa_IN.php │ │ │ │ │ ├── sah.php │ │ │ │ │ ├── sah_RU.php │ │ │ │ │ ├── saq.php │ │ │ │ │ ├── sat.php │ │ │ │ │ ├── sat_IN.php │ │ │ │ │ ├── sbp.php │ │ │ │ │ ├── sc.php │ │ │ │ │ ├── sc_IT.php │ │ │ │ │ ├── sd.php │ │ │ │ │ ├── sd_IN.php │ │ │ │ │ ├── sd_IN@devanagari.php │ │ │ │ │ ├── se.php │ │ │ │ │ ├── se_FI.php │ │ │ │ │ ├── se_NO.php │ │ │ │ │ ├── se_SE.php │ │ │ │ │ ├── seh.php │ │ │ │ │ ├── ses.php │ │ │ │ │ ├── sg.php │ │ │ │ │ ├── sgs.php │ │ │ │ │ ├── sgs_LT.php │ │ │ │ │ ├── sh.php │ │ │ │ │ ├── shi.php │ │ │ │ │ ├── shi_Latn.php │ │ │ │ │ ├── shi_Tfng.php │ │ │ │ │ ├── shn.php │ │ │ │ │ ├── shn_MM.php │ │ │ │ │ ├── shs.php │ │ │ │ │ ├── shs_CA.php │ │ │ │ │ ├── si.php │ │ │ │ │ ├── si_LK.php │ │ │ │ │ ├── sid.php │ │ │ │ │ ├── sid_ET.php │ │ │ │ │ ├── sk.php │ │ │ │ │ ├── sk_SK.php │ │ │ │ │ ├── sl.php │ │ │ │ │ ├── sl_SI.php │ │ │ │ │ ├── sm.php │ │ │ │ │ ├── sm_WS.php │ │ │ │ │ ├── smn.php │ │ │ │ │ ├── sn.php │ │ │ │ │ ├── so.php │ │ │ │ │ ├── so_DJ.php │ │ │ │ │ ├── so_ET.php │ │ │ │ │ ├── so_KE.php │ │ │ │ │ ├── so_SO.php │ │ │ │ │ ├── sq.php │ │ │ │ │ ├── sq_AL.php │ │ │ │ │ ├── sq_MK.php │ │ │ │ │ ├── sq_XK.php │ │ │ │ │ ├── sr.php │ │ │ │ │ ├── sr_Cyrl.php │ │ │ │ │ ├── sr_Cyrl_BA.php │ │ │ │ │ ├── sr_Cyrl_ME.php │ │ │ │ │ ├── sr_Cyrl_XK.php │ │ │ │ │ ├── sr_Latn.php │ │ │ │ │ ├── sr_Latn_BA.php │ │ │ │ │ ├── sr_Latn_ME.php │ │ │ │ │ ├── sr_Latn_XK.php │ │ │ │ │ ├── sr_ME.php │ │ │ │ │ ├── sr_RS.php │ │ │ │ │ ├── sr_RS@latin.php │ │ │ │ │ ├── ss.php │ │ │ │ │ ├── ss_ZA.php │ │ │ │ │ ├── st.php │ │ │ │ │ ├── st_ZA.php │ │ │ │ │ ├── sv.php │ │ │ │ │ ├── sv_AX.php │ │ │ │ │ ├── sv_FI.php │ │ │ │ │ ├── sv_SE.php │ │ │ │ │ ├── sw.php │ │ │ │ │ ├── sw_CD.php │ │ │ │ │ ├── sw_KE.php │ │ │ │ │ ├── sw_TZ.php │ │ │ │ │ ├── sw_UG.php │ │ │ │ │ ├── szl.php │ │ │ │ │ ├── szl_PL.php │ │ │ │ │ ├── ta.php │ │ │ │ │ ├── ta_IN.php │ │ │ │ │ ├── ta_LK.php │ │ │ │ │ ├── ta_MY.php │ │ │ │ │ ├── ta_SG.php │ │ │ │ │ ├── tcy.php │ │ │ │ │ ├── tcy_IN.php │ │ │ │ │ ├── te.php │ │ │ │ │ ├── te_IN.php │ │ │ │ │ ├── teo.php │ │ │ │ │ ├── teo_KE.php │ │ │ │ │ ├── tet.php │ │ │ │ │ ├── tg.php │ │ │ │ │ ├── tg_TJ.php │ │ │ │ │ ├── th.php │ │ │ │ │ ├── th_TH.php │ │ │ │ │ ├── the.php │ │ │ │ │ ├── the_NP.php │ │ │ │ │ ├── ti.php │ │ │ │ │ ├── ti_ER.php │ │ │ │ │ ├── ti_ET.php │ │ │ │ │ ├── tig.php │ │ │ │ │ ├── tig_ER.php │ │ │ │ │ ├── tk.php │ │ │ │ │ ├── tk_TM.php │ │ │ │ │ ├── tl.php │ │ │ │ │ ├── tl_PH.php │ │ │ │ │ ├── tlh.php │ │ │ │ │ ├── tn.php │ │ │ │ │ ├── tn_ZA.php │ │ │ │ │ ├── to.php │ │ │ │ │ ├── to_TO.php │ │ │ │ │ ├── tpi.php │ │ │ │ │ ├── tpi_PG.php │ │ │ │ │ ├── tr.php │ │ │ │ │ ├── tr_CY.php │ │ │ │ │ ├── tr_TR.php │ │ │ │ │ ├── ts.php │ │ │ │ │ ├── ts_ZA.php │ │ │ │ │ ├── tt.php │ │ │ │ │ ├── tt_RU.php │ │ │ │ │ ├── tt_RU@iqtelif.php │ │ │ │ │ ├── twq.php │ │ │ │ │ ├── tzl.php │ │ │ │ │ ├── tzm.php │ │ │ │ │ ├── tzm_Latn.php │ │ │ │ │ ├── ug.php │ │ │ │ │ ├── ug_CN.php │ │ │ │ │ ├── uk.php │ │ │ │ │ ├── uk_UA.php │ │ │ │ │ ├── unm.php │ │ │ │ │ ├── unm_US.php │ │ │ │ │ ├── ur.php │ │ │ │ │ ├── ur_IN.php │ │ │ │ │ ├── ur_PK.php │ │ │ │ │ ├── uz.php │ │ │ │ │ ├── uz_Arab.php │ │ │ │ │ ├── uz_Cyrl.php │ │ │ │ │ ├── uz_Latn.php │ │ │ │ │ ├── uz_UZ.php │ │ │ │ │ ├── uz_UZ@cyrillic.php │ │ │ │ │ ├── vai.php │ │ │ │ │ ├── vai_Latn.php │ │ │ │ │ ├── vai_Vaii.php │ │ │ │ │ ├── ve.php │ │ │ │ │ ├── ve_ZA.php │ │ │ │ │ ├── vi.php │ │ │ │ │ ├── vi_VN.php │ │ │ │ │ ├── vo.php │ │ │ │ │ ├── vun.php │ │ │ │ │ ├── wa.php │ │ │ │ │ ├── wa_BE.php │ │ │ │ │ ├── wae.php │ │ │ │ │ ├── wae_CH.php │ │ │ │ │ ├── wal.php │ │ │ │ │ ├── wal_ET.php │ │ │ │ │ ├── wo.php │ │ │ │ │ ├── wo_SN.php │ │ │ │ │ ├── xh.php │ │ │ │ │ ├── xh_ZA.php │ │ │ │ │ ├── xog.php │ │ │ │ │ ├── yav.php │ │ │ │ │ ├── yi.php │ │ │ │ │ ├── yi_US.php │ │ │ │ │ ├── yo.php │ │ │ │ │ ├── yo_BJ.php │ │ │ │ │ ├── yo_NG.php │ │ │ │ │ ├── yue.php │ │ │ │ │ ├── yue_HK.php │ │ │ │ │ ├── yue_Hans.php │ │ │ │ │ ├── yue_Hant.php │ │ │ │ │ ├── yuw.php │ │ │ │ │ ├── yuw_PG.php │ │ │ │ │ ├── zgh.php │ │ │ │ │ ├── zh.php │ │ │ │ │ ├── zh_CN.php │ │ │ │ │ ├── zh_HK.php │ │ │ │ │ ├── zh_Hans.php │ │ │ │ │ ├── zh_Hans_HK.php │ │ │ │ │ ├── zh_Hans_MO.php │ │ │ │ │ ├── zh_Hans_SG.php │ │ │ │ │ ├── zh_Hant.php │ │ │ │ │ ├── zh_Hant_HK.php │ │ │ │ │ ├── zh_Hant_MO.php │ │ │ │ │ ├── zh_Hant_TW.php │ │ │ │ │ ├── zh_MO.php │ │ │ │ │ ├── zh_SG.php │ │ │ │ │ ├── zh_TW.php │ │ │ │ │ ├── zh_YUE.php │ │ │ │ │ ├── zu.php │ │ │ │ │ └── zu_ZA.php │ │ │ │ ├── Language.php │ │ │ │ ├── Laravel/ │ │ │ │ │ └── ServiceProvider.php │ │ │ │ ├── List/ │ │ │ │ │ ├── languages.php │ │ │ │ │ └── regions.php │ │ │ │ ├── MessageFormatter/ │ │ │ │ │ └── MessageFormatterMapper.php │ │ │ │ ├── PHPStan/ │ │ │ │ │ ├── AbstractMacro.php │ │ │ │ │ ├── Macro.php │ │ │ │ │ ├── MacroExtension.php │ │ │ │ │ └── MacroScanner.php │ │ │ │ ├── Traits/ │ │ │ │ │ ├── Boundaries.php │ │ │ │ │ ├── Cast.php │ │ │ │ │ ├── Comparison.php │ │ │ │ │ ├── Converter.php │ │ │ │ │ ├── Creator.php │ │ │ │ │ ├── Date.php │ │ │ │ │ ├── DeprecatedProperties.php │ │ │ │ │ ├── Difference.php │ │ │ │ │ ├── IntervalRounding.php │ │ │ │ │ ├── IntervalStep.php │ │ │ │ │ ├── Localization.php │ │ │ │ │ ├── Macro.php │ │ │ │ │ ├── MagicParameter.php │ │ │ │ │ ├── Mixin.php │ │ │ │ │ ├── Modifiers.php │ │ │ │ │ ├── Mutability.php │ │ │ │ │ ├── ObjectInitialisation.php │ │ │ │ │ ├── Options.php │ │ │ │ │ ├── Rounding.php │ │ │ │ │ ├── Serialization.php │ │ │ │ │ ├── Test.php │ │ │ │ │ ├── Timestamp.php │ │ │ │ │ ├── ToStringFormat.php │ │ │ │ │ ├── Units.php │ │ │ │ │ └── Week.php │ │ │ │ ├── Translator.php │ │ │ │ ├── TranslatorImmutable.php │ │ │ │ └── TranslatorStrongTypeInterface.php │ │ │ ├── paragonie/ │ │ │ │ └── random_compat/ │ │ │ │ ├── LICENSE │ │ │ │ ├── build-phar.sh │ │ │ │ ├── composer.json │ │ │ │ ├── dist/ │ │ │ │ │ ├── random_compat.phar.pubkey │ │ │ │ │ └── random_compat.phar.pubkey.asc │ │ │ │ ├── lib/ │ │ │ │ │ └── random.php │ │ │ │ ├── other/ │ │ │ │ │ └── build_phar.php │ │ │ │ ├── psalm-autoload.php │ │ │ │ └── psalm.xml │ │ │ ├── php-mime-mail-parser/ │ │ │ │ └── php-mime-mail-parser/ │ │ │ │ ├── .github/ │ │ │ │ │ └── workflows/ │ │ │ │ │ └── main.yml │ │ │ │ ├── LICENSE │ │ │ │ ├── README.md │ │ │ │ ├── compile_mailparse.sh │ │ │ │ ├── composer.json │ │ │ │ ├── mailparse-stubs.php │ │ │ │ ├── phpunit.xml.dist │ │ │ │ └── src/ │ │ │ │ ├── Attachment.php │ │ │ │ ├── Charset.php │ │ │ │ ├── Contracts/ │ │ │ │ │ ├── CharsetManager.php │ │ │ │ │ └── Middleware.php │ │ │ │ ├── Exception.php │ │ │ │ ├── Middleware.php │ │ │ │ ├── MiddlewareStack.php │ │ │ │ ├── MimePart.php │ │ │ │ └── Parser.php │ │ │ ├── phpmailer/ │ │ │ │ └── phpmailer/ │ │ │ │ ├── COMMITMENT │ │ │ │ ├── LICENSE │ │ │ │ ├── README.md │ │ │ │ ├── SECURITY.md │ │ │ │ ├── VERSION │ │ │ │ ├── composer.json │ │ │ │ ├── get_oauth_token.php │ │ │ │ ├── language/ │ │ │ │ │ ├── phpmailer.lang-af.php │ │ │ │ │ ├── phpmailer.lang-ar.php │ │ │ │ │ ├── phpmailer.lang-az.php │ │ │ │ │ ├── phpmailer.lang-ba.php │ │ │ │ │ ├── phpmailer.lang-be.php │ │ │ │ │ ├── phpmailer.lang-bg.php │ │ │ │ │ ├── phpmailer.lang-ca.php │ │ │ │ │ ├── phpmailer.lang-ch.php │ │ │ │ │ ├── phpmailer.lang-cs.php │ │ │ │ │ ├── phpmailer.lang-da.php │ │ │ │ │ ├── phpmailer.lang-de.php │ │ │ │ │ ├── phpmailer.lang-el.php │ │ │ │ │ ├── phpmailer.lang-eo.php │ │ │ │ │ ├── phpmailer.lang-es.php │ │ │ │ │ ├── phpmailer.lang-et.php │ │ │ │ │ ├── phpmailer.lang-fa.php │ │ │ │ │ ├── phpmailer.lang-fi.php │ │ │ │ │ ├── phpmailer.lang-fo.php │ │ │ │ │ ├── phpmailer.lang-fr.php │ │ │ │ │ ├── phpmailer.lang-gl.php │ │ │ │ │ ├── phpmailer.lang-he.php │ │ │ │ │ ├── phpmailer.lang-hi.php │ │ │ │ │ ├── phpmailer.lang-hr.php │ │ │ │ │ ├── phpmailer.lang-hu.php │ │ │ │ │ ├── phpmailer.lang-hy.php │ │ │ │ │ ├── phpmailer.lang-id.php │ │ │ │ │ ├── phpmailer.lang-it.php │ │ │ │ │ ├── phpmailer.lang-ja.php │ │ │ │ │ ├── phpmailer.lang-ka.php │ │ │ │ │ ├── phpmailer.lang-ko.php │ │ │ │ │ ├── phpmailer.lang-lt.php │ │ │ │ │ ├── phpmailer.lang-lv.php │ │ │ │ │ ├── phpmailer.lang-mg.php │ │ │ │ │ ├── phpmailer.lang-ms.php │ │ │ │ │ ├── phpmailer.lang-nb.php │ │ │ │ │ ├── phpmailer.lang-nl.php │ │ │ │ │ ├── phpmailer.lang-pl.php │ │ │ │ │ ├── phpmailer.lang-pt.php │ │ │ │ │ ├── phpmailer.lang-pt_br.php │ │ │ │ │ ├── phpmailer.lang-ro.php │ │ │ │ │ ├── phpmailer.lang-ru.php │ │ │ │ │ ├── phpmailer.lang-sk.php │ │ │ │ │ ├── phpmailer.lang-sl.php │ │ │ │ │ ├── phpmailer.lang-sr.php │ │ │ │ │ ├── phpmailer.lang-sr_latn.php │ │ │ │ │ ├── phpmailer.lang-sv.php │ │ │ │ │ ├── phpmailer.lang-tl.php │ │ │ │ │ ├── phpmailer.lang-tr.php │ │ │ │ │ ├── phpmailer.lang-uk.php │ │ │ │ │ ├── phpmailer.lang-vi.php │ │ │ │ │ ├── phpmailer.lang-zh.php │ │ │ │ │ └── phpmailer.lang-zh_cn.php │ │ │ │ └── src/ │ │ │ │ ├── Exception.php │ │ │ │ ├── OAuth.php │ │ │ │ ├── OAuthTokenProvider.php │ │ │ │ ├── PHPMailer.php │ │ │ │ ├── POP3.php │ │ │ │ └── SMTP.php │ │ │ ├── psr/ │ │ │ │ ├── clock/ │ │ │ │ │ ├── CHANGELOG.md │ │ │ │ │ ├── LICENSE │ │ │ │ │ ├── README.md │ │ │ │ │ ├── composer.json │ │ │ │ │ └── src/ │ │ │ │ │ └── ClockInterface.php │ │ │ │ ├── container/ │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── LICENSE │ │ │ │ │ ├── README.md │ │ │ │ │ ├── composer.json │ │ │ │ │ └── src/ │ │ │ │ │ ├── ContainerExceptionInterface.php │ │ │ │ │ ├── ContainerInterface.php │ │ │ │ │ └── NotFoundExceptionInterface.php │ │ │ │ ├── http-client/ │ │ │ │ │ ├── CHANGELOG.md │ │ │ │ │ ├── LICENSE │ │ │ │ │ ├── README.md │ │ │ │ │ ├── composer.json │ │ │ │ │ └── src/ │ │ │ │ │ ├── ClientExceptionInterface.php │ │ │ │ │ ├── ClientInterface.php │ │ │ │ │ ├── NetworkExceptionInterface.php │ │ │ │ │ └── RequestExceptionInterface.php │ │ │ │ ├── http-message/ │ │ │ │ │ ├── CHANGELOG.md │ │ │ │ │ ├── LICENSE │ │ │ │ │ ├── README.md │ │ │ │ │ ├── composer.json │ │ │ │ │ └── src/ │ │ │ │ │ ├── MessageInterface.php │ │ │ │ │ ├── RequestInterface.php │ │ │ │ │ ├── ResponseInterface.php │ │ │ │ │ ├── ServerRequestInterface.php │ │ │ │ │ ├── StreamInterface.php │ │ │ │ │ ├── UploadedFileInterface.php │ │ │ │ │ └── UriInterface.php │ │ │ │ ├── log/ │ │ │ │ │ ├── LICENSE │ │ │ │ │ ├── README.md │ │ │ │ │ ├── composer.json │ │ │ │ │ └── src/ │ │ │ │ │ ├── AbstractLogger.php │ │ │ │ │ ├── InvalidArgumentException.php │ │ │ │ │ ├── LogLevel.php │ │ │ │ │ ├── LoggerAwareInterface.php │ │ │ │ │ ├── LoggerAwareTrait.php │ │ │ │ │ ├── LoggerInterface.php │ │ │ │ │ ├── LoggerTrait.php │ │ │ │ │ └── NullLogger.php │ │ │ │ └── simple-cache/ │ │ │ │ ├── .editorconfig │ │ │ │ ├── LICENSE.md │ │ │ │ ├── README.md │ │ │ │ ├── composer.json │ │ │ │ └── src/ │ │ │ │ ├── CacheException.php │ │ │ │ ├── CacheInterface.php │ │ │ │ └── InvalidArgumentException.php │ │ │ ├── ralouphie/ │ │ │ │ └── getallheaders/ │ │ │ │ ├── LICENSE │ │ │ │ ├── README.md │ │ │ │ ├── composer.json │ │ │ │ └── src/ │ │ │ │ └── getallheaders.php │ │ │ ├── robthree/ │ │ │ │ └── twofactorauth/ │ │ │ │ ├── .github/ │ │ │ │ │ ├── FUNDING.yml │ │ │ │ │ └── workflows/ │ │ │ │ │ └── test.yml │ │ │ │ ├── .gitignore │ │ │ │ ├── LICENSE │ │ │ │ ├── README.md │ │ │ │ ├── TwoFactorAuth.phpproj │ │ │ │ ├── TwoFactorAuth.sln │ │ │ │ ├── composer.json │ │ │ │ ├── demo/ │ │ │ │ │ └── demo.php │ │ │ │ ├── docs/ │ │ │ │ │ ├── _config.yml │ │ │ │ │ ├── _layouts/ │ │ │ │ │ │ └── post.html │ │ │ │ │ ├── assets/ │ │ │ │ │ │ └── css/ │ │ │ │ │ │ └── style.scss │ │ │ │ │ ├── getting-started.md │ │ │ │ │ ├── improved-code-verification.md │ │ │ │ │ ├── index.md │ │ │ │ │ ├── optional-configuration.md │ │ │ │ │ ├── qr-codes/ │ │ │ │ │ │ ├── bacon.md │ │ │ │ │ │ ├── endroid.md │ │ │ │ │ │ ├── image-charts.md │ │ │ │ │ │ ├── qr-server.md │ │ │ │ │ │ └── qrickit.md │ │ │ │ │ └── qr-codes.md │ │ │ │ ├── lib/ │ │ │ │ │ ├── Providers/ │ │ │ │ │ │ ├── Qr/ │ │ │ │ │ │ │ ├── BaconQrCodeProvider.php │ │ │ │ │ │ │ ├── BaseHTTPQRCodeProvider.php │ │ │ │ │ │ │ ├── EndroidQrCodeProvider.php │ │ │ │ │ │ │ ├── EndroidQrCodeWithLogoProvider.php │ │ │ │ │ │ │ ├── GoogleChartsQrCodeProvider.php │ │ │ │ │ │ │ ├── IQRCodeProvider.php │ │ │ │ │ │ │ ├── ImageChartsQRCodeProvider.php │ │ │ │ │ │ │ ├── QRException.php │ │ │ │ │ │ │ ├── QRServerProvider.php │ │ │ │ │ │ │ └── QRicketProvider.php │ │ │ │ │ │ ├── Rng/ │ │ │ │ │ │ │ ├── CSRNGProvider.php │ │ │ │ │ │ │ ├── HashRNGProvider.php │ │ │ │ │ │ │ ├── IRNGProvider.php │ │ │ │ │ │ │ ├── MCryptRNGProvider.php │ │ │ │ │ │ │ ├── OpenSSLRNGProvider.php │ │ │ │ │ │ │ └── RNGException.php │ │ │ │ │ │ └── Time/ │ │ │ │ │ │ ├── HttpTimeProvider.php │ │ │ │ │ │ ├── ITimeProvider.php │ │ │ │ │ │ ├── LocalMachineTimeProvider.php │ │ │ │ │ │ ├── NTPTimeProvider.php │ │ │ │ │ │ └── TimeException.php │ │ │ │ │ ├── TwoFactorAuth.php │ │ │ │ │ └── TwoFactorAuthException.php │ │ │ │ ├── phpunit.xml │ │ │ │ └── tests/ │ │ │ │ ├── MightNotMakeAssertions.php │ │ │ │ ├── Providers/ │ │ │ │ │ ├── Qr/ │ │ │ │ │ │ ├── IQRCodeProviderTest.php │ │ │ │ │ │ └── TestQrProvider.php │ │ │ │ │ ├── Rng/ │ │ │ │ │ │ ├── CSRNGProviderTest.php │ │ │ │ │ │ ├── HashRNGProviderTest.php │ │ │ │ │ │ ├── IRNGProviderTest.php │ │ │ │ │ │ ├── MCryptRNGProviderTest.php │ │ │ │ │ │ ├── NeedsRngLengths.php │ │ │ │ │ │ ├── OpenSSLRNGProviderTest.php │ │ │ │ │ │ └── TestRNGProvider.php │ │ │ │ │ └── Time/ │ │ │ │ │ ├── ITimeProviderTest.php │ │ │ │ │ └── TestTimeProvider.php │ │ │ │ └── TwoFactorAuthTest.php │ │ │ ├── stevenmaguire/ │ │ │ │ └── oauth2-keycloak/ │ │ │ │ ├── .gitignore │ │ │ │ ├── .scrutinizer.yml │ │ │ │ ├── .travis.yml │ │ │ │ ├── CHANGELOG.md │ │ │ │ ├── CONTRIBUTING.md │ │ │ │ ├── LICENSE │ │ │ │ ├── README.md │ │ │ │ ├── composer.json │ │ │ │ ├── examples/ │ │ │ │ │ └── index.php │ │ │ │ ├── phpunit.xml.dist │ │ │ │ ├── src/ │ │ │ │ │ └── Provider/ │ │ │ │ │ ├── Exception/ │ │ │ │ │ │ └── EncryptionConfigurationException.php │ │ │ │ │ ├── Keycloak.php │ │ │ │ │ └── KeycloakResourceOwner.php │ │ │ │ └── test/ │ │ │ │ └── src/ │ │ │ │ └── Provider/ │ │ │ │ └── KeycloakTest.php │ │ │ ├── symfony/ │ │ │ │ ├── deprecation-contracts/ │ │ │ │ │ ├── CHANGELOG.md │ │ │ │ │ ├── LICENSE │ │ │ │ │ ├── README.md │ │ │ │ │ ├── composer.json │ │ │ │ │ └── function.php │ │ │ │ ├── polyfill-ctype/ │ │ │ │ │ ├── Ctype.php │ │ │ │ │ ├── LICENSE │ │ │ │ │ ├── README.md │ │ │ │ │ ├── bootstrap.php │ │ │ │ │ ├── bootstrap80.php │ │ │ │ │ └── composer.json │ │ │ │ ├── polyfill-mbstring/ │ │ │ │ │ ├── LICENSE │ │ │ │ │ ├── Mbstring.php │ │ │ │ │ ├── README.md │ │ │ │ │ ├── Resources/ │ │ │ │ │ │ └── unidata/ │ │ │ │ │ │ ├── caseFolding.php │ │ │ │ │ │ ├── lowerCase.php │ │ │ │ │ │ ├── titleCaseRegexp.php │ │ │ │ │ │ └── upperCase.php │ │ │ │ │ ├── bootstrap.php │ │ │ │ │ ├── bootstrap80.php │ │ │ │ │ └── composer.json │ │ │ │ ├── polyfill-php80/ │ │ │ │ │ ├── LICENSE │ │ │ │ │ ├── Php80.php │ │ │ │ │ ├── PhpToken.php │ │ │ │ │ ├── README.md │ │ │ │ │ ├── Resources/ │ │ │ │ │ │ └── stubs/ │ │ │ │ │ │ ├── Attribute.php │ │ │ │ │ │ ├── PhpToken.php │ │ │ │ │ │ ├── Stringable.php │ │ │ │ │ │ ├── UnhandledMatchError.php │ │ │ │ │ │ └── ValueError.php │ │ │ │ │ ├── bootstrap.php │ │ │ │ │ └── composer.json │ │ │ │ ├── polyfill-php81/ │ │ │ │ │ ├── LICENSE │ │ │ │ │ ├── Php81.php │ │ │ │ │ ├── README.md │ │ │ │ │ ├── Resources/ │ │ │ │ │ │ └── stubs/ │ │ │ │ │ │ ├── CURLStringFile.php │ │ │ │ │ │ └── ReturnTypeWillChange.php │ │ │ │ │ ├── bootstrap.php │ │ │ │ │ └── composer.json │ │ │ │ ├── translation/ │ │ │ │ │ ├── CHANGELOG.md │ │ │ │ │ ├── Catalogue/ │ │ │ │ │ │ ├── AbstractOperation.php │ │ │ │ │ │ ├── MergeOperation.php │ │ │ │ │ │ ├── OperationInterface.php │ │ │ │ │ │ └── TargetOperation.php │ │ │ │ │ ├── CatalogueMetadataAwareInterface.php │ │ │ │ │ ├── Command/ │ │ │ │ │ │ ├── TranslationPullCommand.php │ │ │ │ │ │ ├── TranslationPushCommand.php │ │ │ │ │ │ ├── TranslationTrait.php │ │ │ │ │ │ └── XliffLintCommand.php │ │ │ │ │ ├── DataCollector/ │ │ │ │ │ │ └── TranslationDataCollector.php │ │ │ │ │ ├── DataCollectorTranslator.php │ │ │ │ │ ├── DependencyInjection/ │ │ │ │ │ │ ├── DataCollectorTranslatorPass.php │ │ │ │ │ │ ├── LoggingTranslatorPass.php │ │ │ │ │ │ ├── TranslationDumperPass.php │ │ │ │ │ │ ├── TranslationExtractorPass.php │ │ │ │ │ │ ├── TranslatorPass.php │ │ │ │ │ │ └── TranslatorPathsPass.php │ │ │ │ │ ├── Dumper/ │ │ │ │ │ │ ├── CsvFileDumper.php │ │ │ │ │ │ ├── DumperInterface.php │ │ │ │ │ │ ├── FileDumper.php │ │ │ │ │ │ ├── IcuResFileDumper.php │ │ │ │ │ │ ├── IniFileDumper.php │ │ │ │ │ │ ├── JsonFileDumper.php │ │ │ │ │ │ ├── MoFileDumper.php │ │ │ │ │ │ ├── PhpFileDumper.php │ │ │ │ │ │ ├── PoFileDumper.php │ │ │ │ │ │ ├── QtFileDumper.php │ │ │ │ │ │ ├── XliffFileDumper.php │ │ │ │ │ │ └── YamlFileDumper.php │ │ │ │ │ ├── Exception/ │ │ │ │ │ │ ├── ExceptionInterface.php │ │ │ │ │ │ ├── IncompleteDsnException.php │ │ │ │ │ │ ├── InvalidArgumentException.php │ │ │ │ │ │ ├── InvalidResourceException.php │ │ │ │ │ │ ├── LogicException.php │ │ │ │ │ │ ├── MissingRequiredOptionException.php │ │ │ │ │ │ ├── NotFoundResourceException.php │ │ │ │ │ │ ├── ProviderException.php │ │ │ │ │ │ ├── ProviderExceptionInterface.php │ │ │ │ │ │ ├── RuntimeException.php │ │ │ │ │ │ └── UnsupportedSchemeException.php │ │ │ │ │ ├── Extractor/ │ │ │ │ │ │ ├── AbstractFileExtractor.php │ │ │ │ │ │ ├── ChainExtractor.php │ │ │ │ │ │ ├── ExtractorInterface.php │ │ │ │ │ │ ├── PhpAstExtractor.php │ │ │ │ │ │ ├── PhpExtractor.php │ │ │ │ │ │ ├── PhpStringTokenParser.php │ │ │ │ │ │ └── Visitor/ │ │ │ │ │ │ ├── AbstractVisitor.php │ │ │ │ │ │ ├── ConstraintVisitor.php │ │ │ │ │ │ ├── TransMethodVisitor.php │ │ │ │ │ │ └── TranslatableMessageVisitor.php │ │ │ │ │ ├── Formatter/ │ │ │ │ │ │ ├── IntlFormatter.php │ │ │ │ │ │ ├── IntlFormatterInterface.php │ │ │ │ │ │ ├── MessageFormatter.php │ │ │ │ │ │ └── MessageFormatterInterface.php │ │ │ │ │ ├── IdentityTranslator.php │ │ │ │ │ ├── LICENSE │ │ │ │ │ ├── Loader/ │ │ │ │ │ │ ├── ArrayLoader.php │ │ │ │ │ │ ├── CsvFileLoader.php │ │ │ │ │ │ ├── FileLoader.php │ │ │ │ │ │ ├── IcuDatFileLoader.php │ │ │ │ │ │ ├── IcuResFileLoader.php │ │ │ │ │ │ ├── IniFileLoader.php │ │ │ │ │ │ ├── JsonFileLoader.php │ │ │ │ │ │ ├── LoaderInterface.php │ │ │ │ │ │ ├── MoFileLoader.php │ │ │ │ │ │ ├── PhpFileLoader.php │ │ │ │ │ │ ├── PoFileLoader.php │ │ │ │ │ │ ├── QtFileLoader.php │ │ │ │ │ │ ├── XliffFileLoader.php │ │ │ │ │ │ └── YamlFileLoader.php │ │ │ │ │ ├── LocaleSwitcher.php │ │ │ │ │ ├── LoggingTranslator.php │ │ │ │ │ ├── MessageCatalogue.php │ │ │ │ │ ├── MessageCatalogueInterface.php │ │ │ │ │ ├── MetadataAwareInterface.php │ │ │ │ │ ├── Provider/ │ │ │ │ │ │ ├── AbstractProviderFactory.php │ │ │ │ │ │ ├── Dsn.php │ │ │ │ │ │ ├── FilteringProvider.php │ │ │ │ │ │ ├── NullProvider.php │ │ │ │ │ │ ├── NullProviderFactory.php │ │ │ │ │ │ ├── ProviderFactoryInterface.php │ │ │ │ │ │ ├── ProviderInterface.php │ │ │ │ │ │ ├── TranslationProviderCollection.php │ │ │ │ │ │ └── TranslationProviderCollectionFactory.php │ │ │ │ │ ├── PseudoLocalizationTranslator.php │ │ │ │ │ ├── README.md │ │ │ │ │ ├── Reader/ │ │ │ │ │ │ ├── TranslationReader.php │ │ │ │ │ │ └── TranslationReaderInterface.php │ │ │ │ │ ├── Resources/ │ │ │ │ │ │ ├── bin/ │ │ │ │ │ │ │ └── translation-status.php │ │ │ │ │ │ ├── data/ │ │ │ │ │ │ │ └── parents.json │ │ │ │ │ │ ├── functions.php │ │ │ │ │ │ └── schemas/ │ │ │ │ │ │ ├── xliff-core-1.2-transitional.xsd │ │ │ │ │ │ ├── xliff-core-2.0.xsd │ │ │ │ │ │ └── xml.xsd │ │ │ │ │ ├── Test/ │ │ │ │ │ │ ├── ProviderFactoryTestCase.php │ │ │ │ │ │ └── ProviderTestCase.php │ │ │ │ │ ├── TranslatableMessage.php │ │ │ │ │ ├── Translator.php │ │ │ │ │ ├── TranslatorBag.php │ │ │ │ │ ├── TranslatorBagInterface.php │ │ │ │ │ ├── Util/ │ │ │ │ │ │ ├── ArrayConverter.php │ │ │ │ │ │ └── XliffUtils.php │ │ │ │ │ ├── Writer/ │ │ │ │ │ │ ├── TranslationWriter.php │ │ │ │ │ │ └── TranslationWriterInterface.php │ │ │ │ │ └── composer.json │ │ │ │ └── translation-contracts/ │ │ │ │ ├── CHANGELOG.md │ │ │ │ ├── LICENSE │ │ │ │ ├── LocaleAwareInterface.php │ │ │ │ ├── README.md │ │ │ │ ├── Test/ │ │ │ │ │ └── TranslatorTest.php │ │ │ │ ├── TranslatableInterface.php │ │ │ │ ├── TranslatorInterface.php │ │ │ │ ├── TranslatorTrait.php │ │ │ │ └── composer.json │ │ │ └── twig/ │ │ │ └── twig/ │ │ │ ├── CHANGELOG │ │ │ ├── LICENSE │ │ │ ├── README.rst │ │ │ ├── composer.json │ │ │ └── src/ │ │ │ ├── AbstractTwigCallable.php │ │ │ ├── Attribute/ │ │ │ │ ├── FirstClassTwigCallableReady.php │ │ │ │ └── YieldReady.php │ │ │ ├── Cache/ │ │ │ │ ├── CacheInterface.php │ │ │ │ ├── ChainCache.php │ │ │ │ ├── FilesystemCache.php │ │ │ │ ├── NullCache.php │ │ │ │ └── ReadOnlyFilesystemCache.php │ │ │ ├── Compiler.php │ │ │ ├── Environment.php │ │ │ ├── Error/ │ │ │ │ ├── Error.php │ │ │ │ ├── LoaderError.php │ │ │ │ ├── RuntimeError.php │ │ │ │ └── SyntaxError.php │ │ │ ├── ExpressionParser.php │ │ │ ├── Extension/ │ │ │ │ ├── AbstractExtension.php │ │ │ │ ├── CoreExtension.php │ │ │ │ ├── DebugExtension.php │ │ │ │ ├── EscaperExtension.php │ │ │ │ ├── ExtensionInterface.php │ │ │ │ ├── GlobalsInterface.php │ │ │ │ ├── OptimizerExtension.php │ │ │ │ ├── ProfilerExtension.php │ │ │ │ ├── RuntimeExtensionInterface.php │ │ │ │ ├── SandboxExtension.php │ │ │ │ ├── StagingExtension.php │ │ │ │ ├── StringLoaderExtension.php │ │ │ │ └── YieldNotReadyExtension.php │ │ │ ├── ExtensionSet.php │ │ │ ├── FileExtensionEscapingStrategy.php │ │ │ ├── Lexer.php │ │ │ ├── Loader/ │ │ │ │ ├── ArrayLoader.php │ │ │ │ ├── ChainLoader.php │ │ │ │ ├── FilesystemLoader.php │ │ │ │ └── LoaderInterface.php │ │ │ ├── Markup.php │ │ │ ├── Node/ │ │ │ │ ├── AutoEscapeNode.php │ │ │ │ ├── BlockNode.php │ │ │ │ ├── BlockReferenceNode.php │ │ │ │ ├── BodyNode.php │ │ │ │ ├── CaptureNode.php │ │ │ │ ├── CheckSecurityCallNode.php │ │ │ │ ├── CheckSecurityNode.php │ │ │ │ ├── CheckToStringNode.php │ │ │ │ ├── DeprecatedNode.php │ │ │ │ ├── DoNode.php │ │ │ │ ├── EmbedNode.php │ │ │ │ ├── Expression/ │ │ │ │ │ ├── AbstractExpression.php │ │ │ │ │ ├── ArrayExpression.php │ │ │ │ │ ├── ArrowFunctionExpression.php │ │ │ │ │ ├── AssignNameExpression.php │ │ │ │ │ ├── Binary/ │ │ │ │ │ │ ├── AbstractBinary.php │ │ │ │ │ │ ├── AddBinary.php │ │ │ │ │ │ ├── AndBinary.php │ │ │ │ │ │ ├── BitwiseAndBinary.php │ │ │ │ │ │ ├── BitwiseOrBinary.php │ │ │ │ │ │ ├── BitwiseXorBinary.php │ │ │ │ │ │ ├── ConcatBinary.php │ │ │ │ │ │ ├── DivBinary.php │ │ │ │ │ │ ├── EndsWithBinary.php │ │ │ │ │ │ ├── EqualBinary.php │ │ │ │ │ │ ├── FloorDivBinary.php │ │ │ │ │ │ ├── GreaterBinary.php │ │ │ │ │ │ ├── GreaterEqualBinary.php │ │ │ │ │ │ ├── HasEveryBinary.php │ │ │ │ │ │ ├── HasSomeBinary.php │ │ │ │ │ │ ├── InBinary.php │ │ │ │ │ │ ├── LessBinary.php │ │ │ │ │ │ ├── LessEqualBinary.php │ │ │ │ │ │ ├── MatchesBinary.php │ │ │ │ │ │ ├── ModBinary.php │ │ │ │ │ │ ├── MulBinary.php │ │ │ │ │ │ ├── NotEqualBinary.php │ │ │ │ │ │ ├── NotInBinary.php │ │ │ │ │ │ ├── OrBinary.php │ │ │ │ │ │ ├── PowerBinary.php │ │ │ │ │ │ ├── RangeBinary.php │ │ │ │ │ │ ├── SpaceshipBinary.php │ │ │ │ │ │ ├── StartsWithBinary.php │ │ │ │ │ │ └── SubBinary.php │ │ │ │ │ ├── BlockReferenceExpression.php │ │ │ │ │ ├── CallExpression.php │ │ │ │ │ ├── ConditionalExpression.php │ │ │ │ │ ├── ConstantExpression.php │ │ │ │ │ ├── Filter/ │ │ │ │ │ │ ├── DefaultFilter.php │ │ │ │ │ │ └── RawFilter.php │ │ │ │ │ ├── FilterExpression.php │ │ │ │ │ ├── FunctionExpression.php │ │ │ │ │ ├── FunctionNode/ │ │ │ │ │ │ └── EnumCasesFunction.php │ │ │ │ │ ├── GetAttrExpression.php │ │ │ │ │ ├── InlinePrint.php │ │ │ │ │ ├── MethodCallExpression.php │ │ │ │ │ ├── NameExpression.php │ │ │ │ │ ├── NullCoalesceExpression.php │ │ │ │ │ ├── ParentExpression.php │ │ │ │ │ ├── TempNameExpression.php │ │ │ │ │ ├── Test/ │ │ │ │ │ │ ├── ConstantTest.php │ │ │ │ │ │ ├── DefinedTest.php │ │ │ │ │ │ ├── DivisiblebyTest.php │ │ │ │ │ │ ├── EvenTest.php │ │ │ │ │ │ ├── NullTest.php │ │ │ │ │ │ ├── OddTest.php │ │ │ │ │ │ └── SameasTest.php │ │ │ │ │ ├── TestExpression.php │ │ │ │ │ ├── Unary/ │ │ │ │ │ │ ├── AbstractUnary.php │ │ │ │ │ │ ├── NegUnary.php │ │ │ │ │ │ ├── NotUnary.php │ │ │ │ │ │ └── PosUnary.php │ │ │ │ │ └── VariadicExpression.php │ │ │ │ ├── FlushNode.php │ │ │ │ ├── ForLoopNode.php │ │ │ │ ├── ForNode.php │ │ │ │ ├── IfNode.php │ │ │ │ ├── ImportNode.php │ │ │ │ ├── IncludeNode.php │ │ │ │ ├── MacroNode.php │ │ │ │ ├── ModuleNode.php │ │ │ │ ├── NameDeprecation.php │ │ │ │ ├── Node.php │ │ │ │ ├── NodeCaptureInterface.php │ │ │ │ ├── NodeOutputInterface.php │ │ │ │ ├── PrintNode.php │ │ │ │ ├── SandboxNode.php │ │ │ │ ├── SetNode.php │ │ │ │ ├── TextNode.php │ │ │ │ ├── TypesNode.php │ │ │ │ └── WithNode.php │ │ │ ├── NodeTraverser.php │ │ │ ├── NodeVisitor/ │ │ │ │ ├── AbstractNodeVisitor.php │ │ │ │ ├── EscaperNodeVisitor.php │ │ │ │ ├── MacroAutoImportNodeVisitor.php │ │ │ │ ├── NodeVisitorInterface.php │ │ │ │ ├── OptimizerNodeVisitor.php │ │ │ │ ├── SafeAnalysisNodeVisitor.php │ │ │ │ ├── SandboxNodeVisitor.php │ │ │ │ └── YieldNotReadyNodeVisitor.php │ │ │ ├── Parser.php │ │ │ ├── Profiler/ │ │ │ │ ├── Dumper/ │ │ │ │ │ ├── BaseDumper.php │ │ │ │ │ ├── BlackfireDumper.php │ │ │ │ │ ├── HtmlDumper.php │ │ │ │ │ └── TextDumper.php │ │ │ │ ├── Node/ │ │ │ │ │ ├── EnterProfileNode.php │ │ │ │ │ └── LeaveProfileNode.php │ │ │ │ ├── NodeVisitor/ │ │ │ │ │ └── ProfilerNodeVisitor.php │ │ │ │ └── Profile.php │ │ │ ├── Resources/ │ │ │ │ ├── core.php │ │ │ │ ├── debug.php │ │ │ │ ├── escaper.php │ │ │ │ └── string_loader.php │ │ │ ├── Runtime/ │ │ │ │ └── EscaperRuntime.php │ │ │ ├── RuntimeLoader/ │ │ │ │ ├── ContainerRuntimeLoader.php │ │ │ │ ├── FactoryRuntimeLoader.php │ │ │ │ └── RuntimeLoaderInterface.php │ │ │ ├── Sandbox/ │ │ │ │ ├── SecurityError.php │ │ │ │ ├── SecurityNotAllowedFilterError.php │ │ │ │ ├── SecurityNotAllowedFunctionError.php │ │ │ │ ├── SecurityNotAllowedMethodError.php │ │ │ │ ├── SecurityNotAllowedPropertyError.php │ │ │ │ ├── SecurityNotAllowedTagError.php │ │ │ │ ├── SecurityPolicy.php │ │ │ │ ├── SecurityPolicyInterface.php │ │ │ │ └── SourcePolicyInterface.php │ │ │ ├── Source.php │ │ │ ├── Template.php │ │ │ ├── TemplateWrapper.php │ │ │ ├── Test/ │ │ │ │ ├── IntegrationTestCase.php │ │ │ │ └── NodeTestCase.php │ │ │ ├── Token.php │ │ │ ├── TokenParser/ │ │ │ │ ├── AbstractTokenParser.php │ │ │ │ ├── ApplyTokenParser.php │ │ │ │ ├── AutoEscapeTokenParser.php │ │ │ │ ├── BlockTokenParser.php │ │ │ │ ├── DeprecatedTokenParser.php │ │ │ │ ├── DoTokenParser.php │ │ │ │ ├── EmbedTokenParser.php │ │ │ │ ├── ExtendsTokenParser.php │ │ │ │ ├── FlushTokenParser.php │ │ │ │ ├── ForTokenParser.php │ │ │ │ ├── FromTokenParser.php │ │ │ │ ├── IfTokenParser.php │ │ │ │ ├── ImportTokenParser.php │ │ │ │ ├── IncludeTokenParser.php │ │ │ │ ├── MacroTokenParser.php │ │ │ │ ├── SandboxTokenParser.php │ │ │ │ ├── SetTokenParser.php │ │ │ │ ├── TokenParserInterface.php │ │ │ │ ├── TypesTokenParser.php │ │ │ │ ├── UseTokenParser.php │ │ │ │ └── WithTokenParser.php │ │ │ ├── TokenStream.php │ │ │ ├── TwigCallableInterface.php │ │ │ ├── TwigFilter.php │ │ │ ├── TwigFunction.php │ │ │ ├── TwigTest.php │ │ │ └── Util/ │ │ │ ├── CallableArgumentsExtractor.php │ │ │ ├── DeprecationCollector.php │ │ │ ├── ReflectionCallable.php │ │ │ └── TemplateDirIterator.php │ │ ├── prerequisites.inc.php │ │ ├── presets/ │ │ │ ├── rspamd/ │ │ │ │ ├── preset_1.yml │ │ │ │ ├── preset_3.yml │ │ │ │ └── preset_4.yml │ │ │ └── sieve/ │ │ │ ├── sieve_1.yml │ │ │ ├── sieve_2.yml │ │ │ ├── sieve_3.yml │ │ │ ├── sieve_4.yml │ │ │ ├── sieve_5.yml │ │ │ ├── sieve_6.yml │ │ │ ├── sieve_7.yml │ │ │ └── sieve_8.yml │ │ ├── sessions.inc.php │ │ ├── spf.inc.php │ │ ├── triggers.admin.inc.php │ │ ├── triggers.domainadmin.inc.php │ │ ├── triggers.global.inc.php │ │ ├── triggers.user.inc.php │ │ ├── twig.inc.php │ │ └── vars.inc.php │ ├── index.php │ ├── js/ │ │ ├── build/ │ │ │ ├── 003-bootstrap-select.js │ │ │ ├── 004-datatables.js │ │ │ ├── 007-chart.js │ │ │ ├── 008-chartjs-plugin-datalabels.js │ │ │ ├── 011-api.js │ │ │ └── 013-mailcow.js │ │ ├── presets/ │ │ │ ├── rspamd.js │ │ │ └── sieveMailbox.js │ │ └── site/ │ │ ├── admin.js │ │ ├── dashboard.js │ │ ├── edit.js │ │ ├── index.js │ │ ├── mailbox.js │ │ ├── pwgen.js │ │ ├── qhandler.js │ │ ├── quarantine.js │ │ ├── queue.js │ │ └── user.js │ ├── json_api.php │ ├── lang/ │ │ ├── lang.bg-bg.json │ │ ├── lang.ca-es.json │ │ ├── lang.cs-cz.json │ │ ├── lang.da-dk.json │ │ ├── lang.de-de.json │ │ ├── lang.en-gb.json │ │ ├── lang.es-es.json │ │ ├── lang.fi-fi.json │ │ ├── lang.fr-fr.json │ │ ├── lang.gr-gr.json │ │ ├── lang.hu-hu.json │ │ ├── lang.it-it.json │ │ ├── lang.ja-jp.json │ │ ├── lang.ko-kr.json │ │ ├── lang.lt-lt.json │ │ ├── lang.lv-lv.json │ │ ├── lang.nb-no.json │ │ ├── lang.nl-nl.json │ │ ├── lang.pl-pl.json │ │ ├── lang.pt-br.json │ │ ├── lang.pt-pt.json │ │ ├── lang.ro-ro.json │ │ ├── lang.ru-ru.json │ │ ├── lang.si-si.json │ │ ├── lang.sk-sk.json │ │ ├── lang.sv-se.json │ │ ├── lang.tr-tr.json │ │ ├── lang.uk-ua.json │ │ ├── lang.vi-vn.json │ │ ├── lang.zh-cn.json │ │ └── lang.zh-tw.json │ ├── mobileconfig.php │ ├── mta-sts.php │ ├── oauth/ │ │ ├── authorize.php │ │ ├── profile.php │ │ └── token.php │ ├── qhandler.php │ ├── quarantine.php │ ├── reset-password.php │ ├── resource.php │ ├── robots.txt │ ├── sogo-auth.php │ ├── templates/ │ │ ├── admin/ │ │ │ ├── customize/ │ │ │ │ └── logo.twig │ │ │ ├── tab-config-admins.twig │ │ │ ├── tab-config-customize.twig │ │ │ ├── tab-config-dkim.twig │ │ │ ├── tab-config-f2b.twig │ │ │ ├── tab-config-fwdhosts.twig │ │ │ ├── tab-config-identity-provider.twig │ │ │ ├── tab-config-oauth2.twig │ │ │ ├── tab-config-password-settings.twig │ │ │ ├── tab-config-quarantine.twig │ │ │ ├── tab-config-quota.twig │ │ │ ├── tab-config-rsettings.twig │ │ │ ├── tab-config-rspamd.twig │ │ │ ├── tab-globalfilter-regex.twig │ │ │ ├── tab-ldap.twig │ │ │ ├── tab-routing.twig │ │ │ └── tab-sys-mails.twig │ │ ├── admin.twig │ │ ├── admin_index.twig │ │ ├── base.twig │ │ ├── cache/ │ │ │ └── .gitkeep │ │ ├── dashboard.twig │ │ ├── domainadmin.twig │ │ ├── domainadmin_index.twig │ │ ├── edit/ │ │ │ ├── admin.twig │ │ │ ├── alias.twig │ │ │ ├── aliasdomain.twig │ │ │ ├── app-passwd.twig │ │ │ ├── bcc.twig │ │ │ ├── domain-templates.twig │ │ │ ├── domain.twig │ │ │ ├── domainadmin.twig │ │ │ ├── filter.twig │ │ │ ├── mailbox-templates.twig │ │ │ ├── mailbox.twig │ │ │ ├── oauth2client.twig │ │ │ ├── recipient_map.twig │ │ │ ├── relayhost.twig │ │ │ ├── resource.twig │ │ │ ├── syncjob.twig │ │ │ ├── tls_policy_map.twig │ │ │ └── transport.twig │ │ ├── edit.twig │ │ ├── fido2.twig │ │ ├── mailbox/ │ │ │ ├── rl-frame.twig │ │ │ ├── tab-bcc.twig │ │ │ ├── tab-domain-aliases.twig │ │ │ ├── tab-domains.twig │ │ │ ├── tab-filters.twig │ │ │ ├── tab-mailboxes.twig │ │ │ ├── tab-mbox-aliases.twig │ │ │ ├── tab-resources.twig │ │ │ ├── tab-syncjobs.twig │ │ │ ├── tab-templates-domains.twig │ │ │ ├── tab-templates-mbox.twig │ │ │ └── tab-tls-policy.twig │ │ ├── mailbox.twig │ │ ├── modals/ │ │ │ ├── admin.twig │ │ │ ├── footer.twig │ │ │ ├── mailbox.twig │ │ │ ├── quarantine.twig │ │ │ ├── queue.twig │ │ │ └── user.twig │ │ ├── oauth/ │ │ │ └── authorize.twig │ │ ├── qhandler.twig │ │ ├── quarantine.twig │ │ ├── queue.twig │ │ ├── reset-password.twig │ │ ├── tfa_keys.twig │ │ ├── user/ │ │ │ ├── AppPasswds.twig │ │ │ ├── Pushover.twig │ │ │ ├── SpamAliases.twig │ │ │ ├── Spamfilter.twig │ │ │ ├── Syncjobs.twig │ │ │ ├── tab-user-auth.twig │ │ │ ├── tab-user-details.twig │ │ │ └── tab-user-settings.twig │ │ ├── user.twig │ │ ├── user_domainadmin_common.twig │ │ └── user_index.twig │ └── user.php ├── docker-compose.yml ├── generate_config.sh ├── helper-scripts/ │ ├── _cold-standby.sh │ ├── add-new-lang-keys.php │ ├── backup_and_restore.sh │ ├── check_translations.rb │ ├── dev_tests/ │ │ ├── test_backup_and_restore.sh │ │ └── view_autodiscover.sh │ ├── expiry-dates.sh │ ├── generate_caa_record.py │ ├── mailcow-reset-admin.sh │ ├── reset-learns.sh │ ├── update_compose.sh │ └── update_postscreen_whitelist.sh └── update.sh ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ # EditorConfig helps developers define and maintain consistent # coding styles between different editors and IDEs # editorconfig.org root = true [*] # Change these settings to your own preference indent_style = space indent_size = 2 # We recommend you to keep these unchanged end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true [*.md] trim_trailing_whitespace = false ================================================ FILE: .github/FUNDING.yml ================================================ github: mailcow custom: ["https://www.servercow.de/mailcow?lang=en#sal"] ================================================ FILE: .github/ISSUE_TEMPLATE/Bug_report.yml ================================================ name: 🐞 Bug Report description: Report a reproducible bug for mailcow. (NOT to be used for support questions.) labels: ["bug"] body: - type: checkboxes attributes: label: Contribution guidelines description: Please read the contribution guidelines before proceeding. options: - label: I've read the [contribution guidelines](https://github.com/mailcow/mailcow-dockerized/blob/master/CONTRIBUTING.md) and wholeheartedly agree required: true - type: checkboxes attributes: label: Checklist prior issue creation description: Prior to creating the issue... options: - label: I understand that failure to follow below instructions may cause this issue to be closed. required: true - label: I understand that vague, incomplete or inaccurate information may cause this issue to be closed. required: true - label: I understand that this form is intended solely for reporting software bugs and not for support-related inquiries. required: true - label: I understand that all responses are voluntary and community-driven, and do not constitute commercial support. required: true - label: I confirm that I have reviewed previous [issues](https://github.com/mailcow/mailcow-dockerized/issues) to ensure this matter has not already been addressed. required: true - label: I confirm that my environment meets all [prerequisite requirements](https://docs.mailcow.email/getstarted/prerequisite-system/) as specified in the official documentation. required: true - type: textarea attributes: label: Description description: Please provide a brief description of the bug. If applicable, add screenshots to help explain your problem. (Very useful for bugs in mailcow UI.) validations: required: true - type: textarea attributes: label: "Steps to reproduce:" description: "Please describe the steps to reproduce the bug. Screenshots can be added, if helpful." placeholder: |- 1. ... 2. ... 3. ... validations: required: true - type: textarea attributes: label: "Logs:" description: "Please take a look at the [official documentation](https://docs.mailcow.email/troubleshooting/debug-logs/) and post the last few lines of logs, when the error occurs. For example, docker container logs of affected containers. This will be automatically formatted into code, so no need for backticks." render: plain text validations: required: true - type: markdown attributes: value: | ## System information In this stage we would kindly ask you to attach general system information about your setup. - type: dropdown attributes: label: "Which branch are you using?" description: "#### Run: `git rev-parse --abbrev-ref HEAD`" multiple: false options: - master (stable) - staging - nightly validations: required: true - type: dropdown attributes: label: "Which architecture are you using?" description: "#### Run: `uname -m`" multiple: false options: - x86_64 - ARM64 (aarch64) validations: required: true - type: input attributes: label: "Operating System:" description: "#### Run: `lsb_release -ds`" placeholder: "e.g. Ubuntu 22.04 LTS" validations: required: true - type: input attributes: label: "Server/VM specifications:" placeholder: "Memory, CPU Cores" validations: required: true - type: input attributes: label: "Is Apparmor, SELinux or similar active?" placeholder: "yes/no" validations: required: true - type: input attributes: label: "Virtualization technology:" description: "LXC and OpenVZ are not supported!" placeholder: "KVM, VMware ESXi, Xen, etc" validations: required: true - type: input attributes: label: "Docker version:" description: "#### Run: `docker version`" placeholder: "20.10.21" validations: required: true - type: input attributes: label: "docker-compose version or docker compose version:" description: "#### Run: `docker-compose version` or `docker compose version`" placeholder: "v2.12.2" validations: required: true - type: input attributes: label: "mailcow version:" description: "#### Run: ```git describe --tags `git rev-list --tags --max-count=1` ```" placeholder: "2022-08x" validations: required: true - type: input attributes: label: "Reverse proxy:" placeholder: "e.g. nginx/Traefik, or none" validations: required: true - type: textarea attributes: label: "Logs of git diff:" description: "#### Output of `git diff origin/master`, any other changes to the code? Sanitize if needed. If so, **please post them**:" render: plain text validations: required: false - type: textarea attributes: label: "Logs of iptables -L -vn:" description: "#### Output of `iptables -L -vn`" render: plain text validations: required: true - type: textarea attributes: label: "Logs of ip6tables -L -vn:" description: "#### Output of `ip6tables -L -vn`" render: plain text validations: required: true - type: textarea attributes: label: "Logs of iptables -L -vn -t nat:" description: "#### Output of `iptables -L -vn -t nat`" render: plain text validations: required: true - type: textarea attributes: label: "Logs of ip6tables -L -vn -t nat:" description: "#### Output of `ip6tables -L -vn -t nat`" render: plain text validations: required: true - type: textarea attributes: label: "DNS check:" description: "#### Output of `docker exec -it $(docker ps -qf name=acme-mailcow) dig +short stackoverflow.com @172.22.1.254` (set the IP accordingly, if you changed the internal mailcow network)" render: plain text validations: required: true ================================================ FILE: .github/ISSUE_TEMPLATE/Feature_request.yml ================================================ name: 💡 Feature Request description: Suggest an idea for mailcow. labels: ["enhancement"] body: - type: textarea attributes: label: Summary description: Please describe your idea in a reasonable amount of detail. validations: required: true - type: textarea attributes: label: Motivation description: Please describe how your idea would benefit you and other users. validations: required: true - type: textarea attributes: label: Additional context description: Add any other context or screenshots about the feature request. ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false contact_links: - name: ❓ Community-driven support (Free) url: https://docs.mailcow.email/#community-support-and-chat about: Please use the community forum for questions or assistance - name: 🔥 Premium Support (Paid) url: https://www.servercow.de/mailcow?lang=en#support about: Buy a support subscription for any critical issues and get assisted by the mailcow Team. See conditions! - name: 🚨 Report a security vulnerability url: "mailto:info@servercow.de?subject=mailcow: dockerized Security Vulnerability" about: Please give us appropriate time to verify, respond and fix before disclosure. ================================================ FILE: .github/ISSUE_TEMPLATE/pr_to_nighty_template.yml ================================================ ## :file_folder: Modified files ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ ## Contribution Guidelines * [ ] I've read the [contribution guidelines](https://github.com/mailcow/mailcow-dockerized/blob/master/CONTRIBUTING.md) and wholeheartedly agree them ## What does this PR include? ### Short Description ### Affected Containers ## Did you run tests? ### What did you tested? ### What were the final results? (Awaited, got) ================================================ FILE: .github/renovate.json ================================================ { "enabled": true, "timezone": "Europe/Berlin", "dependencyDashboard": true, "dependencyDashboardTitle": "Renovate Dashboard", "commitBody": "Signed-off-by: milkmaker ", "rebaseWhen": "auto", "labels": ["renovate"], "assignees": [ "@magiccc" ], "baseBranches": ["staging"], "enabledManagers": ["github-actions", "regex", "docker-compose"], "ignorePaths": [ "data\/web\/inc\/lib\/vendor\/**" ], "regexManagers": [ { "fileMatch": ["(^|/)Dockerfile[^/]*$"], "matchStrings": [ "#\\srenovate:\\sdatasource=(?.*?) depName=(?.*?)( versioning=(?.*?))?( extractVersion=(?.*?))?\\s(ENV|ARG) .*?_VERSION=(?.*)\\s" ] } ] } ================================================ FILE: .github/workflows/check_if_support_labeled.yml ================================================ name: Check if labeled support, if so send message and close issue on: issues: types: - labeled jobs: add-comment: if: github.event.label.name == 'support' runs-on: ubuntu-latest permissions: issues: write steps: - name: Add comment run: gh issue comment "$NUMBER" --body "$BODY" env: GH_TOKEN: ${{ secrets.SUPPORTISSUES_ACTION_PAT }} GH_REPO: ${{ github.repository }} NUMBER: ${{ github.event.issue.number }} BODY: | **THIS IS A AUTOMATED MESSAGE!** It seems your issue is not a bug. Therefore we highly advise you to get support! You can get support either by: - ordering a paid [support contract at Servercow](https://www.servercow.de/mailcow?lang=en#support/) (Directly from the developers) or - using the [community forum](https://community.mailcow.email) (**Based on volunteers! NO guaranteed answer**) or - using the [Telegram support channel](https://t.me/mailcow) (**Based on volunteers! NO guaranteed answer**) This issue will be closed. If you think your reported issue is not a support case feel free to comment above and if so the issue will reopened. - name: Close issue env: GH_TOKEN: ${{ secrets.SUPPORTISSUES_ACTION_PAT }} GH_REPO: ${{ github.repository }} NUMBER: ${{ github.event.issue.number }} run: gh issue close "$NUMBER" -r "not planned" ================================================ FILE: .github/workflows/check_prs_if_on_staging.yml ================================================ name: Check PRs if on staging on: pull_request_target: types: [opened, edited] permissions: {} jobs: is_not_staging: runs-on: ubuntu-latest if: github.event.pull_request.base.ref != 'staging' #check if the target branch is not staging steps: - name: Send message uses: thollander/actions-comment-pull-request@v3.0.1 with: github-token: ${{ secrets.CHECKIFPRISSTAGING_ACTION_PAT }} message: | Thanks for contributing! I noticed that you didn't select `staging` as your base branch. Please change the base branch to `staging`. See the attached picture on how to change the base branch to `staging`: ![check_prs_if_on_staging.png](https://raw.githubusercontent.com/mailcow/mailcow-dockerized/master/.github/workflows/assets/check_prs_if_on_staging.png) - name: Fail #we want to see failed checks in the PR if: ${{ success() }} #set exit code to 1 even if commenting somehow failed run: exit 1 is_staging: runs-on: ubuntu-latest if: github.event.pull_request.base.ref == 'staging' #check if the target branch is staging steps: - name: Success run: exit 0 ================================================ FILE: .github/workflows/close_old_issues_and_prs.yml ================================================ name: 'Close stale issues and PRs' on: schedule: # Once every day at midnight UTC - cron: "0 0 * * *" workflow_dispatch: # Allow to run workflow manually issue_comment: # Run workflow on comments jobs: stale: runs-on: ubuntu-latest permissions: issues: write pull-requests: write steps: - name: Mark/Close Stale Issues and Pull Requests 🗑️ uses: actions/stale@v10.2.0 with: repo-token: ${{ secrets.STALE_ACTION_PAT }} days-before-stale: 60 days-before-close: 7 stale-issue-message: > This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. stale-pr-message: > This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. exempt-issue-labels: "pinned,security,enhancement,investigating,neverstale" exempt-pr-labels: "pinned,security,enhancement,investigating,neverstale" stale-issue-label: "stale" stale-pr-label: "stale" exempt-draft-pr: "true" close-issue-reason: "not_planned" operations-per-run: "250" ascending: "true" #DRY-RUN debug-only: "false" ================================================ FILE: .github/workflows/image_builds.yml ================================================ name: Build mailcow Docker Images on: push: branches: [ "master", "staging" ] workflow_dispatch: permissions: contents: read # to fetch code (actions/checkout) jobs: docker_image_builds: strategy: matrix: images: - "acme-mailcow" - "clamd-mailcow" - "dockerapi-mailcow" - "dovecot-mailcow" - "netfilter-mailcow" - "olefy-mailcow" - "php-fpm-mailcow" - "postfix-mailcow" - "rspamd-mailcow" - "sogo-mailcow" - "unbound-mailcow" - "watchdog-mailcow" runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - name: Setup Docker run: | curl -sSL https://get.docker.com/ | CHANNEL=stable sudo sh sudo service docker start - name: Prepair Image Builds run: | cp helper-scripts/docker-compose.override.yml.d/BUILD_FLAGS/docker-compose.override.yml docker-compose.override.yml - name: Build Docker Images run: | docker compose build ${image} env: image: ${{ matrix.images }} ================================================ FILE: .github/workflows/pr_to_nightly.yml ================================================ name: Create PR to merge to nightly from staging on: push: branches: - staging jobs: action-pull-request: runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v6 with: fetch-depth: 0 - name: Run the Action uses: devops-infra/action-pull-request@v1.0.2 with: github_token: ${{ secrets.PRTONIGHTLY_ACTION_PAT }} title: Automatic PR to nightly from ${{ github.event.repository.updated_at}} assignee: DerLinkman source_branch: staging target_branch: nightly reviewer: DerLinkman label: upstream template: .github/ISSUE_TEMPLATE/pr_to_nighty_template.yml get_diff: true ================================================ FILE: .github/workflows/rebuild_backup_image.yml ================================================ name: Build mailcow backup image on: schedule: # At 00:00 on Sunday - cron: "0 0 * * 0" workflow_dispatch: # Allow to run workflow manually jobs: docker_image_build: runs-on: ubuntu-latest permissions: packages: write steps: - name: Checkout uses: actions/checkout@v6 - name: Set up QEMU uses: docker/setup-qemu-action@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v4 - name: Login to GHCR if: github.event_name != 'pull_request' uses: docker/login-action@v4 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push uses: docker/build-push-action@v7 with: context: . platforms: linux/amd64,linux/arm64 file: data/Dockerfiles/backup/Dockerfile push: true tags: ghcr.io/mailcow/backup:latest ================================================ FILE: .github/workflows/update_postscreen_access_list.yml ================================================ name: Update postscreen_access.cidr on: schedule: # Monthly - cron: "0 0 1 * *" workflow_dispatch: # Allow to run workflow manually permissions: contents: read # to fetch code (actions/checkout) jobs: Update-postscreen_access_cidr: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v6 - name: Generate postscreen_access.cidr run: | bash helper-scripts/update_postscreen_whitelist.sh - name: Create Pull Request uses: peter-evans/create-pull-request@v8 with: token: ${{ secrets.mailcow_action_Update_postscreen_access_cidr_pat }} commit-message: update postscreen_access.cidr committer: milkmaker author: milkmaker signoff: false branch: update/postscreen_access.cidr base: staging delete-branch: true add-paths: | data/conf/postfix/postscreen_access.cidr title: '[Postfix] update postscreen_access.cidr' body: | This PR updates the postscreen_access.cidr using GitHub Actions and [helper-scripts/update_postscreen_whitelist.sh](https://github.com/mailcow/mailcow-dockerized/blob/master/helper-scripts/update_postscreen_whitelist.sh) ================================================ FILE: .gitignore ================================================ !data/conf/nginx/dynmaps.conf !data/conf/nginx/meta_exporter.conf !data/conf/nginx/site.conf !/**/.gitkeep *.iml .idea .vscode/* data/assets/ssl-example/* data/assets/ssl/* data/conf/borgmatic/ data/conf/clamav/whitelist.ign2 data/conf/dovecot/acl_anyone data/conf/dovecot/dovecot-master.passwd data/conf/dovecot/dovecot-master.userdb data/conf/dovecot/extra.conf data/conf/dovecot/mail_replica.conf data/conf/dovecot/global_sieve_* data/conf/dovecot/last_login data/conf/dovecot/lua data/conf/dovecot/mail_plugins* data/conf/dovecot/shared_namespace.conf data/conf/dovecot/sni.conf data/conf/dovecot/sogo-sso.conf data/conf/dovecot/sogo_trusted_ip.conf data/conf/dovecot/sql data/conf/dovecot/conf.d/fts.conf data/conf/nextcloud-*.bak data/conf/nginx/*.active data/conf/nginx/*.bak data/conf/nginx/*.conf data/conf/nginx/*.custom data/conf/phpfpm/sogo-sso/sogo-sso.pass data/conf/portainer/ data/conf/postfix/allow_mailcow_local.regexp data/conf/postfix/custom_postscreen_whitelist.cidr data/conf/postfix/custom_transport.pcre data/conf/postfix/extra.cf data/conf/postfix/sni.map data/conf/postfix/sni.map.db data/conf/postfix/sql data/conf/postfix/dns_blocklists.cf data/conf/postfix/dnsbl_reply.map data/conf/rspamd/custom/* data/conf/rspamd/local.d/* data/conf/rspamd/override.d/* data/conf/sogo/custom-theme.js data/conf/sogo/plist_ldap data/conf/sogo/plist_ldap.sh data/conf/sogo/sieve.creds data/conf/sogo/cron.creds data/conf/sogo/custom-fulllogo.svg data/conf/sogo/custom-shortlogo.svg data/conf/sogo/custom-fulllogo.png data/conf/acme/dns-01.conf data/gitea/ data/gogs/ data/hooks/dovecot/* data/hooks/phpfpm/* data/hooks/postfix/* data/hooks/rspamd/* data/hooks/sogo/* data/hooks/unbound/* data/web/templates/cache/* !data/web/templates/cache/.gitkeep data/web/.well-known/acme-challenge data/web/css/build/0081-custom-mailcow.css data/web/inc/vars.local.inc.php data/web/inc/app_info.inc.php data/web/nextcloud*/ data/web/rc*/ docker-compose.override.yml mailcow.conf mailcow.conf_backup rebuild-images.sh refresh_images.sh update_diffs/ create_cold_standby.sh !data/conf/nginx/mailcow_auth.conf data/conf/postfix/postfix-tlspol ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, documentation edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at info@servercow.de. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/4/ ================================================ FILE: CONTRIBUTING.md ================================================ # Contribution Guidelines **_Last modified on 12th November 2025_** First of all, thank you for wanting to provide a bugfix or a new feature for the mailcow community, it's because of your help that the project can continue to grow! As we want to keep mailcow's development structured we setup these Guidelines which helps you to create your issue/pull request accordingly. **PLEASE NOTE, THAT WE WILL CLOSE ISSUES/PULL REQUESTS IF THEY DON'T FULFILL OUR WRITTEN GUIDELINES WRITTEN INSIDE THIS DOCUMENT**. So please check this guidelines before you propose a Issue/Pull Request. ## Topics - [Pull Requests](#pull-requests) - [Issue Reporting](#issue-reporting) - [Guidelines](#issue-reporting-guidelines) - [Issue Report Guide](#issue-report-guide) ## Pull Requests **_Last modified on 15th August 2024_** However, please note the following regarding pull requests: 1. **ALWAYS** create your PR using the staging branch of your locally cloned mailcow instance, as the pull request will end up in said staging branch of mailcow once approved. Ideally, you should simply create a new branch for your pull request that is named after the type of your PR (e.g. `feat/` for function updates or `fix/` for bug fixes) and the actual content (e.g. `sogo-6.0.0` for an update from SOGo to version 6 or `html-escape` for a fix that includes escaping HTML in mailcow). 2. **ALWAYS** report/request issues/features in the english language, even though mailcow is a german based company. This is done to allow other GitHub users to reply to your issues/requests too which did not speak german or other languages besides english. 3. Please **keep** this pull request branch **clean** and free of commits that have nothing to do with the changes you have made (e.g. commits from other users from other branches). *If you make changes to the `update.sh` script or other scripts that trigger a commit, there is usually a developer mode for clean working in this case.* 4. **Test your changes before you commit them as a pull request.** If possible, write a small **test log** or demonstrate the functionality with a **screenshot or GIF**. *We will of course also test your pull request ourselves, but proof from you will save us the question of whether you have tested your own changes yourself.* 5. **Please use** the pull request template we provide once creating a pull request. *HINT: During editing you encounter comments which looks like: ``. These can be removed or kept, as they will not rendered later on GitHub! Please only create actual content without the said comments.* 6. Please **ALWAYS** create the actual pull request against the staging branch and **NEVER** directly against the master branch. *If you forget to do this, our moobot will remind you to switch the branch to staging.* 7. Wait for a merge commit: It may happen that we do not accept your pull request immediately or sometimes not at all for various reasons. Please do not be disappointed if this is the case. We always endeavor to incorporate any meaningful changes from the community into the mailcow project. 8. If you are planning larger and therefore more complex pull requests, it would be advisable to first announce this in a separate issue and then start implementing it after the idea has been accepted in order to avoid unnecessary frustration and effort! 9. If your PR requires a Docker image rebuild (changes to Dockerfiles or files in data/Dockerfiles/), update the image tag in docker-compose.yml. Use the base-image versioning (e.g. ghcr.io/mailcow/sogo:5.12.4 → :5.12.5 for version bumps; append a letter for patch fixes, e.g. :5.12.4a). Follow this scheme. --- ## Issue Reporting **_Last modified on 12th November 2025_** If you plan to report a issue within mailcow please read and understand the following rules: ### Security disclosures / Security-related fixes - Security vulnerabilities and security fixes must always be reported confidentially first to the contact address specified in SECURITY.md before they are integrated, published, or publicly disclosed in issues/PRs. Please wait for a response from the specified contact to ensure coordinated and responsible disclosure. ### Issue Reporting Guidelines 1. **ONLY** use the issue tracker for bug reports or improvement requests and NOT for support questions. For support questions you can either contact the [mailcow community on Telegram](https://docs.mailcow.email/#community-support-and-chat) or the mailcow team directly in exchange for a [support fee](https://docs.mailcow.email/#commercial-support). 2. **ONLY** report an error if you have the **necessary know-how (at least the basics)** for the administration of an e-mail server and the usage of Docker. mailcow is a complex and fully-fledged e-mail server including groupware components on a Docker basement and it requires a bit of technical know-how for debugging and operating. 3. **ALWAYS** report/request issues/features in the english language, even though mailcow is a german based company. This is done to allow other GitHub users to reply to your issues/requests too which did not speak german or other languages besides english. 4. **ONLY** report bugs that are contained in the latest mailcow release series. *The definition of the latest release series includes the last major patch (e.g. 2023-12) and all minor patches (revisions) below it (e.g. 2023-12a, b, c etc.).* New issue reports published starting from January 1, 2024 must meet this criterion, as versions below the latest releases are no longer supported by us. 5. When reporting a problem, please be as detailed as possible and include even the smallest changes to your mailcow installation. Simply fill out the corresponding bug report form in detail and accurately to minimize possible questions. 6. **Before you open an issue/feature request**, please first check whether a similar request already exists in the mailcow tracker on GitHub. If so, please include yourself in this request. 7. When you create a issue/feature request: Please note that the creation does **not guarantee an instant implementation or fix by the mailcow team or the community**. 8. Please **ALWAYS** anonymize any sensitive information in your bug report or feature request before submitting it. ### Issue Report Guide 1. Read your logs; follow them to see what the reason for your problem is. 2. Follow the leads given to you in your logfiles and start investigating. 3. Restarting the troubled service or the whole stack to see if the problem persists. 4. Read the [documentation](https://docs.mailcow.email/) of the troubled service and search its bugtracker for your problem. 5. Search our [issues](https://github.com/mailcow/mailcow-dockerized/issues) for your problem. 6. [Create an issue](https://github.com/mailcow/mailcow-dockerized/issues/new/choose) over at our GitHub repository if you think your problem might be a bug or a missing feature you badly need. But please make sure, that you include **all the logs** and a full description to your problem. 7. Ask your questions in our community-driven [support channels](https://docs.mailcow.email/#community-support-and-chat). ## When creating an issue/feature request or a pull request, you will be asked to confirm these guidelines. ================================================ FILE: LICENSE ================================================ GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. {one line to give the program's name and a brief idea of what it does.} Copyright (C) {year} {name of author} 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 (at your option) 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, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: {project} Copyright (C) {year} {fullname} This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . ================================================ FILE: README.md ================================================ # mailcow: dockerized - 🐮 + 🐋 = 💕 [![Translation status](https://translate.mailcow.email/widgets/mailcow-dockerized/-/translation/svg-badge.svg)](https://translate.mailcow.email/engage/mailcow-dockerized/) [![Twitter URL](https://img.shields.io/twitter/url/https/twitter.com/mailcow_email.svg?style=social&label=Follow%20%40mailcow_email)](https://twitter.com/mailcow_email) ![Mastodon Follow](https://img.shields.io/mastodon/follow/109388212176073348?domain=https%3A%2F%2Fmailcow.social&label=Follow%20%40doncow%40mailcow.social&link=https%3A%2F%2Fmailcow.social%2F%40doncow) ## Want to support mailcow? Please [consider a support contract with Servercow](https://www.servercow.de/mailcow?lang=en#support) to support further development. _We_ support _you_ while _you_ support _us_. :) You can also [get a SAL](https://www.servercow.de/mailcow?lang=en#sal) which is a one-time payment with no liabilities or returning fees. Or just spread the word: moo. ## Many thanks to our GitHub Sponsors ❤️ A big thank you to everyone supporting us on GitHub Sponsors—your contributions mean the world to us! Special thanks to the following amazing supporters: ### 100$/Month Sponsors ### 50$/Month Sponsors ## Info, documentation and support Please see [the official documentation](https://docs.mailcow.email/) for installation and support instructions. 🐄 🐛 **If you found a critical security issue, please mail us to [info at servercow.de](mailto:info@servercow.de).** ## Cowmunity [mailcow community](https://community.mailcow.email) [Telegram mailcow channel](https://telegram.me/mailcow) [Telegram mailcow Off-Topic channel](https://t.me/mailcowOfftopic) [Official 𝕏 (Twitter) Account](https://twitter.com/mailcow_email) [Official Mastodon Account](https://mailcow.social/@doncow) Telegram desktop clients are available for [multiple platforms](https://desktop.telegram.org). You can search the groups history for keywords. ## Misc **Important**: mailcow makes use of various open-source software. Please assure you agree with their license before using mailcow. Any part of mailcow itself is released under **GNU General Public License, Version 3**. mailcow is a registered word mark of The Infrastructure Company GmbH, Parkstr. 42, 47877 Willich, Germany. The project is managed and maintained by The Infrastructure Company GmbH. Originated from @andryyy (André) ================================================ FILE: SECURITY.md ================================================ # Security Policies and Procedures This document outlines security procedures and general policies for the _mailcow: dockerized_ project as found on [mailcow-dockerized](https://github.com/mailcow/mailcow-dockerized). * [Reporting a Vulnerability](#reporting-a-vulnerability) * [Disclosure Policy](#disclosure-policy) * [Comments on this Policy](#comments-on-this-policy) ## Reporting a Vulnerability The mailcow team and community take all security vulnerabilities seriously. Thank you for improving the security of our open source software. We appreciate your efforts and responsible disclosure and will make every effort to acknowledge your contributions. Report security vulnerabilities by emailing the mailcow team at: info at servercow.de mailcow team will acknowledge your email as soon as possible, and will send a more detailed response afterwards indicating the next steps in handling your report. After the initial reply to your report, the mailcow team will endeavor to keep you informed of the progress towards a fix and full announcement, and may ask for additional information or guidance. Report security vulnerabilities in third-party modules to the person or team maintaining the module. ## Disclosure Policy When the mailcow team receives a security bug report, they will assign it to a primary handler. This person will coordinate the fix and release process, involving the following steps: * Confirm the problem and determine the affected versions. * Audit code to find any potential similar problems. * Prepare fixes for all releases still under maintenance. ## Comments on this Policy If you have suggestions on how this process could be improved please submit a pull request. ================================================ FILE: _modules/scripts/core.sh ================================================ #!/usr/bin/env bash # _modules/scripts/core.sh # THIS SCRIPT IS DESIGNED TO BE RUNNING BY MAILCOW SCRIPTS ONLY! # DO NOT, AGAIN, NOT TRY TO RUN THIS SCRIPT STANDALONE!!!!!! # ANSI color for red errors RED='\e[31m' GREEN='\e[32m' YELLOW='\e[33m' BLUE='\e[34m' MAGENTA='\e[35m' LIGHT_RED='\e[91m' LIGHT_GREEN='\e[92m' NC='\e[0m' caller="${BASH_SOURCE[1]##*/}" get_installed_tools(){ for bin in openssl curl docker git awk sha1sum grep cut jq; do if [[ -z $(command -v ${bin}) ]]; then echo "Error: Cannot find command '${bin}'. Cannot proceed." echo "Solution: Please review system requirements and install requirements. Then, re-run the script." echo "See System Requirements: https://docs.mailcow.email/getstarted/install/" echo "Exiting..." exit 1 fi done if grep --help 2>&1 | head -n 1 | grep -q -i "busybox"; then echo -e "${LIGHT_RED}BusyBox grep detected, please install gnu grep, \"apk add --no-cache --upgrade grep\"${NC}"; exit 1; fi # This will also cover sort if cp --help 2>&1 | head -n 1 | grep -q -i "busybox"; then echo -e "${LIGHT_RED}BusyBox cp detected, please install coreutils, \"apk add --no-cache --upgrade coreutils\"${NC}"; exit 1; fi if sed --help 2>&1 | head -n 1 | grep -q -i "busybox"; then echo -e "${LIGHT_RED}BusyBox sed detected, please install gnu sed, \"apk add --no-cache --upgrade sed\"${NC}"; exit 1; fi } get_docker_version(){ # Check Docker Version (need at least 24.X) docker_version=$(docker version --format '{{.Server.Version}}' | cut -d '.' -f 1) } get_compose_type(){ if docker compose > /dev/null 2>&1; then if docker compose version --short | grep -e "^[2-9]\." -e "^v[2-9]\." -e "^[1-9][0-9]\." -e "^v[1-9][0-9]\." > /dev/null 2>&1; then COMPOSE_VERSION=native COMPOSE_COMMAND="docker compose" if [[ "$caller" == "update.sh" ]]; then sed -i 's/^DOCKER_COMPOSE_VERSION=.*/DOCKER_COMPOSE_VERSION=native/' "$SCRIPT_DIR/mailcow.conf" fi echo -e "\e[33mFound Docker Compose Plugin (native).\e[0m" echo -e "\e[33mSetting the DOCKER_COMPOSE_VERSION Variable to native\e[0m" sleep 2 echo -e "\e[33mNotice: You'll have to update this Compose Version via your Package Manager manually!\e[0m" else echo -e "\e[31mCannot find Docker Compose with a Version Higher than 2.X.X.\e[0m" echo -e "\e[31mPlease update/install it manually regarding to this doc site: https://docs.mailcow.email/install/\e[0m" exit 1 fi elif docker-compose > /dev/null 2>&1; then if ! [[ $(alias docker-compose 2> /dev/null) ]] ; then if docker-compose version --short | grep -e "^[2-9]\." -e "^[1-9][0-9]\." > /dev/null 2>&1; then COMPOSE_VERSION=standalone COMPOSE_COMMAND="docker-compose" if [[ "$caller" == "update.sh" ]]; then sed -i 's/^DOCKER_COMPOSE_VERSION=.*/DOCKER_COMPOSE_VERSION=standalone/' "$SCRIPT_DIR/mailcow.conf" fi echo -e "\e[33mFound Docker Compose Standalone.\e[0m" echo -e "\e[33mSetting the DOCKER_COMPOSE_VERSION Variable to standalone\e[0m" sleep 2 echo -e "\e[33mNotice: For an automatic update of docker-compose please use the update_compose.sh scripts located at the helper-scripts folder.\e[0m" else echo -e "\e[31mCannot find Docker Compose with a Version Higher than 2.X.X.\e[0m" echo -e "\e[31mPlease update/install manually regarding to this doc site: https://docs.mailcow.email/install/\e[0m" exit 1 fi fi else echo -e "\e[31mCannot find Docker Compose.\e[0m" echo -e "\e[31mPlease install it regarding to this doc site: https://docs.mailcow.email/install/\e[0m" exit 1 fi } detect_bad_asn() { echo -e "\e[33mDetecting if your IP is listed on Spamhaus Bad ASN List...\e[0m" response=$(curl --connect-timeout 15 --max-time 30 -s -o /dev/null -w "%{http_code}" "https://asn-check.mailcow.email") if [ "$response" -eq 503 ]; then if [ -z "$SPAMHAUS_DQS_KEY" ]; then echo -e "\e[33mYour server's public IP uses an AS that is blocked by Spamhaus to use their DNS public blocklists for Postfix.\e[0m" echo -e "\e[33mmailcow did not detected a value for the variable SPAMHAUS_DQS_KEY inside mailcow.conf!\e[0m" sleep 2 echo "" echo -e "\e[33mTo use the Spamhaus DNS Blocklists again, you will need to create a FREE account for their Data Query Service (DQS) at: https://www.spamhaus.com/free-trial/sign-up-for-a-free-data-query-service-account\e[0m" echo -e "\e[33mOnce done, enter your DQS API key in mailcow.conf and mailcow will do the rest for you!\e[0m" echo "" sleep 2 else echo -e "\e[33mYour server's public IP uses an AS that is blocked by Spamhaus to use their DNS public blocklists for Postfix.\e[0m" echo -e "\e[32mmailcow detected a Value for the variable SPAMHAUS_DQS_KEY inside mailcow.conf. Postfix will use DQS with the given API key...\e[0m" fi elif [ "$response" -eq 200 ]; then echo -e "\e[33mCheck completed! Your IP is \e[32mclean\e[0m" elif [ "$response" -eq 429 ]; then echo -e "\e[33mCheck completed! \e[31mYour IP seems to be rate limited on the ASN Check service... please try again later!\e[0m" else echo -e "\e[31mCheck failed! \e[0mMaybe a DNS or Network problem?\e[0m" fi } check_online_status() { CHECK_ONLINE_DOMAINS=('https://github.com' 'https://hub.docker.com') for domain in "${CHECK_ONLINE_DOMAINS[@]}"; do if timeout 6 curl --head --silent --output /dev/null ${domain}; then return 0 fi done return 1 } prefetch_images() { [[ -z ${BRANCH} ]] && { echo -e "\e[33m\nUnknown branch...\e[0m"; exit 1; } git fetch origin #${BRANCH} while read image; do RET_C=0 until docker pull "${image}"; do RET_C=$((RET_C + 1)) echo -e "\e[33m\nError pulling $image, retrying...\e[0m" [ ${RET_C} -gt 3 ] && { echo -e "\e[31m\nToo many failed retries, exiting\e[0m"; exit 1; } sleep 1 done done < <(git show "origin/${BRANCH}:docker-compose.yml" | grep "image:" | awk '{ gsub("image:","", $3); print $2 }') } docker_garbage() { SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )/../.." && pwd )" IMGS_TO_DELETE=() declare -A IMAGES_INFO COMPOSE_IMAGES=($(grep -oP "image: \K(ghcr\.io/)?mailcow.+" "${SCRIPT_DIR}/docker-compose.yml")) for existing_image in $(docker images --format "{{.ID}}:{{.Repository}}:{{.Tag}}" | grep -E '(mailcow/|ghcr\.io/mailcow/)'); do ID=$(echo "$existing_image" | cut -d ':' -f 1) REPOSITORY=$(echo "$existing_image" | cut -d ':' -f 2) TAG=$(echo "$existing_image" | cut -d ':' -f 3) if [[ "$REPOSITORY" == "mailcow/backup" || "$REPOSITORY" == "ghcr.io/mailcow/backup" ]]; then if [[ "$TAG" != "" ]]; then continue fi fi if [[ " ${COMPOSE_IMAGES[@]} " =~ " ${REPOSITORY}:${TAG} " ]]; then continue else IMGS_TO_DELETE+=("$ID") IMAGES_INFO["$ID"]="$REPOSITORY:$TAG" fi done if [[ ! -z ${IMGS_TO_DELETE[*]} ]]; then echo "The following unused mailcow images were found:" for id in "${IMGS_TO_DELETE[@]}"; do echo " ${IMAGES_INFO[$id]} ($id)" done if [ -z "$FORCE" ]; then read -r -p "Do you want to delete them to free up some space? [y/N] " response if [[ "$response" =~ ^([yY][eE][sS]|[yY])+$ ]]; then docker rmi ${IMGS_TO_DELETE[*]} else echo "OK, skipped." fi else echo "Running in forced mode! Force removing old mailcow images..." docker rmi ${IMGS_TO_DELETE[*]} fi echo -e "\e[32mFurther cleanup...\e[0m" echo "If you want to cleanup further garbage collected by Docker, please make sure all containers are up and running before cleaning your system by executing \"docker system prune\"" fi } in_array() { local e match="$1" shift for e; do [[ "$e" == "$match" ]] && return 0; done return 1 } detect_major_update() { if [ ${BRANCH} == "master" ]; then # Array with major versions # Add major versions here MAJOR_VERSIONS=( "2025-02" "2025-03" "2025-09" ) current_version="" if [[ -f "${SCRIPT_DIR}/data/web/inc/app_info.inc.php" ]]; then current_version=$(grep 'MAILCOW_GIT_VERSION' ${SCRIPT_DIR}/data/web/inc/app_info.inc.php | sed -E 's/.*MAILCOW_GIT_VERSION="([^"]+)".*/\1/') fi if [[ -z "$current_version" ]]; then return 1 fi release_url="https://github.com/mailcow/mailcow-dockerized/releases/tag" updates_to_apply=() for version in "${MAJOR_VERSIONS[@]}"; do if [[ "$current_version" < "$version" ]]; then updates_to_apply+=("$version") fi done if [[ ${#updates_to_apply[@]} -gt 0 ]]; then echo -e "\e[33m\nMAJOR UPDATES to be applied:\e[0m" for update in "${updates_to_apply[@]}"; do echo "$update - $release_url/$update" done echo -e "\nPlease read the release notes before proceeding." read -p "Do you want to proceed with the update? [y/n] " response if [[ "${response}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then echo "Proceeding with the update..." else echo "Update canceled. Exiting." exit 1 fi fi fi } ================================================ FILE: _modules/scripts/ipv6_controller.sh ================================================ #!/usr/bin/env bash # _modules/scripts/ipv6_controller.sh # THIS SCRIPT IS DESIGNED TO BE RUNNING BY MAILCOW SCRIPTS ONLY! # DO NOT, AGAIN, NOT TRY TO RUN THIS SCRIPT STANDALONE!!!!!! # 1) Check if the host supports IPv6 get_ipv6_support() { # ---- helper: probe external IPv6 connectivity without DNS ---- _probe_ipv6_connectivity() { # Use literal, always-on IPv6 echo responders (no DNS required) local PROBE_IPS=("2001:4860:4860::8888" "2606:4700:4700::1111") local ip rc=1 for ip in "${PROBE_IPS[@]}"; do if command -v ping6 &>/dev/null; then ping6 -c1 -W2 "$ip" &>/dev/null || ping6 -c1 -w2 "$ip" &>/dev/null rc=$? elif command -v ping &>/dev/null; then ping -6 -c1 -W2 "$ip" &>/dev/null || ping -6 -c1 -w2 "$ip" &>/dev/null rc=$? else rc=1 fi [[ $rc -eq 0 ]] && return 0 done return 1 } if [[ ! -f /proc/net/if_inet6 ]] || grep -qs '^1' /proc/sys/net/ipv6/conf/all/disable_ipv6 2>/dev/null; then DETECTED_IPV6=false echo -e "${YELLOW}IPv6 not detected on host – ${LIGHT_RED}IPv6 is administratively disabled${YELLOW}.${NC}" return fi if ip -6 route show default 2>/dev/null | grep -qE '^default'; then echo -e "${YELLOW}Default IPv6 route found – testing external IPv6 connectivity...${NC}" if _probe_ipv6_connectivity; then DETECTED_IPV6=true echo -e "IPv6 detected on host – ${LIGHT_GREEN}leaving IPv6 support enabled${YELLOW}.${NC}" else DETECTED_IPV6=false echo -e "${YELLOW}Default IPv6 route present but external IPv6 connectivity failed – ${LIGHT_RED}disabling IPv6 support${YELLOW}.${NC}" fi return fi if ip -6 addr show scope global 2>/dev/null | grep -q 'inet6'; then DETECTED_IPV6=false echo -e "${YELLOW}Global IPv6 address present but no default route – ${LIGHT_RED}disabling IPv6 support${YELLOW}.${NC}" return fi if ip -6 addr show scope link 2>/dev/null | grep -q 'inet6'; then echo -e "${YELLOW}Only link-local IPv6 addresses found – testing external IPv6 connectivity...${NC}" if _probe_ipv6_connectivity; then DETECTED_IPV6=true echo -e "External IPv6 connectivity available – ${LIGHT_GREEN}leaving IPv6 support enabled${YELLOW}.${NC}" else DETECTED_IPV6=false echo -e "${YELLOW}Only link-local IPv6 present and no external connectivity – ${LIGHT_RED}disabling IPv6 support${YELLOW}.${NC}" fi return fi DETECTED_IPV6=false echo -e "${YELLOW}IPv6 not detected on host – ${LIGHT_RED}disabling IPv6 support${YELLOW}.${NC}" } # 2) Ensure Docker daemon.json has (or create) the required IPv6 settings docker_daemon_edit(){ DOCKER_DAEMON_CONFIG="/etc/docker/daemon.json" DOCKER_MAJOR=$(docker version --format '{{.Server.Version}}' 2>/dev/null | cut -d. -f1) MISSING=() _has_kv() { grep -Eq "\"$1\"[[:space:]]*:[[:space:]]*$2" "$DOCKER_DAEMON_CONFIG" 2>/dev/null; } if [[ -f "$DOCKER_DAEMON_CONFIG" ]]; then # reject empty or whitespace-only file immediately if [[ ! -s "$DOCKER_DAEMON_CONFIG" ]] || ! grep -Eq '[{}]' "$DOCKER_DAEMON_CONFIG"; then echo -e "${RED}ERROR: $DOCKER_DAEMON_CONFIG exists but is empty or contains no JSON braces – please initialize it with valid JSON (e.g. {}).${NC}" exit 1 fi # Validate JSON if jq is present if command -v jq &>/dev/null && ! jq empty "$DOCKER_DAEMON_CONFIG" &>/dev/null; then echo -e "${RED}ERROR: Invalid JSON in $DOCKER_DAEMON_CONFIG – please correct manually.${NC}" exit 1 fi # Gather missing keys ! _has_kv ipv6 true && MISSING+=("ipv6: true") # For Docker < 28, keep requiring fixed-cidr-v6 (default bridge needs it on old engines) if [[ -n "$DOCKER_MAJOR" && "$DOCKER_MAJOR" -lt 28 ]]; then ! grep -Eq '"fixed-cidr-v6"[[:space:]]*:[[:space:]]*".+"' "$DOCKER_DAEMON_CONFIG" \ && MISSING+=('fixed-cidr-v6: "fd00:dead:beef:c0::/80"') fi # For Docker < 27, ip6tables needed and was tied to experimental in older releases if [[ -n "$DOCKER_MAJOR" && "$DOCKER_MAJOR" -lt 27 ]]; then _has_kv ipv6 true && ! _has_kv ip6tables true && MISSING+=("ip6tables: true") ! _has_kv experimental true && MISSING+=("experimental: true") fi # Fix if needed if ((${#MISSING[@]}>0)); then echo -e "${MAGENTA}Your daemon.json is missing: ${YELLOW}${MISSING[*]}${NC}" if [[ -n "$FORCE" ]]; then ans=Y else read -p "Would you like to update $DOCKER_DAEMON_CONFIG now? [Y/n] " ans ans=${ans:-Y} fi if [[ $ans =~ ^[Yy]$ ]]; then cp "$DOCKER_DAEMON_CONFIG" "${DOCKER_DAEMON_CONFIG}.bak" if command -v jq &>/dev/null; then TMP=$(mktemp) # Base filter: ensure ipv6 = true JQ_FILTER='.ipv6 = true' # Add fixed-cidr-v6 only for Docker < 28 if [[ -n "$DOCKER_MAJOR" && "$DOCKER_MAJOR" -lt 28 ]]; then JQ_FILTER+=' | .["fixed-cidr-v6"] = (.["fixed-cidr-v6"] // "fd00:dead:beef:c0::/80")' fi # Add ip6tables/experimental only for Docker < 27 if [[ -n "$DOCKER_MAJOR" && "$DOCKER_MAJOR" -lt 27 ]]; then JQ_FILTER+=' | .ip6tables = true | .experimental = true' fi jq "$JQ_FILTER" "$DOCKER_DAEMON_CONFIG" >"$TMP" && mv "$TMP" "$DOCKER_DAEMON_CONFIG" echo -e "${LIGHT_GREEN}daemon.json updated. Restarting Docker...${NC}" (command -v systemctl &>/dev/null && systemctl restart docker) || service docker restart echo -e "${YELLOW}Docker restarted.${NC}" else echo -e "${RED}Please install jq or manually update daemon.json and restart Docker.${NC}" exit 1 fi else echo -e "${YELLOW}User declined Docker update – please insert these changes manually:${NC}" echo "${MISSING[*]}" exit 1 fi fi else # Create new daemon.json if missing if [[ -n "$FORCE" ]]; then ans=Y else read -p "$DOCKER_DAEMON_CONFIG not found. Create it with IPv6 settings? [Y/n] " ans ans=${ans:-Y} fi if [[ $ans =~ ^[Yy]$ ]]; then mkdir -p "$(dirname "$DOCKER_DAEMON_CONFIG")" if [[ -n "$DOCKER_MAJOR" && "$DOCKER_MAJOR" -lt 27 ]]; then cat > "$DOCKER_DAEMON_CONFIG" < "$DOCKER_DAEMON_CONFIG" < "$DOCKER_DAEMON_CONFIG" </dev/null && systemctl restart docker) || service docker restart echo "Docker restarted." else echo "User declined to create daemon.json – please manually merge the docker daemon with these configs:" echo "${MISSING[*]}" exit 1 fi fi } # 3) Main wrapper for generate_config.sh and update.sh configure_ipv6() { # detect manual override if mailcow.conf is present if [[ -n "$MAILCOW_CONF" && -f "$MAILCOW_CONF" ]] && grep -q '^ENABLE_IPV6=' "$MAILCOW_CONF"; then MANUAL_SETTING=$(grep '^ENABLE_IPV6=' "$MAILCOW_CONF" | cut -d= -f2) elif [[ -z "$MAILCOW_CONF" ]] && [[ -n "${ENABLE_IPV6:-}" ]]; then MANUAL_SETTING="$ENABLE_IPV6" else MANUAL_SETTING="" fi get_ipv6_support # if user manually set it, check for mismatch if [[ "$DETECTED_IPV6" != "true" ]]; then if [[ -n "$MAILCOW_CONF" && -f "$MAILCOW_CONF" ]]; then if grep -q '^ENABLE_IPV6=' "$MAILCOW_CONF"; then sed -i 's/^ENABLE_IPV6=.*/ENABLE_IPV6=false/' "$MAILCOW_CONF" else echo "ENABLE_IPV6=false" >> "$MAILCOW_CONF" fi else export IPV6_BOOL=false fi echo "Skipping Docker IPv6 configuration because host does not support IPv6." echo "Make sure to check if your docker daemon.json does not include \"enable_ipv6\": true if you do not want IPv6." echo "IPv6 configuration complete: ENABLE_IPV6=false" sleep 2 return fi docker_daemon_edit if [[ -n "$MAILCOW_CONF" && -f "$MAILCOW_CONF" ]]; then if grep -q '^ENABLE_IPV6=' "$MAILCOW_CONF"; then sed -i 's/^ENABLE_IPV6=.*/ENABLE_IPV6=true/' "$MAILCOW_CONF" else echo "ENABLE_IPV6=true" >> "$MAILCOW_CONF" fi else export IPV6_BOOL=true fi echo "IPv6 configuration complete: ENABLE_IPV6=true" } ================================================ FILE: _modules/scripts/migrate_options.sh ================================================ #!/usr/bin/env bash # _modules/scripts/migrate_options.sh # THIS SCRIPT IS DESIGNED TO BE RUNNING BY MAILCOW SCRIPTS ONLY! # DO NOT, AGAIN, NOT TRY TO RUN THIS SCRIPT STANDALONE!!!!!! migrate_config_options() { sed -i --follow-symlinks '$a\' mailcow.conf KEYS=( SOLR_HEAP SKIP_SOLR SOLR_PORT FLATCURVE_EXPERIMENTAL DISABLE_IPv6 ACME_CONTACT ) for key in "${KEYS[@]}"; do if grep -q "${key}" mailcow.conf; then case "${key}" in SOLR_HEAP) echo "Removing ${key} in mailcow.conf" sed -i '/# Solr heap size in MB\b/d' mailcow.conf sed -i '/# Solr is a prone to run\b/d' mailcow.conf sed -i '/SOLR_HEAP\b/d' mailcow.conf ;; SKIP_SOLR) echo "Removing ${key} in mailcow.conf" sed -i '/\bSkip Solr on low-memory\b/d' mailcow.conf sed -i '/\bSolr is disabled by default\b/d' mailcow.conf sed -i '/\bDisable Solr or\b/d' mailcow.conf sed -i '/\bSKIP_SOLR\b/d' mailcow.conf ;; SOLR_PORT) echo "Removing ${key} in mailcow.conf" sed -i '/\bSOLR_PORT\b/d' mailcow.conf ;; FLATCURVE_EXPERIMENTAL) echo "Removing ${key} in mailcow.conf" sed -i '/\bFLATCURVE_EXPERIMENTAL\b/d' mailcow.conf ;; DISABLE_IPv6) echo "Migrating ${key} to ENABLE_IPv6 in mailcow.conf" local old=$(grep '^DISABLE_IPv6=' "mailcow.conf" | cut -d'=' -f2) local new if [[ "$old" == "y" ]]; then new="false" else new="true" fi sed -i '/^DISABLE_IPv6=/d' "mailcow.conf" echo "ENABLE_IPV6=$new" >> "mailcow.conf" ;; ACME_CONTACT) echo "Deleting obsoleted ${key} in mailcow.conf" sed -i '/^# Lets Encrypt registration contact information/d' mailcow.conf sed -i '/^# Optional: Leave empty for none/d' mailcow.conf sed -i '/^# This value is only used on first order!/d' mailcow.conf sed -i '/^# Setting it at a later point will require the following steps:/d' mailcow.conf sed -i '/^# https:\/\/docs.mailcow.email\/troubleshooting\/debug-reset_tls\//d' mailcow.conf sed -i '/^ACME_CONTACT=.*/d' mailcow.conf sed -i '/^#ACME_CONTACT=.*/d' mailcow.conf ;; esac fi done solr_volume=$(docker volume ls -qf name=^${COMPOSE_PROJECT_NAME}_solr-vol-1) if [[ -n $solr_volume ]]; then echo -e "\e[34mSolr has been replaced within mailcow since 2025-01.\nThe volume $solr_volume is unused.\e[0m" sleep 1 if [ ! "$FORCE" ]; then read -r -p "Remove $solr_volume? [y/N] " response if [[ "$response" =~ ^([yY][eE][sS]|[yY])+$ ]]; then echo -e "\e[33mRemoving $solr_volume...\e[0m" docker volume rm $solr_volume || echo -e "\e[31mFailed to remove. Remove it manually!\e[0m" echo -e "\e[32mSuccessfully removed $solr_volume!\e[0m" else echo -e "Not removing $solr_volume. Run \`docker volume rm $solr_volume\` manually if needed." fi else echo -e "\e[33mForce removing $solr_volume...\e[0m" docker volume rm $solr_volume || echo -e "\e[31mFailed to remove. Remove it manually!\e[0m" echo -e "\e[32mSuccessfully removed $solr_volume!\e[0m" fi fi # Delete old fts.conf before forced switch to flatcurve to ensure update is working properly FTS_CONF_PATH="${SCRIPT_DIR}/data/conf/dovecot/conf.d/fts.conf" if [[ -f "$FTS_CONF_PATH" ]]; then if grep -q "Autogenerated by mailcow" "$FTS_CONF_PATH"; then rm -rf $FTS_CONF_PATH fi fi } ================================================ FILE: _modules/scripts/new_options.sh ================================================ #!/usr/bin/env bash # _modules/scripts/new_options.sh # THIS SCRIPT IS DESIGNED TO BE RUNNING BY MAILCOW SCRIPTS ONLY! # DO NOT, AGAIN, NOT TRY TO RUN THIS SCRIPT STANDALONE!!!!!! adapt_new_options() { CONFIG_ARRAY=( "AUTODISCOVER_SAN" "SKIP_LETS_ENCRYPT" "SKIP_SOGO" "USE_WATCHDOG" "WATCHDOG_NOTIFY_EMAIL" "WATCHDOG_NOTIFY_WEBHOOK" "WATCHDOG_NOTIFY_WEBHOOK_BODY" "WATCHDOG_NOTIFY_BAN" "WATCHDOG_NOTIFY_START" "WATCHDOG_EXTERNAL_CHECKS" "WATCHDOG_SUBJECT" "SKIP_CLAMD" "SKIP_OLEFY" "SKIP_IP_CHECK" "ADDITIONAL_SAN" "DOVEADM_PORT" "IPV4_NETWORK" "IPV6_NETWORK" "LOG_LINES" "SNAT_TO_SOURCE" "SNAT6_TO_SOURCE" "COMPOSE_PROJECT_NAME" "DOCKER_COMPOSE_VERSION" "SQL_PORT" "API_KEY" "API_KEY_READ_ONLY" "API_ALLOW_FROM" "MAILDIR_GC_TIME" "MAILDIR_SUB" "ACL_ANYONE" "FTS_HEAP" "FTS_PROCS" "SKIP_FTS" "ENABLE_SSL_SNI" "ALLOW_ADMIN_EMAIL_LOGIN" "SKIP_HTTP_VERIFICATION" "SOGO_EXPIRE_SESSION" "SOGO_URL_ENCRYPTION_KEY" "REDIS_PORT" "REDISPASS" "DOVECOT_MASTER_USER" "DOVECOT_MASTER_PASS" "MAILCOW_PASS_SCHEME" "ADDITIONAL_SERVER_NAMES" "WATCHDOG_VERBOSE" "WEBAUTHN_ONLY_TRUSTED_VENDORS" "SPAMHAUS_DQS_KEY" "SKIP_UNBOUND_HEALTHCHECK" "DISABLE_NETFILTER_ISOLATION_RULE" "HTTP_REDIRECT" "ENABLE_IPV6" "ACME_DNS_CHALLENGE" "ACME_DNS_PROVIDER" "ACME_ACCOUNT_EMAIL" ) sed -i --follow-symlinks '$a\' mailcow.conf for option in ${CONFIG_ARRAY[@]}; do if grep -q "^#\?${option}=" mailcow.conf; then continue fi echo "Adding new option \"${option}\" to mailcow.conf" case "${option}" in AUTODISCOVER_SAN) echo '# Obtain certificates for autodiscover.* and autoconfig.* domains.' >> mailcow.conf echo '# This can be useful to switch off in case you are in a scenario where a reverse proxy already handles those.' >> mailcow.conf echo '# There are mixed scenarios where ports 80,443 are occupied and you do not want to share certs' >> mailcow.conf echo '# between services. So acme-mailcow obtains for maildomains and all web-things get handled' >> mailcow.conf echo '# in the reverse proxy.' >> mailcow.conf echo 'AUTODISCOVER_SAN=y' >> mailcow.conf ;; DOCKER_COMPOSE_VERSION) echo "# Used Docker Compose version" >> mailcow.conf echo "# Switch here between native (compose plugin) and standalone" >> mailcow.conf echo "# For more informations take a look at the mailcow docs regarding the configuration options." >> mailcow.conf echo "# Normally this should be untouched but if you decided to use either of those you can switch it manually here." >> mailcow.conf echo "# Please be aware that at least one of those variants should be installed on your machine or mailcow will fail." >> mailcow.conf echo "" >> mailcow.conf echo "DOCKER_COMPOSE_VERSION=${DOCKER_COMPOSE_VERSION}" >> mailcow.conf ;; DOVEADM_PORT) echo "DOVEADM_PORT=127.0.0.1:19991" >> mailcow.conf ;; LOG_LINES) echo '# Max log lines per service to keep in Redis logs' >> mailcow.conf echo "LOG_LINES=9999" >> mailcow.conf ;; IPV4_NETWORK) echo '# Internal IPv4 /24 subnet, format n.n.n. (expands to n.n.n.0/24)' >> mailcow.conf echo "IPV4_NETWORK=172.22.1" >> mailcow.conf ;; IPV6_NETWORK) echo '# Internal IPv6 subnet in fc00::/7' >> mailcow.conf echo "IPV6_NETWORK=fd4d:6169:6c63:6f77::/64" >> mailcow.conf ;; SQL_PORT) echo '# Bind SQL to 127.0.0.1 on port 13306' >> mailcow.conf echo "SQL_PORT=127.0.0.1:13306" >> mailcow.conf ;; API_KEY) echo '# Create or override API key for web UI' >> mailcow.conf echo "#API_KEY=" >> mailcow.conf ;; API_KEY_READ_ONLY) echo '# Create or override read-only API key for web UI' >> mailcow.conf echo "#API_KEY_READ_ONLY=" >> mailcow.conf ;; API_ALLOW_FROM) echo '# Must be set for API_KEY to be active' >> mailcow.conf echo '# IPs only, no networks (networks can be set via UI)' >> mailcow.conf echo "#API_ALLOW_FROM=" >> mailcow.conf ;; SNAT_TO_SOURCE) echo '# Use this IPv4 for outgoing connections (SNAT)' >> mailcow.conf echo "#SNAT_TO_SOURCE=" >> mailcow.conf ;; SNAT6_TO_SOURCE) echo '# Use this IPv6 for outgoing connections (SNAT)' >> mailcow.conf echo "#SNAT6_TO_SOURCE=" >> mailcow.conf ;; MAILDIR_GC_TIME) echo '# Garbage collector cleanup' >> mailcow.conf echo '# Deleted domains and mailboxes are moved to /var/vmail/_garbage/timestamp_sanitizedstring' >> mailcow.conf echo '# How long should objects remain in the garbage until they are being deleted? (value in minutes)' >> mailcow.conf echo '# Check interval is hourly' >> mailcow.conf echo 'MAILDIR_GC_TIME=1440' >> mailcow.conf ;; ACL_ANYONE) echo '# Set this to "allow" to enable the anyone pseudo user. Disabled by default.' >> mailcow.conf echo '# When enabled, ACL can be created, that apply to "All authenticated users"' >> mailcow.conf echo '# This should probably only be activated on mail hosts, that are used exclusively by one organisation.' >> mailcow.conf echo '# Otherwise a user might share data with too many other users.' >> mailcow.conf echo 'ACL_ANYONE=disallow' >> mailcow.conf ;; FTS_HEAP) echo '# Dovecot Indexing (FTS) Process maximum heap size in MB, there is no recommendation, please see Dovecot docs.' >> mailcow.conf echo '# Flatcurve is used as FTS Engine. It is supposed to be pretty efficient in CPU and RAM consumption.' >> mailcow.conf echo '# Please always monitor your Resource consumption!' >> mailcow.conf echo "FTS_HEAP=128" >> mailcow.conf ;; SKIP_FTS) echo '# Skip FTS (Fulltext Search) for Dovecot on low-memory, low-threaded systems or if you simply want to disable it.' >> mailcow.conf echo "# Dovecot inside mailcow use Flatcurve as FTS Backend." >> mailcow.conf echo "SKIP_FTS=y" >> mailcow.conf ;; FTS_PROCS) echo '# Controls how many processes the Dovecot indexing process can spawn at max.' >> mailcow.conf echo '# Too many indexing processes can use a lot of CPU and Disk I/O' >> mailcow.conf echo '# Please visit: https://doc.dovecot.org/configuration_manual/service_configuration/#indexer-worker for more informations' >> mailcow.conf echo "FTS_PROCS=1" >> mailcow.conf ;; ENABLE_SSL_SNI) echo '# Create seperate certificates for all domains - y/n' >> mailcow.conf echo '# this will allow adding more than 100 domains, but some email clients will not be able to connect with alternative hostnames' >> mailcow.conf echo '# see https://wiki.dovecot.org/SSL/SNIClientSupport' >> mailcow.conf echo "ENABLE_SSL_SNI=n" >> mailcow.conf ;; SKIP_SOGO) echo '# Skip SOGo: Will disable SOGo integration and therefore webmail, DAV protocols and ActiveSync support (experimental, unsupported, not fully implemented) - y/n' >> mailcow.conf echo "SKIP_SOGO=n" >> mailcow.conf ;; MAILDIR_SUB) echo '# MAILDIR_SUB defines a path in a users virtual home to keep the maildir in. Leave empty for updated setups.' >> mailcow.conf echo "#MAILDIR_SUB=Maildir" >> mailcow.conf echo "MAILDIR_SUB=" >> mailcow.conf ;; WATCHDOG_NOTIFY_WEBHOOK) echo '# Send notifications to a webhook URL that receives a POST request with the content type "application/json".' >> mailcow.conf echo '# You can use this to send notifications to services like Discord, Slack and others.' >> mailcow.conf echo '#WATCHDOG_NOTIFY_WEBHOOK=https://discord.com/api/webhooks/XXXXXXXXXXXXXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' >> mailcow.conf ;; WATCHDOG_NOTIFY_WEBHOOK_BODY) echo '# JSON body included in the webhook POST request. Needs to be in single quotes.' >> mailcow.conf echo '# Following variables are available: SUBJECT, BODY' >> mailcow.conf WEBHOOK_BODY='{"username": "mailcow Watchdog", "content": "**${SUBJECT}**\n${BODY}"}' echo "#WATCHDOG_NOTIFY_WEBHOOK_BODY='${WEBHOOK_BODY}'" >> mailcow.conf ;; WATCHDOG_NOTIFY_BAN) echo '# Notify about banned IP. Includes whois lookup.' >> mailcow.conf echo "WATCHDOG_NOTIFY_BAN=y" >> mailcow.conf ;; WATCHDOG_NOTIFY_START) echo '# Send a notification when the watchdog is started.' >> mailcow.conf echo "WATCHDOG_NOTIFY_START=y" >> mailcow.conf ;; WATCHDOG_SUBJECT) echo '# Subject for watchdog mails. Defaults to "Watchdog ALERT" followed by the error message.' >> mailcow.conf echo "#WATCHDOG_SUBJECT=" >> mailcow.conf ;; WATCHDOG_EXTERNAL_CHECKS) echo '# Checks if mailcow is an open relay. Requires a SAL. More checks will follow.' >> mailcow.conf echo '# No data is collected. Opt-in and anonymous.' >> mailcow.conf echo '# Will only work with unmodified mailcow setups.' >> mailcow.conf echo "WATCHDOG_EXTERNAL_CHECKS=n" >> mailcow.conf ;; SOGO_EXPIRE_SESSION) echo '# SOGo session timeout in minutes' >> mailcow.conf echo "SOGO_EXPIRE_SESSION=480" >> mailcow.conf ;; REDIS_PORT) echo "REDIS_PORT=127.0.0.1:7654" >> mailcow.conf ;; DOVECOT_MASTER_USER) echo '# DOVECOT_MASTER_USER and _PASS must _both_ be provided. No special chars.' >> mailcow.conf echo '# Empty by default to auto-generate master user and password on start.' >> mailcow.conf echo '# User expands to DOVECOT_MASTER_USER@mailcow.local' >> mailcow.conf echo '# LEAVE EMPTY IF UNSURE' >> mailcow.conf echo "DOVECOT_MASTER_USER=" >> mailcow.conf ;; DOVECOT_MASTER_PASS) echo '# LEAVE EMPTY IF UNSURE' >> mailcow.conf echo "DOVECOT_MASTER_PASS=" >> mailcow.conf ;; MAILCOW_PASS_SCHEME) echo '# Password hash algorithm' >> mailcow.conf echo '# Only certain password hash algorithm are supported. For a fully list of supported schemes,' >> mailcow.conf echo '# see https://docs.mailcow.email/models/model-passwd/' >> mailcow.conf echo "MAILCOW_PASS_SCHEME=BLF-CRYPT" >> mailcow.conf ;; ADDITIONAL_SERVER_NAMES) echo '# Additional server names for mailcow UI' >> mailcow.conf echo '#' >> mailcow.conf echo '# Specify alternative addresses for the mailcow UI to respond to' >> mailcow.conf echo '# This is useful when you set mail.* as ADDITIONAL_SAN and want to make sure mail.maildomain.com will always point to the mailcow UI.' >> mailcow.conf echo '# If the server name does not match a known site, Nginx decides by best-guess and may redirect users to the wrong web root.' >> mailcow.conf echo '# You can understand this as server_name directive in Nginx.' >> mailcow.conf echo '# Comma separated list without spaces! Example: ADDITIONAL_SERVER_NAMES=a.b.c,d.e.f' >> mailcow.conf echo 'ADDITIONAL_SERVER_NAMES=' >> mailcow.conf ;; WEBAUTHN_ONLY_TRUSTED_VENDORS) echo "# WebAuthn device manufacturer verification" >> mailcow.conf echo '# After setting WEBAUTHN_ONLY_TRUSTED_VENDORS=y only devices from trusted manufacturers are allowed' >> mailcow.conf echo '# root certificates can be placed for validation under mailcow-dockerized/data/web/inc/lib/WebAuthn/rootCertificates' >> mailcow.conf echo 'WEBAUTHN_ONLY_TRUSTED_VENDORS=n' >> mailcow.conf ;; SPAMHAUS_DQS_KEY) echo "# Spamhaus Data Query Service Key" >> mailcow.conf echo '# Optional: Leave empty for none' >> mailcow.conf echo '# Enter your key here if you are using a blocked ASN (OVH, AWS, Cloudflare e.g) for the unregistered Spamhaus Blocklist.' >> mailcow.conf echo '# If empty, it will completely disable Spamhaus blocklists if it detects that you are running on a server using a blocked AS.' >> mailcow.conf echo '# Otherwise it will work as usual.' >> mailcow.conf echo 'SPAMHAUS_DQS_KEY=' >> mailcow.conf ;; WATCHDOG_VERBOSE) echo '# Enable watchdog verbose logging' >> mailcow.conf echo 'WATCHDOG_VERBOSE=n' >> mailcow.conf ;; SKIP_UNBOUND_HEALTHCHECK) echo '# Skip Unbound (DNS Resolver) Healthchecks (NOT Recommended!) - y/n' >> mailcow.conf echo 'SKIP_UNBOUND_HEALTHCHECK=n' >> mailcow.conf ;; DISABLE_NETFILTER_ISOLATION_RULE) echo '# Prevent netfilter from setting an iptables/nftables rule to isolate the mailcow docker network - y/n' >> mailcow.conf echo '# CAUTION: Disabling this may expose container ports to other neighbors on the same subnet, even if the ports are bound to localhost' >> mailcow.conf echo 'DISABLE_NETFILTER_ISOLATION_RULE=n' >> mailcow.conf ;; HTTP_REDIRECT) echo '# Redirect HTTP connections to HTTPS - y/n' >> mailcow.conf echo 'HTTP_REDIRECT=n' >> mailcow.conf ;; ENABLE_IPV6) echo '# IPv6 Controller Section' >> mailcow.conf echo '# This variable controls the usage of IPv6 within mailcow.' >> mailcow.conf echo '# Can either be true or false | Defaults to true' >> mailcow.conf echo '# WARNING: MAKE SURE TO PROPERLY CONFIGURE IPv6 ON YOUR HOST FIRST BEFORE ENABLING THIS AS FAULTY CONFIGURATIONS CAN LEAD TO OPEN RELAYS!' >> mailcow.conf echo '# A COMPLETE DOCKER STACK REBUILD (compose down && compose up -d) IS NEEDED TO APPLY THIS.' >> mailcow.conf echo ENABLE_IPV6=${IPV6_BOOL} >> mailcow.conf ;; SKIP_CLAMD) echo '# Skip ClamAV (clamd-mailcow) anti-virus (Rspamd will auto-detect a missing ClamAV container) - y/n' >> mailcow.conf echo 'SKIP_CLAMD=n' >> mailcow.conf ;; SKIP_OLEFY) echo '# Skip Olefy (olefy-mailcow) anti-virus for Office documents (Rspamd will auto-detect a missing Olefy container) - y/n' >> mailcow.conf echo 'SKIP_OLEFY=n' >> mailcow.conf ;; REDISPASS) echo "REDISPASS=$(LC_ALL=C /dev/null | head -c 28)" >> mailcow.conf ;; SOGO_URL_ENCRYPTION_KEY) echo '# SOGo URL encryption key (exactly 16 characters, limited to A–Z, a–z, 0–9)' >> mailcow.conf echo '# This key is used to encrypt email addresses within SOGo URLs' >> mailcow.conf echo "SOGO_URL_ENCRYPTION_KEY=$(LC_ALL=C /dev/null | head -c 16)" >> mailcow.conf ;; ACME_DNS_CHALLENGE) echo '# Enable DNS-01 challenge for ACME (acme-mailcow) - y/n' >> mailcow.conf echo '# This requires you to set ACME_DNS_PROVIDER and ACME_ACCOUNT_EMAIL below' >> mailcow.conf echo 'ACME_DNS_CHALLENGE=n' >> mailcow.conf ;; ACME_DNS_PROVIDER) echo '# DNS provider for DNS-01 challenge (e.g. dns_cf, dns_azure, dns_gd, etc.)' >> mailcow.conf echo '# See the dns-01 provider documentation for more information.' >> mailcow.conf echo 'ACME_DNS_PROVIDER=dns_xxx' >> mailcow.conf ;; ACME_ACCOUNT_EMAIL) echo '# Account email for ACME DNS-01 challenge registration' >> mailcow.conf echo 'ACME_ACCOUNT_EMAIL=me@example.com' >> mailcow.conf ;; *) echo "${option}=" >> mailcow.conf ;; esac done } ================================================ FILE: data/Dockerfiles/acme/Dockerfile ================================================ FROM alpine:3.23 LABEL maintainer = "The Infrastructure Company GmbH " RUN apk upgrade --no-cache \ && apk add --update --no-cache \ bash \ curl \ openssl \ bind-tools \ jq \ mariadb-client \ redis \ tini \ tzdata \ python3 \ acme-tiny \ git \ socat \ && git clone --depth 1 https://github.com/acmesh-official/acme.sh.git /opt/acme.sh \ && chmod +x /opt/acme.sh/acme.sh \ && mkdir -p /var/lib/acme/acme-sh ENV ACME_SH_BIN=/opt/acme.sh/acme.sh \ ACME_SH_HOME=/opt/acme.sh \ ACME_SH_CONFIG_HOME=/var/lib/acme/acme-sh COPY acme.sh /srv/acme.sh COPY functions.sh /srv/functions.sh COPY obtain-certificate.sh /srv/obtain-certificate.sh COPY obtain-certificate-dns.sh /srv/obtain-certificate-dns.sh COPY load-dns-config.sh /srv/load-dns-config.sh COPY reload-configurations.sh /srv/reload-configurations.sh COPY expand6.sh /srv/expand6.sh RUN chmod +x /srv/*.sh CMD ["/sbin/tini", "-g", "--", "/srv/acme.sh"] ================================================ FILE: data/Dockerfiles/acme/acme.sh ================================================ #!/bin/bash set -o pipefail exec 5>&1 # Do not attempt to write to slave if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then export REDIS_CMDLINE="redis-cli -h ${REDIS_SLAVEOF_IP} -p ${REDIS_SLAVEOF_PORT} -a ${REDISPASS} --no-auth-warning" else export REDIS_CMDLINE="redis-cli -h redis -p 6379 -a ${REDISPASS} --no-auth-warning" fi until [[ $(${REDIS_CMDLINE} PING) == "PONG" ]]; do echo "Waiting for Redis..." sleep 2 done # Create DNS-01 configuration template if it doesn't exist if [[ ! -f /etc/acme/dns-01.conf ]]; then mkdir -p /etc/acme cat > /etc/acme/dns-01.conf <<'EOF' # Add here your DNS-01 challenge configuration # For more information, visit the acme.sh documentation: # https://github.com/acmesh-official/acme.sh/wiki/dnsapi EOF echo "Created DNS-01 configuration template at /etc/acme/dns-01.conf" fi source /srv/functions.sh # Thanks to https://github.com/cvmiller -> https://github.com/cvmiller/expand6 source /srv/expand6.sh # Skipping IP check when we like to live dangerously if [[ "${SKIP_IP_CHECK}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then SKIP_IP_CHECK=y fi # Skipping HTTP check when we like to live dangerously if [[ "${SKIP_HTTP_VERIFICATION}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then SKIP_HTTP_VERIFICATION=y fi # Request certificate for MAILCOW_HOSTNAME only if [[ "${ONLY_MAILCOW_HOSTNAME}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then ONLY_MAILCOW_HOSTNAME=y fi if [[ "${AUTODISCOVER_SAN}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then AUTODISCOVER_SAN=y fi # Request individual certificate for every domain if [[ "${ENABLE_SSL_SNI}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then ENABLE_SSL_SNI=y fi if [[ "${ACME_DNS_CHALLENGE}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then ACME_DNS_CHALLENGE=y fi if [[ "${SKIP_LETS_ENCRYPT}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then log_f "SKIP_LETS_ENCRYPT=y, skipping Let's Encrypt..." sleep 365d exec $(readlink -f "$0") fi log_f "Waiting for Docker API..." until ping dockerapi -c1 > /dev/null; do sleep 1 done log_f "Docker API OK" log_f "Waiting for Postfix..." until ping postfix -c1 > /dev/null; do sleep 1 done log_f "Postfix OK" log_f "Waiting for Dovecot..." until ping dovecot -c1 > /dev/null; do sleep 1 done log_f "Dovecot OK" ACME_BASE=/var/lib/acme SSL_EXAMPLE=/var/lib/ssl-example mkdir -p ${ACME_BASE}/acme # Migrate [[ -f ${ACME_BASE}/acme/private/privkey.pem ]] && mv ${ACME_BASE}/acme/private/privkey.pem ${ACME_BASE}/acme/key.pem [[ -f ${ACME_BASE}/acme/private/account.key ]] && mv ${ACME_BASE}/acme/private/account.key ${ACME_BASE}/acme/account.pem if [[ -f ${ACME_BASE}/acme/key.pem && -f ${ACME_BASE}/acme/cert.pem ]]; then if verify_hash_match ${ACME_BASE}/acme/cert.pem ${ACME_BASE}/acme/key.pem; then log_f "Migrating to SNI folder structure..." CERT_DOMAIN=($(openssl x509 -noout -text -in ${ACME_BASE}/acme/cert.pem | grep "Subject:" | sed -e 's/\(Subject:\)\|\(CN = \)\|\(CN=\)//g' | sed -e 's/^[[:space:]]*//')) CERT_DOMAINS=(${CERT_DOMAIN} $(openssl x509 -noout -text -in ${ACME_BASE}/acme/cert.pem | grep "DNS:" | sed -e 's/\(DNS:\)\|,//g' | sed "s/${CERT_DOMAIN}//" | sed -e 's/^[[:space:]]*//')) mkdir -p ${ACME_BASE}/${CERT_DOMAIN} mv ${ACME_BASE}/acme/cert.pem ${ACME_BASE}/${CERT_DOMAIN}/cert.pem # key is only copied, not moved, because it is used by all other requests too cp ${ACME_BASE}/acme/key.pem ${ACME_BASE}/${CERT_DOMAIN}/key.pem chmod 600 ${ACME_BASE}/${CERT_DOMAIN}/key.pem echo -n ${CERT_DOMAINS[*]} > ${ACME_BASE}/${CERT_DOMAIN}/domains mv ${ACME_BASE}/acme/acme.csr ${ACME_BASE}/${CERT_DOMAIN}/acme.csr log_f "OK" no_date fi fi [[ ! -f ${ACME_BASE}/dhparams.pem ]] && cp ${SSL_EXAMPLE}/dhparams.pem ${ACME_BASE}/dhparams.pem if [[ -f ${ACME_BASE}/cert.pem ]] && [[ -f ${ACME_BASE}/key.pem ]] && [[ $(stat -c%s ${ACME_BASE}/cert.pem) != 0 ]]; then ISSUER=$(openssl x509 -in ${ACME_BASE}/cert.pem -noout -issuer) if [[ ${ISSUER} != *"Let's Encrypt"* && ${ISSUER} != *"mailcow"* && ${ISSUER} != *"Fake LE Intermediate"* ]]; then log_f "Found certificate with issuer other than mailcow snake-oil CA and Let's Encrypt, skipping ACME client..." sleep 3650d exec $(readlink -f "$0") fi else if [[ -f ${ACME_BASE}/${MAILCOW_HOSTNAME}/cert.pem ]] && [[ -f ${ACME_BASE}/${MAILCOW_HOSTNAME}/key.pem ]] && verify_hash_match ${ACME_BASE}/${MAILCOW_HOSTNAME}/cert.pem ${ACME_BASE}/${MAILCOW_HOSTNAME}/key.pem; then log_f "Restoring previous acme certificate and restarting script..." cp ${ACME_BASE}/${MAILCOW_HOSTNAME}/cert.pem ${ACME_BASE}/cert.pem cp ${ACME_BASE}/${MAILCOW_HOSTNAME}/key.pem ${ACME_BASE}/key.pem # Restarting with env var set to trigger a restart, exec env TRIGGER_RESTART=1 $(readlink -f "$0") else log_f "Restoring mailcow snake-oil certificates and restarting script..." cp ${SSL_EXAMPLE}/cert.pem ${ACME_BASE}/cert.pem cp ${SSL_EXAMPLE}/key.pem ${ACME_BASE}/key.pem exec env TRIGGER_RESTART=1 $(readlink -f "$0") fi fi chmod 600 ${ACME_BASE}/key.pem log_f "Waiting for database..." while ! /usr/bin/mariadb-admin status --ssl=false --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent > /dev/null; do sleep 2 done log_f "Database OK" log_f "Waiting for Nginx..." until $(curl --output /dev/null --silent --head --fail http://nginx.${COMPOSE_PROJECT_NAME}_mailcow-network:8081); do sleep 2 done log_f "Nginx OK" log_f "Waiting for resolver..." until dig letsencrypt.org +time=3 +tries=1 @unbound > /dev/null; do sleep 2 done log_f "Resolver OK" # Waiting for domain table log_f "Waiting for domain table..." while [[ -z ${DOMAIN_TABLE} ]]; do curl --silent http://nginx.${COMPOSE_PROJECT_NAME}_mailcow-network/ >/dev/null 2>&1 DOMAIN_TABLE=$(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SHOW TABLES LIKE 'domain'" -Bs) [[ -z ${DOMAIN_TABLE} ]] && sleep 10 done log_f "OK" no_date log_f "Initializing, please wait..." while true; do POSTFIX_CERT_SERIAL="$(echo | openssl s_client -connect postfix:25 -starttls smtp 2>/dev/null | openssl x509 -inform pem -noout -serial | cut -d "=" -f 2)" DOVECOT_CERT_SERIAL="$(echo | openssl s_client -connect dovecot:143 -starttls imap 2>/dev/null | openssl x509 -inform pem -noout -serial | cut -d "=" -f 2)" POSTFIX_CERT_SERIAL_NEW="$(echo | openssl s_client -connect postfix:25 -starttls smtp 2>/dev/null | openssl x509 -inform pem -noout -serial | cut -d "=" -f 2)" DOVECOT_CERT_SERIAL_NEW="$(echo | openssl s_client -connect dovecot:143 -starttls imap 2>/dev/null | openssl x509 -inform pem -noout -serial | cut -d "=" -f 2)" # Re-using previous acme-mailcow account and domain keys if [[ ! -f ${ACME_BASE}/acme/key.pem ]]; then log_f "Generating missing domain private rsa key..." openssl genrsa 4096 > ${ACME_BASE}/acme/key.pem else log_f "Using existing domain rsa key ${ACME_BASE}/acme/key.pem" fi if [[ ! -f ${ACME_BASE}/acme/account.pem ]]; then log_f "Generating missing Lets Encrypt account key..." openssl genrsa 4096 > ${ACME_BASE}/acme/account.pem else log_f "Using existing Lets Encrypt account key ${ACME_BASE}/acme/account.pem" fi chmod 600 ${ACME_BASE}/acme/key.pem chmod 600 ${ACME_BASE}/acme/account.pem unset EXISTING_CERTS declare -a EXISTING_CERTS for cert_dir in ${ACME_BASE}/*/ ; do if [[ ! -f ${cert_dir}domains ]] || [[ ! -f ${cert_dir}cert.pem ]] || [[ ! -f ${cert_dir}key.pem ]]; then continue fi EXISTING_CERTS+=("$(basename ${cert_dir})") done # Cleaning up and init validation arrays unset SQL_DOMAIN_ARR unset VALIDATED_CONFIG_DOMAINS unset ADDITIONAL_VALIDATED_SAN unset ADDITIONAL_WC_ARR unset ADDITIONAL_SAN_ARR unset CERT_ERRORS unset CERT_CHANGED unset CERT_AMOUNT_CHANGED unset VALIDATED_CERTIFICATES CERT_ERRORS=0 CERT_CHANGED=0 CERT_AMOUNT_CHANGED=0 declare -a SQL_DOMAIN_ARR declare -a VALIDATED_CONFIG_DOMAINS declare -a ADDITIONAL_VALIDATED_SAN declare -a ADDITIONAL_WC_ARR declare -a ADDITIONAL_SAN_ARR declare -a VALIDATED_CERTIFICATES IFS=',' read -r -a TMP_ARR <<< "${ADDITIONAL_SAN}" for i in "${TMP_ARR[@]}" ; do if [[ "$i" =~ \.\*$ ]]; then ADDITIONAL_WC_ARR+=(${i::-2}) else ADDITIONAL_SAN_ARR+=($i) fi done if [[ ${AUTODISCOVER_SAN} == "y" ]]; then # Fetch certs for autoconfig and autodiscover subdomains ADDITIONAL_WC_ARR+=('autodiscover' 'autoconfig' 'mta-sts') fi if [[ ${SKIP_IP_CHECK} != "y" ]]; then # Start IP detection log_f "Detecting IP addresses..." IPV4=$(get_ipv4) IPV6=$(get_ipv6) log_f "OK: ${IPV4}, ${IPV6:-"0000:0000:0000:0000:0000:0000:0000:0000"}" fi ######################################### # IP and webroot challenge verification # SQL_DOMAINS=$(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT domain FROM domain WHERE backupmx=0 and active=1" -Bs) if [[ ! $? -eq 0 ]]; then log_f "Failed to read SQL domains, retrying in 1 minute..." sleep 1m exec $(readlink -f "$0") fi while read domains; do if [[ -z "${domains}" ]]; then # ignore empty lines continue fi SQL_DOMAIN_ARR+=("${domains}") done <<< "${SQL_DOMAINS}" if [[ ${ONLY_MAILCOW_HOSTNAME} != "y" ]]; then for SQL_DOMAIN in "${SQL_DOMAIN_ARR[@]}"; do unset VALIDATED_CONFIG_DOMAINS_SUBDOMAINS declare -a VALIDATED_CONFIG_DOMAINS_SUBDOMAINS for SUBDOMAIN in "${ADDITIONAL_WC_ARR[@]}"; do FULL_SUBDOMAIN="${SUBDOMAIN}.${SQL_DOMAIN}" # Skip if subdomain matches MAILCOW_HOSTNAME if [[ "${FULL_SUBDOMAIN}" == "${MAILCOW_HOSTNAME}" ]]; then continue fi # Skip if subdomain is covered by a wildcard in ADDITIONAL_SAN if is_covered_by_wildcard "${FULL_SUBDOMAIN}"; then log_f "Subdomain '${FULL_SUBDOMAIN}' is covered by wildcard - skipping explicit subdomain" continue fi # Validate and add subdomain if check_domain "${FULL_SUBDOMAIN}"; then VALIDATED_CONFIG_DOMAINS_SUBDOMAINS+=("${FULL_SUBDOMAIN}") fi done VALIDATED_CONFIG_DOMAINS+=("${VALIDATED_CONFIG_DOMAINS_SUBDOMAINS[*]}") done # Fetch alias domains where target domain has MTA-STS enabled if [[ ${AUTODISCOVER_SAN} == "y" ]]; then SQL_ALIAS_DOMAINS=$(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT ad.alias_domain FROM alias_domain ad INNER JOIN mta_sts m ON ad.target_domain = m.domain WHERE ad.active = 1 AND m.active = 1" -Bs) if [[ $? -eq 0 ]]; then while read alias_domain; do if [[ -z "${alias_domain}" ]]; then # ignore empty lines continue fi # Only add mta-sts subdomain for alias domains if [[ "mta-sts.${alias_domain}" != "${MAILCOW_HOSTNAME}" ]]; then # Skip if mta-sts subdomain is covered by a wildcard if is_covered_by_wildcard "mta-sts.${alias_domain}"; then log_f "Alias domain mta-sts subdomain 'mta-sts.${alias_domain}' is covered by wildcard - skipping" elif check_domain "mta-sts.${alias_domain}"; then VALIDATED_CONFIG_DOMAINS+=("mta-sts.${alias_domain}") fi fi done <<< "${SQL_ALIAS_DOMAINS}" fi fi fi if check_domain ${MAILCOW_HOSTNAME}; then VALIDATED_MAILCOW_HOSTNAME="${MAILCOW_HOSTNAME}" fi if [[ ${ONLY_MAILCOW_HOSTNAME} != "y" ]]; then for SAN in "${ADDITIONAL_SAN_ARR[@]}"; do # Skip on CAA errors for SAN SAN_PARENT_DOMAIN=$(echo ${SAN} | cut -d. -f2-) SAN_CAAS=( $(dig CAA ${SAN_PARENT_DOMAIN} +short | sed -n 's/\d issue "\(.*\)"/\1/p') ) if [[ ! -z ${SAN_CAAS} ]]; then if [[ ${SAN_CAAS[@]} =~ "letsencrypt.org" ]]; then log_f "Validated CAA for parent domain ${SAN_PARENT_DOMAIN} of ${SAN}" else log_f "Skipping ACME validation for ${SAN}: Lets Encrypt disallowed for ${SAN} by CAA record" continue fi fi if [[ ${SAN} == ${MAILCOW_HOSTNAME} ]]; then continue fi if check_domain ${SAN}; then ADDITIONAL_VALIDATED_SAN+=("${SAN}") fi done fi # Check if MAILCOW_HOSTNAME is covered by a wildcard in ADDITIONAL_SAN MAILCOW_HOSTNAME_COVERED=0 if [[ ! -z ${VALIDATED_MAILCOW_HOSTNAME} ]]; then if is_covered_by_wildcard "${VALIDATED_MAILCOW_HOSTNAME}"; then MAILCOW_PARENT_DOMAIN=$(echo ${VALIDATED_MAILCOW_HOSTNAME} | cut -d. -f2-) log_f "MAILCOW_HOSTNAME '${VALIDATED_MAILCOW_HOSTNAME}' is covered by wildcard '*.${MAILCOW_PARENT_DOMAIN}' - skipping explicit hostname" MAILCOW_HOSTNAME_COVERED=1 fi fi # Unique domains for server certificate if [[ ${ENABLE_SSL_SNI} == "y" ]]; then # create certificate for server name and fqdn SANs only if [[ ${MAILCOW_HOSTNAME_COVERED} == "1" ]]; then SERVER_SAN_VALIDATED=($(echo ${ADDITIONAL_VALIDATED_SAN[*]} | xargs -n1 | sort -u | xargs)) else SERVER_SAN_VALIDATED=(${VALIDATED_MAILCOW_HOSTNAME} $(echo ${ADDITIONAL_VALIDATED_SAN[*]} | xargs -n1 | sort -u | xargs)) fi else # create certificate for all domains, including all subdomains from other domains [*] if [[ ${MAILCOW_HOSTNAME_COVERED} == "1" ]]; then SERVER_SAN_VALIDATED=($(echo ${VALIDATED_CONFIG_DOMAINS[*]} ${ADDITIONAL_VALIDATED_SAN[*]} | xargs -n1 | sort -u | xargs)) else SERVER_SAN_VALIDATED=(${VALIDATED_MAILCOW_HOSTNAME} $(echo ${VALIDATED_CONFIG_DOMAINS[*]} ${ADDITIONAL_VALIDATED_SAN[*]} | xargs -n1 | sort -u | xargs)) fi fi if [[ ! -z ${SERVER_SAN_VALIDATED[*]} ]]; then CERT_NAME=${SERVER_SAN_VALIDATED[0]} VALIDATED_CERTIFICATES+=("${CERT_NAME}") # obtain server certificate if required DOMAINS=${SERVER_SAN_VALIDATED[@]} /srv/obtain-certificate.sh rsa RETURN="$?" if [[ "$RETURN" == "0" ]]; then # 0 = cert created successfully CERT_AMOUNT_CHANGED=1 CERT_CHANGED=1 elif [[ "$RETURN" == "1" ]]; then # 1 = cert renewed successfully CERT_CHANGED=1 elif [[ "$RETURN" == "2" ]]; then # 2 = cert not due for renewal : else CERT_ERRORS=1 fi # copy hostname certificate to default/server certificate # do not a key when cert is missing, this can lead to a mismatch of cert/key if [[ -f ${ACME_BASE}/${CERT_NAME}/cert.pem ]]; then cp ${ACME_BASE}/${CERT_NAME}/cert.pem ${ACME_BASE}/cert.pem cp ${ACME_BASE}/${CERT_NAME}/key.pem ${ACME_BASE}/key.pem fi fi # individual certificates for SNI [@] if [[ ${ENABLE_SSL_SNI} == "y" ]]; then for VALIDATED_DOMAINS in "${VALIDATED_CONFIG_DOMAINS[@]}"; do VALIDATED_DOMAINS_ARR=(${VALIDATED_DOMAINS}) unset VALIDATED_DOMAINS_SORTED declare -a VALIDATED_DOMAINS_SORTED VALIDATED_DOMAINS_SORTED=(${VALIDATED_DOMAINS_ARR[0]} $(echo ${VALIDATED_DOMAINS_ARR[@]:1} | xargs -n1 | sort -u | xargs)) # remove all domain names that are already inside the server certificate (SERVER_SAN_VALIDATED) for domain in "${SERVER_SAN_VALIDATED[@]}"; do for i in "${!VALIDATED_DOMAINS_SORTED[@]}"; do if [[ ${VALIDATED_DOMAINS_SORTED[i]} = $domain ]]; then unset 'VALIDATED_DOMAINS_SORTED[i]' fi done done if [[ ! -z ${VALIDATED_DOMAINS_SORTED[*]} ]]; then CERT_NAME=${VALIDATED_DOMAINS_SORTED[0]} VALIDATED_CERTIFICATES+=("${CERT_NAME}") # obtain certificate if required DOMAINS=${VALIDATED_DOMAINS_SORTED[@]} /srv/obtain-certificate.sh rsa RETURN="$?" if [[ "$RETURN" == "0" ]]; then # 0 = cert created successfully CERT_AMOUNT_CHANGED=1 CERT_CHANGED=1 elif [[ "$RETURN" == "1" ]]; then # 1 = cert renewed successfully CERT_CHANGED=1 elif [[ "$RETURN" == "2" ]]; then # 2 = cert not due for renewal : else CERT_ERRORS=1 fi fi done fi if [[ -z ${VALIDATED_CERTIFICATES[*]} ]]; then log_f "Cannot validate any hostnames, skipping Let's Encrypt for 1 hour." log_f "Use SKIP_LETS_ENCRYPT=y in mailcow.conf to skip it permanently." ${REDIS_CMDLINE} SET ACME_FAIL_TIME "$(date +%s)" sleep 1h exec $(readlink -f "$0") fi # find orphaned certificates if no errors occurred if [[ "${CERT_ERRORS}" == "0" ]]; then for EXISTING_CERT in "${EXISTING_CERTS[@]}"; do if [[ ! "`printf '_%s_\n' "${VALIDATED_CERTIFICATES[@]}"`" == *"_${EXISTING_CERT}_"* ]]; then DATE=$(date +%Y-%m-%d_%H_%M_%S) log_f "Found orphaned certificate: ${EXISTING_CERT} - archiving it at ${ACME_BASE}/backups/${EXISTING_CERT}/" BACKUP_DIR=${ACME_BASE}/backups/${EXISTING_CERT}/${DATE} # archive rsa cert and any other files mkdir -p ${ACME_BASE}/backups/${EXISTING_CERT} mv ${ACME_BASE}/${EXISTING_CERT} ${BACKUP_DIR} CERT_CHANGED=1 CERT_AMOUNT_CHANGED=1 fi done fi # reload on new or changed certificates if [[ "${CERT_CHANGED}" == "1" ]]; then rm -f "${ACME_BASE}/force_renew" 2> /dev/null RELOAD_LOOP_C=1 while [[ "${POSTFIX_CERT_SERIAL}" == "${POSTFIX_CERT_SERIAL_NEW}" ]] || [[ "${DOVECOT_CERT_SERIAL}" == "${DOVECOT_CERT_SERIAL_NEW}" ]] || [[ ${#POSTFIX_CERT_SERIAL_NEW} -ne 36 ]] || [[ ${#DOVECOT_CERT_SERIAL_NEW} -ne 36 ]]; do log_f "Reloading or restarting services... (${RELOAD_LOOP_C})" RELOAD_LOOP_C=$((RELOAD_LOOP_C + 1)) CERT_AMOUNT_CHANGED=${CERT_AMOUNT_CHANGED} /srv/reload-configurations.sh log_f "Waiting for containers to settle..." sleep 10 until nc -z dovecot 143; do sleep 1 done until nc -z postfix 25; do sleep 1 done POSTFIX_CERT_SERIAL_NEW="$(echo | openssl s_client -connect postfix:25 -starttls smtp 2>/dev/null | openssl x509 -inform pem -noout -serial | cut -d "=" -f 2)" DOVECOT_CERT_SERIAL_NEW="$(echo | openssl s_client -connect dovecot:143 -starttls imap 2>/dev/null | openssl x509 -inform pem -noout -serial | cut -d "=" -f 2)" if [[ ${RELOAD_LOOP_C} -gt 3 ]]; then log_f "Some services do return old end dates, something went wrong!" ${REDIS_CMDLINE} SET ACME_FAIL_TIME "$(date +%s)" break; fi done fi case "$CERT_ERRORS" in 0) # all successful if [[ "${CERT_CHANGED}" == "1" ]]; then if [[ "${CERT_AMOUNT_CHANGED}" == "1" ]]; then log_f "Certificates successfully requested and renewed where required, sleeping one day" else log_f "Certificates were successfully renewed where required, sleeping for another day." fi else log_f "Certificates were successfully validated, no changes or renewals required, sleeping for another day." fi sleep 1d ;; *) # non-zero log_f "Some errors occurred, retrying in 30 minutes..." ${REDIS_CMDLINE} SET ACME_FAIL_TIME "$(date +%s)" sleep 30m exec $(readlink -f "$0") ;; esac done ================================================ FILE: data/Dockerfiles/acme/expand6.sh ================================================ #!/bin/bash ################################################################################## # # Copyright (C) 2017 Craig Miller # # See the file "LICENSE" for information on usage and redistribution # of this file, and for a DISCLAIMER OF ALL WARRANTIES. # Distributed under GPLv2 License # ################################################################################## # IPv6 Address Expansion functions # # by Craig Miller 19 Feb 2017 # # 16 Nov 2017 v0.93 - added CLI functionality VERSION=0.93 empty_addr="0000:0000:0000:0000:0000:0000:0000:0000" empty_addr_len=${#empty_addr} function usage { echo " $0 - expand compressed IPv6 addresss " echo " e.g. $0 2001:db8:1:12:123::456 " echo " " echo " -t self test" echo " " echo " By Craig Miller - Version: $VERSION" exit 1 } if [ "$1" == "-h" ]; then #call help usage fi # # Expands IPv6 quibble to 4 digits with leading zeros e.g. db8 -> 0db8 # # Returns string with expanded quibble function expand_quibble() { addr=$1 # create array of quibbles addr_array=(${addr//:/ }) addr_array_len=${#addr_array[@]} # step thru quibbles for ((i=0; i< $addr_array_len ; i++ )) do quibble=${addr_array[$i]} quibble_len=${#quibble} case $quibble_len in 1) quibble="000$quibble";; 2) quibble="00$quibble";; 3) quibble="0$quibble";; esac addr_array[$i]=$quibble done # reconstruct addr from quibbles return_str=${addr_array[*]} return_str="${return_str// /:}" echo $return_str } # # Expands IPv6 address :: format to full zeros # # Returns string with expanded address function expand() { if [[ $1 == *"::"* ]]; then # check for leading zeros on front_addr if [[ $1 == "::"* ]]; then front_addr=0 else front_addr=$(echo $1 | sed -r 's;([^ ]+)::.*;\1;') fi # check for trailing zeros on back_addr if [[ $1 == *"::" ]]; then back_addr=0 else back_addr=$(echo $1 | sed -r 's;.*::([^ ]+);\1;') fi front_addr=$(expand_quibble $front_addr) back_addr=$(expand_quibble $back_addr) new_addr=$empty_addr front_addr_len=${#front_addr} back_addr_len=${#back_addr} # calculate fill needed num_zeros=$(($empty_addr_len - $front_addr_len - $back_addr_len - 1)) #fill_str=${empty_addr[0]:0:$num_zeros} new_addr="$front_addr:${empty_addr[0]:0:$num_zeros}$back_addr" # return expanded address echo $new_addr else # return input with expandd quibbles expand_quibble $1 fi } # self test - call with '-t' parameter if [ "$1" == "-t" ]; then # add address examples to test expand fd11::1d70:cf84:18ef:d056 expand 2a01::1 expand fe80::f203:8cff:fe3f:f041 expand 2001:db8:123::5 expand 2001:470:ebbd:0:f203:8cff:fe3f:f041 # special cases expand ::1 expand fd32:197d:3022:1101:: exit 1 fi # allow script to be sourced (with no arguements) if [[ $1 != "" ]]; then # validate input is an IPv6 address if [[ $1 == *":"* ]]; then expand $1 else echo "ERROR: unregcognized IPv6 address $1" exit 1 fi fi ================================================ FILE: data/Dockerfiles/acme/functions.sh ================================================ #!/bin/bash log_f() { if [[ ${2} == "no_nl" ]]; then echo -n "$(date) - ${1}" elif [[ ${2} == "no_date" ]]; then echo "${1}" elif [[ ${2} != "redis_only" ]]; then echo "$(date) - ${1}" fi if [[ ${3} == "b64" ]]; then ${REDIS_CMDLINE} LPUSH ACME_LOG "{\"time\":\"$(date +%s)\",\"message\":\"base64,$(printf '%s' "${MAILCOW_HOSTNAME} - ${1}")\"}" > /dev/null else ${REDIS_CMDLINE} LPUSH ACME_LOG "{\"time\":\"$(date +%s)\",\"message\":\"$(printf '%s' "${MAILCOW_HOSTNAME} - ${1}" | \ tr '%&;$"[]{}-\r\n' ' ')\"}" > /dev/null fi } verify_email(){ regex="^(([A-Za-z0-9]+((\.|\-|\_|\+)?[A-Za-z0-9]?)*[A-Za-z0-9]+)|[A-Za-z0-9]+)@(([A-Za-z0-9]+)+((\.|\-|\_)?([A-Za-z0-9]+)+)*)+\.([A-Za-z]{2,})+$" if [[ $1 =~ ${regex} ]]; then return 0 else return 1 fi } verify_hash_match(){ CERT_HASH=$(openssl x509 -in "${1}" -noout -pubkey | openssl md5) KEY_HASH=$(openssl pkey -in "${2}" -pubout | openssl md5) if [[ ${CERT_HASH} != ${KEY_HASH} ]]; then log_f "Certificate and key hashes do not match!" return 1 else log_f "Verified hashes." return 0 fi } get_ipv4(){ local IPV4= local IPV4_SRCS= local TRY= IPV4_SRCS[0]="ip4.mailcow.email" IPV4_SRCS[1]="ip4.nevondo.com" until [[ ! -z ${IPV4} ]] || [[ ${TRY} -ge 10 ]]; do IPV4=$(curl --connect-timeout 3 -m 10 -L4s ${IPV4_SRCS[$RANDOM % ${#IPV4_SRCS[@]} ]} | grep -E "^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$") [[ ! -z ${TRY} ]] && sleep 1 TRY=$((TRY+1)) done echo ${IPV4} } get_ipv6(){ local IPV6= local IPV6_SRCS= local TRY= IPV6_SRCS[0]="ip6.mailcow.email" IPV6_SRCS[1]="ip6.nevondo.com" until [[ ! -z ${IPV6} ]] || [[ ${TRY} -ge 10 ]]; do IPV6=$(curl --connect-timeout 3 -m 10 -L6s ${IPV6_SRCS[$RANDOM % ${#IPV6_SRCS[@]} ]} | grep "^\([0-9a-fA-F]\{0,4\}:\)\{1,7\}[0-9a-fA-F]\{0,4\}$") [[ ! -z ${TRY} ]] && sleep 1 TRY=$((TRY+1)) done echo ${IPV6} } check_domain(){ DOMAIN=$1 A_DOMAIN=$(dig A ${DOMAIN} +short | tail -n 1) AAAA_DOMAIN=$(dig AAAA ${DOMAIN} +short | tail -n 1) # Hard-fail on CAA errors for MAILCOW_HOSTNAME PARENT_DOMAIN=$(echo ${DOMAIN} | cut -d. -f2-) CAAS=( $(dig CAA ${PARENT_DOMAIN} +short | sed -n 's/\d issue "\(.*\)"/\1/p') ) if [[ ! -z ${CAAS} ]]; then if [[ ${CAAS[@]} =~ "letsencrypt.org" ]]; then log_f "Validated CAA for parent domain ${PARENT_DOMAIN}" else log_f "Lets Encrypt disallowed for ${PARENT_DOMAIN} by CAA record" return 1 fi fi if [[ ${ACME_DNS_CHALLENGE} == "y" ]]; then log_f "ACME_DNS_CHALLENGE=y - skipping IP and HTTP validation for ${DOMAIN}" return 0 fi # Check if CNAME without v6 enabled target if [[ ! -z ${AAAA_DOMAIN} ]] && [[ -z $(echo ${AAAA_DOMAIN} | grep "^\([0-9a-fA-F]\{0,4\}:\)\{1,7\}[0-9a-fA-F]\{0,4\}$") ]]; then AAAA_DOMAIN= fi if [[ ! -z ${AAAA_DOMAIN} ]]; then log_f "Found AAAA record for ${DOMAIN}: ${AAAA_DOMAIN} - skipping A record check" if [[ $(expand ${IPV6:-"0000:0000:0000:0000:0000:0000:0000:0000"}) == $(expand ${AAAA_DOMAIN}) ]] || [[ ${SKIP_IP_CHECK} == "y" ]] || [[ ${SNAT6_TO_SOURCE} != "n" ]]; then if verify_challenge_path "${DOMAIN}" 6; then log_f "Confirmed AAAA record with IP $(expand ${AAAA_DOMAIN})" return 0 else log_f "Confirmed AAAA record with IP $(expand ${AAAA_DOMAIN}), but HTTP validation failed" fi else log_f "Cannot match your IP $(expand ${IPV6:-"0000:0000:0000:0000:0000:0000:0000:0000"}) against hostname ${DOMAIN} (DNS returned $(expand ${AAAA_DOMAIN}))" fi elif [[ ! -z ${A_DOMAIN} ]]; then log_f "Found A record for ${DOMAIN}: ${A_DOMAIN}" if [[ ${IPV4:-ERR} == ${A_DOMAIN} ]] || [[ ${SKIP_IP_CHECK} == "y" ]] || [[ ${SNAT_TO_SOURCE} != "n" ]]; then if verify_challenge_path "${DOMAIN}" 4; then log_f "Confirmed A record ${A_DOMAIN}" return 0 else log_f "Confirmed A record with IP ${A_DOMAIN}, but HTTP validation failed" fi else log_f "Cannot match your IP ${IPV4} against hostname ${DOMAIN} (DNS returned ${A_DOMAIN})" fi else log_f "No A or AAAA record found for hostname ${DOMAIN}" fi return 1 } verify_challenge_path(){ if [[ ${SKIP_HTTP_VERIFICATION} == "y" ]]; then echo '(skipping check, returning 0)' return 0 fi # verify_challenge_path URL 4|6 RANDOM_N=${RANDOM}${RANDOM}${RANDOM} echo ${RANDOM_N} > /var/www/acme/${RANDOM_N} if [[ "$(curl --insecure -${2} -L http://${1}/.well-known/acme-challenge/${RANDOM_N} --silent)" == "${RANDOM_N}" ]]; then rm /var/www/acme/${RANDOM_N} return 0 else rm /var/www/acme/${RANDOM_N} return 1 fi } # Check if a domain is covered by a wildcard (*.example.com) in ADDITIONAL_SAN # Usage: is_covered_by_wildcard "subdomain.example.com" # Returns: 0 if covered, 1 if not covered # Note: Only returns 0 (covered) when DNS-01 challenge is enabled, # as wildcards cannot be validated with HTTP-01 challenge is_covered_by_wildcard() { local DOMAIN=$1 # Only skip if DNS challenge is enabled (wildcards require DNS-01) if [[ ${ACME_DNS_CHALLENGE} != "y" ]]; then return 1 fi # Return early if no ADDITIONAL_SAN is set if [[ -z ${ADDITIONAL_SAN} ]]; then return 1 fi # Extract parent domain (e.g., mail.example.com -> example.com) local PARENT_DOMAIN=$(echo ${DOMAIN} | cut -d. -f2-) # Check if ADDITIONAL_SAN contains a wildcard for this parent domain if [[ "${ADDITIONAL_SAN}" == *"*.${PARENT_DOMAIN}"* ]]; then return 0 # Covered by wildcard fi return 1 # Not covered } ================================================ FILE: data/Dockerfiles/acme/load-dns-config.sh ================================================ #!/bin/bash SCRIPT_SOURCE="${BASH_SOURCE[0]:-${0}}" if [[ "${SCRIPT_SOURCE}" == "${0}" ]]; then __dns_loader_standalone=1 else __dns_loader_standalone=0 fi CONFIG_PATH="${ACME_DNS_CONFIG_FILE:-/etc/acme/dns-01.conf}" if [[ ! -f "${CONFIG_PATH}" ]]; then if [[ $__dns_loader_standalone -eq 1 ]]; then exit 0 else return 0 fi fi source /srv/functions.sh log_f "Loading DNS-01 configuration from ${CONFIG_PATH}" LINE_NO=0 while IFS= read -r line || [[ -n "${line}" ]]; do LINE_NO=$((LINE_NO+1)) line="${line%$'\r'}" line_trimmed="$(printf '%s' "${line}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')" [[ -z "${line_trimmed}" ]] && continue [[ "${line_trimmed:0:1}" == "#" ]] && continue if [[ "${line_trimmed}" != *=* ]]; then log_f "Skipping invalid DNS config line ${LINE_NO} (missing key=value)" continue fi KEY="${line_trimmed%%=*}" VALUE="${line_trimmed#*=}" KEY="$(printf '%s' "${KEY}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')" VALUE="$(printf '%s' "${VALUE}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')" if [[ -z "${KEY}" ]]; then log_f "Skipping invalid DNS config line ${LINE_NO} (empty key)" continue fi if [[ "${VALUE}" =~ ^\".*\"$ ]]; then VALUE="${VALUE:1:-1}" elif [[ "${VALUE}" =~ ^\'.*\'$ ]]; then VALUE="${VALUE:1:-1}" fi export "${KEY}"="${VALUE}" log_f "Exported DNS config key ${KEY}" done < "${CONFIG_PATH}" if [[ $__dns_loader_standalone -eq 1 ]]; then exit 0 else return 0 fi ================================================ FILE: data/Dockerfiles/acme/obtain-certificate-dns.sh ================================================ #!/bin/bash # Return values / exit codes # 0 = cert created successfully # 1 = cert renewed successfully # 2 = cert not due for renewal # * = errors source /srv/functions.sh CERT_DOMAINS=(${DOMAINS[@]}) CERT_DOMAIN=${CERT_DOMAINS[0]} ACME_BASE=/var/lib/acme # Load optional DNS provider secrets from /etc/acme/dns-01.conf if [[ -f /srv/load-dns-config.sh ]]; then source /srv/load-dns-config.sh if declare -F log_f >/dev/null; then log_f "ACME_DNS_CHALLENGE is enabled, DNS provider secrets loaded" fi fi TYPE=${1} PREFIX="" # only support rsa certificates for now if [[ "${TYPE}" != "rsa" ]]; then log_f "Unknown certificate type '${TYPE}' requested" exit 5 fi if [[ -z "${ACME_DNS_PROVIDER}" ]]; then log_f "ACME_DNS_PROVIDER is required when ACME_DNS_CHALLENGE is enabled" exit 6 fi DOMAINS_FILE=${ACME_BASE}/${CERT_DOMAIN}/domains CERT=${ACME_BASE}/${CERT_DOMAIN}/${PREFIX}cert.pem SHARED_KEY=${ACME_BASE}/acme/${PREFIX}key.pem # must already exist KEY=${ACME_BASE}/${CERT_DOMAIN}/${PREFIX}key.pem CSR=${ACME_BASE}/${CERT_DOMAIN}/${PREFIX}acme.csr if [[ -z ${CERT_DOMAINS[*]} ]]; then log_f "Missing CERT_DOMAINS to obtain a certificate" exit 3 fi if [[ "${LE_STAGING}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then if [[ ! -z "${DIRECTORY_URL}" ]]; then log_f "Cannot use DIRECTORY_URL with LE_STAGING=y - ignoring DIRECTORY_URL" fi log_f "Using Let's Encrypt staging servers" ACME_SH_SERVER_ARGS=("--staging") elif [[ ! -z "${DIRECTORY_URL}" ]]; then log_f "Using custom directory URL ${DIRECTORY_URL}" ACME_SH_SERVER_ARGS=("--server" "${DIRECTORY_URL}") else log_f "Using Let's Encrypt production servers" ACME_SH_SERVER_ARGS=("--server" "letsencrypt") fi if [[ -f ${DOMAINS_FILE} && "$(cat ${DOMAINS_FILE})" == "${CERT_DOMAINS[*]}" ]]; then if [[ ! -f ${CERT} || ! -f "${KEY}" || -f "${ACME_BASE}/force_renew" ]]; then log_f "Certificate ${CERT} doesn't exist yet or forced renewal - start obtaining" elif ! openssl x509 -checkend 2592000 -noout -in ${CERT} > /dev/null; then log_f "Certificate ${CERT} is due for renewal (< 30 days) - start renewing" else log_f "Certificate ${CERT} validation done, neither changed nor due for renewal." exit 2 fi else log_f "Certificate ${CERT} missing or changed domains '${CERT_DOMAINS[*]}' - start obtaining" fi # Make backup if [[ -f ${CERT} ]]; then DATE=$(date +%Y-%m-%d_%H_%M_%S) BACKUP_DIR=${ACME_BASE}/backups/${CERT_DOMAIN}/${PREFIX}${DATE} log_f "Creating backups in ${BACKUP_DIR} ..." mkdir -p ${BACKUP_DIR}/ [[ -f ${DOMAINS_FILE} ]] && cp ${DOMAINS_FILE} ${BACKUP_DIR}/ [[ -f ${CERT} ]] && cp ${CERT} ${BACKUP_DIR}/ [[ -f ${KEY} ]] && cp ${KEY} ${BACKUP_DIR}/ [[ -f ${CSR} ]] && cp ${CSR} ${BACKUP_DIR}/ fi mkdir -p ${ACME_BASE}/${CERT_DOMAIN} if [[ ! -f ${KEY} ]]; then log_f "Copying shared private key for this certificate..." cp ${SHARED_KEY} ${KEY} chmod 600 ${KEY} fi # Generating CSR to keep layout parity with HTTP challenge flow printf "[SAN]\nsubjectAltName=" > /tmp/_SAN printf "DNS:%s," "${CERT_DOMAINS[@]}" >> /tmp/_SAN sed -i '$s/,$//' /tmp/_SAN openssl req -new -sha256 -key ${KEY} -subj "/" -reqexts SAN -config <(cat "$(openssl version -d | sed 's/.*\"\(.*\)\"/\1/g')/openssl.cnf" /tmp/_SAN) > ${CSR} log_f "Checking resolver..." until dig letsencrypt.org +time=3 +tries=1 @unbound > /dev/null; do sleep 2 done log_f "Resolver OK" ACME_SH_BIN_PATH=${ACME_SH_BIN:-/opt/acme.sh/acme.sh} ACME_SH_WORK_HOME=${ACME_SH_CONFIG_HOME:-/var/lib/acme/acme-sh} mkdir -p ${ACME_SH_WORK_HOME} if [[ ! -x ${ACME_SH_BIN_PATH} ]]; then log_f "acme.sh binary not found at ${ACME_SH_BIN_PATH}" exit 7 fi if [[ ! -f ${ACME_SH_WORK_HOME}/account.conf ]]; then if [[ -z "${ACME_ACCOUNT_EMAIL}" ]]; then log_f "ACME_ACCOUNT_EMAIL is required to register a new acme.sh account" exit 8 fi log_f "Registering acme.sh account for ${ACME_ACCOUNT_EMAIL}" REGISTER_CMD=("${ACME_SH_BIN_PATH}" "--home" "${ACME_SH_WORK_HOME}" "--config-home" "${ACME_SH_WORK_HOME}" "--cert-home" "${ACME_SH_WORK_HOME}" "--register-account" "-m" "${ACME_ACCOUNT_EMAIL}") REGISTER_CMD+=("${ACME_SH_SERVER_ARGS[@]}") REGISTER_RESPONSE=$("${REGISTER_CMD[@]}" 2>&1) if [[ $? -ne 0 ]]; then log_f "Failed to register acme.sh account: ${REGISTER_RESPONSE}" exit 9 fi fi TMP_CERT=$(mktemp /tmp/acme-cert.XXXXXX) TMP_FULLCHAIN=$(mktemp /tmp/acme-fullchain.XXXXXX) ACME_CMD=("${ACME_SH_BIN_PATH}" "--home" "${ACME_SH_WORK_HOME}" "--config-home" "${ACME_SH_WORK_HOME}" "--cert-home" "${ACME_SH_WORK_HOME}") ACME_CMD+=("${ACME_SH_SERVER_ARGS[@]}") ACME_CMD+=("--issue" "--dns" "${ACME_DNS_PROVIDER}" "--key-file" "${KEY}" "--cert-file" "${TMP_CERT}" "--fullchain-file" "${TMP_FULLCHAIN}" "--force") for domain in "${CERT_DOMAINS[@]}"; do ACME_CMD+=("-d" "${domain}") done log_f "Using command ${ACME_CMD[*]}" if [[ -n "${ACME_DNS_PROVIDER}" ]]; then log_f "DNS provider: ${ACME_DNS_PROVIDER}" fi if compgen -A variable | grep -Eq "^DNS_|^ACME_"; then LOG_KEYS=$(env | grep -E "^(DNS_|ACME_)" | cut -d= -f1 | tr '\n' ' ') log_f "Available DNS/ACME env keys: ${LOG_KEYS}" redis_only fi ACME_RESPONSE=$("${ACME_CMD[@]}" 2>&1 | tee /dev/fd/5; exit ${PIPESTATUS[0]}) SUCCESS="$?" ACME_RESPONSE_B64=$(echo "${ACME_RESPONSE}" | openssl enc -e -A -base64) log_f "${ACME_RESPONSE_B64}" redis_only b64 case "$SUCCESS" in 0) log_f "Deploying certificate ${CERT}..." if verify_hash_match ${TMP_FULLCHAIN} ${KEY}; then RETURN=0 if [[ -f ${CERT} ]]; then RETURN=1 fi mv -f ${TMP_FULLCHAIN} ${CERT} rm -f ${TMP_CERT} echo -n ${CERT_DOMAINS[*]} > ${DOMAINS_FILE} log_f "Certificate successfully obtained via DNS challenge" exit ${RETURN} else log_f "Certificate was requested, but key and certificate hashes do not match" rm -f ${TMP_CERT} ${TMP_FULLCHAIN} exit 4 fi ;; *) log_f "Failed to obtain certificate ${CERT} for domains '${CERT_DOMAINS[*]}' via DNS challenge" redis-cli -h redis -a ${REDISPASS} --no-auth-warning SET ACME_FAIL_TIME "$(date +%s)" rm -f ${TMP_CERT} ${TMP_FULLCHAIN} exit 100${SUCCESS} ;; esac ================================================ FILE: data/Dockerfiles/acme/obtain-certificate.sh ================================================ #!/bin/bash # Return values / exit codes # 0 = cert created successfully # 1 = cert renewed successfully # 2 = cert not due for renewal # * = errors source /srv/functions.sh CERT_DOMAINS=(${DOMAINS[@]}) CERT_DOMAIN=${CERT_DOMAINS[0]} ACME_BASE=/var/lib/acme TYPE=${1} PREFIX="" # only support rsa certificates for now if [[ "${TYPE}" != "rsa" ]]; then log_f "Unknown certificate type '${TYPE}' requested" exit 5 fi if [[ "${ACME_DNS_CHALLENGE}" == "y" ]]; then exec /srv/obtain-certificate-dns.sh "$@" fi DOMAINS_FILE=${ACME_BASE}/${CERT_DOMAIN}/domains CERT=${ACME_BASE}/${CERT_DOMAIN}/${PREFIX}cert.pem SHARED_KEY=${ACME_BASE}/acme/${PREFIX}key.pem # must already exist KEY=${ACME_BASE}/${CERT_DOMAIN}/${PREFIX}key.pem CSR=${ACME_BASE}/${CERT_DOMAIN}/${PREFIX}acme.csr if [[ -z ${CERT_DOMAINS[*]} ]]; then log_f "Missing CERT_DOMAINS to obtain a certificate" exit 3 fi if [[ "${LE_STAGING}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then if [[ ! -z "${DIRECTORY_URL}" ]]; then log_f "Cannot use DIRECTORY_URL with LE_STAGING=y - ignoring DIRECTORY_URL" fi log_f "Using Let's Encrypt staging servers" DIRECTORY_URL='--directory-url https://acme-staging-v02.api.letsencrypt.org/directory' elif [[ ! -z "${DIRECTORY_URL}" ]]; then log_f "Using custom directory URL ${DIRECTORY_URL}" DIRECTORY_URL="--directory-url ${DIRECTORY_URL}" fi if [[ -f ${DOMAINS_FILE} && "$(cat ${DOMAINS_FILE})" == "${CERT_DOMAINS[*]}" ]]; then if [[ ! -f ${CERT} || ! -f "${KEY}" || -f "${ACME_BASE}/force_renew" ]]; then log_f "Certificate ${CERT} doesn't exist yet or forced renewal - start obtaining" # Certificate exists and did not change but could be due for renewal (30 days) elif ! openssl x509 -checkend 2592000 -noout -in ${CERT} > /dev/null; then log_f "Certificate ${CERT} is due for renewal (< 30 days) - start renewing" else log_f "Certificate ${CERT} validation done, neither changed nor due for renewal." exit 2 fi else log_f "Certificate ${CERT} missing or changed domains '${CERT_DOMAINS[*]}' - start obtaining" fi # Make backup if [[ -f ${CERT} ]]; then DATE=$(date +%Y-%m-%d_%H_%M_%S) BACKUP_DIR=${ACME_BASE}/backups/${CERT_DOMAIN}/${PREFIX}${DATE} log_f "Creating backups in ${BACKUP_DIR} ..." mkdir -p ${BACKUP_DIR}/ [[ -f ${DOMAINS_FILE} ]] && cp ${DOMAINS_FILE} ${BACKUP_DIR}/ [[ -f ${CERT} ]] && cp ${CERT} ${BACKUP_DIR}/ [[ -f ${KEY} ]] && cp ${KEY} ${BACKUP_DIR}/ [[ -f ${CSR} ]] && cp ${CSR} ${BACKUP_DIR}/ fi mkdir -p ${ACME_BASE}/${CERT_DOMAIN} if [[ ! -f ${KEY} ]]; then log_f "Copying shared private key for this certificate..." cp ${SHARED_KEY} ${KEY} chmod 600 ${KEY} fi # Generating CSR printf "[SAN]\nsubjectAltName=" > /tmp/_SAN printf "DNS:%s," "${CERT_DOMAINS[@]}" >> /tmp/_SAN sed -i '$s/,$//' /tmp/_SAN openssl req -new -sha256 -key ${KEY} -subj "/" -reqexts SAN -config <(cat "$(openssl version -d | sed 's/.*"\(.*\)"/\1/g')/openssl.cnf" /tmp/_SAN) > ${CSR} # acme-tiny writes info to stderr and ceritifcate to stdout # The redirects will do the following: # - redirect stdout to temp certificate file # - redirect acme-tiny stderr to stdout (logs to variable ACME_RESPONSE) # - tee stderr to get live output and log to dockerd log_f "Checking resolver..." until dig letsencrypt.org +time=3 +tries=1 @unbound > /dev/null; do sleep 2 done log_f "Resolver OK" log_f "Using command acme-tiny ${DIRECTORY_URL} --account-key ${ACME_BASE}/acme/account.pem --disable-check --csr ${CSR} --acme-dir /var/www/acme/" ACME_RESPONSE=$(acme-tiny ${DIRECTORY_URL} \ --account-key ${ACME_BASE}/acme/account.pem \ --disable-check \ --csr ${CSR} \ --acme-dir /var/www/acme/ 2>&1 > /tmp/_cert.pem | tee /dev/fd/5; exit ${PIPESTATUS[0]}) SUCCESS="$?" ACME_RESPONSE_B64=$(echo "${ACME_RESPONSE}" | openssl enc -e -A -base64) log_f "${ACME_RESPONSE_B64}" redis_only b64 case "$SUCCESS" in 0) # cert requested log_f "Deploying certificate ${CERT}..." # Deploy the new certificate and key # Moving temp cert to {domain} folder if verify_hash_match /tmp/_cert.pem ${KEY}; then RETURN=0 # certificate created if [[ -f ${CERT} ]]; then RETURN=1 # certificate renewed fi mv -f /tmp/_cert.pem ${CERT} echo -n ${CERT_DOMAINS[*]} > ${DOMAINS_FILE} rm /var/www/acme/* 2> /dev/null log_f "Certificate successfully obtained" exit ${RETURN} else log_f "Certificate was successfully requested, but key and certificate have non-matching hashes, ignoring certificate" exit 4 fi ;; *) # non-zero is non-fun log_f "Failed to obtain certificate ${CERT} for domains '${CERT_DOMAINS[*]}'" redis-cli -h redis -a ${REDISPASS} --no-auth-warning SET ACME_FAIL_TIME "$(date +%s)" exit 100${SUCCESS} ;; esac ================================================ FILE: data/Dockerfiles/acme/reload-configurations.sh ================================================ #!/bin/bash # Reading container IDs # Wrapping as array to ensure trimmed content when calling $NGINX etc. NGINX=($(curl --silent --insecure https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], project: .Config.Labels[\"com.docker.compose.project\"], id: .Id}" | jq -rc "select( .name | tostring | contains(\"nginx-mailcow\")) | select( .project | tostring | contains(\"${COMPOSE_PROJECT_NAME,,}\")) | .id" | tr "\n" " ")) DOVECOT=($(curl --silent --insecure https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], project: .Config.Labels[\"com.docker.compose.project\"], id: .Id}" | jq -rc "select( .name | tostring | contains(\"dovecot-mailcow\")) | select( .project | tostring | contains(\"${COMPOSE_PROJECT_NAME,,}\")) | .id" | tr "\n" " ")) POSTFIX=($(curl --silent --insecure https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], project: .Config.Labels[\"com.docker.compose.project\"], id: .Id}" | jq -rc "select( .name | tostring | contains(\"postfix-mailcow\")) | select( .project | tostring | contains(\"${COMPOSE_PROJECT_NAME,,}\")) | .id" | tr "\n" " ")) reload_nginx(){ echo "Reloading Nginx..." NGINX_RELOAD_RET=$(curl -X POST --insecure https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/${NGINX}/exec -d '{"cmd":"reload", "task":"nginx"}' --silent -H 'Content-type: application/json' | jq -r .type) [[ ${NGINX_RELOAD_RET} != 'success' ]] && { echo "Could not reload Nginx, restarting container..."; restart_container ${NGINX} ; } } reload_dovecot(){ echo "Reloading Dovecot..." DOVECOT_RELOAD_RET=$(curl -X POST --insecure https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/${DOVECOT}/exec -d '{"cmd":"reload", "task":"dovecot"}' --silent -H 'Content-type: application/json' | jq -r .type) [[ ${DOVECOT_RELOAD_RET} != 'success' ]] && { echo "Could not reload Dovecot, restarting container..."; restart_container ${DOVECOT} ; } } reload_postfix(){ echo "Reloading Postfix..." POSTFIX_RELOAD_RET=$(curl -X POST --insecure https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/${POSTFIX}/exec -d '{"cmd":"reload", "task":"postfix"}' --silent -H 'Content-type: application/json' | jq -r .type) [[ ${POSTFIX_RELOAD_RET} != 'success' ]] && { echo "Could not reload Postfix, restarting container..."; restart_container ${POSTFIX} ; } } restart_container(){ for container in $*; do echo "Restarting ${container}..." C_REST_OUT=$(curl -X POST --insecure https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/${container}/restart --silent | jq -r '.msg') echo "${C_REST_OUT}" done } if [[ "${CERT_AMOUNT_CHANGED}" == "1" ]]; then restart_container ${NGINX} restart_container ${DOVECOT} restart_container ${POSTFIX} else reload_nginx #reload_dovecot restart_container ${DOVECOT} #reload_postfix restart_container ${POSTFIX} fi ================================================ FILE: data/Dockerfiles/backup/Dockerfile ================================================ FROM debian:trixie-slim RUN apt update && apt install pigz zstd -y --no-install-recommends ================================================ FILE: data/Dockerfiles/clamd/Dockerfile ================================================ FROM alpine:3.21 AS builder WORKDIR /src ENV CLAMD_VERSION=1.4.2 RUN apk upgrade --no-cache \ && apk add --update --no-cache \ g++ \ gcc \ gdb \ make \ cmake \ py3-pytest \ python3 \ valgrind \ bzip2-dev \ check-dev \ curl-dev \ json-c-dev \ libmilter-dev \ libxml2-dev \ linux-headers \ ncurses-dev \ openssl-dev \ pcre2-dev \ zlib-dev \ cargo \ rust RUN wget -P /src https://www.clamav.net/downloads/production/clamav-${CLAMD_VERSION}.tar.gz \ && tar xzfv /src/clamav-${CLAMD_VERSION}.tar.gz \ && cd /src/clamav-${CLAMD_VERSION} \ && cmake . \ -D CMAKE_BUILD_TYPE="Release" \ -D CMAKE_INSTALL_PREFIX="/usr" \ -D CMAKE_INSTALL_LIBDIR="/usr/lib" \ -D APP_CONFIG_DIRECTORY="/etc/clamav" \ -D DATABASE_DIRECTORY="/var/lib/clamav" \ -D ENABLE_CLAMONACC=OFF \ -D ENABLE_EXAMPLES=OFF \ -D ENABLE_MILTER=ON \ -D ENABLE_MAN_PAGES=OFF \ -D ENABLE_STATIC_LIB=OFF \ -D ENABLE_JSON_SHARED=ON \ && cmake --build . \ && make DESTDIR="/clamav" -j$(($(nproc) - 1)) install \ && rm -r "/clamav/usr/lib/pkgconfig/" \ && sed -e "s|^\(Example\)|\# \1|" \ -e "s|.*\(LocalSocket\) .*|\1 /tmp/clamd.sock|" \ -e "s|.*\(TCPSocket\) .*|\1 3310|" \ -e "s|.*\(TCPAddr\) .*|#\1 0.0.0.0|" \ -e "s|.*\(User\) .*|\1 clamav|" \ -e "s|^\#\(LogFile\) .*|\1 /var/log/clamav/clamd.log|" \ -e "s|^\#\(LogTime\).*|\1 yes|" \ "/clamav/etc/clamav/clamd.conf.sample" > "/clamav/etc/clamav/clamd.conf" \ && sed -e "s|^\(Example\)|\# \1|" \ -e "s|.*\(DatabaseOwner\) .*|\1 clamav|" \ -e "s|^\#\(UpdateLogFile\) .*|\1 /var/log/clamav/freshclam.log|" \ -e "s|^\#\(NotifyClamd\).*|\1 /etc/clamav/clamd.conf|" \ -e "s|^\#\(ScriptedUpdates\).*|\1 yes|" \ "/clamav/etc/clamav/freshclam.conf.sample" > "/clamav/etc/clamav/freshclam.conf" \ && sed -e "s|^\(Example\)|\# \1|" \ -e "s|.*\(MilterSocket\) .*|\1 inet:7357|" \ -e "s|.*\(User\) .*|\1 clamav|" \ -e "s|^\#\(LogFile\) .*|\1 /var/log/clamav/milter.log|" \ -e "s|^\#\(LogTime\).*|\1 yes|" \ -e "s|.*\(\ClamdSocket\) .*|\1 unix:/tmp/clamd.sock|" \ "/clamav/etc/clamav/clamav-milter.conf.sample" > "/clamav/etc/clamav/clamav-milter.conf" || exit 1 FROM alpine:3.21 LABEL maintainer = "The Infrastructure Company GmbH " RUN apk upgrade --no-cache \ && apk add --update --no-cache \ tzdata \ rsync \ bind-tools \ bash \ tini \ json-c \ libbz2 \ libcurl \ libmilter \ libxml2 \ ncurses-libs \ pcre2 \ zlib \ libgcc \ && addgroup -S "clamav" && \ adduser -D -G "clamav" -h "/var/lib/clamav" -s "/bin/false" -S "clamav" && \ install -d -m 755 -g "clamav" -o "clamav" "/var/log/clamav" && \ chown -R clamav:clamav /var/lib/clamav COPY --from=builder "/clamav" "/" # init COPY clamd.sh /clamd.sh RUN chmod +x /sbin/tini # healthcheck COPY healthcheck.sh /healthcheck.sh COPY clamdcheck.sh /usr/local/bin RUN chmod +x /healthcheck.sh RUN chmod +x /usr/local/bin/clamdcheck.sh HEALTHCHECK --start-period=6m CMD "/healthcheck.sh" ENTRYPOINT [] CMD ["/sbin/tini", "-g", "--", "/clamd.sh"] ================================================ FILE: data/Dockerfiles/clamd/clamd.sh ================================================ #!/bin/bash if [[ "${SKIP_CLAMD}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then echo "SKIP_CLAMD=y, skipping ClamAV..." sleep 365d exit 0 fi # Cleaning up garbage echo "Cleaning up tmp files..." rm -rf /var/lib/clamav/tmp.* # Prepare whitelist mkdir -p /run/clamav /var/lib/clamav if [[ -s /etc/clamav/whitelist.ign2 ]]; then echo "Copying non-empty whitelist.ign2 to /var/lib/clamav/whitelist.ign2" cp /etc/clamav/whitelist.ign2 /var/lib/clamav/whitelist.ign2 fi if [[ ! -f /var/lib/clamav/whitelist.ign2 ]]; then echo "Creating /var/lib/clamav/whitelist.ign2" cat < /var/lib/clamav/whitelist.ign2 # Please restart ClamAV after changing signatures Example-Signature.Ignore-1 PUA.Win.Trojan.EmbeddedPDF-1 PUA.Pdf.Trojan.EmbeddedJavaScript-1 PUA.Pdf.Trojan.OpenActionObjectwithJavascript-1 EOF fi chown clamav:clamav -R /var/lib/clamav /run/clamav chmod 755 /var/lib/clamav chmod 644 -R /var/lib/clamav/* chmod 750 /run/clamav stat /var/lib/clamav/whitelist.ign2 dos2unix /var/lib/clamav/whitelist.ign2 sed -i '/^\s*$/d' /var/lib/clamav/whitelist.ign2 # Copying to /etc/clamav to expose file as-is to administrator cp -p /var/lib/clamav/whitelist.ign2 /etc/clamav/whitelist.ign2 BACKGROUND_TASKS=() echo "Running freshclam..." freshclam ( while true; do sleep 12600 freshclam done ) & BACKGROUND_TASKS+=($!) ( while true; do sleep 10m SANE_MIRRORS="$(dig +ignore +short rsync.sanesecurity.net)" for sane_mirror in ${SANE_MIRRORS}; do CE= rsync -avp --chown=clamav:clamav --chmod=Du=rwx,Dgo=rx,Fu=rw,Fog=r --timeout=5 rsync://${sane_mirror}/sanesecurity/ \ --include 'blurl.ndb' \ --include 'junk.ndb' \ --include 'jurlbl.ndb' \ --include 'jurbla.ndb' \ --include 'phishtank.ndb' \ --include 'phish.ndb' \ --include 'spamimg.hdb' \ --include 'scam.ndb' \ --include 'rogue.hdb' \ --include 'sanesecurity.ftm' \ --include 'sigwhitelist.ign2' \ --exclude='*' /var/lib/clamav/ CE=$? chmod 755 /var/lib/clamav/ if [ ${CE} -eq 0 ]; then while [ ! -z "$(pidof freshclam)" ]; do echo "Freshclam is active, waiting..." sleep 5 done echo RELOAD | nc clamd-mailcow 3310 break fi done sleep 12h done ) & BACKGROUND_TASKS+=($!) echo "$(clamd -V) is starting... please wait a moment." nice -n10 clamd & BACKGROUND_TASKS+=($!) while true; do for bg_task in ${BACKGROUND_TASKS[*]}; do if ! kill -0 ${bg_task} 1>&2; then echo "Worker ${bg_task} died, stopping container waiting for respawn..." kill -TERM 1 fi sleep 10 done done ================================================ FILE: data/Dockerfiles/clamd/clamdcheck.sh ================================================ #!/bin/sh set -eu if [ "${CLAMAV_NO_CLAMD:-}" != "false" ]; then if [ "$(echo "PING" | nc localhost 3310)" != "PONG" ]; then echo "ERROR: Unable to contact server" exit 1 fi echo "Clamd is up" fi exit 0 ================================================ FILE: data/Dockerfiles/clamd/healthcheck.sh ================================================ #!/bin/bash if [[ "${SKIP_CLAMD}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then echo "SKIP_CLAMD=y, skipping ClamAV..." exit 0 fi # run clamd healthcheck /usr/local/bin/clamdcheck.sh ================================================ FILE: data/Dockerfiles/dockerapi/Dockerfile ================================================ FROM alpine:3.23 LABEL maintainer = "The Infrastructure Company GmbH " ARG PIP_BREAK_SYSTEM_PACKAGES=1 WORKDIR /app RUN apk add --update --no-cache python3 \ py3-pip \ openssl \ tzdata \ py3-psutil \ py3-redis \ py3-async-timeout \ && pip3 install --upgrade pip \ fastapi \ uvicorn \ aiodocker \ docker RUN mkdir /app/modules COPY docker-entrypoint.sh /app/ COPY main.py /app/main.py COPY modules/ /app/modules/ ENTRYPOINT ["/bin/sh", "/app/docker-entrypoint.sh"] CMD ["python", "main.py"] ================================================ FILE: data/Dockerfiles/dockerapi/docker-entrypoint.sh ================================================ #!/bin/bash `openssl req -x509 -newkey rsa:4096 -sha256 -days 3650 -nodes \ -keyout /app/dockerapi_key.pem \ -out /app/dockerapi_cert.pem \ -subj /CN=dockerapi/O=mailcow \ -addext subjectAltName=DNS:dockerapi` exec "$@" ================================================ FILE: data/Dockerfiles/dockerapi/main.py ================================================ import os import sys import uvicorn import json import uuid import async_timeout import asyncio import aiodocker import docker import logging from logging.config import dictConfig from fastapi import FastAPI, Response, Request from modules.DockerApi import DockerApi from redis import asyncio as aioredis from contextlib import asynccontextmanager dockerapi = None @asynccontextmanager async def lifespan(app: FastAPI): global dockerapi # Initialize a custom logger logger = logging.getLogger("dockerapi") logger.setLevel(logging.INFO) # Configure the logger to output logs to the terminal handler = logging.StreamHandler() handler.setLevel(logging.INFO) formatter = logging.Formatter("%(levelname)s: %(message)s") handler.setFormatter(formatter) logger.addHandler(handler) logger.info("Init APP") # Init redis client if os.environ['REDIS_SLAVEOF_IP'] != "": redis_client = redis = await aioredis.from_url(f"redis://{os.environ['REDIS_SLAVEOF_IP']}:{os.environ['REDIS_SLAVEOF_PORT']}/0", password=os.environ['REDISPASS']) else: redis_client = redis = await aioredis.from_url("redis://redis-mailcow:6379/0", password=os.environ['REDISPASS']) # Init docker clients sync_docker_client = docker.DockerClient(base_url='unix://var/run/docker.sock', version='auto') async_docker_client = aiodocker.Docker(url='unix:///var/run/docker.sock') dockerapi = DockerApi(redis_client, sync_docker_client, async_docker_client, logger) logger.info("Subscribe to redis channel") # Subscribe to redis channel dockerapi.pubsub = redis.pubsub() await dockerapi.pubsub.subscribe("MC_CHANNEL") asyncio.create_task(handle_pubsub_messages(dockerapi.pubsub)) yield # Close docker connections dockerapi.sync_docker_client.close() await dockerapi.async_docker_client.close() # Close redis await dockerapi.pubsub.unsubscribe("MC_CHANNEL") await dockerapi.redis_client.close() app = FastAPI(lifespan=lifespan) # Define Routes @app.get("/host/stats") async def get_host_update_stats(): global dockerapi if dockerapi.host_stats_isUpdating == False: asyncio.create_task(dockerapi.get_host_stats()) dockerapi.host_stats_isUpdating = True while True: if await dockerapi.redis_client.exists('host_stats'): break await asyncio.sleep(1.5) stats = json.loads(await dockerapi.redis_client.get('host_stats')) return Response(content=json.dumps(stats, indent=4), media_type="application/json") @app.get("/containers/{container_id}/json") async def get_container(container_id : str): global dockerapi if container_id and container_id.isalnum(): try: for container in (await dockerapi.async_docker_client.containers.list()): if container._id == container_id: container_info = await container.show() return Response(content=json.dumps(container_info, indent=4), media_type="application/json") res = { "type": "danger", "msg": "no container found" } return Response(content=json.dumps(res, indent=4), media_type="application/json") except Exception as e: res = { "type": "danger", "msg": str(e) } return Response(content=json.dumps(res, indent=4), media_type="application/json") else: res = { "type": "danger", "msg": "no or invalid id defined" } return Response(content=json.dumps(res, indent=4), media_type="application/json") @app.get("/containers/json") async def get_containers(all: bool = False): global dockerapi containers = {} try: for container in (await dockerapi.async_docker_client.containers.list(all=all)): container_info = await container.show() containers.update({container_info['Id']: container_info}) return Response(content=json.dumps(containers, indent=4), media_type="application/json") except Exception as e: res = { "type": "danger", "msg": str(e) } return Response(content=json.dumps(res, indent=4), media_type="application/json") @app.post("/containers/{container_id}/{post_action}") async def post_containers(container_id : str, post_action : str, request: Request): global dockerapi try: request_json = await request.json() except Exception as err: request_json = {} if container_id and container_id.isalnum() and post_action: try: """Dispatch container_post api call""" if post_action == 'exec': if not request_json or not 'cmd' in request_json: res = { "type": "danger", "msg": "cmd is missing" } return Response(content=json.dumps(res, indent=4), media_type="application/json") if not request_json or not 'task' in request_json: res = { "type": "danger", "msg": "task is missing" } return Response(content=json.dumps(res, indent=4), media_type="application/json") api_call_method_name = '__'.join(['container_post', str(post_action), str(request_json['cmd']), str(request_json['task']) ]) else: api_call_method_name = '__'.join(['container_post', str(post_action) ]) api_call_method = getattr(dockerapi, api_call_method_name, lambda container_id: Response(content=json.dumps({'type': 'danger', 'msg':'container_post - unknown api call' }, indent=4), media_type="application/json")) dockerapi.logger.info("api call: %s, container_id: %s" % (api_call_method_name, container_id)) return api_call_method(request_json, container_id=container_id) except Exception as e: dockerapi.logger.error("error - container_post: %s" % str(e)) res = { "type": "danger", "msg": str(e) } return Response(content=json.dumps(res, indent=4), media_type="application/json") else: res = { "type": "danger", "msg": "invalid container id or missing action" } return Response(content=json.dumps(res, indent=4), media_type="application/json") @app.post("/container/{container_id}/stats/update") async def post_container_update_stats(container_id : str): global dockerapi # start update task for container if no task is running if container_id not in dockerapi.containerIds_to_update: asyncio.create_task(dockerapi.get_container_stats(container_id)) dockerapi.containerIds_to_update.append(container_id) while True: if await dockerapi.redis_client.exists(container_id + '_stats'): break await asyncio.sleep(1.5) stats = json.loads(await dockerapi.redis_client.get(container_id + '_stats')) return Response(content=json.dumps(stats, indent=4), media_type="application/json") # PubSub Handler async def handle_pubsub_messages(channel: aioredis.client.PubSub): global dockerapi while True: try: async with async_timeout.timeout(60): message = await channel.get_message(ignore_subscribe_messages=True, timeout=30) if message is not None: # Parse message data_json = json.loads(message['data'].decode('utf-8')) dockerapi.logger.info(f"PubSub Received - {json.dumps(data_json)}") # Handle api_call if 'api_call' in data_json: # api_call: container_post if data_json['api_call'] == "container_post": if 'post_action' in data_json and 'container_name' in data_json: try: """Dispatch container_post api call""" request_json = {} if data_json['post_action'] == 'exec': if 'request' in data_json: request_json = data_json['request'] if 'cmd' in request_json: if 'task' in request_json: api_call_method_name = '__'.join(['container_post', str(data_json['post_action']), str(request_json['cmd']), str(request_json['task']) ]) else: dockerapi.logger.error("api call: task missing") else: dockerapi.logger.error("api call: cmd missing") else: dockerapi.logger.error("api call: request missing") else: api_call_method_name = '__'.join(['container_post', str(data_json['post_action'])]) if api_call_method_name: api_call_method = getattr(dockerapi, api_call_method_name) if api_call_method: dockerapi.logger.info("api call: %s, container_name: %s" % (api_call_method_name, data_json['container_name'])) api_call_method(request_json, container_name=data_json['container_name']) else: dockerapi.logger.error("api call not found: %s, container_name: %s" % (api_call_method_name, data_json['container_name'])) except Exception as e: dockerapi.logger.error("container_post: %s" % str(e)) else: dockerapi.logger.error("api call: missing container_name, post_action or request") else: dockerapi.logger.error("Unknown PubSub received - %s" % json.dumps(data_json)) else: dockerapi.logger.error("Unknown PubSub received - %s" % json.dumps(data_json)) await asyncio.sleep(0.0) except asyncio.TimeoutError: pass if __name__ == '__main__': uvicorn.run( app, host="0.0.0.0", port=443, ssl_certfile="/app/dockerapi_cert.pem", ssl_keyfile="/app/dockerapi_key.pem", log_level="info", loop="none" ) ================================================ FILE: data/Dockerfiles/dockerapi/modules/DockerApi.py ================================================ import psutil import sys import os import re import time import json import asyncio import platform from datetime import datetime from fastapi import FastAPI, Response, Request class DockerApi: def __init__(self, redis_client, sync_docker_client, async_docker_client, logger): self.redis_client = redis_client self.sync_docker_client = sync_docker_client self.async_docker_client = async_docker_client self.logger = logger self.host_stats_isUpdating = False self.containerIds_to_update = [] # api call: container_post - post_action: stop def container_post__stop(self, request_json, **kwargs): if 'container_id' in kwargs: filters = {"id": kwargs['container_id']} elif 'container_name' in kwargs: filters = {"name": kwargs['container_name']} for container in self.sync_docker_client.containers.list(all=True, filters=filters): container.stop() res = { 'type': 'success', 'msg': 'command completed successfully'} return Response(content=json.dumps(res, indent=4), media_type="application/json") # api call: container_post - post_action: start def container_post__start(self, request_json, **kwargs): if 'container_id' in kwargs: filters = {"id": kwargs['container_id']} elif 'container_name' in kwargs: filters = {"name": kwargs['container_name']} for container in self.sync_docker_client.containers.list(all=True, filters=filters): container.start() res = { 'type': 'success', 'msg': 'command completed successfully'} return Response(content=json.dumps(res, indent=4), media_type="application/json") # api call: container_post - post_action: restart def container_post__restart(self, request_json, **kwargs): if 'container_id' in kwargs: filters = {"id": kwargs['container_id']} elif 'container_name' in kwargs: filters = {"name": kwargs['container_name']} for container in self.sync_docker_client.containers.list(all=True, filters=filters): container.restart() res = { 'type': 'success', 'msg': 'command completed successfully'} return Response(content=json.dumps(res, indent=4), media_type="application/json") # api call: container_post - post_action: top def container_post__top(self, request_json, **kwargs): if 'container_id' in kwargs: filters = {"id": kwargs['container_id']} elif 'container_name' in kwargs: filters = {"name": kwargs['container_name']} for container in self.sync_docker_client.containers.list(all=True, filters=filters): res = { 'type': 'success', 'msg': container.top()} return Response(content=json.dumps(res, indent=4), media_type="application/json") # api call: container_post - post_action: stats def container_post__stats(self, request_json, **kwargs): if 'container_id' in kwargs: filters = {"id": kwargs['container_id']} elif 'container_name' in kwargs: filters = {"name": kwargs['container_name']} for container in self.sync_docker_client.containers.list(all=True, filters=filters): for stat in container.stats(decode=True, stream=True): res = { 'type': 'success', 'msg': stat} return Response(content=json.dumps(res, indent=4), media_type="application/json") # api call: container_post - post_action: exec - cmd: mailq - task: delete def container_post__exec__mailq__delete(self, request_json, **kwargs): if 'container_id' in kwargs: filters = {"id": kwargs['container_id']} elif 'container_name' in kwargs: filters = {"name": kwargs['container_name']} if 'items' in request_json: r = re.compile("^[0-9a-fA-F]+$") filtered_qids = filter(r.match, request_json['items']) if filtered_qids: flagged_qids = ['-d %s' % i for i in filtered_qids] sanitized_string = str(' '.join(flagged_qids)) for container in self.sync_docker_client.containers.list(filters=filters): postsuper_r = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postsuper " + sanitized_string]) return self.exec_run_handler('generic', postsuper_r) # api call: container_post - post_action: exec - cmd: mailq - task: hold def container_post__exec__mailq__hold(self, request_json, **kwargs): if 'container_id' in kwargs: filters = {"id": kwargs['container_id']} elif 'container_name' in kwargs: filters = {"name": kwargs['container_name']} if 'items' in request_json: r = re.compile("^[0-9a-fA-F]+$") filtered_qids = filter(r.match, request_json['items']) if filtered_qids: flagged_qids = ['-h %s' % i for i in filtered_qids] sanitized_string = str(' '.join(flagged_qids)) for container in self.sync_docker_client.containers.list(filters=filters): postsuper_r = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postsuper " + sanitized_string]) return self.exec_run_handler('generic', postsuper_r) # api call: container_post - post_action: exec - cmd: mailq - task: cat def container_post__exec__mailq__cat(self, request_json, **kwargs): if 'container_id' in kwargs: filters = {"id": kwargs['container_id']} elif 'container_name' in kwargs: filters = {"name": kwargs['container_name']} if 'items' in request_json: r = re.compile("^[0-9a-fA-F]+$") filtered_qids = filter(r.match, request_json['items']) if filtered_qids: sanitized_string = str(' '.join(filtered_qids)) for container in self.sync_docker_client.containers.list(filters=filters): postcat_return = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postcat -q " + sanitized_string], user='postfix') if not postcat_return: postcat_return = 'err: invalid' return self.exec_run_handler('utf8_text_only', postcat_return) # api call: container_post - post_action: exec - cmd: mailq - task: unhold def container_post__exec__mailq__unhold(self, request_json, **kwargs): if 'container_id' in kwargs: filters = {"id": kwargs['container_id']} elif 'container_name' in kwargs: filters = {"name": kwargs['container_name']} if 'items' in request_json: r = re.compile("^[0-9a-fA-F]+$") filtered_qids = filter(r.match, request_json['items']) if filtered_qids: flagged_qids = ['-H %s' % i for i in filtered_qids] sanitized_string = str(' '.join(flagged_qids)) for container in self.sync_docker_client.containers.list(filters=filters): postsuper_r = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postsuper " + sanitized_string]) return self.exec_run_handler('generic', postsuper_r) # api call: container_post - post_action: exec - cmd: mailq - task: deliver def container_post__exec__mailq__deliver(self, request_json, **kwargs): if 'container_id' in kwargs: filters = {"id": kwargs['container_id']} elif 'container_name' in kwargs: filters = {"name": kwargs['container_name']} if 'items' in request_json: r = re.compile("^[0-9a-fA-F]+$") filtered_qids = filter(r.match, request_json['items']) if filtered_qids: flagged_qids = ['-i %s' % i for i in filtered_qids] for container in self.sync_docker_client.containers.list(filters=filters): for i in flagged_qids: postqueue_r = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postqueue " + i], user='postfix') # todo: check each exit code res = { 'type': 'success', 'msg': 'Scheduled immediate delivery'} return Response(content=json.dumps(res, indent=4), media_type="application/json") # api call: container_post - post_action: exec - cmd: mailq - task: list def container_post__exec__mailq__list(self, request_json, **kwargs): if 'container_id' in kwargs: filters = {"id": kwargs['container_id']} elif 'container_name' in kwargs: filters = {"name": kwargs['container_name']} for container in self.sync_docker_client.containers.list(filters=filters): mailq_return = container.exec_run(["/usr/sbin/postqueue", "-j"], user='postfix') return self.exec_run_handler('utf8_text_only', mailq_return) # api call: container_post - post_action: exec - cmd: mailq - task: flush def container_post__exec__mailq__flush(self, request_json, **kwargs): if 'container_id' in kwargs: filters = {"id": kwargs['container_id']} elif 'container_name' in kwargs: filters = {"name": kwargs['container_name']} for container in self.sync_docker_client.containers.list(filters=filters): postqueue_r = container.exec_run(["/usr/sbin/postqueue", "-f"], user='postfix') return self.exec_run_handler('generic', postqueue_r) # api call: container_post - post_action: exec - cmd: mailq - task: super_delete def container_post__exec__mailq__super_delete(self, request_json, **kwargs): if 'container_id' in kwargs: filters = {"id": kwargs['container_id']} elif 'container_name' in kwargs: filters = {"name": kwargs['container_name']} for container in self.sync_docker_client.containers.list(filters=filters): postsuper_r = container.exec_run(["/usr/sbin/postsuper", "-d", "ALL"]) return self.exec_run_handler('generic', postsuper_r) # api call: container_post - post_action: exec - cmd: system - task: fts_rescan def container_post__exec__system__fts_rescan(self, request_json, **kwargs): if 'container_id' in kwargs: filters = {"id": kwargs['container_id']} elif 'container_name' in kwargs: filters = {"name": kwargs['container_name']} if 'username' in request_json: for container in self.sync_docker_client.containers.list(filters=filters): rescan_return = container.exec_run(["/bin/bash", "-c", "/usr/bin/doveadm fts rescan -u '" + request_json['username'].replace("'", "'\\''") + "'"], user='vmail') if rescan_return.exit_code == 0: res = { 'type': 'success', 'msg': 'fts_rescan: rescan triggered'} return Response(content=json.dumps(res, indent=4), media_type="application/json") else: res = { 'type': 'warning', 'msg': 'fts_rescan error'} return Response(content=json.dumps(res, indent=4), media_type="application/json") if 'all' in request_json: for container in self.sync_docker_client.containers.list(filters=filters): rescan_return = container.exec_run(["/bin/bash", "-c", "/usr/bin/doveadm fts rescan -A"], user='vmail') if rescan_return.exit_code == 0: res = { 'type': 'success', 'msg': 'fts_rescan: rescan triggered'} return Response(content=json.dumps(res, indent=4), media_type="application/json") else: res = { 'type': 'warning', 'msg': 'fts_rescan error'} return Response(content=json.dumps(res, indent=4), media_type="application/json") # api call: container_post - post_action: exec - cmd: system - task: df def container_post__exec__system__df(self, request_json, **kwargs): if 'container_id' in kwargs: filters = {"id": kwargs['container_id']} elif 'container_name' in kwargs: filters = {"name": kwargs['container_name']} if 'dir' in request_json: for container in self.sync_docker_client.containers.list(filters=filters): df_return = container.exec_run(["/bin/bash", "-c", "/bin/df -H '" + request_json['dir'].replace("'", "'\\''") + "' | /usr/bin/tail -n1 | /usr/bin/tr -s [:blank:] | /usr/bin/tr ' ' ','"], user='nobody') if df_return.exit_code == 0: return df_return.output.decode('utf-8').rstrip() else: return "0,0,0,0,0,0" # api call: container_post - post_action: exec - cmd: system - task: mysql_upgrade def container_post__exec__system__mysql_upgrade(self, request_json, **kwargs): if 'container_id' in kwargs: filters = {"id": kwargs['container_id']} elif 'container_name' in kwargs: filters = {"name": kwargs['container_name']} for container in self.sync_docker_client.containers.list(filters=filters): sql_return = container.exec_run(["/bin/bash", "-c", "/usr/bin/mysql_upgrade -uroot -p'" + os.environ['DBROOT'].replace("'", "'\\''") + "'\n"], user='mysql') if sql_return.exit_code == 0: matched = False for line in sql_return.output.decode('utf-8').split("\n"): if 'is already upgraded to' in line: matched = True if matched: res = { 'type': 'success', 'msg':'mysql_upgrade: already upgraded', 'text': sql_return.output.decode('utf-8')} return Response(content=json.dumps(res, indent=4), media_type="application/json") else: container.restart() res = { 'type': 'warning', 'msg':'mysql_upgrade: upgrade was applied', 'text': sql_return.output.decode('utf-8')} return Response(content=json.dumps(res, indent=4), media_type="application/json") else: res = { 'type': 'error', 'msg': 'mysql_upgrade: error running command', 'text': sql_return.output.decode('utf-8')} return Response(content=json.dumps(res, indent=4), media_type="application/json") # api call: container_post - post_action: exec - cmd: system - task: mysql_tzinfo_to_sql def container_post__exec__system__mysql_tzinfo_to_sql(self, request_json, **kwargs): if 'container_id' in kwargs: filters = {"id": kwargs['container_id']} elif 'container_name' in kwargs: filters = {"name": kwargs['container_name']} for container in self.sync_docker_client.containers.list(filters=filters): sql_return = container.exec_run(["/bin/bash", "-c", "/usr/bin/mysql_tzinfo_to_sql /usr/share/zoneinfo | /bin/sed 's/Local time zone must be set--see zic manual page/FCTY/' | /usr/bin/mysql -uroot -p'" + os.environ['DBROOT'].replace("'", "'\\''") + "' mysql \n"], user='mysql') if sql_return.exit_code == 0: res = { 'type': 'info', 'msg': 'mysql_tzinfo_to_sql: command completed successfully', 'text': sql_return.output.decode('utf-8')} return Response(content=json.dumps(res, indent=4), media_type="application/json") else: res = { 'type': 'error', 'msg': 'mysql_tzinfo_to_sql: error running command', 'text': sql_return.output.decode('utf-8')} return Response(content=json.dumps(res, indent=4), media_type="application/json") # api call: container_post - post_action: exec - cmd: reload - task: dovecot def container_post__exec__reload__dovecot(self, request_json, **kwargs): if 'container_id' in kwargs: filters = {"id": kwargs['container_id']} elif 'container_name' in kwargs: filters = {"name": kwargs['container_name']} for container in self.sync_docker_client.containers.list(filters=filters): reload_return = container.exec_run(["/bin/bash", "-c", "/usr/sbin/dovecot reload"]) return self.exec_run_handler('generic', reload_return) # api call: container_post - post_action: exec - cmd: reload - task: postfix def container_post__exec__reload__postfix(self, request_json, **kwargs): if 'container_id' in kwargs: filters = {"id": kwargs['container_id']} elif 'container_name' in kwargs: filters = {"name": kwargs['container_name']} for container in self.sync_docker_client.containers.list(filters=filters): reload_return = container.exec_run(["/bin/bash", "-c", "/usr/sbin/postfix reload"]) return self.exec_run_handler('generic', reload_return) # api call: container_post - post_action: exec - cmd: reload - task: nginx def container_post__exec__reload__nginx(self, request_json, **kwargs): if 'container_id' in kwargs: filters = {"id": kwargs['container_id']} elif 'container_name' in kwargs: filters = {"name": kwargs['container_name']} for container in self.sync_docker_client.containers.list(filters=filters): reload_return = container.exec_run(["/bin/sh", "-c", "/usr/sbin/nginx -s reload"]) return self.exec_run_handler('generic', reload_return) # api call: container_post - post_action: exec - cmd: sieve - task: list def container_post__exec__sieve__list(self, request_json, **kwargs): if 'container_id' in kwargs: filters = {"id": kwargs['container_id']} elif 'container_name' in kwargs: filters = {"name": kwargs['container_name']} if 'username' in request_json: for container in self.sync_docker_client.containers.list(filters=filters): sieve_return = container.exec_run(["/bin/bash", "-c", "/usr/bin/doveadm sieve list -u '" + request_json['username'].replace("'", "'\\''") + "'"]) return self.exec_run_handler('utf8_text_only', sieve_return) # api call: container_post - post_action: exec - cmd: sieve - task: print def container_post__exec__sieve__print(self, request_json, **kwargs): if 'container_id' in kwargs: filters = {"id": kwargs['container_id']} elif 'container_name' in kwargs: filters = {"name": kwargs['container_name']} if 'username' in request_json and 'script_name' in request_json: for container in self.sync_docker_client.containers.list(filters=filters): cmd = ["/bin/bash", "-c", "/usr/bin/doveadm sieve get -u '" + request_json['username'].replace("'", "'\\''") + "' '" + request_json['script_name'].replace("'", "'\\''") + "'"] sieve_return = container.exec_run(cmd) return self.exec_run_handler('utf8_text_only', sieve_return) # api call: container_post - post_action: exec - cmd: maildir - task: cleanup def container_post__exec__maildir__cleanup(self, request_json, **kwargs): if 'container_id' in kwargs: filters = {"id": kwargs['container_id']} elif 'container_name' in kwargs: filters = {"name": kwargs['container_name']} if 'maildir' in request_json: for container in self.sync_docker_client.containers.list(filters=filters): sane_name = re.sub(r'\W+', '', request_json['maildir']) vmail_name = request_json['maildir'].replace("'", "'\\''") cmd_vmail = "if [[ -d '/var/vmail/" + vmail_name + "' ]]; then /bin/mv '/var/vmail/" + vmail_name + "' '/var/vmail/_garbage/" + str(int(time.time())) + "_" + sane_name + "'; fi" index_name = request_json['maildir'].split("/") if len(index_name) > 1: index_name = index_name[1].replace("'", "'\\''") + "@" + index_name[0].replace("'", "'\\''") cmd_vmail_index = "if [[ -d '/var/vmail_index/" + index_name + "' ]]; then /bin/mv '/var/vmail_index/" + index_name + "' '/var/vmail/_garbage/" + str(int(time.time())) + "_" + sane_name + "_index'; fi" cmd = ["/bin/bash", "-c", cmd_vmail + " && " + cmd_vmail_index] else: cmd = ["/bin/bash", "-c", cmd_vmail] maildir_cleanup = container.exec_run(cmd, user='vmail') return self.exec_run_handler('generic', maildir_cleanup) # api call: container_post - post_action: exec - cmd: maildir - task: move def container_post__exec__maildir__move(self, request_json, **kwargs): if 'container_id' in kwargs: filters = {"id": kwargs['container_id']} elif 'container_name' in kwargs: filters = {"name": kwargs['container_name']} if 'old_maildir' in request_json and 'new_maildir' in request_json: for container in self.sync_docker_client.containers.list(filters=filters): vmail_name = request_json['old_maildir'].replace("'", "'\\''") new_vmail_name = request_json['new_maildir'].replace("'", "'\\''") cmd_vmail = f"if [[ -d '/var/vmail/{vmail_name}' ]]; then /bin/mv '/var/vmail/{vmail_name}' '/var/vmail/{new_vmail_name}'; fi" index_name = request_json['old_maildir'].split("/") new_index_name = request_json['new_maildir'].split("/") if len(index_name) > 1 and len(new_index_name) > 1: index_name = index_name[1].replace("'", "'\\''") + "@" + index_name[0].replace("'", "'\\''") new_index_name = new_index_name[1].replace("'", "'\\''") + "@" + new_index_name[0].replace("'", "'\\''") cmd_vmail_index = f"if [[ -d '/var/vmail_index/{index_name}' ]]; then /bin/mv '/var/vmail_index/{index_name}' '/var/vmail_index/{new_index_name}_index'; fi" cmd = ["/bin/bash", "-c", cmd_vmail + " && " + cmd_vmail_index] else: cmd = ["/bin/bash", "-c", cmd_vmail] maildir_move = container.exec_run(cmd, user='vmail') return self.exec_run_handler('generic', maildir_move) # api call: container_post - post_action: exec - cmd: rspamd - task: worker_password def container_post__exec__rspamd__worker_password(self, request_json, **kwargs): if 'container_id' in kwargs: filters = {"id": kwargs['container_id']} elif 'container_name' in kwargs: filters = {"name": kwargs['container_name']} if 'raw' in request_json: for container in self.sync_docker_client.containers.list(filters=filters): cmd = "/usr/bin/rspamadm pw -e -p '" + request_json['raw'].replace("'", "'\\''") + "' 2> /dev/null" cmd_response = self.exec_cmd_container(container, cmd, user="_rspamd") matched = False for line in cmd_response.split("\n"): if '$2$' in line: hash = line.strip() hash_out = re.search(r'\$2\$.+$', hash).group(0) rspamd_passphrase_hash = re.sub(r'[^0-9a-zA-Z\$]+', '', hash_out.rstrip()) rspamd_password_filename = "/etc/rspamd/override.d/worker-controller-password.inc" cmd = '''/bin/echo 'enable_password = "%s";' > %s && cat %s''' % (rspamd_passphrase_hash, rspamd_password_filename, rspamd_password_filename) cmd_response = self.exec_cmd_container(container, cmd, user="_rspamd") if rspamd_passphrase_hash.startswith("$2$") and rspamd_passphrase_hash in cmd_response: container.restart() matched = True if matched: res = { 'type': 'success', 'msg': 'command completed successfully' } self.logger.info('success changing Rspamd password') return Response(content=json.dumps(res, indent=4), media_type="application/json") else: self.logger.error('failed changing Rspamd password') res = { 'type': 'danger', 'msg': 'command did not complete' } return Response(content=json.dumps(res, indent=4), media_type="application/json") # api call: container_post - post_action: exec - cmd: sogo - task: rename def container_post__exec__sogo__rename_user(self, request_json, **kwargs): if 'container_id' in kwargs: filters = {"id": kwargs['container_id']} elif 'container_name' in kwargs: filters = {"name": kwargs['container_name']} if 'old_username' in request_json and 'new_username' in request_json: for container in self.sync_docker_client.containers.list(filters=filters): old_username = request_json['old_username'].replace("'", "'\\''") new_username = request_json['new_username'].replace("'", "'\\''") sogo_return = container.exec_run(["/bin/bash", "-c", f"sogo-tool rename-user '{old_username}' '{new_username}'"], user='sogo') return self.exec_run_handler('generic', sogo_return) # api call: container_post - post_action: exec - cmd: doveadm - task: get_acl def container_post__exec__doveadm__get_acl(self, request_json, **kwargs): if 'container_id' in kwargs: filters = {"id": kwargs['container_id']} elif 'container_name' in kwargs: filters = {"name": kwargs['container_name']} for container in self.sync_docker_client.containers.list(filters=filters): id = request_json['id'].replace("'", "'\\''") shared_folders = container.exec_run(["/bin/bash", "-c", f"doveadm mailbox list -u '{id}'"]) shared_folders = shared_folders.output.decode('utf-8') shared_folders = shared_folders.splitlines() formatted_acls = [] mailbox_seen = [] for shared_folder in shared_folders: if "Shared" not in shared_folder: mailbox = shared_folder.replace("'", "'\\''") if mailbox in mailbox_seen: continue acls = container.exec_run(["/bin/bash", "-c", f"doveadm acl get -u '{id}' '{mailbox}'"]) acls = acls.output.decode('utf-8').strip().splitlines() if len(acls) >= 2: for acl in acls[1:]: user_id, rights = acl.split(maxsplit=1) user_id = user_id.split('=')[1] mailbox_seen.append(mailbox) formatted_acls.append({ 'user': id, 'id': user_id, 'mailbox': mailbox, 'rights': rights.split() }) elif "Shared" in shared_folder and "/" in shared_folder: shared_folder = shared_folder.split("/") if len(shared_folder) < 3: continue user = shared_folder[1].replace("'", "'\\''") mailbox = '/'.join(shared_folder[2:]).replace("'", "'\\''") if mailbox in mailbox_seen: continue acls = container.exec_run(["/bin/bash", "-c", f"doveadm acl get -u '{user}' '{mailbox}'"]) acls = acls.output.decode('utf-8').strip().splitlines() if len(acls) >= 2: for acl in acls[1:]: user_id, rights = acl.split(maxsplit=1) user_id = user_id.split('=')[1].replace("'", "'\\''") if user_id == id and mailbox not in mailbox_seen: mailbox_seen.append(mailbox) formatted_acls.append({ 'user': user, 'id': id, 'mailbox': mailbox, 'rights': rights.split() }) return Response(content=json.dumps(formatted_acls, indent=4), media_type="application/json") # api call: container_post - post_action: exec - cmd: doveadm - task: delete_acl def container_post__exec__doveadm__delete_acl(self, request_json, **kwargs): if 'container_id' in kwargs: filters = {"id": kwargs['container_id']} elif 'container_name' in kwargs: filters = {"name": kwargs['container_name']} for container in self.sync_docker_client.containers.list(filters=filters): user = request_json['user'].replace("'", "'\\''") mailbox = request_json['mailbox'].replace("'", "'\\''") id = request_json['id'].replace("'", "'\\''") if user and mailbox and id: acl_delete_return = container.exec_run(["/bin/bash", "-c", f"doveadm acl delete -u '{user}' '{mailbox}' 'user={id}'"]) return self.exec_run_handler('generic', acl_delete_return) # api call: container_post - post_action: exec - cmd: doveadm - task: set_acl def container_post__exec__doveadm__set_acl(self, request_json, **kwargs): if 'container_id' in kwargs: filters = {"id": kwargs['container_id']} elif 'container_name' in kwargs: filters = {"name": kwargs['container_name']} for container in self.sync_docker_client.containers.list(filters=filters): user = request_json['user'].replace("'", "'\\''") mailbox = request_json['mailbox'].replace("'", "'\\''") id = request_json['id'].replace("'", "'\\''") rights = "" available_rights = [ "admin", "create", "delete", "expunge", "insert", "lookup", "post", "read", "write", "write-deleted", "write-seen" ] for right in request_json['rights']: right = right.replace("'", "'\\''").lower() if right in available_rights: rights += right + " " if user and mailbox and id and rights: acl_set_return = container.exec_run(["/bin/bash", "-c", f"doveadm acl set -u '{user}' '{mailbox}' 'user={id}' {rights}"]) return self.exec_run_handler('generic', acl_set_return) # Collect host stats async def get_host_stats(self, wait=5): try: system_time = datetime.now() host_stats = { "cpu": { "cores": psutil.cpu_count(), "usage": psutil.cpu_percent() }, "memory": { "total": psutil.virtual_memory().total, "usage": psutil.virtual_memory().percent, "swap": psutil.swap_memory() }, "uptime": time.time() - psutil.boot_time(), "system_time": system_time.strftime("%d.%m.%Y %H:%M:%S"), "architecture": platform.machine() } await self.redis_client.set('host_stats', json.dumps(host_stats), ex=10) except Exception as e: res = { "type": "danger", "msg": str(e) } await asyncio.sleep(wait) self.host_stats_isUpdating = False # Collect container stats async def get_container_stats(self, container_id, wait=5, stop=False): if container_id and container_id.isalnum(): try: for container in (await self.async_docker_client.containers.list()): if container._id == container_id: res = await container.stats(stream=False) if await self.redis_client.exists(container_id + '_stats'): stats = json.loads(await self.redis_client.get(container_id + '_stats')) else: stats = [] stats.append(res[0]) if len(stats) > 3: del stats[0] await self.redis_client.set(container_id + '_stats', json.dumps(stats), ex=60) except Exception as e: res = { "type": "danger", "msg": str(e) } else: res = { "type": "danger", "msg": "no or invalid id defined" } await asyncio.sleep(wait) if stop == True: # update task was called second time, stop self.containerIds_to_update.remove(container_id) else: # call update task a second time await self.get_container_stats(container_id, wait=0, stop=True) def exec_cmd_container(self, container, cmd, user, timeout=2, shell_cmd="/bin/bash"): def recv_socket_data(c_socket, timeout): c_socket.setblocking(0) total_data=[] data='' begin=time.time() while True: if total_data and time.time()-begin > timeout: break elif time.time()-begin > timeout*2: break try: data = c_socket.recv(8192) if data: total_data.append(data.decode('utf-8')) #change the beginning time for measurement begin=time.time() else: #sleep for sometime to indicate a gap time.sleep(0.1) break except: pass return ''.join(total_data) try : socket = container.exec_run([shell_cmd], stdin=True, socket=True, user=user).output._sock if not cmd.endswith("\n"): cmd = cmd + "\n" socket.send(cmd.encode('utf-8')) data = recv_socket_data(socket, timeout) socket.close() return data except Exception as e: self.logger.error("error - exec_cmd_container: %s" % str(e)) traceback.print_exc(file=sys.stdout) def exec_run_handler(self, type, output): if type == 'generic': if output.exit_code == 0: res = { 'type': 'success', 'msg': 'command completed successfully' } return Response(content=json.dumps(res, indent=4), media_type="application/json") else: res = { 'type': 'danger', 'msg': 'command failed: ' + output.output.decode('utf-8') } return Response(content=json.dumps(res, indent=4), media_type="application/json") if type == 'utf8_text_only': return Response(content=output.output.decode('utf-8'), media_type="text/plain") ================================================ FILE: data/Dockerfiles/dockerapi/modules/__init__.py ================================================ ================================================ FILE: data/Dockerfiles/dovecot/Dockerfile ================================================ FROM alpine:3.21 LABEL maintainer="The Infrastructure Company GmbH " # renovate: datasource=github-releases depName=tianon/gosu versioning=semver-coerced extractVersion=^(?.*)$ ARG GOSU_VERSION=1.19 ENV LANG=C.UTF-8 ENV LC_ALL=C.UTF-8 # Add groups and users before installing Dovecot to not break compatibility RUN addgroup -g 5000 vmail \ && addgroup -g 401 dovecot \ && addgroup -g 402 dovenull \ && sed -i "s/999/99/" /etc/group \ && addgroup -g 999 sogo \ && addgroup nobody sogo \ && adduser -D -u 5000 -G vmail -h /var/vmail vmail \ && adduser -D -G dovecot -u 401 -h /dev/null -s /sbin/nologin dovecot \ && adduser -D -G dovenull -u 402 -h /dev/null -s /sbin/nologin dovenull \ && apk add --no-cache --update \ bash \ bind-tools \ findutils \ envsubst \ ca-certificates \ curl \ coreutils \ jq \ lua \ lua-cjson \ lua-socket \ lua-sql-mysql \ lua5.3-sql-mysql \ icu-data-full \ mariadb-connector-c \ lua-sec \ mariadb-dev \ glib-dev \ gcompat \ mariadb-client \ perl \ perl-dev \ perl-ntlm \ perl-cgi \ perl-crypt-openssl-rsa \ perl-utils \ perl-crypt-ssleay \ perl-data-uniqid \ perl-dbd-mysql \ perl-dbi \ perl-digest-hmac \ perl-dist-checkconflicts \ perl-encode-imaputf7 \ perl-file-copy-recursive \ perl-file-tail \ perl-io-socket-inet6 \ perl-io-gzip \ perl-io-socket-ssl \ perl-io-tee \ perl-ipc-run \ perl-json-webtoken \ perl-mail-imapclient \ perl-module-implementation \ perl-module-scandeps \ perl-net-ssleay \ perl-package-stash \ perl-package-stash-xs \ perl-par-packer \ perl-parse-recdescent \ perl-lockfile-simple \ libproc2 \ perl-readonly \ perl-regexp-common \ perl-sys-meminfo \ perl-term-readkey \ perl-test-deep \ perl-test-fatal \ perl-test-mockobject \ perl-test-mock-guard \ perl-test-pod \ perl-test-requires \ perl-test-simple \ perl-test-warn \ perl-try-tiny \ perl-unicode-string \ perl-proc-processtable \ perl-app-cpanminus \ procps \ python3 \ py3-mysqlclient \ py3-html2text \ py3-jinja2 \ py3-redis \ redis \ syslog-ng \ syslog-ng-redis \ syslog-ng-json \ supervisor \ tzdata \ wget \ dovecot \ dovecot-dev \ dovecot-lmtpd \ dovecot-lua \ dovecot-ldap \ dovecot-mysql \ dovecot-sql \ dovecot-submissiond \ dovecot-pigeonhole-plugin \ dovecot-pop3d \ dovecot-fts-flatcurve \ && arch=$(arch | sed s/aarch64/arm64/ | sed s/x86_64/amd64/) \ && wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$arch" \ && chmod +x /usr/local/bin/gosu \ && gosu nobody true COPY trim_logs.sh /usr/local/bin/trim_logs.sh COPY clean_q_aged.sh /usr/local/bin/clean_q_aged.sh COPY syslog-ng.conf /etc/syslog-ng/syslog-ng.conf COPY syslog-ng-redis_slave.conf /etc/syslog-ng/syslog-ng-redis_slave.conf COPY imapsync /usr/local/bin/imapsync COPY imapsync_runner.pl /usr/local/bin/imapsync_runner.pl COPY report-spam.sieve /usr/lib/dovecot/sieve/report-spam.sieve COPY report-ham.sieve /usr/lib/dovecot/sieve/report-ham.sieve COPY rspamd-pipe-ham /usr/lib/dovecot/sieve/rspamd-pipe-ham COPY rspamd-pipe-spam /usr/lib/dovecot/sieve/rspamd-pipe-spam COPY sa-rules.sh /usr/local/bin/sa-rules.sh COPY maildir_gc.sh /usr/local/bin/maildir_gc.sh COPY docker-entrypoint.sh / COPY supervisord.conf /etc/supervisor/supervisord.conf COPY stop-supervisor.sh /usr/local/sbin/stop-supervisor.sh COPY quarantine_notify.py /usr/local/bin/quarantine_notify.py COPY quota_notify.py /usr/local/bin/quota_notify.py COPY repl_health.sh /usr/local/bin/repl_health.sh COPY optimize-fts.sh /usr/local/bin/optimize-fts.sh ENTRYPOINT ["/docker-entrypoint.sh"] CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/supervisord.conf"] ================================================ FILE: data/Dockerfiles/dovecot/clean_q_aged.sh ================================================ #!/bin/bash source /source_env.sh MAX_AGE=$(redis-cli --raw -h redis-mailcow -a ${REDISPASS} --no-auth-warning GET Q_MAX_AGE) if [[ -z ${MAX_AGE} ]]; then echo "Max age for quarantine items not defined" exit 1 fi NUM_REGEXP='^[0-9]+$' if ! [[ ${MAX_AGE} =~ ${NUM_REGEXP} ]] ; then echo "Max age for quarantine items invalid" exit 1 fi TO_DELETE=$(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT COUNT(id) FROM quarantine WHERE created < NOW() - INTERVAL ${MAX_AGE//[!0-9]/} DAY" -BN) mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "DELETE FROM quarantine WHERE created < NOW() - INTERVAL ${MAX_AGE//[!0-9]/} DAY" echo "Deleted ${TO_DELETE} items from quarantine table (max age is ${MAX_AGE//[!0-9]/} days)" ================================================ FILE: data/Dockerfiles/dovecot/docker-entrypoint.sh ================================================ #!/bin/bash set -e # Wait for MySQL to warm-up while ! mariadb-admin status --ssl=false --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do echo "Waiting for database to come up..." sleep 2 done until dig +short mailcow.email > /dev/null; do echo "Waiting for DNS..." sleep 1 done # Do not attempt to write to slave if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then REDIS_CMDLINE="redis-cli -h ${REDIS_SLAVEOF_IP} -p ${REDIS_SLAVEOF_PORT} -a ${REDISPASS} --no-auth-warning" else REDIS_CMDLINE="redis-cli -h redis -p 6379 -a ${REDISPASS} --no-auth-warning" fi until [[ $(${REDIS_CMDLINE} PING) == "PONG" ]]; do echo "Waiting for Redis..." sleep 2 done ${REDIS_CMDLINE} SET DOVECOT_REPL_HEALTH 1 > /dev/null # Create missing directories [[ ! -d /etc/dovecot/sql/ ]] && mkdir -p /etc/dovecot/sql/ [[ ! -d /etc/dovecot/auth/ ]] && mkdir -p /etc/dovecot/auth/ [[ ! -d /etc/dovecot/conf.d/ ]] && mkdir -p /etc/dovecot/conf.d/ [[ ! -d /var/vmail/_garbage ]] && mkdir -p /var/vmail/_garbage [[ ! -d /var/vmail/sieve ]] && mkdir -p /var/vmail/sieve [[ ! -d /etc/sogo ]] && mkdir -p /etc/sogo [[ ! -d /var/volatile ]] && mkdir -p /var/volatile # Set Dovecot sql config parameters, escape " in db password DBPASS=$(echo ${DBPASS} | sed 's/"/\\"/g') # Create quota dict for Dovecot if [[ "${MASTER}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then QUOTA_TABLE=quota2 else QUOTA_TABLE=quota2replica fi cat < /etc/dovecot/sql/dovecot-dict-sql-quota.conf # Autogenerated by mailcow connect = "host=/var/run/mysqld/mysqld.sock dbname=${DBNAME} user=${DBUSER} password=${DBPASS}" map { pattern = priv/quota/storage table = ${QUOTA_TABLE} username_field = username value_field = bytes } map { pattern = priv/quota/messages table = ${QUOTA_TABLE} username_field = username value_field = messages } EOF # Create dict used for sieve pre and postfilters cat < /etc/dovecot/sql/dovecot-dict-sql-sieve_before.conf # Autogenerated by mailcow connect = "host=/var/run/mysqld/mysqld.sock dbname=${DBNAME} user=${DBUSER} password=${DBPASS}" map { pattern = priv/sieve/name/\$script_name table = sieve_before username_field = username value_field = id fields { script_name = \$script_name } } map { pattern = priv/sieve/data/\$id table = sieve_before username_field = username value_field = script_data fields { id = \$id } } EOF cat < /etc/dovecot/sql/dovecot-dict-sql-sieve_after.conf # Autogenerated by mailcow connect = "host=/var/run/mysqld/mysqld.sock dbname=${DBNAME} user=${DBUSER} password=${DBPASS}" map { pattern = priv/sieve/name/\$script_name table = sieve_after username_field = username value_field = id fields { script_name = \$script_name } } map { pattern = priv/sieve/data/\$id table = sieve_after username_field = username value_field = script_data fields { id = \$id } } EOF echo -n ${ACL_ANYONE} > /etc/dovecot/acl_anyone if [[ "${SKIP_FTS}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then echo -e "\e[33mDetecting SKIP_FTS=y... not enabling Flatcurve (FTS) then...\e[0m" echo -n 'quota acl zlib mail_crypt mail_crypt_acl mail_log notify listescape replication lazy_expunge' > /etc/dovecot/mail_plugins echo -n 'quota imap_quota imap_acl acl zlib imap_zlib imap_sieve mail_crypt mail_crypt_acl notify listescape replication mail_log' > /etc/dovecot/mail_plugins_imap echo -n 'quota sieve acl zlib mail_crypt mail_crypt_acl notify listescape replication' > /etc/dovecot/mail_plugins_lmtp else echo -e "\e[32mDetecting SKIP_FTS=n... enabling Flatcurve (FTS)\e[0m" echo -n 'quota acl zlib mail_crypt mail_crypt_acl mail_log notify fts fts_flatcurve listescape replication lazy_expunge' > /etc/dovecot/mail_plugins echo -n 'quota imap_quota imap_acl acl zlib imap_zlib imap_sieve mail_crypt mail_crypt_acl notify mail_log fts fts_flatcurve listescape replication' > /etc/dovecot/mail_plugins_imap echo -n 'quota sieve acl zlib mail_crypt mail_crypt_acl fts fts_flatcurve notify listescape replication' > /etc/dovecot/mail_plugins_lmtp fi chmod 644 /etc/dovecot/mail_plugins /etc/dovecot/mail_plugins_imap /etc/dovecot/mail_plugins_lmtp /templates/quarantine.tpl cat < /etc/dovecot/sql/dovecot-dict-sql-userdb.conf # Autogenerated by mailcow driver = mysql connect = "host=/var/run/mysqld/mysqld.sock dbname=${DBNAME} user=${DBUSER} password=${DBPASS}" user_query = SELECT CONCAT(JSON_UNQUOTE(JSON_VALUE(attributes, '$.mailbox_format')), mailbox_path_prefix, '%d/%n/${MAILDIR_SUB}:VOLATILEDIR=/var/volatile/%u:INDEX=/var/vmail_index/%u') AS mail, '%s' AS protocol, 5000 AS uid, 5000 AS gid, concat('*:bytes=', quota) AS quota_rule FROM mailbox WHERE username = '%u' AND (active = '1' OR active = '2') iterate_query = SELECT username FROM mailbox WHERE active = '1' OR active = '2'; EOF # Migrate old sieve_after file [[ -f /etc/dovecot/sieve_after ]] && mv /etc/dovecot/sieve_after /etc/dovecot/global_sieve_after # Create global sieve scripts cat /etc/dovecot/global_sieve_after > /var/vmail/sieve/global_sieve_after.sieve cat /etc/dovecot/global_sieve_before > /var/vmail/sieve/global_sieve_before.sieve # Check permissions of vmail/index/garbage directories. # Do not do this every start-up, it may take a very long time. So we use a stat check here. if [[ $(stat -c %U /var/vmail/) != "vmail" ]] ; then chown -R vmail:vmail /var/vmail ; fi if [[ $(stat -c %U /var/vmail/_garbage) != "vmail" ]] ; then chown -R vmail:vmail /var/vmail/_garbage ; fi if [[ $(stat -c %U /var/vmail_index) != "vmail" ]] ; then chown -R vmail:vmail /var/vmail_index ; fi # Cleanup random user maildirs rm -rf /var/vmail/mailcow.local/* # Cleanup PIDs [[ -f /tmp/quarantine_notify.pid ]] && rm /tmp/quarantine_notify.pid # create sni configuration echo "" > /etc/dovecot/sni.conf for cert_dir in /etc/ssl/mail/*/ ; do if [[ ! -f ${cert_dir}domains ]] || [[ ! -f ${cert_dir}cert.pem ]] || [[ ! -f ${cert_dir}key.pem ]]; then continue fi domains=($(cat ${cert_dir}domains)) for domain in ${domains[@]}; do echo 'local_name '${domain}' {' >> /etc/dovecot/sni.conf; echo ' ssl_cert = <'${cert_dir}'cert.pem' >> /etc/dovecot/sni.conf; echo ' ssl_key = <'${cert_dir}'key.pem' >> /etc/dovecot/sni.conf; echo '}' >> /etc/dovecot/sni.conf; done done # Create random master for SOGo sieve features RAND_USER=$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 16 | head -n 1) RAND_PASS=$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 24 | head -n 1) if [[ ! -z ${DOVECOT_MASTER_USER} ]] && [[ ! -z ${DOVECOT_MASTER_PASS} ]]; then RAND_USER=${DOVECOT_MASTER_USER} RAND_PASS=${DOVECOT_MASTER_PASS} fi echo ${RAND_USER}@mailcow.local:{SHA1}$(echo -n ${RAND_PASS} | sha1sum | awk '{print $1}'):::::: > /etc/dovecot/dovecot-master.passwd echo ${RAND_USER}@mailcow.local::5000:5000:::: > /etc/dovecot/dovecot-master.userdb echo ${RAND_USER}@mailcow.local:${RAND_PASS} > /etc/sogo/sieve.creds if [[ -z ${MAILDIR_SUB} ]]; then MAILDIR_SUB_SHARED= else MAILDIR_SUB_SHARED=/${MAILDIR_SUB} fi cat < /etc/dovecot/shared_namespace.conf # Autogenerated by mailcow namespace { type = shared separator = / prefix = Shared/%%u/ location = maildir:%%h${MAILDIR_SUB_SHARED}:INDEX=~${MAILDIR_SUB_SHARED}/Shared/%%u subscriptions = no list = children } EOF cat < /etc/dovecot/sogo_trusted_ip.conf # Autogenerated by mailcow remote ${IPV4_NETWORK}.248 { disable_plaintext_auth = no } EOF # Create random master Password for SOGo SSO RAND_PASS=$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 32 | head -n 1) echo -n ${RAND_PASS} > /etc/phpfpm/sogo-sso.pass cat < /etc/dovecot/sogo-sso.conf # Autogenerated by mailcow passdb { driver = static args = allow_nets=${IPV4_NETWORK}.248/32 password={plain}${RAND_PASS} } EOF # Creating additional creds file for SOGo notify crons (calendars, etc) (dummy user, sso password) echo -n ${RAND_USER}@mailcow.local:${RAND_PASS} > /etc/sogo/cron.creds if [[ "${MASTER}" =~ ^([nN][oO]|[nN])+$ ]]; then # Toggling MASTER will result in a rebuild of containers, so the quota script will be recreated cat <<'EOF' > /usr/local/bin/quota_notify.py #!/usr/bin/python3 import sys sys.exit() EOF fi # Set mail_replica for HA setups if [[ -n ${MAILCOW_REPLICA_IP} && -n ${DOVEADM_REPLICA_PORT} ]]; then cat < /etc/dovecot/mail_replica.conf # Autogenerated by mailcow mail_replica = tcp:${MAILCOW_REPLICA_IP}:${DOVEADM_REPLICA_PORT} EOF fi # Setting variables for indexer-worker inside fts.conf automatically according to mailcow.conf settings if [[ "${SKIP_FTS}" =~ ^([nN][oO]|[nN])+$ ]]; then echo -e "\e[94mConfiguring FTS Settings...\e[0m" echo -e "\e[94mSetting FTS Memory Limit (per process) to ${FTS_HEAP} MB\e[0m" sed -i "s/vsz_limit\s*=\s*[0-9]*\s*MB*/vsz_limit=${FTS_HEAP} MB/" /etc/dovecot/conf.d/fts.conf echo -e "\e[94mSetting FTS Process Limit to ${FTS_PROCS}\e[0m" sed -i "s/process_limit\s*=\s*[0-9]*/process_limit=${FTS_PROCS}/" /etc/dovecot/conf.d/fts.conf fi # 401 is user dovecot if [[ ! -s /mail_crypt/ecprivkey.pem || ! -s /mail_crypt/ecpubkey.pem ]]; then openssl ecparam -name prime256v1 -genkey | openssl pkey -out /mail_crypt/ecprivkey.pem openssl pkey -in /mail_crypt/ecprivkey.pem -pubout -out /mail_crypt/ecpubkey.pem chown 401 /mail_crypt/ecprivkey.pem /mail_crypt/ecpubkey.pem else chown 401 /mail_crypt/ecprivkey.pem /mail_crypt/ecpubkey.pem fi # Fix OpenSSL 3.X TLS1.0, 1.1 support (https://community.mailcow.email/d/4062-hi-all/20) if grep -qE 'ssl_min_protocol\s*=\s*(TLSv1|TLSv1\.1)\s*$' /etc/dovecot/dovecot.conf /etc/dovecot/extra.conf; then sed -i '/\[openssl_init\]/a ssl_conf = ssl_configuration' /etc/ssl/openssl.cnf echo "[ssl_configuration]" >> /etc/ssl/openssl.cnf echo "system_default = tls_system_default" >> /etc/ssl/openssl.cnf echo "[tls_system_default]" >> /etc/ssl/openssl.cnf echo "MinProtocol = TLSv1" >> /etc/ssl/openssl.cnf echo "CipherString = DEFAULT@SECLEVEL=0" >> /etc/ssl/openssl.cnf fi # Compile sieve scripts sievec /var/vmail/sieve/global_sieve_before.sieve sievec /var/vmail/sieve/global_sieve_after.sieve sievec /usr/lib/dovecot/sieve/report-spam.sieve sievec /usr/lib/dovecot/sieve/report-ham.sieve # Fix permissions chown root:root /etc/dovecot/sql/*.conf chown root:dovecot /etc/dovecot/sql/dovecot-dict-sql-sieve* /etc/dovecot/sql/dovecot-dict-sql-quota* /etc/dovecot/auth/passwd-verify.lua chmod 640 /etc/dovecot/sql/*.conf /etc/dovecot/auth/passwd-verify.lua chown -R vmail:vmail /var/vmail/sieve chown -R vmail:vmail /var/volatile chown -R vmail:vmail /var/vmail_index adduser vmail tty chmod g+rw /dev/console chown root:tty /dev/console chmod +x /usr/lib/dovecot/sieve/rspamd-pipe-ham \ /usr/lib/dovecot/sieve/rspamd-pipe-spam \ /usr/local/bin/imapsync_runner.pl \ /usr/local/bin/imapsync \ /usr/local/bin/trim_logs.sh \ /usr/local/bin/sa-rules.sh \ /usr/local/bin/clean_q_aged.sh \ /usr/local/bin/maildir_gc.sh \ /usr/local/sbin/stop-supervisor.sh \ /usr/local/bin/quota_notify.py \ /usr/local/bin/repl_health.sh \ /usr/local/bin/optimize-fts.sh # Prepare environment file for cronjobs printenv | sed 's/^\(.*\)$/export \1/g' > /source_env.sh # Clean old PID if any [[ -f /var/run/dovecot/master.pid ]] && rm /var/run/dovecot/master.pid # Clean stopped imapsync jobs rm -f /tmp/imapsync_busy.lock IMAPSYNC_TABLE=$(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SHOW TABLES LIKE 'imapsync'" -Bs) [[ ! -z ${IMAPSYNC_TABLE} ]] && mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "UPDATE imapsync SET is_running='0'" # Envsubst maildir_gc echo "$(envsubst < /usr/local/bin/maildir_gc.sh)" > /usr/local/bin/maildir_gc.sh # GUID generation while [[ ${VERSIONS_OK} != 'OK' ]]; do if [[ ! -z $(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "SELECT 'OK' FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = \"${DBNAME}\" AND TABLE_NAME = 'versions'") ]]; then VERSIONS_OK=OK else echo "Waiting for versions table to be created..." sleep 3 fi done PUBKEY_MCRYPT=$(doveconf -P 2> /dev/null | grep -i mail_crypt_global_public_key | cut -d '<' -f2) if [ -f ${PUBKEY_MCRYPT} ]; then GUID=$(cat <(echo ${MAILCOW_HOSTNAME}) /mail_crypt/ecpubkey.pem | sha256sum | cut -d ' ' -f1 | tr -cd "[a-fA-F0-9.:/] ") if [ ${#GUID} -eq 64 ]; then mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF REPLACE INTO versions (application, version) VALUES ("GUID", "${GUID}"); EOF else mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF REPLACE INTO versions (application, version) VALUES ("GUID", "INVALID"); EOF fi fi # Collect SA rules once now /usr/local/bin/sa-rules.sh # Run hooks for file in /hooks/*; do if [ -x "${file}" ]; then echo "Running hook ${file}" "${file}" fi done # For some strange, unknown and stupid reason, Dovecot may run into a race condition, when this file is not touched before it is read by dovecot/auth # May be related to something inside Docker, I seriously don't know touch /etc/dovecot/auth/passwd-verify.lua if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then cp /etc/syslog-ng/syslog-ng-redis_slave.conf /etc/syslog-ng/syslog-ng.conf fi exec "$@" ================================================ FILE: data/Dockerfiles/dovecot/imapsync ================================================ #!/usr/bin/env perl # $Id: imapsync,v 2.178 2022/01/12 21:28:37 gilles Exp gilles $ # structure # pod documentation # use pragmas # main program # global variables initialization # get_options( ) ; # default values # folder loop # subroutines # sub usage # pod documentation =pod =head1 NAME imapsync - Email IMAP tool for syncing, copying, migrating and archiving email mailboxes between two imap servers, one way, and without duplicates. =head1 VERSION This documentation refers to Imapsync $Revision: 2.178 $ =head1 USAGE To synchronize the source imap account "test1" on server "test1.lamiral.info" with password "secret1" to the destination imap account "test2" on server "test2.lamiral.info" with password "secret2" do: imapsync \ --host1 test1.lamiral.info --user1 test1 --password1 secret1 \ --host2 test2.lamiral.info --user2 test2 --password2 secret2 =head1 DESCRIPTION We sometimes need to transfer mailboxes from one imap server to one another. Imapsync command is a tool allowing incremental and recursive imap transfers from one mailbox to another. If you don't understand the previous sentence, it's normal, it's pedantic computer-oriented jargon. All folders are transferred, recursively, meaning the whole folder hierarchy is taken, all messages in them, and all message flags (\Seen \Answered \Flagged etc.) are synced too. Imapsync reduces the amount of data transferred by not transferring a given message if it already resides on the destination side. Messages that are on the destination side but not on the source side stay as they are. See the --delete2 option to have strict sync and delete them. How imapsync know a message is already on both sides? Same specific headers and the transfer is done only once. By default, the identification headers are "Message-Id:" and "Received:" lines but this choice can be changed with the --useheader option, most often a duplicate problem is solved by using --useheader "Message-Id" All flags are preserved, unread messages will stay unread, read ones will stay read, deleted will stay deleted. In the IMAP protocol, a deleted message is not really deleted, it is marked \Deleted and can be undelete. Real destruction comes with the EXPUNGE or UIDEXPUNGE IMAP commands. You can abort the transfer at any time and restart it later, imapsync works well with bad connections and interruptions, by design. On a terminal hit Ctr-c twice within two seconds to abort the program. Hit Ctr-c just once makes imapsync reconnect to both imap servers. How do you know the sync is finished and well done? When imapsync ends by itself it mentions it with lines like those: Exiting with return value 0 (EX_OK: successful termination) 0/50 nb_errors/max_errors PID 301 Removing pidfile /tmp/imapsync.pid Log file is LOG_imapsync/2020_11_17_15_59_22_761_test1_test2.txt ( to change it, use --logfile filepath ; or use --nolog to turn off logging ) If you don't have those lines it means that either the sync process is still running (or eventually hanging indefinitely) or that it ended without a whisper, a strong kill -9 on Linux for example. If you have those final lines then it means the sync process is properly finished. It may have encountered problems though. A good synchronization is mentioned by some lines above the last ones, especially those three lines: The sync looks good, all 1745 identified messages in host1 are on host2. There is no unidentified message on host1. Detected 0 errors A classical scenario is synchronizing a mailbox B from another mailbox A where you just want to keep a strict copy of A in B. Strict meaning all messages in A will be in B but no more. For this, option --delete2 can be used, it deletes messages in the host2 folder B that are not in the host1 folder A. If you also need to destroy host2 folders that are not in host1 then use --delete2folders. See also --delete2foldersonly and --delete2foldersbutnot to set up exceptions on folders to destroy. INBOX will never be destroyed, it's a mandatory folder in IMAP so imapsync doesn't even try to remove it. A different scenario is to delete the messages from the source mailbox after a successful transfer, it can be a good feature when migrating mailboxes since messages will be only on one side. The source account will only have messages that are not on the destination yet, ie, messages that arrived after a sync or that failed to be transferred. In that case, use the --delete1 option. Option --delete1 implies also the option --expunge1 so all messages marked deleted on host1 will be deleted. In IMAP protocol deleting a message does not delete it, it marks it with the flag \Deleted, allowing an undelete. Expunging a folder removes, definitively, all the messages marked as \Deleted in this folder. You can also decide to remove empty folders once all of their messages have been transferred. Add --delete1emptyfolders to obtain this behavior. Imapsync is not adequate for maintaining two active imap accounts in synchronization when the user plays independently on both sides. Use offlineimap (written by John Goerzen) or mbsync (written by Michael R. Elkins) for a 2 ways synchronization. =head1 OPTIONS usage: imapsync [options] The standard options are the six values forming the credentials. Three values on each side are needed in order to login into the IMAP servers. These six values are a hostname, a username, and a password, two times. Conventions used in the following descriptions of the options: str means string int means integer number flo means float number reg means regular expression cmd means command --dry : Makes imapsync doing nothing for real; it just print what would be done without --dry. =head2 OPTIONS/credentials --host1 str : Source or "from" imap server. --port1 int : Port to connect on host1. Optional since default ports are the well known ports imap/143 or imaps/993. --user1 str : User to login on host1. --password1 str : Password of user1. --host2 str : "destination" imap server. --port2 int : Port to connect on host2. Optional --user2 str : User to login on host2. --password2 str : Password of user2. --showpasswords : Shows passwords on output instead of "MASKED". Useful to restart a complete run by just reading the command line used in the log, or to debug passwords. It's not a secure practice at all! --passfile1 str : Password file for the user1. It must contain the password on the first line. This option avoids showing the password on the command line like --password1 does. --passfile2 str : Password file for the user2. You can also pass the passwords in the environment variables IMAPSYNC_PASSWORD1 and IMAPSYNC_PASSWORD2. If you don't pass the user1 password via --password1 nor --passfile1 nor $IMAPSYNC_PASSWORD1 then imapsync will prompt to enter the password on the terminal. Same thing for user2 password. =head2 OPTIONS/encryption --nossl1 : Do not use a SSL connection on host1. --ssl1 : Use a SSL connection on host1. On by default if possible. --nossl2 : Do not use a SSL connection on host2. --ssl2 : Use a SSL connection on host2. On by default if possible. --notls1 : Do not use a TLS connection on host1. --tls1 : Use a TLS connection on host1. On by default if possible. --notls2 : Do not use a TLS connection on host2. --tls2 : Use a TLS connection on host2. On by default if possible. --debugssl int : SSL debug mode from 0 to 4. --sslargs1 str : Pass any ssl parameter for host1 ssl or tls connection. Example: --sslargs1 SSL_verify_mode=1 --sslargs1 SSL_version=SSLv3 See all possibilities in the new() method of IO::Socket::SSL http://search.cpan.org/perldoc?IO::Socket::SSL#Description_Of_Methods --sslargs2 str : Pass any ssl parameter for host2 ssl or tls connection. See --sslargs1 =head2 OPTIONS/authentication --authmech1 str : Auth mechanism to use with host1: PLAIN, LOGIN, CRAM-MD5 etc. Use UPPERCASE. --authmech2 str : Auth mechanism to use with host2. See --authmech1 --authuser1 str : User to auth with on host1 (admin user). Avoid using --authmech1 SOMETHING with --authuser1. --authuser2 str : User to auth with on host2 (admin user). --proxyauth1 : Use proxyauth on host1. Requires --authuser1. Required by Sun/iPlanet/Netscape IMAP servers to be able to use an administrative user. --proxyauth2 : Use proxyauth on host2. Requires --authuser2. --authmd51 : Use MD5 authentication for host1. --authmd52 : Use MD5 authentication for host2. --domain1 str : Domain on host1 (NTLM authentication). --domain2 str : Domain on host2 (NTLM authentication). --oauthaccesstoken1 str : The access token to authenticate with OAUTH2. It will be combined with the --user1 value to form the string to pass with XOAUTH2 authentication. The password given by --password1 or --passfile1 is ignored. Instead of the access token itself, the value can be a file containing the access token on the first line. If the value is a file, imapsync reads its first line and take this line as the access token. The advantage of the file is that if the access token changes then imapsync can read it again when it needs to reconnect during a run. --oauthaccesstoken2 str : same thing as --oauthaccesstoken1 --oauthdirect1 str : The direct string to pass with XOAUTH2 authentication. The password given by --password1 or --passfile1 and the user given by --user1 are ignored. --oauthdirect2 str : same thing as oauthdirect1 =head2 OPTIONS/folders --folder str : Sync this folder. --folder str : and this one, etc. --folderrec str : Sync this folder recursively. --folderrec str : and this one, etc. --folderfirst str : Sync this folder first. Ex. --folderfirst "INBOX" --folderfirst str : then this one, etc. --folderlast str : Sync this folder last. --folderlast "[Gmail]/All Mail" --folderlast str : then this one, etc. --nomixfolders : Do not merge folders when host1 is case-sensitive while host2 is not (like Exchange). Only the first similar folder is synced (example: with folders "Sent", "SENT" and "sent" on host1 only "Sent" will be synced to host2). --skipemptyfolders : Empty host1 folders are not created on host2. --include reg : Sync folders matching this regular expression --include reg : or this one, etc. If both --include --exclude options are used, then include is done before. --exclude reg : Skips folders matching this regular expression Several folders to avoid: --exclude 'fold1|fold2|f3' skips fold1, fold2 and f3. --exclude reg : or this one, etc. --automap : guesses folders mapping, for folders well known as "Sent", "Junk", "Drafts", "All", "Archive", "Flagged". --f1f2 str1=str2 : Force folder str1 to be synced to str2, --f1f2 overrides --automap and --regextrans2. Use several --f1f2 options to map several folders. Option --f1f2 is a one to one only folder mapping, str1 and str2 have to be full path folder names. --subfolder2 str : Syncs the whole host1 folders hierarchy under the host2 folder named str. It does it internally by adding three --regextrans2 options before all others. Add --debug to see what's really going on. --subfolder1 str : Syncs the host1 folders hierarchy which is under folder str to the root hierarchy of host2. It's the couterpart of a sync done by --subfolder2 when doing it in the reverse order. Backup/Restore scenario: Use --subfolder2 str for a backup to the folder str on host2. Then use --subfolder1 str for restoring from the folder str, after inverting host1/host2 user1/user2 values. --subscribed : Transfers subscribed folders. --subscribe : Subscribe to the folders transferred on the host2 that are subscribed on host1. On by default. --subscribeall : Subscribe to the folders transferred on the host2 even if they are not subscribed on host1. --prefix1 str : Remove prefix str to all destination folders, usually "INBOX." or "INBOX/" or an empty string "". imapsync guesses the prefix if host1 imap server does not have NAMESPACE capability. So this option should not be used most of the time. --prefix2 str : Add prefix to all host2 folders. See --prefix1 --sep1 str : Host1 separator. This option should not be used most of the time. Imapsync gets the separator from the server itself, by using NAMESPACE, or it tries to guess it from the folders listing (it counts characters / . \\ \ in folder names and choose the more frequent, or finally / if nothing is found. --sep2 str : Host2 separator. See --sep1 --regextrans2 reg : Apply the whole regex to each destination folders. --regextrans2 reg : and this one. etc. When you play with the --regextrans2 option, first add also the safe options --dry --justfolders Then, when happy, remove --dry for a run, then remove --justfolders for the next ones. Have in mind that --regextrans2 is applied after the automatic prefix and separator inversion. For examples see: https://imapsync.lamiral.info/FAQ.d/FAQ.Folders_Mapping.txt =head2 OPTIONS/folders sizes --nofoldersizes : Do not calculate the size of each folder at the beginning of the sync. Default is to calculate them. --nofoldersizesatend: Do not calculate the size of each folder at the end of the sync. Default is to calculate them. --justfoldersizes : Exit after having printed the initial folder sizes. =head2 OPTIONS/tmp --tmpdir str : Where to store temporary files and subdirectories. Will be created if it doesn't exist. Default is system specific, Unix is /tmp but /tmp is often too small and deleted at reboot. --tmpdir /var/tmp should be better. --pidfile str : The file where imapsync pid is written, it can be dirname/filename complete path. The default name is imapsync.pid in tmpdir. --pidfilelocking : Abort if pidfile already exists. Useful to avoid concurrent transfers on the same mailbox. =head2 OPTIONS/log --nolog : Turn off logging on file --logfile str : Change the default log filename (can be dirname/filename). --logdir str : Change the default log directory. Default is LOG_imapsync/ The default logfile name is for example LOG_imapsync/2019_12_22_23_57_59_532_user1_user2.txt where: 2019_12_22_23_57_59_532 is nearly the date of the start YYYY_MM_DD_HH_MM_SS_mmm year_month_day_hour_minute_seconde_millisecond and user1 user2 are the --user1 --user2 values. =head2 OPTIONS/messages --skipmess reg : Skips messages matching the regex. Example: 'm/[\x80-\xff]/' # to avoid 8bits messages. --skipmess is applied before --regexmess --skipmess reg : or this one, etc. --skipcrossduplicates : Avoid copying messages that are already copied in another folder, good from Gmail to XYZ when XYZ is not also Gmail. Activated with --gmail1 unless --noskipcrossduplicates --debugcrossduplicates : Prints which messages (UIDs) are skipped with --skipcrossduplicates and in what other folders they are. --pipemess cmd : Apply this cmd command to each message content before the copy. --pipemess cmd : and this one, etc. With several --pipemess, the output of each cmd command (STDOUT) is given to the input (STDIN) of the next command. For example, --pipemess cmd1 --pipemess cmd2 --pipemess cmd3 is like a Unix pipe: "cat message | cmd1 | cmd2 | cmd3" --disarmreadreceipts : Disarms read receipts (host2 Exchange issue) --regexmess reg : Apply the whole regex to each message before transfer. Example: 's/\000/ /g' # to replace null characters by spaces. --regexmess reg : and this one, etc. --truncmess int : truncates messages when their size exceed the int value, specified in bytes. Good to sync too big messages or to "suppress" attachments. Have in mind that this way, messages become uncoherent somehow. =head2 OPTIONS/labels Gmail present labels as folders in imap. Imapsync can accelerate the sync by syncing X-GM-LABELS, it will avoid to transfer messages when they are already on host2 in another folder. --synclabels : Syncs also Gmail labels when a message is copied to host2. Activated by default with --gmail1 --gmail2 unless --nosynclabels is added. --resynclabels : Resyncs Gmail labels when a message is already on host2. Activated by default with --gmail1 --gmail2 unless --noresynclabels is added. For Gmail syncs, see also: https://imapsync.lamiral.info/FAQ.d/FAQ.Gmail.txt =head2 OPTIONS/flags If you encounter flag problems see also: https://imapsync.lamiral.info/FAQ.d/FAQ.Flags.txt --regexflag reg : Apply the whole regex to each flags list. Example: 's/"Junk"//g' # to remove "Junk" flag. --regexflag reg : then this one, etc. --resyncflags : Resync flags for already transferred messages. On by default. --noresyncflags : Do not resync flags for already transferred messages. May be useful when a user has already started to play with its host2 account. --filterbuggyflags : Filter flags known to be buggy and generators of errors "BAD Invalid system flag" or "NO APPEND Invalid flag list". =head2 OPTIONS/deletions --delete1 : Deletes messages on host1 server after a successful transfer. Option --delete1 has the following behavior: it marks messages as deleted with the IMAP flag \Deleted, then messages are really deleted with an EXPUNGE IMAP command. If expunging after each message slows down too much the sync then use --noexpungeaftereach to speed up, expunging will then be done only twice per folder, one at the beginning and one at the end of a folder sync. --expunge1 : Expunge messages on host1 just before syncing a folder. Expunge is done per folder. Expunge aims is to really delete messages marked deleted. An expunge is also done after each message copied if option --delete1 is set (unless --noexpungeaftereach). --noexpunge1 : Do not expunge messages on host1. --delete1emptyfolders : Deletes empty folders on host1, INBOX excepted. Useful with --delete1 since what remains on host1 is only what failed to be synced. --delete2 : Delete messages in the host2 account that are not in the host1 account. Useful for backup or pre-sync. --delete2 implies --uidexpunge2 --delete2duplicates : Deletes messages in host2 that are duplicates in host2. Works only without --useuid since duplicates are detected with an header part of each message. NB: --delete2duplicates is far less violent than --delete2 since it removes only duplicates. --delete2folders : Delete folders in host2 that are not in host1. For safety, first try it like this, it is safe: --delete2folders --dry --justfolders --nofoldersizes and see what folders will be deleted. --delete2foldersonly reg : Delete only folders matching the regex reg. Example: --delete2foldersonly "/^Junk$|^INBOX.Junk$/" This option activates --delete2folders --delete2foldersbutnot reg : Do not delete folders matching the regex rex. Example: --delete2foldersbutnot "/Tasks$|Contacts$|Foo$/" This option activates --delete2folders --noexpunge2 : Do not expunge messages on host2. --nouidexpunge2 : Do not uidexpunge messages on the host2 account that are not on the host1 account. =head2 OPTIONS/dates If you encounter problems with dates, see also: https://imapsync.lamiral.info/FAQ.d/FAQ.Dates.txt --syncinternaldates : Sets the internal dates on host2 as the same as host1. Turned on by default. Internal date is the date a message arrived on a host (Unix mtime usually). --idatefromheader : Sets the internal dates on host2 as same as the ones in "Date:" headers. =head2 OPTIONS/message selection --maxsize int : Skip messages larger (or equal) than int bytes --minsize int : Skip messages smaller (or equal) than int bytes --maxage int : Skip messages older than int days. final stats (skipped) don't count older messages see also --minage --minage int : Skip messages newer than int days. final stats (skipped) don't count newer messages You can do (+ zone are the messages selected): past|----maxage+++++++++++++++>now past|+++++++++++++++minage---->now past|----maxage+++++minage---->now (intersection) past|++++minage-----maxage++++>now (union) --search str : Selects only messages returned by this IMAP SEARCH command. Applied on both sides. For a complete set of what can be search see https://imapsync.lamiral.info/FAQ.d/FAQ.Messages_Selection.txt --search1 str : Same as --search but for selecting host1 messages only. --search2 str : Same as --search but for selecting host2 messages only. So --search CRIT equals --search1 CRIT --search2 CRIT --noabletosearch : Makes --minage and --maxage options use the internal dates given by a FETCH imap command instead of the "Date:" header. Internal date is the arrival date in the mailbox. --noabletosearch equals --noabletosearch1 --noabletosearch2 --noabletosearch1 : Like --noabletosearch but for host1 only. --noabletosearch2 : Like --noabletosearch but for host2 only. --maxlinelength int : skip messages with a line length longer than int bytes. RFC 2822 says it must be no more than 1000 bytes but real life servers and email clients do more. --useheader str : Use this header to compare messages on both sides. Example: "Message-Id" or "Received" or "Date". --useheader str and this one, etc. --syncduplicates : Sync also duplicates. Off by default. --usecache : Use cache to speed up next syncs. Off by default. --nousecache : Do not use cache. Caveat: --useuid --nousecache creates duplicates on multiple runs. --useuid : Use UIDs instead of headers as a criterion to recognize messages. Option --usecache is then implied unless --nousecache is used. =head2 OPTIONS/miscellaneous --syncacls : Synchronizes acls (Access Control Lists). Acls in IMAP are not standardized, be careful since one acl code on one side may signify something else on the other one. --nosyncacls : Does not synchronize acls. This is the default. --addheader : When a message has no headers to be identified, --addheader adds a "Message-Id" header, like "Message-Id: 12345@imapsync", where 12345 is the imap UID of the message on the host1 folder. Useful to sync folders "Sent" or "Draft". =head2 OPTIONS/debugging --debug : Debug mode. --debugfolders : Debug mode for the folders part only. --debugcontent : Debug content of the messages transferred. Huge output. --debugflags : Debug mode for flags. --debugimap1 : IMAP debug mode for host1. Very verbose. --debugimap2 : IMAP debug mode for host2. Very verbose. --debugimap : IMAP debug mode for host1 and host2. Twice very verbose. --debugmemory : Debug mode showing memory consumption after each copy. --errorsmax int : Exit when int number of errors is reached. Default is 50. --tests : Run local non-regression tests. Exit code 0 means all ok. --testslive : Run a live test with test1.lamiral.info imap server. Useful to check the basics. Needs internet connection. --testslive6 : Run a live test with ks6ipv6.lamiral.info imap server. Useful to check the ipv6 connectivity. Needs internet. =head2 OPTIONS/specific --gmail1 : sets --host1 to Gmail and other options. See FAQ.Gmail.txt --gmail2 : sets --host2 to Gmail and other options. See FAQ.Gmail.txt --office1 : sets --host1 to Office365 and other options. See FAQ.Office365.txt --office2 : sets --host2 to Office365 and other options. See FAQ.Office365.txt --exchange1 : sets options for Exchange. See FAQ.Exchange.txt --exchange2 : sets options for Exchange. See FAQ.Exchange.txt --domino1 : sets options for Domino. See FAQ.Domino.txt --domino2 : sets options for Domino. See FAQ.Domino.txt =head2 OPTIONS/behavior --timeout1 flo : Connection timeout in seconds for host1. Default is 120 and 0 means no timeout at all. --timeout2 flo : Connection timeout in seconds for host2. Default is 120 and 0 means no timeout at all. Caveat, under CGI context, you may encounter a timeout from the webserver, killing imapsync and the imap connexions. See the document INSTALL.OnlineUI.txt and search for "Timeout" for how to deal with this issue. --keepalive1 : https://metacpan.org/pod/Mail::IMAPClient#Keepalive Some firewalls and network gears like to timeout connections prematurely if the connection sits idle. This option enables SO_KEEPALIVE on the host1 socket. --keepalive1 is on by default since imapsync release 2.169 Use --nokeepalive1 to disable it. --keepalive2 : Same as --keepalive2 but for host2. Use --nokeepalive2 to disable it. --maxmessagespersecond flo : limits the average number of messages transferred per second. --maxbytespersecond int : limits the average transfer rate per second. --maxbytesafter int : starts --maxbytespersecond limitation only after --maxbytesafter amount of data transferred. --maxsleep flo : do not sleep more than int seconds. On by default, 2 seconds max, like --maxsleep 2 --abort : terminates a previous call still running. It uses the pidfile to know what process to abort. --exitwhenover int : Stop syncing and exits when int total bytes transferred is reached. --version : Print only the software version. --noreleasecheck : Do not check for any new imapsync release. --releasecheck : Check for new imapsync release. it's an http request to http://imapsync.lamiral.info/prj/imapsync/VERSION --noid : Do not send/receive IMAP "ID" command to imap servers. --justconnect : Just connect to both servers and print useful information. Need only --host1 and --host2 options. Obsolete since "imapsync --host1 imaphost" alone implies --justconnect --justlogin : Just login to both host1 and host2 with users credentials, then exit. --justfolders : Do only things about folders (ignore messages). --help : print this help. Example: to synchronize imap account "test1" on "test1.lamiral.info" to imap account "test2" on "test2.lamiral.info" with test1 password "secret1" and test2 password "secret2" imapsync \ --host1 test1.lamiral.info --user1 test1 --password1 secret1 \ --host2 test2.lamiral.info --user2 test2 --password2 secret2 =cut # comment =pod =head1 SECURITY You can use --passfile1 instead of --password1 to mention the password since it is safer. With --password1 option, on Linux, any user on your host can see the password by using the 'ps auxwwww' command. Using a variable (like IMAPSYNC_PASSWORD1) is also dangerous because of the 'ps auxwwwwe' command. So, saving the password in a well protected file (600 or rw-------) is the best solution. Imapsync activates ssl or tls encryption by default, if possible. What detailed behavior is under this "if possible"? Imapsync activates ssl if the well known port imaps port (993) is open on the imap servers. If the imaps port is closed then it open a normal (clear) connection on port 143 but it looks for TLS support in the CAPABILITY list of the servers. If TLS is supported then imapsync goes to encryption with STARTTLS. If the automatic ssl and the tls detections fail then imapsync will not protect against sniffing activities on the network, especially for passwords. If you want to force ssl or tls just use --ssl1 --ssl2 or --tls1 --tls2 See also the document FAQ.Security.txt in the FAQ.d/ directory or at https://imapsync.lamiral.info/FAQ.d/FAQ.Security.txt =head1 EXIT STATUS Imapsync will exit with a 0 status (return code) if everything went good. Otherwise, it exits with a non-zero status. That's classical Unix behavior. Here is the list of the exit code values (an integer between 0 and 255). In Bourne Shells, this exit code value can be retrieved within the variable value "$?" if you read it just after the imapsync call. The names reflect their meaning: =for comment egrep '^Readonly my.*\$EX' imapsync | egrep -o 'EX.*' | sed 's_^_ _' EX_OK => 0 ; #/* successful termination */ EX_USAGE => 64 ; #/* command line usage error */ EX_NOINPUT => 66 ; #/* cannot open input */ EX_UNAVAILABLE => 69 ; #/* service unavailable */ EX_SOFTWARE => 70 ; #/* internal software error */ EXIT_CATCH_ALL => 1 ; # Any other error EXIT_BY_SIGNAL => 6 ; # Should be 128+n where n is the sig_num EXIT_BY_FILE => 7 ; EXIT_PID_FILE_ERROR => 8 ; EXIT_CONNECTION_FAILURE => 10 ; EXIT_TLS_FAILURE => 12 ; EXIT_AUTHENTICATION_FAILURE => 16 ; EXIT_SUBFOLDER1_NO_EXISTS => 21 ; EXIT_WITH_ERRORS => 111 ; EXIT_WITH_ERRORS_MAX => 112 ; EXIT_OVERQUOTA => 113 ; EXIT_ERR_APPEND => 114 ; EXIT_ERR_FETCH => 115 ; EXIT_ERR_CREATE => 116 ; EXIT_ERR_SELECT => 117 ; EXIT_TRANSFER_EXCEEDED => 118 ; EXIT_ERR_APPEND_VIRUS => 119 ; EXIT_TESTS_FAILED => 254 ; # Like Test::More API EXIT_CONNECTION_FAILURE_HOST1 => 101 ; EXIT_CONNECTION_FAILURE_HOST2 => 102 ; EXIT_AUTHENTICATION_FAILURE_USER1 => 161 ; EXIT_AUTHENTICATION_FAILURE_USER2 => 162 ; =head1 LICENSE AND COPYRIGHT Imapsync is free, open, public but not always gratis software cover by the NOLIMIT Public License, now called NLPL. See the LICENSE file included in the distribution or just read this simple sentence as it IS the licence text: "No limits to do anything with this work and this license." In case it is not long enough, I repeat: "No limits to do anything with this work and this license." Look at https://imapsync.lamiral.info/LICENSE =head1 AUTHOR Gilles LAMIRAL Good feedback is always welcome. Bad feedback is very often welcome. Gilles LAMIRAL earns his living by writing, installing, configuring and sometimes teaching free, open and often gratis software. Imapsync used to be "always gratis" but now it is only "often gratis" because imapsync is sold by its author, your servitor, a good way to maintain and support free open public software tools over decades. =head1 BUGS AND LIMITATIONS See https://imapsync.lamiral.info/FAQ.d/FAQ.Reporting_Bugs.txt =head1 IMAP SERVERS supported See https://imapsync.lamiral.info/S/imapservers.shtml =head1 HUGE MIGRATION If you have many mailboxes to migrate think about a little shell program. Write a file called file.txt (for example) containing users and passwords. The separator used in this example is ';' The file.txt file contains: user001_1;password001_1;user001_2;password001_2 user002_1;password002_1;user002_2;password002_2 user003_1;password003_1;user003_2;password003_2 user004_1;password004_1;user004_2;password004_2 user005_1;password005_1;user005_2;password005_2 ... On Unix the shell program can be: { while IFS=';' read u1 p1 u2 p2; do imapsync --host1 imap.side1.org --user1 "$u1" --password1 "$p1" \ --host2 imap.side2.org --user2 "$u2" --password2 "$p2" ... done ; } < file.txt On Windows the batch program can be: FOR /F "tokens=1,2,3,4 delims=; eol=#" %%G IN (file.txt) DO imapsync ^ --host1 imap.side1.org --user1 %%G --password1 %%H ^ --host2 imap.side2.org --user2 %%I --password2 %%J ... The ... have to be replaced by nothing or any imapsync option. Welcome in shell or batch programming ! You will find already written scripts at https://imapsync.lamiral.info/examples/ =head1 INSTALL Imapsync works under any Unix with Perl. Imapsync works under most Windows (2000, XP, Vista, Seven, Eight, Ten and all Server releases 2000, 2003, 2008 and R2, 2012 and R2, 2016) as a standalone binary software called imapsync.exe, usually launched from a batch file in order to avoid always typing the options. There is also a 32bit binary called imapsync_32bit.exe Imapsync works under OS X as a standalone binary software called imapsync_bin_Darwin Purchase latest imapsync at https://imapsync.lamiral.info/ You'll receive a link to a compressed tarball called imapsync-x.xx.tgz where x.xx is the version number. Untar the tarball where you want (on Unix): tar xzvf imapsync-x.xx.tgz Go into the directory imapsync-x.xx and read the INSTALL file. As mentioned at https://imapsync.lamiral.info/#install the INSTALL file can also be found at https://imapsync.lamiral.info/INSTALL.d/INSTALL.ANY.txt It is now split in several files for each system https://imapsync.lamiral.info/INSTALL.d/ =head1 CONFIGURATION There is no specific configuration file for imapsync, everything is specified by the command line parameters and the default behavior. =head1 HACKING Feel free to hack imapsync as the NOLIMIT license permits it. =head1 SIMILAR SOFTWARE See also https://imapsync.lamiral.info/S/external.shtml for a better up to date list. List verified on Friday July 1, 2021. imapsync: https://github.com/imapsync/imapsync (this is an imapsync copy, sometimes delayed, with --noreleasecheck by default since release 1.592, 2014/05/22) imap_tools: https://web.archive.org/web/20161228145952/http://www.athensfbc.com/imap_tools/. The imap_tools code is now at https://github.com/andrewnimmo/rick-sanders-imap-tools imaputils: https://github.com/mtsatsenko/imaputils (very old imap_tools fork) Doveadm-Sync: https://wiki2.dovecot.org/Tools/Doveadm/Sync ( Dovecot sync tool ) davmail: http://davmail.sourceforge.net/ offlineimap: http://offlineimap.org/ fdm: https://github.com/nicm/fdm mbsync: http://isync.sourceforge.net/ mailsync: http://mailsync.sourceforge.net/ mailutil: https://www.washington.edu/imap/ part of the UW IMAP toolkit. (well, seems abandoned now) imaprepl: https://bl0rg.net/software/ http://freecode.com/projects/imap-repl/ imapcopy (Pascal): http://www.ardiehl.de/imapcopy/ imapcopy (Java): https://code.google.com/archive/p/imapcopy/ imapsize: http://www.broobles.com/imapsize/ migrationtool: http://sourceforge.net/projects/migrationtool/ imapmigrate: http://sourceforge.net/projects/cyrus-utils/ larch: https://github.com/rgrove/larch (derived from wonko_imapsync, good at Gmail) wonko_imapsync: http://wonko.com/article/554 (superseded by larch) pop2imap: http://www.linux-france.org/prj/pop2imap/ (I wrote that too) exchange-away: http://exchange-away.sourceforge.net/ SyncBackPro: http://www.2brightsparks.com/syncback/sbpro.html ImapSyncClient: https://github.com/ridaamirini/ImapSyncClient MailStore: https://www.mailstore.com/en/products/mailstore-home/ mnIMAPSync: https://github.com/manusa/mnIMAPSync imap-upload: http://imap-upload.sourceforge.net/ (A tool for uploading a local mbox file to IMAP4 server) imapbackup: https://github.com/rcarmo/imapbackup (A Python script for incremental backups of IMAP mailboxes) BitRecover email-backup 99 USD, 299 USD https://www.bitrecover.com/email-backup/. ImportExportTools: https://addons.thunderbird.net/en-us/thunderbird/addon/importexporttools/ ImportExportTools for Mozilla Thunderbird by Paolo Kaosmos. ImportExportTools does not do IMAP. rximapmail: https://sourceforge.net/projects/rximapmail/ CodeTwo: https://www.codetwo.com/ but CodeTwo does imap source to Office365 only. =head1 HISTORY I initially wrote imapsync in July 2001 because an enterprise, called BaSystemes, paid me to install a new imap server without losing huge old mailboxes located in a far away remote imap server, accessible by an often broken low-bandwidth ISDN link. I had to verify every mailbox was well transferred, all folders, all messages, without wasting bandwidth or creating duplicates upon resyncs. The imapsync design was made with the beautiful rsync command in mind. Imapsync started its life as a patch of the copy_folder.pl script. The script copy_folder.pl comes from the Mail-IMAPClient-2.1.3 perl module tarball source (more precisely in the examples/ directory of the Mail-IMAPClient tarball). So many changes happened since then that I wonder if it remains any lines of the original copy_folder.pl in imapsync source code. =cut # use pragmas # use strict ; use warnings ; use Carp ; use Cwd ; use Compress::Zlib ; use Data::Dumper ; use Digest::HMAC_SHA1 qw( hmac_sha1 hmac_sha1_hex ) ; use Digest::MD5 qw( md5 md5_hex md5_base64 ) ; use Encode ; use Encode::IMAPUTF7 ; use English qw( -no_match_vars ) ; use Errno qw(EAGAIN EPIPE ECONNRESET) ; use Fcntl ; use File::Basename ; use File::Copy::Recursive ; use File::Glob qw( :glob ) ; use File::Path qw( mkpath rmtree ) ; use File::Spec ; use File::stat ; use Getopt::Long ( ) ; use IO::File ; use IO::Socket qw( :crlf SOL_SOCKET SO_KEEPALIVE ) ; use IO::Socket::INET6 ; use IO::Socket::SSL ; use IO::Tee ; use IPC::Open3 'open3' ; #use locale ; use Mail::IMAPClient 3.30 ; use MIME::Base64 ; use Pod::Usage qw(pod2usage) ; use POSIX qw( uname SIGALRM :sys_wait_h ) ; use Sys::Hostname ; use Term::ReadKey ; use Test::More ; use Time::HiRes qw( time sleep ) ; use Time::Local ; use Unicode::String ; use Readonly ; use Sys::MemInfo ; use Regexp::Common ; use Text::ParseWords ; # for quotewords() use File::Tail ; local $OUTPUT_AUTOFLUSH = 1 ; # constants # Let us do like sysexits.h # /usr/include/sysexits.h # and https://www.tldp.org/LDP/abs/html/exitcodes.html # Should avoid 2 126 127 128..128+64=192 255 # Should use 0 1 3..125 193..254 Readonly my $EX_OK => 0 ; #/* successful termination */ Readonly my $EX_USAGE => 64 ; #/* command line usage error */ #Readonly my $EX_DATAERR => 65 ; #/* data format error */ Readonly my $EX_NOINPUT => 66 ; #/* cannot open input */ #Readonly my $EX_NOUSER => 67 ; #/* addressee unknown */ #Readonly my $EX_NOHOST => 68 ; #/* host name unknown */ Readonly my $EX_UNAVAILABLE => 69 ; #/* service unavailable */ Readonly my $EX_SOFTWARE => 70 ; #/* internal software error */ #Readonly my $EX_OSERR => 71 ; #/* system error (e.g., can't fork) */ #Readonly my $EX_OSFILE => 72 ; #/* critical OS file missing */ #Readonly my $EX_CANTCREAT => 73 ; #/* can't create (user) output file */ #Readonly my $EX_IOERR => 74 ; #/* input/output error */ #Readonly my $EX_TEMPFAIL => 75 ; #/* temp failure; user is invited to retry */ #Readonly my $EX_PROTOCOL => 76 ; #/* remote error in protocol */ #Readonly my $EX_NOPERM => 77 ; #/* permission denied */ #Readonly my $EX_CONFIG => 78 ; #/* configuration error */ # Mine Readonly my $EXIT_CATCH_ALL => 1 ; # Any other error Readonly my $EXIT_BY_SIGNAL => 6 ; # Should be 128+n where n is the sig_num Readonly my $EXIT_BY_FILE => 7 ; Readonly my $EXIT_PID_FILE_ERROR => 8 ; Readonly my $EXIT_CONNECTION_FAILURE => 10 ; Readonly my $EXIT_TLS_FAILURE => 12 ; Readonly my $EXIT_AUTHENTICATION_FAILURE => 16 ; Readonly my $EXIT_SUBFOLDER1_NO_EXISTS => 21 ; Readonly my $EXIT_WITH_ERRORS => 111 ; Readonly my $EXIT_WITH_ERRORS_MAX => 112 ; Readonly my $EXIT_OVERQUOTA => 113 ; Readonly my $EXIT_ERR_APPEND => 114 ; Readonly my $EXIT_ERR_FETCH => 115 ; Readonly my $EXIT_ERR_CREATE => 116 ; Readonly my $EXIT_ERR_SELECT => 117 ; Readonly my $EXIT_TRANSFER_EXCEEDED => 118 ; Readonly my $EXIT_ERR_APPEND_VIRUS => 119 ; Readonly my $EXIT_ERR_FLAGS => 120 ; Readonly my $EXIT_TESTS_FAILED => 254 ; # Like Test::More API Readonly my $EXIT_CONNECTION_FAILURE_HOST1 => 101 ; Readonly my $EXIT_CONNECTION_FAILURE_HOST2 => 102 ; Readonly my $EXIT_AUTHENTICATION_FAILURE_USER1 => 161 ; Readonly my $EXIT_AUTHENTICATION_FAILURE_USER2 => 162 ; Readonly my %EXIT_TXT => ( $EX_OK => 'EX_OK: successful termination', $EX_USAGE => 'EX_USAGE: command line usage error', $EX_NOINPUT => 'EX_NOINPUT: cannot open input', $EX_UNAVAILABLE => 'EX_UNAVAILABLE: service unavailable', $EX_SOFTWARE => 'EX_SOFTWARE: internal software error', $EXIT_CATCH_ALL => 'EXIT_CATCH_ALL', $EXIT_BY_SIGNAL => 'EXIT_BY_SIGNAL', $EXIT_BY_FILE => 'EXIT_BY_FILE', $EXIT_PID_FILE_ERROR => 'EXIT_PID_FILE_ERROR' , $EXIT_CONNECTION_FAILURE => 'EXIT_CONNECTION_FAILURE', $EXIT_TLS_FAILURE => 'EXIT_TLS_FAILURE', $EXIT_AUTHENTICATION_FAILURE => 'EXIT_AUTHENTICATION_FAILURE', $EXIT_SUBFOLDER1_NO_EXISTS => 'EXIT_SUBFOLDER1_NO_EXISTS', $EXIT_WITH_ERRORS => 'EXIT_WITH_ERRORS', $EXIT_WITH_ERRORS_MAX => 'EXIT_WITH_ERRORS_MAX', $EXIT_OVERQUOTA => 'EXIT_OVERQUOTA', $EXIT_ERR_APPEND => 'EXIT_ERR_APPEND', $EXIT_ERR_APPEND_VIRUS => 'EXIT_ERR_APPEND_VIRUS', $EXIT_ERR_FETCH => 'EXIT_ERR_FETCH', $EXIT_ERR_FLAGS => 'EXIT_ERR_FLAGS', $EXIT_ERR_CREATE => 'EXIT_ERR_CREATE', $EXIT_ERR_SELECT => 'EXIT_ERR_SELECT', $EXIT_TESTS_FAILED => 'EXIT_TESTS_FAILED', $EXIT_TRANSFER_EXCEEDED => 'EXIT_TRANSFER_EXCEEDED', $EXIT_CONNECTION_FAILURE_HOST1 => 'EXIT_CONNECTION_FAILURE_HOST1', $EXIT_CONNECTION_FAILURE_HOST2 => 'EXIT_CONNECTION_FAILURE_HOST2', $EXIT_AUTHENTICATION_FAILURE_USER1 => 'EXIT_AUTHENTICATION_FAILURE_USER1', $EXIT_AUTHENTICATION_FAILURE_USER2 => 'EXIT_AUTHENTICATION_FAILURE_USER2', ) ; Readonly my %EXIT_VALUE_OF_ERR_TYPE => ( ERR_APPEND_SIZE => $EXIT_ERR_APPEND, ERR_OVERQUOTA => $EXIT_OVERQUOTA, ERR_APPEND => $EXIT_ERR_APPEND, ERR_APPEND_VIRUS => $EXIT_ERR_APPEND_VIRUS, ERR_CREATE => $EXIT_ERR_CREATE, ERR_SELECT => $EXIT_ERR_SELECT, ERR_Host1_FETCH => $EXIT_ERR_FETCH, ERR_FLAGS => $EXIT_ERR_FLAGS, ERR_UNCLASSIFIED => $EXIT_WITH_ERRORS, ERR_NOTHING_REPORTED => $EXIT_WITH_ERRORS, ERR_TRANSFER_EXCEEDED => $EXIT_TRANSFER_EXCEEDED, ERR_CONNECTION_FAILURE_HOST1 => $EXIT_CONNECTION_FAILURE_HOST1, ERR_CONNECTION_FAILURE_HOST2 => $EXIT_CONNECTION_FAILURE_HOST2, ERR_AUTHENTICATION_FAILURE_USER1 => $EXIT_AUTHENTICATION_FAILURE_USER1, ERR_AUTHENTICATION_FAILURE_USER2 => $EXIT_AUTHENTICATION_FAILURE_USER2, ERR_EXIT_TLS_FAILURE => $EXIT_TLS_FAILURE, ) ; Readonly my %COMMENT_OF_ERR_TYPE => ( ERR_APPEND_SIZE => \&comment_err_append_size, ERR_OVERQUOTA => \&comment_err_overquota, ERR_APPEND => \&comment_err_blank, ERR_APPEND_VIRUS => \&comment_err_blank, ERR_CREATE => \&comment_err_blank, ERR_SELECT => \&comment_err_blank, ERR_Host1_FETCH => \&comment_err_blank, ERR_FLAGS => \&comment_err_flags, ERR_UNCLASSIFIED => \&comment_err_blank, ERR_NOTHING_REPORTED => \&comment_err_blank, ERR_TRANSFER_EXCEEDED => \&comment_err_transfer_exceeded, ERR_CONNECTION_FAILURE_HOST1 => \&comment_err_connection_failure_host1, ERR_CONNECTION_FAILURE_HOST2 => \&comment_err_connection_failure_host2, ERR_AUTHENTICATION_FAILURE_USER1 => \&comment_err_authentication_failure_host1, ERR_AUTHENTICATION_FAILURE_USER2 => \&comment_err_authentication_failure_host2, ERR_EXIT_TLS_FAILURE => \&comment_err_blank, ) ; sub comment_err_blank { return '' ; } sub comment_err_append_size { my $mysync = shift @ARG ; my $comment = "The destination server refuses too big messages. Use --truncmess option. Read https://imapsync.lamiral.info/FAQ.d/FAQ.Messages_Too_Big.txt" ; return $comment ; } sub comment_err_authentication_failure_host1 { my $mysync = shift @ARG ; my $comment = "Check the credentials for $mysync->{ user1 }." ; return $comment ; } sub comment_err_authentication_failure_host2 { my $mysync = shift @ARG ; my $comment = "Check the credentials for $mysync->{ user2 }." ; return $comment ; } sub comment_err_connection_failure_host1 { my $mysync = shift @ARG ; my $comment = "Check that host1 $mysync->{ host1 } on port $mysync->{ port1 } is the right IMAP server to be contacted for your mailbox." ; return $comment ; } sub comment_err_connection_failure_host2 { my $mysync = shift @ARG ; my $comment = "Check that host1 $mysync->{ host2 } on port $mysync->{ port2 } is the right IMAP server to be contacted for your mailbox." ; return $comment ; } sub comment_err_overquota { my $mysync = shift @ARG ; my $comment = 'The destination mailbox is 100% full, get free space on it and then resume the sync.' ; return $comment ; } sub comment_err_transfer_exceeded { my $mysync = shift @ARG ; my $size_limit_human = bytes_display_string_dec( $mysync->{ exitwhenover } ) ; my $comment = "The maximum transfer size for a single sync is reached ( over $size_limit_human ). Relaunch the sync to sync more." ; return $comment ; } sub comment_err_flags { my $mysync = shift @ARG ; my $comment = 'Many STORE errors with FLAGS. Retry with the option --noresyncflags' ; return $comment ; } Readonly my $DEFAULT_LOGDIR => 'LOG_imapsync' ; Readonly my $ERRORS_MAX => 50 ; # exit after 50 errors. Readonly my $ERRORS_MAX_CGI => 20 ; # exit after 20 errors in CGI context. Readonly my $INTERVAL_TO_EXIT => 2 ; # interval max to exit instead of reconnect Readonly my $SPLIT => 100 ; # By default, 100 at a time, not more. Readonly my $SPLIT_FACTOR => 10 ; # init_imap() calls Maxcommandlength( $SPLIT_FACTOR * $split ) # which means default Maxcommandlength is 10*100 = 1000 characters ; Readonly my $IMAP_PORT => 143 ; # Well know port for IMAP Readonly my $IMAP_SSL_PORT => 993 ; # Well know port for IMAP over SSL Readonly my $LAST => -1 ; Readonly my $MINUS_ONE => -1 ; Readonly my $MINUS_TWO => -2 ; Readonly my $RELEASE_NUMBER_EXAMPLE_1 => '1.351' ; Readonly my $RELEASE_NUMBER_EXAMPLE_2 => 42.4242 ; Readonly my $TCP_PING_TIMEOUT => 5 ; Readonly my $DEFAULT_TIMEOUT => 120 ; Readonly my $DEFAULT_NB_RECONNECT_PER_IMAP_COMMAND => 3 ; Readonly my $DEFAULT_BUFFER_SIZE => 4096 ; Readonly my $MAX_SLEEP => 2 ; # 2 seconds max for limiting too long sleeps from --maxbytespersecond and --maxmessagespersecond Readonly my $DEFAULT_EXPIRATION_TIME_OAUTH2_PK12 => 3600 ; Readonly my $PERMISSION_FILTER => 7777 ; Readonly my $KIBI => 1024 ; Readonly my $NUMBER_10 => 10 ; Readonly my $NUMBER_42 => 42 ; Readonly my $NUMBER_100 => 100 ; Readonly my $NUMBER_200 => 200 ; Readonly my $NUMBER_300 => 300 ; Readonly my $NUMBER_123456 => 123_456 ; Readonly my $NUMBER_654321 => 654_321 ; Readonly my $NUMBER_20_000 => 20_000 ; Readonly my $QUOTA_PERCENT_LIMIT => 90 ; Readonly my $NUMBER_104_857_600 => 104_857_600 ; Readonly my $SIZE_MAX_STR => 64 ; Readonly my $NB_SECONDS_IN_A_DAY => 86_400 ; Readonly my $STD_CHAR_PER_LINE => 80 ; Readonly my $TRUE => 1 ; Readonly my $FALSE => 0 ; Readonly my $LAST_RESSORT_SEPARATOR => q{/} ; Readonly my $CGI_TMPDIR_TOP => '/var/tmp/imapsync_cgi' ; Readonly my $CGI_HASHFILE => '/var/tmp/imapsync_hash' ; Readonly my $UMASK_PARANO => '0077' ; Readonly my $STR_use_releasecheck => q{Check if a new imapsync release is available by adding --releasecheck} ; Readonly my $GMAIL_MAXSIZE => 35_651_584 ; Readonly my $FORCE => 1 ; # if ( 'MSWin32' eq $OSNAME ) # if ( 'darwin' eq $OSNAME ) # if ( 'linux' eq $OSNAME ) # global variables # Currently working to finish with only $sync, $acc1, $acc2 # Not finished yet... my( $sync, $acc1, $acc2, $debugflags, $debuglist, $debugdev, $debugmaxlinelength, $debugcgi, @include, @exclude, @folderrec, @folderfirst, @folderlast, @h1_folders_all, %h1_folders_all, @h2_folders_all, %h2_folders_all, @h2_folders_from_1_wanted, %h2_folders_from_1_all, %requested_folder, $h1_folders_wanted_nb, $h1_folders_wanted_ct, @h2_folders_not_in_1, %h1_subscribed_folder, %h2_subscribed_folder, %h2_folders_from_1_wanted, %h2_folders_from_1_several, $prefix1, $prefix2, @regexmess, @skipmess, @pipemess, $pipemesscheck, $syncflagsaftercopy, $syncinternaldates, $idatefromheader, $minsize, $maxage, $minage, $search, @useheader, %useheader, $skipsize, $allowsizemismatch, $buffersize, $authmd5, $authmd51, $authmd52, $subscribed, $subscribe, $subscribeall, $help, $nb_msg_skipped_dry_mode, $h2_nb_msg_noheader, $h1_bytes_processed, $h1_nb_msg_end, $h1_bytes_end, $h2_nb_msg_end, $h2_bytes_end, $timestart_int, $uid1, $uid2, $split1, $split2, $modulesversion, $delete2folders, $delete2foldersonly, $delete2foldersbutnot, $usecache, $debugcache, $cacheaftercopy, $wholeheaderifneeded, %h1_msgs_copy_by_uid, $useuid, $h2_uidguess, $checkmessageexists, $messageidnodomain, $fixInboxINBOX, $maxlinelength, $maxlinelengthcmd, $minmaxlinelength, $fixcolonbug, $create_folder_old, $skipcrossduplicates, $debugcrossduplicates, $disarmreadreceipts, $mixfolders, $fetch_hash_set, $cgidir, %month_abrev, $SSL_VERIFY_POLICY, ) ; single_sync( $sync, $acc1, $acc2 ); sub single_sync { # main program # global variables initialization # I'm currently removing all global variables except $sync $acc1 $acc2 # passing each of them under # $sync->{variable_name} # or $acc1->{variable_name} # or $acc1->{variable_name} # $acc1 = {} ; $acc2 = {} ; $sync->{ acc1 } = $acc1 ; $sync->{ acc2 } = $acc2 ; $acc1->{ Side } = 'Host1' ; $acc2->{ Side } = 'Host2' ; $acc1->{ N } = '1' ; $acc2->{ N } = '2' ; $sync->{timestart} = time ; # Is a float because of use Time::HiRres $sync->{rcs} = q{$Id: imapsync,v 2.178 2022/01/12 21:28:37 gilles Exp gilles $} ; $sync->{ memory_consumption_at_start } = memory_consumption( ) || 0 ; my @loadavg = loadavg( ) ; $sync->{ cpu_number } = cpu_number( ) ; $sync->{ loaddelay } = load_and_delay( $sync->{ cpu_number }, @loadavg ) ; $sync->{ loaddelay } = 0 ; $sync->{ loadavg } = join( q{ }, $loadavg[ 0 ] ) . " on $sync->{cpu_number} cores and " . ram_memory_info( ) ; $sync->{ total_bytes_transferred } = 0 ; $sync->{ total_bytes_skipped } = 0 ; $sync->{ nb_msg_transferred } = 0 ; $sync->{ nb_msg_skipped } = $nb_msg_skipped_dry_mode = 0 ; $sync->{ acc1 }->{ nb_msg_deleted } = 0 ; $sync->{ acc2 }->{ nb_msg_deleted } = 0 ; $sync->{ acc1 }->{ nb_msg_duplicate } = 0 ; $sync->{ acc2 }->{ nb_msg_duplicate } = 0 ; $sync->{ h1_nb_msg_noheader } = 0 ; $h2_nb_msg_noheader = 0 ; $sync->{ h1_nb_msg_start } = 0 ; $sync->{ h1_bytes_start } = 0 ; $sync->{ h2_nb_msg_start } = 0 ; $sync->{ h2_bytes_start } = 0 ; $sync->{ h1_nb_msg_processed } = $h1_bytes_processed = 0 ; $sync->{ h2_nb_msg_crossdup } = 0 ; #$h1_nb_msg_end = $h1_bytes_end = 0 ; #$h2_nb_msg_end = $h2_bytes_end = 0 ; $sync->{ nb_errors } = 0; $sync->{ biggest_message_transferred } = 0; %month_abrev = ( Jan => '00', Feb => '01', Mar => '02', Apr => '03', May => '04', Jun => '05', Jul => '06', Aug => '07', Sep => '08', Oct => '09', Nov => '10', Dec => '11', ); # Just create a CGI object if under cgi context only. # Needed for the get_options() call cgibegin( $sync ) ; # In cgi context, printing must start by the header so we delay other prints by using output() storage my $options_good = get_options( $sync, @ARGV ) ; # Is it the first myprint? cgibuildheader( $sync ) ; docker_context( $sync ) ; print_output_if_needed( $sync ) ; output_reset_with( $sync ) ; # don't go on if options are not all known. if ( ! defined $options_good ) { exit $EX_USAGE ; } # If you want releasecheck not to be done by default (like the github maintainer), # then just uncomment the first "$sync->{releasecheck} =" line, the line ending with "0 ;", # the second line (ending with "1 ;") can then stay active or be commented, # the result will be the same: no releasecheck by default (because 0 is then the defined value). #$sync->{releasecheck} = defined $sync->{releasecheck} ? $sync->{releasecheck} : 0 ; $sync->{releasecheck} = defined $sync->{releasecheck} ? $sync->{releasecheck} : 1 ; # just the version if ( $sync->{ version } ) { myprint( imapsync_version( $sync ), "\n" ) ; return 0 ; } #$sync->{debugenv} = 1 ; $sync->{debugenv} and printenv( $sync ) ; # if option --debugenv load_modules( ) ; # after_get_options call usage and exit if --help or options were not well got after_get_options( $sync, $options_good ) ; #local $ENV{TZ} = 'GMT' if ( under_cgi_context( $sync ) and 'MSWin32' ne $OSNAME ) ; #output( $sync, localtime(time) . " " . gmtime(time) . "\n" ) ; # Under CGI environment, fix caveat emptor potential issues cgisetcontext( $sync ) ; get_options_extra( $sync ) ; # --gmail --gmail --exchange --office etc. easyany( $sync ) ; $sync->{ sanitize } = defined $sync->{ sanitize } ? $sync->{ sanitize } : 1 ; sanitize( $sync ) ; $sync->{ tmpdir } ||= File::Spec->tmpdir( ) ; # Unit tests my $unittestssuite = unittestssuite( $sync ) ; if ( condition_to_leave_after_tests( $sync ) ) { return $unittestssuite ; } # init live varaiables if ( $sync->{ testslive } ) { testslive_init( $sync ) ; } if ( $sync->{ testslive6 } ) { testslive6_init( $sync ) ; } define_pidfile( $sync ) ; if ( $sync->{ abortbyfile } ) { $sync->{ abort } = 1 ; } install_signals( $sync ) ; $sync->{ log } = defined $sync->{ log } ? $sync->{ log } : 1 ; $sync->{ errorsdump } = defined $sync->{ errorsdump } ? $sync->{ errorsdump } : 1 ; $sync->{ errorsmax } = defined $sync->{ errorsmax } ? $sync->{ errorsmax } : $ERRORS_MAX ; # log and output binmode STDOUT, ":encoding(UTF-8)" ; if ( $sync->{ log } ) { setlogfile( $sync ) ; teelaunch( $sync ) ; # now $sync->{tee} is a filehandle to STDOUT and the logfile } #binmode STDERR, ":encoding(UTF-8)" ; # STDERR goes to the same place: LOG and STDOUT (if logging is on) # Useful only for --debugssl $sync->{tee} and local *STDERR = *${$sync->{tee}}{IO} ; $timestart_int = int( $sync->{timestart} ) ; $sync->{timebefore} = $sync->{timestart} ; $sync->{ timestart_str } = localtimez( $sync->{timestart} ) ; # The prints in the log starts here myprint( localhost_info( $sync ), "\n" ) ; myprint( "Transfer started at $sync->{ timestart_str }\n" ) ; myprint( "PID is $PROCESS_ID my PPID is ", mygetppid( ), "\n" ) ; announcelogfile( $sync ) ; myprint( "Load is " . ( join( q{ }, loadavg( ) ) || 'unknown' ), " on $sync->{cpu_number} cores\n" ) ; #myprintf( "Memory consumption so far: %.1f MiB\n", memory_consumption( ) / $KIBI / $KIBI ) ; myprint( 'Current directory is ' . getcwd( ) . "\n" ) ; myprint( 'Real user id is ' . getpwuid_any_os( $REAL_USER_ID ) . " (uid $REAL_USER_ID)\n" ) ; myprint( 'Effective user id is ' . getpwuid_any_os( $EFFECTIVE_USER_ID ). " (euid $EFFECTIVE_USER_ID)\n" ) ; $modulesversion = defined $modulesversion ? $modulesversion : 1 ; $sync->{ warn_release } = ( $sync->{ releasecheck } ) ? check_last_release( ) : $STR_use_releasecheck ; $wholeheaderifneeded = defined $wholeheaderifneeded ? $wholeheaderifneeded : 1; # Activate --usecache if --useuid is set and there is no --nousecache $usecache = 1 if ( $useuid and ( ! defined $usecache ) ) ; $cacheaftercopy = 1 if ( $usecache and ( ! defined $cacheaftercopy ) ) ; $sync->{ checkfoldersexist } = defined $sync->{ checkfoldersexist } ? $sync->{ checkfoldersexist } : 1 ; $checkmessageexists = defined $checkmessageexists ? $checkmessageexists : 0 ; $sync->{ expungeaftereach } = defined $sync->{ expungeaftereach } ? $sync->{ expungeaftereach } : 1 ; # abletosearch is on by default $sync->{abletosearch} = defined $sync->{abletosearch} ? $sync->{abletosearch} : 1 ; $sync->{abletosearch1} = defined $sync->{abletosearch1} ? $sync->{abletosearch1} : $sync->{abletosearch} ; $sync->{abletosearch2} = defined $sync->{abletosearch2} ? $sync->{abletosearch2} : $sync->{abletosearch} ; $checkmessageexists = 0 if ( not $sync->{abletosearch1} ) ; $sync->{ trylogin } = defined $sync->{ trylogin } ? $sync->{ trylogin } : 1 ; $sync->{showpasswords} = defined $sync->{showpasswords} ? $sync->{showpasswords} : 0 ; $sync->{ fixslash2 } = defined $sync->{ fixslash2 } ? $sync->{ fixslash2 } : 1 ; $fixInboxINBOX = defined $fixInboxINBOX ? $fixInboxINBOX : 1 ; $create_folder_old = defined $create_folder_old ? $create_folder_old : 0 ; $mixfolders = defined $mixfolders ? $mixfolders : 1 ; $sync->{automap} = defined $sync->{automap} ? $sync->{automap} : 0 ; $sync->{ delete2duplicates } = 1 if ( $sync->{ delete2 } and ( ! defined $sync->{ delete2duplicates } ) ) ; $sync->{maxmessagespersecond} = defined $sync->{maxmessagespersecond} ? $sync->{maxmessagespersecond} : 0 ; $sync->{maxbytespersecond} = defined $sync->{maxbytespersecond} ? $sync->{maxbytespersecond} : 0 ; $sync->{sslcheck} = defined $sync->{sslcheck} ? $sync->{sslcheck} : 1 ; myprint( banner_imapsync( $sync, @ARGV ) ) ; myprint( "Temp directory is $sync->{ tmpdir } ( to change it use --tmpdir dirpath )\n" ) ; myprint( output( $sync ) ) ; output_reset_with( $sync ) ; do_valid_directory( $sync->{ tmpdir } ) || croak "Error creating tmpdir $sync->{ tmpdir } : $OS_ERROR" ; remove_pidfile_not_running( $sync->{ pidfile } ) ; # if another imapsync is running then tail -f its logfile and exit # useful in cgi context if ( $sync->{ tail } and tail( $sync ) ) { exit_clean( $sync, $EX_OK, "Tail -f finished. Now finishing myself processus $PROCESS_ID\n" ) ; exit $EX_OK ; } if ( ! write_pidfile( $sync ) ) { myprint( "Exiting with return value $EXIT_PID_FILE_ERROR ($EXIT_TXT{$EXIT_PID_FILE_ERROR}) $sync->{nb_errors}/$sync->{errorsmax} nb_errors/max_errors PID $PROCESS_ID\n" ) ; exit $EXIT_PID_FILE_ERROR ; } # New place for abort # abort before simulong in order to be able to abort a simulong sync if ( $sync->{ abort } ) { abort( $sync ) ; # well, the abort job is done, because even when not succeeded # in aborting another run, this run has to end without doing any # thing else exit $EX_OK ; } # simulong is just a loop printing some lines for xx seconds with option "--simulong xx". simulong( $sync ) ; # New place for cgiload 2019_03_03 # because I want to log it # Can break here if load is too heavy # Have in mind the CGI header has already a 503 Service Unavailable cgiload( $sync ) ; $fixcolonbug = defined $fixcolonbug ? $fixcolonbug : 1 ; if ( $usecache and $fixcolonbug ) { tmpdir_fix_colon_bug( $sync ) } ; $modulesversion and myprint( "Modules version list ( use --no-modulesversion to turn off printing this Perl modules list ):\n", modulesversion(), "\n" ) ; check_lib_version( $sync ) or croak "imapsync needs perl lib Mail::IMAPClient release 3.30 or superior.\n"; if ( $sync->{ justbanner } ) { myprint( "Exiting because of --justbanner\n" ) ; exit_clean( $sync, $EX_OK ) ; } # turn on RFC standard flags correction like \SEEN -> \Seen $sync->{ flagscase } = defined $sync->{ flagscase } ? $sync->{ flagscase } : 1 ; # Use PERMANENTFLAGS if available $sync->{ filterflags } = defined $sync->{ filterflags } ? $sync->{ filterflags } : 1 ; filterbuggyflags( $sync ) ; # sync flags just after an APPEND, some servers ignore the flags given in the APPEND # like MailEnable IMAP server. # Off by default since it takes time. $syncflagsaftercopy = defined $syncflagsaftercopy ? $syncflagsaftercopy : 0 ; # update flags on host2 for already transferred messages $sync->{resyncflags} = defined $sync->{resyncflags} ? $sync->{resyncflags} : 1 ; if ( $sync->{resyncflags} ) { myprint( "Info: will resync flags for already transferred messages. Use --noresyncflags to not resync flags.\n" ) ; }else{ myprint( "Info: will not resync flags for already transferred messages. Use --resyncflags to resync flags.\n" ) ; } sslcheck( $sync ) ; #print Data::Dumper->Dump( [ \$sync ] ) ; $split1 ||= $SPLIT ; $split2 ||= $SPLIT ; #$sync->{host1} || missing_option( $sync, '--host1' ) ; $sync->{host1} = sanitize_host( $sync->{host1} ) ; $sync->{port1} ||= ( $sync->{ssl1} ) ? $IMAP_SSL_PORT : $IMAP_PORT ; #$sync->{host2} || missing_option( $sync, '--host2' ) ; $sync->{host2} = sanitize_host( $sync->{host2} ) ; $sync->{port2} ||= ( $sync->{ssl2} ) ? $IMAP_SSL_PORT : $IMAP_PORT ; $acc1->{ debugimap } = $acc2->{ debugimap } = 1 if ( $sync->{ debugimap } ) ; # Set on debug mode if one of the imap dialogs are in debug. # imap dialog without the debug mode is scary and useless. $sync->{ debug } = 1 if ( $acc1->{ debugimap } or $acc2->{ debugimap } ) ; # By default, don't take size to compare $skipsize = (defined $skipsize) ? $skipsize : 1; $uid1 = defined $uid1 ? $uid1 : 1; $uid2 = defined $uid2 ? $uid2 : 1; $subscribe = defined $subscribe ? $subscribe : 1; # Allow size mismatch by default $allowsizemismatch = defined $allowsizemismatch ? $allowsizemismatch : 1; if ( defined $delete2foldersbutnot or defined $delete2foldersonly ) { $delete2folders = 1 ; } my %SSL_VERIFY_STR ; Readonly $SSL_VERIFY_POLICY => IO::Socket::SSL::SSL_VERIFY_NONE( ) ; Readonly %SSL_VERIFY_STR => ( IO::Socket::SSL::SSL_VERIFY_NONE( ) => 'SSL_VERIFY_NONE, ie, do not check the certificate server.' , IO::Socket::SSL::SSL_VERIFY_PEER( ) => 'SSL_VERIFY_PEER, ie, check the certificate server' , ) ; $IO::Socket::SSL::DEBUG = defined( $sync->{debugssl} ) ? $sync->{debugssl} : 1 ; if ( $sync->{ssl1} or $sync->{ssl2} or $sync->{tls1} or $sync->{tls2}) { myprint( "SSL debug mode level is --debugssl $IO::Socket::SSL::DEBUG (can be set from 0 meaning no debug to 4 meaning max debug)\n" ) ; } if ( $sync->{ssl1} ) { myprint( qq{Host1: SSL default mode is like --sslargs1 "SSL_verify_mode=$SSL_VERIFY_POLICY", meaning for host1 $SSL_VERIFY_STR{$SSL_VERIFY_POLICY}\n} ) ; myprint( 'Host1: Use --sslargs1 SSL_verify_mode=' . IO::Socket::SSL::SSL_VERIFY_PEER( ) . " to have $SSL_VERIFY_STR{IO::Socket::SSL::SSL_VERIFY_PEER( )} of host1\n" ) ; # $sync->{ acc1 }->{sslargs}->{SSL_verify_mode} } if ( $sync->{ssl2} ) { myprint( qq{Host2: SSL default mode is like --sslargs2 "SSL_verify_mode=$SSL_VERIFY_POLICY", meaning for host2 $SSL_VERIFY_STR{$SSL_VERIFY_POLICY}\n} ) ; myprint( 'Host2: Use --sslargs2 SSL_verify_mode=' . IO::Socket::SSL::SSL_VERIFY_PEER( ) . " to have $SSL_VERIFY_STR{IO::Socket::SSL::SSL_VERIFY_PEER( )} of host2\n" ) ; } # ID on by default since 1.832 $sync->{id} = defined $sync->{id} ? $sync->{id} : 1 ; if ( $sync->{justconnect} or not $sync->{user1} or not $sync->{user2} or not $sync->{host1} or not $sync->{host2} ) { my $justconnect = justconnect( $sync ) ; myprint( debugmemory( $sync, " after justconnect() call" ) ) ; exit_clean( $sync, $EX_OK, "Exiting after a justconnect on host(s): $justconnect\n" ) ; } #$sync->{user1} || missing_option( $sync, '--user1' ) ; #$sync->{user2} || missing_option( $sync, '--user2' ) ; $syncinternaldates = defined $syncinternaldates ? $syncinternaldates : 1; # Turn on expunge if there is not explicit option --noexpunge1 and option # --delete1 is given. # Done because --delete1 --noexpunge1 is very dangerous on the second run: # the Deleted flag is then synced to all previously transferred messages. # So --delete1 implies --expunge1 is a better usability default behavior. if ( $sync->{ delete1 } ) { if ( ! defined $sync->{ expunge1 } ) { myprint( "Info: turning on --expunge1 because --delete1 --noexpunge1 is very dangerous on the second run.\n" ) ; $sync->{ expunge1 } = 1 ; } myprint( "Info: if expunging after each message slows down too much the sync then use --noexpungeaftereach to speed up\n" ) ; } if ( $sync->{ uidexpunge2 } and not Mail::IMAPClient->can( 'uidexpunge' ) ) { myprint( "Failure: uidexpunge not supported (IMAPClient release < 3.17), use nothing or --expunge2 instead\n" ) ; $sync->{nb_errors}++ ; exit_clean( $sync, $EX_SOFTWARE ) ; } if ( ( $sync->{ delete2 } or $sync->{ delete2duplicates } ) and not defined $sync->{ uidexpunge2 } ) { if ( Mail::IMAPClient->can( 'uidexpunge' ) ) { myprint( "Info: will act as --uidexpunge2\n" ) ; $sync->{ uidexpunge2 } = 1 ; }elsif ( not defined $sync->{ expunge2 } ) { myprint( "Info: will act as --expunge2 (no uidexpunge support)\n" ) ; $sync->{ expunge2 } = 1 ; } } if ( $sync->{ delete1 } and $sync->{ delete2 } ) { myprint( "Warning: using --delete1 and --delete2 together is almost always a bad idea. " . "You should probably launch two runs, the first with --delete2 for a strict sync, " . "then the second with --delete1 to remove messages from the source account. " . "Exiting imapsync.\n" ) ; $sync->{ nb_errors }++ ; exit_clean( $sync, $EX_USAGE ) ; } if ( $idatefromheader ) { myprint( 'Turned ON idatefromheader, ', "will set the internal dates on host2 from the 'Date:' header line.\n" ) ; $syncinternaldates = 0 ; } if ( $syncinternaldates ) { myprint( 'Info: turned ON syncinternaldates, ', "will set the internal dates (arrival dates) on host2 same as host1.\n" ) ; }else{ myprint( "Info: turned OFF syncinternaldates\n" ) ; } if ( defined $authmd5 and $authmd5 ) { $authmd51 = 1 ; $authmd52 = 1 ; } if ( defined $authmd51 and $authmd51 ) { $acc1->{ authmech } ||= 'CRAM-MD5' ; } else{ $acc1->{ authmech } ||= $acc1->{ authuser } ? 'PLAIN' : 'LOGIN' ; } if ( defined $authmd52 and $authmd52 ) { $acc2->{ authmech } ||= 'CRAM-MD5'; } else{ $acc2->{ authmech } ||= $acc2->{ authuser } ? 'PLAIN' : 'LOGIN'; } $acc1->{ authmech } = uc $acc1->{ authmech } ; $acc2->{ authmech } = uc $acc2->{ authmech } ; if ( defined $acc1->{ proxyauth } && !$acc1->{ authuser } ) { missing_option( $sync, 'With --proxyauth1, --authuser1' ) ; } if ( defined $acc2->{ proxyauth } && !$acc2->{ authuser } ) { missing_option( $sync, 'With --proxyauth2, --authuser2' ) ; } myprint( "Host1: will try to use $acc1->{ authmech } authentication on host1\n") ; myprint( "Host2: will try to use $acc2->{ authmech } authentication on host2\n") ; $sync->{ timeout } = defined $sync->{ timeout } ?$sync->{ timeout } : $DEFAULT_TIMEOUT ; $sync->{ acc1 }->{timeout} = defined $sync->{ acc1 }->{timeout} ? $sync->{ acc1 }->{timeout} : $sync->{ timeout } ; myprint( "Host1: imap connection timeout is $sync->{ acc1 }->{timeout} seconds\n") ; $sync->{ acc2 }->{timeout} = defined $sync->{ acc2 }->{timeout} ? $sync->{ acc2 }->{timeout} : $sync->{ timeout } ; myprint( "Host2: imap connection timeout is $sync->{ acc2 }->{timeout} seconds\n" ) ; keepalive1( $sync ) ; keepalive2( $sync ) ; if ( under_cgi_context( $sync ) ) { myprint( "Under CGI context, a timeout can occur from the webserver, see https://imapsync.lamiral.info/INSTALL.d/INSTALL.OnlineUI.txt\n" ) ; } $sync->{ syncacls } = defined $sync->{ syncacls } ? $sync->{ syncacls } : 0 ; # No folders sizes at the beginning if --justfolders, unless really wanted. if ( $sync->{ justfolders } and not defined $sync->{ foldersizes } and not $sync->{ justfoldersizes } ) { $sync->{ foldersizes } = 0 ; $sync->{ foldersizesatend } = 1 ; } $sync->{ foldersizes } = ( defined $sync->{ foldersizes } ) ? $sync->{ foldersizes } : 1 ; $sync->{ foldersizesatend } = ( defined $sync->{ foldersizesatend } ) ? $sync->{ foldersizesatend } : $sync->{ foldersizes } ; #$sync->{ checknoabletosearch } = ( defined $sync->{ checknoabletosearch } ) ? $sync->{ checknoabletosearch } : 1 ; set_checknoabletosearch( $sync ) ; $acc1->{ fastio } = defined $acc1->{ fastio } ? $acc1->{ fastio } : 0 ; $acc2->{ fastio } = defined $acc2->{ fastio } ? $acc2->{ fastio } : 0 ; $acc1->{ reconnectretry } = defined $acc1->{ reconnectretry } ? $acc1->{ reconnectretry } : $DEFAULT_NB_RECONNECT_PER_IMAP_COMMAND ; $acc2->{ reconnectretry } = defined $acc2->{ reconnectretry } ? $acc2->{ reconnectretry } : $DEFAULT_NB_RECONNECT_PER_IMAP_COMMAND ; # IMAP compression on by default #$acc1->{ compress } = defined $acc1->{ compress } ? $acc1->{ compress } : 0 ; #$acc2->{ compress } = defined $acc2->{ compress } ? $acc2->{ compress } : 0 ; if ( ! @useheader ) { @useheader = qw( Message-Id Received ) ; } # Make a hash %useheader of each --useheader 'key' in uppercase for ( @useheader ) { $sync->{useheader}->{ uc $_ } = undef } ; #myprint( Data::Dumper->Dump( [ \%useheader ] ) ) ; #exit ; myprint( "Host1: IMAP server [$sync->{host1}] port [$sync->{port1}] user [$sync->{user1}]\n" ) ; myprint( "Host2: IMAP server [$sync->{host2}] port [$sync->{port2}] user [$sync->{user2}]\n" ) ; get_password1( $sync ) ; get_password2( $sync ) ; # --dry1 make imapsync not fetching messages from host1, it is on when --dry is on. # Use --dry --nodry1 to make imapsync fetching messages from host1, # It is useful when debugging transformation options like --pipemess or --regexmess $sync->{dry1} = defined $sync->{dry1} ? $sync->{dry1} : $sync->{dry} ; $sync->{dry_message} = q{} ; if( $sync->{dry} ) { $sync->{dry_message} = "\t(not really since --dry mode)" ; } $sync->{ search1 } ||= $search if ( $search ) ; $sync->{ search2 } ||= $search if ( $search ) ; if ( $disarmreadreceipts ) { push @regexmess, q{s{\A((?:[^\n]+\r\n)+|)(^Disposition-Notification-To:[^\n]*\n)(\r?\n|.*\n\r?\n)}{$1X-$2$3}ims} ; } $pipemesscheck = ( defined $pipemesscheck ) ? $pipemesscheck : 1 ; if ( @pipemess and $pipemesscheck ) { myprint( 'Checking each --pipemess command, ' . join( q{, }, @pipemess ) . ", with an space string. ( Can avoid this check with --nopipemesscheck )\n" ) ; my $string = pipemess( q{ }, @pipemess ) ; # string undef means something was bad. if ( not ( defined $string ) ) { $sync->{nb_errors}++ ; exit_clean( $sync, $EX_USAGE, "Error: one of --pipemess command is bad, check it\n" ) ; } myprint( "Ok with each --pipemess @pipemess\n" ) ; } if ( $maxlinelengthcmd ) { myprint( "Checking --maxlinelengthcmd command, $maxlinelengthcmd, with an space string.\n" ) ; my $string = pipemess( q{ }, $maxlinelengthcmd ) ; # string undef means something was bad. if ( not ( defined $string ) ) { $sync->{nb_errors}++ ; exit_clean( $sync, $EX_USAGE, "Error: --maxlinelengthcmd command is bad, check it\n" ) ; } myprint( "Ok with --maxlinelengthcmd $maxlinelengthcmd\n" ) ; } if ( @regexmess ) { my $string = regexmess( q{ } ) ; myprint( "Checking each --regexmess command with an space string.\n" ) ; # string undef means one of the eval regex was bad. if ( not ( defined $string ) ) { #errors_incr( $sync, 'Warning: one of --regexmess option may be bad, check them' ) ; exit_clean( $sync, $EX_USAGE, "Error: one of --regexmess option is bad, check it\n" ) ; } myprint( "Ok with each --regexmess\n" ) ; } if ( @skipmess ) { myprint( "Checking each --skipmess command with an space string.\n" ) ; my $match = skipmess( q{ } ) ; # match undef means one of the eval regex was bad. if ( not ( defined $match ) ) { $sync->{nb_errors}++ ; exit_clean( $sync, $EX_USAGE, "Error: one of --skipmess option is bad, check it\n" ) ; } myprint( "Ok with each --skipmess\n" ) ; } if ( $sync->{ regexflag } ) { myprint( "Checking each --regexflag command with an space string.\n" ) ; my $string = regexflags( $sync, q{ } ) ; # string undef means one of the eval regex was bad. if ( not ( defined $string ) ) { $sync->{nb_errors}++ ; exit_clean( $sync, $EX_USAGE, "Error: one of --regexflag option is bad, check it\n" ) ; } myprint( "Ok with each --regexflag\n" ) ; } $sync->{imap1} = login_imap( $sync->{host1}, $sync->{port1}, $sync->{user1}, $sync->{password1}, $sync->{ssl1}, $sync->{tls1}, $uid1, $split1, $sync->{ acc1 }, $sync ) ; $sync->{imap2} = login_imap( $sync->{host2}, $sync->{port2}, $sync->{user2}, $sync->{password2}, $sync->{ssl2}, $sync->{tls2}, $uid2, $split2, $sync->{ acc2 }, $sync ) ; $sync->{ debug } and $sync->{imap1} and myprint( 'Host1 Buffer I/O: ', $sync->{imap1}->Buffer(), "\n" ) ; $sync->{ debug } and $sync->{imap2} and myprint( 'Host2 Buffer I/O: ', $sync->{imap2}->Buffer(), "\n" ) ; if ( ! $sync->{imap1} || ! $sync->{imap2} ) { exit_most_errors( $sync ) ; } myprint( "Host1: state Authenticated\n" ) ; myprint( "Host2: state Authenticated\n" ) ; myprint( 'Host1 capability once authenticated: ', join(q{ }, @{ $sync->{imap1}->capability() || [] }), "\n" ) ; #myprint( Data::Dumper->Dump( [ $sync->{imap1} ] ) ) ; #myprint( "imap4rev1: " . $sync->{imap1}->imap4rev1() . "\n" ) ; myprint( 'Host2 capability once authenticated: ', join(q{ }, @{ $sync->{imap2}->capability() || [] }), "\n" ) ; imap_id_stuff( $sync ) ; #quota( $sync, $sync->{imap1}, 'h1' ) ; # quota on host1 is useless and pollute host2 output. quota( $sync, $sync->{imap2}, 'h2' ) ; maxsize_setting( $sync ) ; acc_compress_imap( $acc1 ) ; acc_compress_imap( $acc2 ) ; if ( $sync->{ justlogin } ) { $sync->{imap1}->logout( ) ; $sync->{imap2}->logout( ) ; exit_clean( $sync, $EX_OK, "Exiting because of --justlogin\n" ) ; } # # Folder stuff # $h1_folders_wanted_nb = 0 ; # counter of folders to be done. $h1_folders_wanted_ct = 0 ; # counter of folders done. # All folders on host1 and host2 @h1_folders_all = sort $sync->{imap1}->folders( ) ; @h2_folders_all = sort $sync->{imap2}->folders( ) ; myprint( 'Host1: found ', scalar @h1_folders_all , " folders.\n" ) ; myprint( 'Host2: found ', scalar @h2_folders_all , " folders.\n" ) ; foreach my $f ( @h1_folders_all ) { $h1_folders_all{ $f } = 1 } foreach my $f ( @h2_folders_all ) { $h2_folders_all{ $f } = 1 ; $sync->{h2_folders_all_UPPER}{ uc $f } = 1 ; } $sync->{h1_folders_all} = \%h1_folders_all ; $sync->{h2_folders_all} = \%h2_folders_all ; private_folders_separators_and_prefixes( ) ; # Make a hash of subscribed folders in both servers. for ( $sync->{imap1}->subscribed( ) ) { $h1_subscribed_folder{ $_ } = 1 } ; for ( $sync->{imap2}->subscribed( ) ) { $h2_subscribed_folder{ $_ } = 1 } ; if ( defined $sync->{ subfolder1 } ) { subfolder1( $sync ) ; } if ( defined $sync->{ subfolder2 } ) { subfolder2( $sync ) ; } if ( $fixInboxINBOX and ( my $reg = fix_Inbox_INBOX_mapping( \%h1_folders_all, \%h2_folders_all ) ) ) { push @{ $sync->{ regextrans2 } }, $reg ; } if ( ( $sync->{ folder } and scalar @{ $sync->{ folder } } ) or $subscribed or scalar @folderrec ) { # folders given by option --folder if ( $sync->{ folder } and scalar @{ $sync->{ folder } } ) { add_to_requested_folders( @{ $sync->{ folder } } ) ; } # option --subscribed if ( $subscribed ) { add_to_requested_folders( keys %h1_subscribed_folder ) ; } # option --folderrec if ( scalar @folderrec ) { foreach my $folderrec ( @folderrec ) { add_to_requested_folders( $sync->{imap1}->folders( $folderrec ) ) ; } } } else { # no include, no folder/subscribed/folderrec options => all folders if ( not scalar @include ) { myprint( "Including all folders found by default. Use --subscribed or --folder or --folderrec or --include to select specific folders. Use --exclude to unselect specific folders.\n" ) ; add_to_requested_folders( @h1_folders_all ) ; } } # consider (optional) includes and excludes if ( scalar @include ) { foreach my $include ( @include ) { # No, do not add /x after the regex, never. # Users would kill you! my @included_folders = grep { /$include/ } @h1_folders_all ; add_to_requested_folders( @included_folders ) ; myprint( "Including folders matching pattern $include\n" . jux_utf8_list( @included_folders ) . "\n" ) ; } } if ( scalar @exclude ) { foreach my $exclude ( @exclude ) { my @requested_folder = sort keys %requested_folder ; # No, do not add /x after the regex, never. # Users would kill you! my @excluded_folders = grep { /$exclude/ } @requested_folder ; remove_from_requested_folders( @excluded_folders ) ; myprint( "Excluding folders matching pattern $exclude\n" . jux_utf8_list( @excluded_folders ) . "\n" ) ; } } # sort before is not very powerful # it adds --folderfirst and --folderlast even if they don't exist on host1 #@h1_folders_wanted = sort_requested_folders( ) ; $sync->{h1_folders_wanted} = [ sort_requested_folders( ) ] ; # Remove no selectable folders if ( $sync->{ checkfoldersexist } ) { my @h1_folders_wanted_exist ; myprint( "Host1: Checking wanted folders exist. Use --nocheckfoldersexist to avoid this check (shared of public namespace targeted).\n" ) ; foreach my $folder ( @{ $sync->{h1_folders_wanted} } ) { ( $sync->{ debug } or $sync->{debugfolders} ) and myprint( "Checking $folder exists on host1\n" ) ; if ( ! exists $h1_folders_all{ $folder } ) { myprint( "Host1: warning! ignoring folder $folder because it is not in host1 whole folders list.\n" ) ; next ; }else{ push @h1_folders_wanted_exist, $folder ; } } @{ $sync->{h1_folders_wanted} } = @h1_folders_wanted_exist ; }else{ myprint( "Host1: Not checking that wanted folders exist. Remove --nocheckfoldersexist to get this check.\n" ) ; } setcheckselectable( $sync ) ; checkselectable( $sync ) ; # Bugfix OpenFind folders named like "kk \*123" are in fact "kk *123" (no \) #foreach my $folder ( @{ $sync->{ h1_folders_wanted } } ) #{ # $folder =~ s{ \\\*}{ *}g ; #} # this hack is because LWP post does not pass well a hash in the $form parameter # but it does pass well an array %{ $sync->{f1f2h} } = split_around_equal( @{ $sync->{f1f2} } ) ; automap( $sync ) ; foreach my $h1_fold ( @{ $sync->{h1_folders_wanted} } ) { my $h2_fold ; $h2_fold = imap2_folder_name( $sync, $h1_fold ) ; $h2_folders_from_1_wanted{ $h2_fold }++ ; if ( 1 < $h2_folders_from_1_wanted{ $h2_fold } ) { $h2_folders_from_1_several{ $h2_fold }++ ; } } @h2_folders_from_1_wanted = sort keys %h2_folders_from_1_wanted; foreach my $h1_fold ( @h1_folders_all ) { my $h2_fold ; $h2_fold = imap2_folder_name( $sync, $h1_fold ) ; $h2_folders_from_1_all{ $h2_fold }++ ; # Follows a fix to avoid deleting folder $sync->{ subfolder2 } # because it usually does not exist on host1. if ( $sync->{ subfolder2 } ) { $h2_folders_from_1_all{ $sync->{ h2_prefix } . $sync->{ subfolder2 } }++ ; $h2_folders_from_1_all{ $sync->{ subfolder2 } }++ ; } } myprint( << 'END_LISTING' ) ; ++++ Listing folders All foldernames are presented between brackets like [X] where X is the foldername. When a foldername contains non-ASCII characters it is presented in the form [X] = [Y] where X is the imap foldername you have to use in command line options and Y is the utf8 output just printed for convenience, to recognize it. END_LISTING myprint( "Host1: folders list (first the raw imap format then the [X] = [Y]):\n", $sync->{imap1}->list( ), "\n", jux_utf8_list( @h1_folders_all ), "\n", "Host2: folders list (first the raw imap format then the [X] = [Y]):\n", $sync->{imap2}->list( ), "\n", jux_utf8_list( @h2_folders_all ), "\n", q{} ) ; if ( $subscribed ) { myprint( 'Host1 subscribed folders list: ', jux_utf8_list( sort keys %h1_subscribed_folder ), "\n", ) ; } @h2_folders_not_in_1 = list_folders_in_2_not_in_1( ) ; if ( @h2_folders_not_in_1 ) { myprint( "Folders in host2 not in host1:\n", jux_utf8_list( @h2_folders_not_in_1 ), "\n" ) ; } if ( keys %{ $sync->{f1f2auto} } ) { myprint( "Folders mapping from --automap feature (use --f1f2 to override any mapping):\n" ) ; foreach my $h1_fold ( keys %{ $sync->{f1f2auto} } ) { my $h2_fold = $sync->{f1f2auto}{$h1_fold} ; myprintf( "%-40s -> %-40s\n", jux_utf8( $h1_fold ), jux_utf8( $h2_fold ) ) ; } myprint( "\n" ) ; } if ( keys %{ $sync->{f1f2h} } ) { myprint( "Folders mapping from --f1f2 options, it overrides --automap:\n" ) ; foreach my $h1_fold ( keys %{ $sync->{f1f2h} } ) { my $h2_fold = $sync->{f1f2h}{$h1_fold} ; my $warn = q{} ; if ( not exists $h1_folders_all{ $h1_fold } ) { $warn = "BUT $h1_fold does NOT exist on host1!" ; } myprintf( "%-40s -> %-40s %s\n", jux_utf8( $h1_fold ), jux_utf8( $h2_fold ), $warn ) ; } myprint( "\n" ) ; } exit_clean( $sync, $EX_OK, "Exiting because of --justfolderlists\n" ) if ( $sync->{ justfolderlists } ) ; exit_clean( $sync, $EX_OK, "Exiting because of --justautomap\n" ) if ( $sync->{ justautomap } ) ; debugsleep( $sync ) ; if ( $sync->{ skipemptyfolders } ) { myprint( "Host1: will not syncing empty folders on host1. Use --noskipemptyfolders to create them anyway on host2\n") ; } if ( $sync->{ checknoabletosearch } ) { myprint( "Checking SEARCH ALL works on both accounts. To avoid that check, use --nochecknoabletosearch\n" ) ; my $check1 = checknoabletosearch( $sync, $sync->{ imap1 }, 'INBOX', 'Host1' ) ; my $check2 = checknoabletosearch( $sync, $sync->{ imap2 }, 'INBOX', 'Host2' ) ; if ( $check1 or $check2 ) { myprint( "At least one account can not SEARCH ALL. So acting like --noabletosearch\n" ) ; $sync->{abletosearch} = 0 ; $sync->{abletosearch1} = 0 ; $sync->{abletosearch2} = 0 ; } else { myprint( "Good! SEARCH ALL works on both accounts.\n" ) ; } } if ( $sync->{ foldersizes } ) { foldersizes_at_the_beggining( $sync ) ; #foldersizes_at_the_beggining_old( $sync ) ; } if ( $sync->{ justfoldersizes } ) { exit_clean( $sync, $EX_OK, "Exiting because of --justfoldersizes\n" ) ; } $sync->{can_do_stats} = 1 ; if ( $sync->{ delete1emptyfolders } ) { delete1emptyfolders( $sync ) ; } delete_folders_in_2_not_in_1( ) if $delete2folders ; # folder loop $h1_folders_wanted_nb = scalar @{ $sync->{h1_folders_wanted} } ; myprint( "++++ Looping on each one of $h1_folders_wanted_nb folders to sync\n" ) ; $sync->{begin_transfer_time} = time ; my %uid_candidate_for_deletion ; my %uid_candidate_no_deletion ; $sync->{ h2_folders_of_md5 } = { } ; FOLDER: foreach my $h1_fold ( @{ $sync->{h1_folders_wanted} } ) { $sync->{ h1_current_folder } = $h1_fold ; eta_print( $sync ) ; abortifneeded( $sync ) ; if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; } my $h2_fold = imap2_folder_name( $sync, $h1_fold ) ; $sync->{ h2_current_folder } = $h2_fold ; $h1_folders_wanted_ct++ ; myprintf( "Folder %7s %-35s -> %-35s\n", "$h1_folders_wanted_ct/$h1_folders_wanted_nb", jux_utf8( $h1_fold ), jux_utf8( $h2_fold ) ) ; myprint( debugmemory( $sync, " at folder loop" ) ) ; # host1 can not be fetched read only, select is needed because of expunge. select_folder( $sync, $sync->{imap1}, $h1_fold, 'Host1' ) or next FOLDER ; debugsleep( $sync ) ; my $h1_msgs_all_hash_ref ; my @h1_msgs ; my $h1_msgs_nb ; my $h1_msgs_nb_from_select ; $h1_msgs_nb_from_select = count_from_select( $sync->{imap1}->History ) ; myprint( "Host1: folder [$h1_fold] has $h1_msgs_nb_from_select messages in total (mentioned by SELECT)\n" ) ; if ( $sync->{ skipemptyfolders } and 0 == $h1_msgs_nb_from_select ) { myprint( "Host1: skipping empty host1 folder [$h1_fold]\n" ) ; next FOLDER ; } # Code added from https://github.com/imapsync/imapsync/issues/95 # Thanks jh1995 # Goal: do not create folder if --search or --max/minage return 0 message. # even if there are messages by SELECT (no not real empty, empty for the user point of vue). if ( $sync->{ skipemptyfolders } or $sync->{ dry } ) { $h1_msgs_all_hash_ref = { } ; @h1_msgs = select_msgs( $sync->{imap1}, $h1_msgs_all_hash_ref, $sync->{ search1 }, $sync->{abletosearch1}, $h1_fold ) ; $h1_msgs_nb = scalar( @h1_msgs ) ; if ( 0 == $h1_msgs_nb and $sync->{ skipemptyfolders } ) { myprint( "Host1: skipping empty host1 folder [$h1_fold] (0 message found by SEARCH)\n" ) ; next FOLDER ; } } if ( ! exists $h2_folders_all{ $h2_fold } ) { # In --dry mode I could count the messages to be transfered instead of 0 # Messages transferred : 0 (could be 0 without dry mode) if ( ! create_folder( $sync, $sync->{imap2}, $h2_fold, $h1_fold ) ) { if ( $sync->{ dry } ) { $nb_msg_skipped_dry_mode += $h1_msgs_nb ; } next FOLDER ; } } acls_sync( $sync, $h1_fold, $h2_fold ) ; # Sometimes the folder on host2 is listed (it exists) but is # not selectable but becomes selectable by a create (Gmail) select_folder( $sync, $sync->{imap2}, $h2_fold, 'Host2' ) or ( create_folder( $sync, $sync->{imap2}, $h2_fold, $h1_fold ) and select_folder( $sync, $sync->{imap2}, $h2_fold, 'Host2' ) ) or next FOLDER ; my @select_results = $sync->{imap2}->Results( ) ; my $h2_fold_nb_messages = count_from_select( @select_results ) ; myprint( "Host2: folder [$h2_fold] has $h2_fold_nb_messages messages in total (mentioned by SELECT)\n" ) ; my $permanentflags2 = permanentflags( $sync, @select_results ) ; myprint( "Host2: folder [$h2_fold] permanentflags: $permanentflags2\n" ) ; if ( $sync->{ expunge1 } ) { myprint( "Host1: Expunging $h1_fold $sync->{dry_message}\n" ) ; if ( ! $sync->{dry} ) { $sync->{imap1}->expunge( ) ; } } if ( ( ( $subscribe and exists $h1_subscribed_folder{ $h1_fold } ) or $subscribeall ) and not exists $h2_subscribed_folder{ $h2_fold } ) { myprint( "Host2: Subscribing to folder $h2_fold\n" ) ; if ( ! $sync->{dry} ) { $sync->{imap2}->subscribe( $h2_fold ) } ; } next FOLDER if ( $sync->{ justfolders } ) ; if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; } if ( ! defined $h1_msgs_nb ) { $h1_msgs_all_hash_ref = { } ; @h1_msgs = select_msgs( $sync->{imap1}, $h1_msgs_all_hash_ref, $sync->{ search1 }, $sync->{abletosearch1}, $h1_fold ); $h1_msgs_nb = scalar @h1_msgs ; }else{ # select_msgs already done. } if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; } myprint( "Host1: folder [$h1_fold] considering $h1_msgs_nb messages\n" ) ; ( $sync->{ debug } or $debuglist ) and myprint( "Host1: folder [$h1_fold] considering $h1_msgs_nb messages, LIST gives: @h1_msgs\n" ) ; $sync->{ debug } and myprint( "Host1: selecting messages of folder [$h1_fold] took ", timenext( $sync ), " s\n" ) ; my $h2_msgs_all_hash_ref = { } ; my @h2_msgs = select_msgs( $sync->{imap2}, $h2_msgs_all_hash_ref, $sync->{ search2 }, $sync->{abletosearch2}, $h2_fold ) ; if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; } my $h2_msgs_nb = scalar @h2_msgs ; myprint( "Host2: folder [$h2_fold] considering $h2_msgs_nb messages\n" ) ; ( $sync->{ debug } or $debuglist ) and myprint( "Host2: folder [$h2_fold] considering $h2_msgs_nb messages, LIST gives: @h2_msgs\n" ) ; $sync->{ debug } and myprint( "Host2: selecting messages of folder [$h2_fold] took ", timenext( $sync ), " s\n" ) ; my $cache_base = "$sync->{ tmpdir }/imapsync_cache/" ; my $cache_dir = cache_folder( $cache_base, "$sync->{host1}/$sync->{user1}/$sync->{host2}/$sync->{user2}", $h1_fold, $h2_fold ) ; my ( $cache_1_2_ref, $cache_2_1_ref ) = ( {}, {} ) ; my $h1_uidvalidity = $sync->{imap1}->uidvalidity( ) || q{} ; my $h2_uidvalidity = $sync->{imap2}->uidvalidity( ) || q{} ; if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; } if ( $usecache ) { myprint( "Local cache directory: $cache_dir ( " . length( $cache_dir ) . " characters long )\n" ) ; mkpath( "$cache_dir" ) ; ( $cache_1_2_ref, $cache_2_1_ref ) = get_cache( $cache_dir, \@h1_msgs, \@h2_msgs, $h1_msgs_all_hash_ref, $h2_msgs_all_hash_ref ) ; myprint( 'CACHE h1 h2: ', scalar keys %{ $cache_1_2_ref } , " files\n" ) ; $sync->{ debug } and myprint( '[', map ( { "$_->$cache_1_2_ref->{$_} " } keys %{ $cache_1_2_ref } ), " ]\n" ) ; } my %h1_hash = ( ) ; my %h2_hash = ( ) ; my ( %h1_msgs, %h2_msgs ) ; @h1_msgs{ @h1_msgs } = ( ) ; @h2_msgs{ @h2_msgs } = ( ) ; my @h1_msgs_in_cache = sort { $a <=> $b } keys %{ $cache_1_2_ref } ; my @h2_msgs_in_cache = sort { $a <=> $b } keys %{ $cache_2_1_ref } ; my ( %h1_msgs_not_in_cache, %h2_msgs_not_in_cache ) ; %h1_msgs_not_in_cache = %h1_msgs ; %h2_msgs_not_in_cache = %h2_msgs ; delete @h1_msgs_not_in_cache{ @h1_msgs_in_cache } ; delete @h2_msgs_not_in_cache{ @h2_msgs_in_cache } ; my @h1_msgs_not_in_cache = sort { $a <=> $b } keys %h1_msgs_not_in_cache ; #myprint( "h1_msgs_not_in_cache: [@h1_msgs_not_in_cache]\n" ) ; my @h2_msgs_not_in_cache = sort { $a <=> $b } keys %h2_msgs_not_in_cache ; my @h2_msgs_delete2_not_in_cache = () ; %h1_msgs_copy_by_uid = ( ) ; if ( $useuid ) { # use uid so we have to avoid getting header @h1_msgs_copy_by_uid{ @h1_msgs_not_in_cache } = ( ) ; @h2_msgs_delete2_not_in_cache = @h2_msgs_not_in_cache if $usecache ; @h1_msgs_not_in_cache = ( ) ; @h2_msgs_not_in_cache = ( ) ; #myprint( "delete2: @h2_msgs_delete2_not_in_cache\n" ) ; } $sync->{ debug } and myprint( "Host1: parsing headers of folder [$h1_fold]\n" ) ; my ($h1_heads_ref, $h1_fir_ref) = ({}, {}); $h1_heads_ref = $sync->{imap1}->parse_headers([@h1_msgs_not_in_cache], @useheader) if (@h1_msgs_not_in_cache); $sync->{ debug } and myprint( "Host1: parsing headers of folder [$h1_fold] took ", timenext( $sync ), " s\n" ) ; @{ $h1_fir_ref }{@h1_msgs} = ( undef ) ; $sync->{ debug } and myprint( "Host1: getting flags idate and sizes of folder [$h1_fold]\n" ) ; my @h1_common_fetch_param = ( 'FLAGS', 'INTERNALDATE', 'RFC822.SIZE' ) ; if ( $sync->{ synclabels } or $sync->{ resynclabels } ) { push @h1_common_fetch_param, 'X-GM-LABELS' ; } if ( $sync->{abletosearch1} ) { $h1_fir_ref = $sync->{imap1}->fetch_hash( \@h1_msgs, @h1_common_fetch_param, $h1_fir_ref ) if ( @h1_msgs ) ; } else { my $fetch_hash_uids = $fetch_hash_set || "1:*" ; $h1_fir_ref = $sync->{imap1}->fetch_hash( $fetch_hash_uids, @h1_common_fetch_param, $h1_fir_ref ) if ( @h1_msgs ) ; } $sync->{ debug } and myprint( "Host1: getting flags idate and sizes of folder [$h1_fold] took ", timenext( $sync ), " s\n" ) ; if ( ! $h1_fir_ref ) { my $error = join( q{}, "Host1: folder $h1_fold : Could not fetch_hash ", scalar @h1_msgs, ' msgs: ', $sync->{imap1}->LastError || q{}, "\n" ) ; errors_incr( $sync, $error ) ; next FOLDER ; } my @h1_msgs_duplicate; foreach my $m ( @h1_msgs_not_in_cache ) { my $rc = parse_header_msg( $sync, $sync->{imap1}, $m, $h1_heads_ref, $h1_fir_ref, 'Host1', \%h1_hash ) ; if ( ! defined $rc ) { my $h1_size = $h1_fir_ref->{$m}->{'RFC822.SIZE'} || 0; myprint( "Host1: $h1_fold/$m size $h1_size ignored (no wanted headers so we ignore this message. To solve this: use --addheader)\n" ) ; $sync->{ total_bytes_skipped } += $h1_size ; $sync->{ nb_msg_skipped } += 1 ; $sync->{ h1_nb_msg_noheader } +=1 ; $sync->{ h1_nb_msg_processed } +=1 ; } elsif( 0 == $rc ) { # duplicate push @h1_msgs_duplicate, $m; # duplicate, same id same size? my $h1_size = $h1_fir_ref->{$m}->{'RFC822.SIZE'} || 0; $sync->{ acc1 }->{ nb_msg_duplicate } += 1; if ( ! $sync->{ syncduplicates } ) { $sync->{ nb_msg_skipped } += 1 ; $sync->{ h1_nb_msg_processed } +=1 ; } } } my $h1_msgs_duplicate_nb = scalar @h1_msgs_duplicate ; myprint( "Host1: folder [$h1_fold] selected $h1_msgs_nb messages, duplicates $h1_msgs_duplicate_nb\n" ) ; $sync->{ debug } and myprint( 'Host1: whole time parsing headers took ', timenext( $sync ), " s\n" ) ; # Getting headers and metada can be so long that host2 might be disconnected here if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; } $sync->{ debug } and myprint( "Host2: parsing headers of folder [$h2_fold]\n" ) ; my ($h2_heads_ref, $h2_fir_ref) = ( {}, {} ); $h2_heads_ref = $sync->{imap2}->parse_headers([@h2_msgs_not_in_cache], @useheader) if (@h2_msgs_not_in_cache); $sync->{ debug } and myprint( "Host2: parsing headers of folder [$h2_fold] took ", timenext( $sync ), " s\n" ) ; $sync->{ debug } and myprint( "Host2: getting flags idate and sizes of folder [$h2_fold]\n" ) ; @{ $h2_fir_ref }{@h2_msgs} = ( ); # fetch_hash can select by uid with last arg as ref my @h2_common_fetch_param = ( 'FLAGS', 'INTERNALDATE', 'RFC822.SIZE' ) ; if ( $sync->{ synclabels } or $sync->{ resynclabels } ) { push @h2_common_fetch_param, 'X-GM-LABELS' ; } if ( $sync->{abletosearch2} and scalar( @h2_msgs ) ) { $h2_fir_ref = $sync->{imap2}->fetch_hash( \@h2_msgs, @h2_common_fetch_param, $h2_fir_ref) ; }else{ my $fetch_hash_uids = $fetch_hash_set || "1:*" ; $h2_fir_ref = $sync->{imap2}->fetch_hash( $fetch_hash_uids, @h2_common_fetch_param, $h2_fir_ref ) if ( @h2_msgs ) ; } $sync->{ debug } and myprint( "Host2: getting flags idate and sizes of folder [$h2_fold] took ", timenext( $sync ), " s\n" ) ; my @h2_msgs_duplicate; foreach my $m (@h2_msgs_not_in_cache) { my $rc = parse_header_msg( $sync, $sync->{imap2}, $m, $h2_heads_ref, $h2_fir_ref, 'Host2', \%h2_hash ) ; my $h2_size = $h2_fir_ref->{$m}->{'RFC822.SIZE'} || 0 ; if (! defined $rc ) { myprint( "Host2: $h2_fold/$m size $h2_size ignored (no wanted headers so we ignore this message)\n" ) ; $h2_nb_msg_noheader += 1 ; } elsif( 0 == $rc ) { # duplicate $sync->{ acc2 }->{ nb_msg_duplicate } += 1 ; push @h2_msgs_duplicate, $m ; } } # %h2_folders_of_md5 foreach my $md5 ( keys %h2_hash ) { $sync->{ h2_folders_of_md5 }->{ $md5 }->{ $h2_fold } ++ ; } # %h1_folders_of_md5 foreach my $md5 ( keys %h1_hash ) { $sync->{ h1_folders_of_md5 }->{ $md5 }->{ $h2_fold } ++ ; } my $h2_msgs_duplicate_nb = scalar @h2_msgs_duplicate ; myprint( "Host2: folder [$h2_fold] selected $h2_msgs_nb messages, duplicates $h2_msgs_duplicate_nb\n" ) ; $sync->{ debug } and myprint( 'Host2 whole time parsing headers took ', timenext( $sync ), " s\n" ) ; $sync->{ debug } and myprint( "++++ Verifying [$h1_fold] -> [$h2_fold]\n" ) ; # messages in host1 that are not in host2 my @h1_hash_keys_sorted_by_uid = sort {$h1_hash{$a}{'m'} <=> $h1_hash{$b}{'m'}} keys %h1_hash; #myprint( map { $h1_hash{$_}{'m'} . q{ }} @h1_hash_keys_sorted_by_uid ) ; my @h2_hash_keys_sorted_by_uid = sort {$h2_hash{$a}{'m'} <=> $h2_hash{$b}{'m'}} keys %h2_hash; # Deletions on account2. if( $sync->{ delete2duplicates } and not exists $h2_folders_from_1_several{ $h2_fold } ) { my @h2_expunge ; foreach my $h2_msg ( @h2_msgs_duplicate ) { myprint( "Host2: msg $h2_fold/$h2_msg marked \\Deleted [duplicate] on host2 $sync->{dry_message}\n" ) ; push @h2_expunge, $h2_msg if $sync->{ uidexpunge2 } ; if ( ! $sync->{ dry } ) { $sync->{ imap2 }->delete_message( $h2_msg ) ; $sync->{ acc2 }->{ nb_msg_deleted } += 1 ; } } my $cnt = scalar @h2_expunge ; if( @h2_expunge and not $sync->{ expunge2 } ) { myprint( "Host2: UidExpunging $cnt message(s) in folder $h2_fold $sync->{dry_message}\n" ) ; $sync->{imap2}->uidexpunge( \@h2_expunge ) if ! $sync->{dry} ; } if ( $sync->{ expunge2 } ){ myprint( "Host2: Expunging folder $h2_fold $sync->{dry_message}\n" ) ; $sync->{imap2}->expunge( ) if ! $sync->{dry} ; } } if( $sync->{ delete2 } and not exists $h2_folders_from_1_several{ $h2_fold } ) { # No host1 folders f1a f1b ... going all to same f2 (via --regextrans2) my @h2_expunge; foreach my $m_id (@h2_hash_keys_sorted_by_uid) { #myprint( "$m_id " ) ; if ( ! exists $h1_hash{$m_id} ) { my $h2_msg = $h2_hash{$m_id}{'m'}; my $h2_flags = $h2_hash{$m_id}{'F'} || q{}; my $isdel = $h2_flags =~ /\B\\Deleted\b/x ? 1 : 0; myprint( "Host2: msg $h2_fold/$h2_msg marked \\Deleted on host2 [$m_id] $sync->{dry_message}\n" ) if ! $isdel; push @h2_expunge, $h2_msg if $sync->{ uidexpunge2 }; if ( ! ( $sync->{ dry } or $isdel ) ) { $sync->{ imap2 }->delete_message( $h2_msg ); $sync->{ acc2 }->{ nb_msg_deleted } += 1; } } } foreach my $h2_msg ( @h2_msgs_delete2_not_in_cache ) { myprint( "Host2: msg $h2_fold/$h2_msg marked \\Deleted [not in cache] on host2 $sync->{dry_message}\n" ) ; push @h2_expunge, $h2_msg if $sync->{ uidexpunge2 }; if ( ! $sync->{dry} ) { $sync->{ imap2 }->delete_message( $h2_msg ); $sync->{ acc2 }->{ nb_msg_deleted } += 1; } } my $cnt = scalar @h2_expunge ; if( @h2_expunge and not $sync->{ expunge2 } ) { myprint( "Host2: UidExpunging $cnt message(s) in folder $h2_fold $sync->{dry_message}\n" ) ; $sync->{imap2}->uidexpunge( \@h2_expunge ) if ! $sync->{dry} ; } if ( $sync->{ expunge2 } ) { myprint( "Host2: Expunging folder $h2_fold $sync->{dry_message}\n" ) ; $sync->{imap2}->expunge( ) if ! $sync->{dry} ; } } if( $sync->{ delete2 } and exists $h2_folders_from_1_several{ $h2_fold } ) { myprint( "Host2: folder $h2_fold $h2_folders_from_1_several{ $h2_fold } folders left to sync there\n" ) ; my @h2_expunge; foreach my $m_id ( @h2_hash_keys_sorted_by_uid ) { my $h2_msg = $h2_hash{ $m_id }{ 'm' } ; if ( ! exists $h1_hash{ $m_id } ) { my $h2_flags = $h2_hash{ $m_id }{ 'F' } || q{} ; my $isdel = $h2_flags =~ /\B\\Deleted\b/x ? 1 : 0 ; if ( ! $isdel ) { $sync->{ debug } and myprint( "Host2: msg $h2_fold/$h2_msg candidate for deletion [$m_id]\n" ) ; $uid_candidate_for_deletion{ $h2_fold }{ $h2_msg }++ ; } }else{ $sync->{ debug } and myprint( "Host2: msg $h2_fold/$h2_msg will cancel deletion [$m_id]\n" ) ; $uid_candidate_no_deletion{ $h2_fold }{ $h2_msg }++ ; } } foreach my $h2_msg ( @h2_msgs_delete2_not_in_cache ) { myprint( "Host2: msg $h2_fold/$h2_msg candidate for deletion [not in cache]\n" ) ; $uid_candidate_for_deletion{ $h2_fold }{ $h2_msg }++ ; } foreach my $h2_msg ( @h2_msgs_in_cache ) { myprint( "Host2: msg $h2_fold/$h2_msg will cancel deletion [in cache]\n" ) ; $uid_candidate_no_deletion{ $h2_fold }{ $h2_msg }++ ; } if ( 0 == $h2_folders_from_1_several{ $h2_fold } ) { # last host1 folder going to $h2_fold myprint( "Last host1 folder going to $h2_fold\n" ) ; foreach my $h2_msg ( keys %{ $uid_candidate_for_deletion{ $h2_fold } } ) { $sync->{ debug } and myprint( "Host2: msg $h2_fold/$h2_msg candidate for deletion\n" ) ; if ( exists $uid_candidate_no_deletion{ $h2_fold }{ $h2_msg } ) { $sync->{ debug } and myprint( "Host2: msg $h2_fold/$h2_msg canceled deletion\n" ) ; }else{ myprint( "Host2: msg $h2_fold/$h2_msg marked \\Deleted $sync->{dry_message}\n" ) ; push @h2_expunge, $h2_msg if $sync->{ uidexpunge2 } ; if ( ! $sync->{ dry} ) { $sync->{ imap2 }->delete_message( $h2_msg ) ; $sync->{ acc2 }->{ nb_msg_deleted } += 1 ; } } } } my $cnt = scalar @h2_expunge ; if( @h2_expunge and not $sync->{ expunge2 } ) { myprint( "Host2: UidExpunging $cnt message(s) in folder $h2_fold $sync->{dry_message}\n" ) ; $sync->{imap2}->uidexpunge( \@h2_expunge ) if ! $sync->{dry} ; } if ( $sync->{ expunge2 } ) { myprint( "Host2: Expunging host2 folder $h2_fold $sync->{dry_message}\n" ) ; $sync->{imap2}->expunge( ) if ! $sync->{dry} ; } $h2_folders_from_1_several{ $h2_fold }-- ; } my $h2_uidnext = $sync->{imap2}->uidnext( $h2_fold ) ; $sync->{ debug } and myprint( "Host2: uidnext is $h2_uidnext\n" ) ; $h2_uidguess = $h2_uidnext ; # Getting host2 headers, metada and delete2 stuff can be so long that host1 might be disconnected here if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; } my @h1_msgs_to_delete ; MESS: foreach my $m_id (@h1_hash_keys_sorted_by_uid) { abortifneeded( $sync ) ; if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; } #myprint( "h1_nb_msg_processed: $sync->{ h1_nb_msg_processed }\n" ) ; my $h1_size = $h1_hash{$m_id}{'s'}; my $h1_msg = $h1_hash{$m_id}{'m'}; my $h1_idate = $h1_hash{$m_id}{'D'}; #my $labels = labels( $sync->{imap1}, $h1_msg ) ; #print "LABELS: $labels\n" ; if ( ( not exists $h2_hash{ $m_id } ) and ( not ( exists $sync->{ h2_folders_of_md5 }->{ $m_id } ) or not $skipcrossduplicates ) ) { # copy my $h2_msg = copy_message( $sync, $h1_msg, $h1_fold, $h2_fold, $h1_fir_ref, $permanentflags2, $cache_dir ) ; if ( $h2_msg and $sync->{ delete1 } and not $sync->{ expungeaftereach } ) { # not expunged push @h1_msgs_to_delete, $h1_msg ; } # A bug here with imapsync 1.920, fixed in 1.921 # Added $h2_msg in the condition. Errors of APPEND were not counted as missing messages on host2! if ( $h2_msg and not $sync->{ dry } ) { $sync->{ h2_folders_of_md5 }->{ $m_id }->{ $h2_fold } ++ ; } # if( $sync->{ delete2 } and ( exists $h2_folders_from_1_several{ $h2_fold } ) and $h2_msg ) { myprint( "Host2: msg $h2_fold/$h2_msg will cancel deletion [fresh copy] on host2\n" ) ; $uid_candidate_no_deletion{ $h2_fold }{ $h2_msg }++ ; } if ( total_bytes_max_reached( $sync ) ) { # Still a bug when using --delete1 --noexpungeaftereach # same thing below on all total_bytes_max_reached! last FOLDER ; } next MESS; } else { # already on host2 if ( exists $h2_hash{ $m_id } ) { my $h2_msg = $h2_hash{$m_id}{'m'} ; $sync->{ debug } and myprint( "Host1: found that msg $h1_fold/$h1_msg equals Host2 $h2_fold/$h2_msg\n" ) ; if ( $usecache ) { $debugcache and myprint( "touch $cache_dir/${h1_msg}_$h2_msg\n" ) ; touch( "$cache_dir/${h1_msg}_$h2_msg" ) or croak( "Couldn't touch $cache_dir/${h1_msg}_$h2_msg" ) ; } } elsif( exists $sync->{ h2_folders_of_md5 }->{ $m_id } ) { my @folders_dup = keys %{ $sync->{ h2_folders_of_md5 }->{ $m_id } } ; ( $sync->{ debug } or $debugcrossduplicates ) and myprint( "Host1: found that msg $h1_fold/$h1_msg is also in Host2 folders @folders_dup\n" ) ; $sync->{ h2_nb_msg_crossdup } +=1 ; } $sync->{ total_bytes_skipped } += $h1_size ; $sync->{ nb_msg_skipped } += 1 ; $sync->{ h1_nb_msg_processed } +=1 ; } if ( exists $h2_hash{ $m_id } ) { #$debug and myprint( "MESSAGE $m_id\n" ) ; my $h2_msg = $h2_hash{$m_id}{'m'}; if ( $sync->{resyncflags} ) { sync_flags_fir( $sync, $h1_fold, $h1_msg, $h2_fold, $h2_msg, $permanentflags2, $h1_fir_ref, $h2_fir_ref ) ; } # Good my $h2_size = $h2_hash{$m_id}{'s'}; $sync->{ debug } and myprint( "Host1: size msg $h1_fold/$h1_msg = $h1_size <> $h2_size = Host2 $h2_fold/$h2_msg\n" ) ; if ( $sync->{ resynclabels } ) { resynclabels( $sync, $h1_msg, $h2_msg, $h1_fir_ref, $h2_fir_ref, $h1_fold ) } } if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; } if ( $sync->{ delete1 } ) { push @h1_msgs_to_delete, $h1_msg ; } } # END MESS: loop # @h1_msgs_in_cache are already synced too. delete_message_on_host1( $sync, $h1_fold, $sync->{ expunge1 }, @h1_msgs_to_delete, @h1_msgs_in_cache ) ; if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; } # MESS_IN_CACHE: if ( ! $sync->{ delete1 } ) { foreach my $h1_msg ( @h1_msgs_in_cache ) { my $h2_msg = $cache_1_2_ref->{ $h1_msg } ; $debugcache and myprint( "cache messages update flags $h1_msg->$h2_msg\n" ) ; if ( $sync->{resyncflags} ) { sync_flags_fir( $sync, $h1_fold, $h1_msg, $h2_fold, $h2_msg, $permanentflags2, $h1_fir_ref, $h2_fir_ref ) ; } my $h1_size = $h1_fir_ref->{ $h1_msg }->{ 'RFC822.SIZE' } || 0 ; $sync->{ total_bytes_skipped } += $h1_size; $sync->{ nb_msg_skipped } += 1; $sync->{ h1_nb_msg_processed } +=1 ; } } if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; } @h1_msgs_to_delete = ( ) ; #myprint( "Messages by uid: ", map { "$_ " } keys %h1_msgs_copy_by_uid, "\n" ) ; # MESS_BY_UID: foreach my $h1_msg ( sort { $a <=> $b } keys %h1_msgs_copy_by_uid ) { abortifneeded( $sync ) ; $sync->{ debug } and myprint( "Copy by uid $h1_fold/$h1_msg\n" ) ; if ( ! reconnect_12_if_needed( $sync ) ) { last FOLDER ; } my $h2_msg = copy_message( $sync, $h1_msg, $h1_fold, $h2_fold, $h1_fir_ref, $permanentflags2, $cache_dir ) ; if( $sync->{ delete2 } and exists $h2_folders_from_1_several{ $h2_fold } and $h2_msg ) { myprint( "Host2: msg $h2_fold/$h2_msg will cancel deletion [fresh copy] on host2\n" ) ; $uid_candidate_no_deletion{ $h2_fold }{ $h2_msg }++ ; } last FOLDER if total_bytes_max_reached( $sync ) ; } if ( $sync->{ expunge1 } ){ myprint( "Host1: Expunging folder $h1_fold $sync->{dry_message}\n" ) ; if ( ! $sync->{dry} ) { $sync->{imap1}->expunge( ) } ; } if ( $sync->{ expunge2 } ){ myprint( "Host2: Expunging folder $h2_fold $sync->{dry_message}\n" ) ; if ( ! $sync->{dry} ) { $sync->{imap2}->expunge( ) } ; } $sync->{ debug } and myprint( 'Time: ', timenext( $sync ), " s\n" ) ; } eta_print( $sync ) ; myprint( "++++ End looping on each folder\n" ) ; if ( $sync->{ delete1 } and $sync->{ delete1emptyfolders } ) { delete1emptyfolders( $sync ) ; } ( $sync->{ debug } or $sync->{debugfolders} ) and myprint( 'Time: ', timenext( $sync ), " s\n" ) ; if ( $sync->{ foldersizesatend } ) { myprint( << 'END_SIZE' ) ; Folders sizes after the synchronization. You can remove this foldersizes listing by using "--nofoldersizesatend" END_SIZE foldersizesatend( $sync ) ; } #$sync->{imap1}->State( 0 ); # Unconnected if ( ! lost_connection( $sync, $sync->{imap1}, "for host1 [$sync->{host1}]" ) ) { $sync->{imap1}->logout( ) ; } if ( ! lost_connection( $sync, $sync->{imap2}, "for host2 [$sync->{host2}]" ) ) { $sync->{imap2}->logout( ) ; } do_and_print_stats( $sync ) ; if ( $sync->{ nb_errors } ) { myprint( errors_listing( $sync ) ) ; } if ( $sync->{ testslive } or $sync->{ testslive6 } ) { tests_live_result( $sync->{ nb_errors } ) ; } if ( $sync->{ nb_errors } ) { my $exit_value = exit_value( $sync, $sync->{ most_common_error } ) ; exit_clean( $sync, $exit_value ) ; } else { exit_clean( $sync, $EX_OK ) ; } return ; } # END of sub single_sync # subroutines sub myprint { #print @ARG ; print { $sync->{ tee } || \*STDOUT } @ARG ; return ; } sub myprintf { printf { $sync->{ tee } || \*STDOUT } @ARG ; return ; } sub mysprintf { my( $format, @list ) = @ARG ; return sprintf $format, @list ; } sub output_start { my $mysync = shift @ARG ; if ( not $mysync ) { return ; } my @output = @ARG ; $mysync->{ output } = join( q{}, @output ) . ( $mysync->{ output } || q{} ) ; return $mysync->{ output } ; } sub tests_output_start { note( 'Entering tests_output_start()' ) ; my $mysync = { } ; is( undef, output_start( ), 'output_start: no args => undef' ) ; is( q{}, output_start( $mysync ), 'output_start: one arg => ""' ) ; is( 'rrrr', output_start( $mysync, 'rrrr' ), 'output_start: rrrr => rrrr' ) ; is( 'aaaarrrr', output_start( $mysync, 'aaaa' ), 'output_start: aaaa => aaaarrrr' ) ; is( "\naaaarrrr", output_start( $mysync, "\n" ), 'output_start: \n => \naaaarrrr' ) ; is( "ABC\naaaarrrr", output_start( $mysync, 'A', 'B', 'C' ), 'output_start: A B C => ABC\naaaarrrr' ) ; note( 'Leaving tests_output_start()' ) ; return ; } sub tests_output { note( 'Entering tests_output()' ) ; my $mysync = { } ; is( undef, output( ), 'output: no args => undef' ) ; is( q{}, output( $mysync ), 'output: one arg => ""' ) ; is( 'rrrr', output( $mysync, 'rrrr' ), 'output: rrrr => rrrr' ) ; is( 'rrrraaaa', output( $mysync, 'aaaa' ), 'output: aaaa => rrrraaaa' ) ; is( "rrrraaaa\n", output( $mysync, "\n" ), 'output: \n => rrrraaaa\n' ) ; is( "rrrraaaa\nABC", output( $mysync, 'A', 'B', 'C' ), 'output: A B C => rrrraaaaABC\n' ) ; note( 'Leaving tests_output()' ) ; return ; } sub output { my $mysync = shift @ARG ; if ( not $mysync ) { return ; } my @output = @ARG ; $mysync->{ output } .= join( q{}, @output ) ; return $mysync->{ output } ; } sub tests_output_reset_with { note( 'Entering tests_output_reset_with()' ) ; my $mysync = { } ; is( undef, output_reset_with( ), 'output_reset_with: no args => undef' ) ; is( q{}, output_reset_with( $mysync ), 'output_reset_with: one arg => ""' ) ; is( 'rrrr', output_reset_with( $mysync, 'rrrr' ), 'output_reset_with: rrrr => rrrr' ) ; is( 'aaaa', output_reset_with( $mysync, 'aaaa' ), 'output_reset_with: aaaa => aaaa' ) ; is( "\n", output_reset_with( $mysync, "\n" ), 'output_reset_with: \n => \n' ) ; note( 'Leaving tests_output_reset_with()' ) ; return ; } sub output_reset_with { my $mysync = shift @ARG ; if ( not $mysync ) { return ; } my @output = @ARG ; $mysync->{ output } = join( q{}, @output ) ; return $mysync->{ output } ; } sub tests_print_output_if_needed { note( 'Entering tests_print_output_if_needed()' ) ; is( undef, print_output_if_needed( ), 'print_output_if_needed: no args => undef' ) ; my $mysync = { } ; is( q{}, print_output_if_needed( $mysync ), 'print_output_if_needed: undef => undef' ) ; output( $mysync, "Hello\n" ) ; is( "Hello\n", print_output_if_needed( $mysync ), 'print_output_if_needed: Hello => Hello' ) ; $mysync->{ dockercontext } = 1 ; is( "Hello\n", print_output_if_needed( $mysync ), 'print_output_if_needed: dockercontext + Hello => Hello' ) ; $mysync->{ version } = 1 ; is( q{}, print_output_if_needed( $mysync ), 'print_output_if_needed: dockercontext + Hello + --version => ""' ) ; $mysync->{ dockercontext } = 0 ; is( "Hello\n", print_output_if_needed( $mysync ), 'print_output_if_needed: Hello + --version => Hello' ) ; note( 'Leaving tests_print_output_if_needed()' ) ; return ; } sub print_output_if_needed { my $mysync = shift @ARG ; if ( ! defined $mysync ) { return ; } my $output = output( $mysync ) ; if ( $mysync->{ version } && under_docker_context( $mysync ) ) { return q{} ; } else { myprint( $output ) ; return $output ; } } sub define_pidfile { my $mysync = shift @ARG ; $mysync->{ pidfilelocking } = defined $mysync->{ pidfilelocking } ? $mysync->{ pidfilelocking } : 0 ; my $host1 = $mysync->{ host1 } || q{} ; my $user1 = $mysync->{ user1 } || q{} ; my $host2 = $mysync->{ host2 } || q{} ; my $user2 = $mysync->{ user2 } || q{} ; my $account1_filtered = filter_forbidden_characters( slash_to_underscore( $host1 . '_' . $user1 ) ) || q{} ; my $account2_filtered = filter_forbidden_characters( slash_to_underscore( $host2 . '_' . $user2 ) ) || q{} ; my $pidfile_basename ; if ( $ENV{ 'NET_SERVER_SOFTWARE' } and ( $ENV{ 'NET_SERVER_SOFTWARE' } =~ /Net::Server::HTTP/ ) ) { # under local webserver $pidfile_basename = 'imapsync' . '_' . $account1_filtered . '_' . $account2_filtered . '.pid' ; } else { $pidfile_basename = 'imapsync.pid' ; } $mysync->{ pidfile } = defined $mysync->{ pidfile } ? $mysync-> { pidfile } : $mysync->{ tmpdir } . "/$pidfile_basename" ; $mysync->{ abortfile } = abortfile( $mysync, $PROCESS_ID ) ; return ; } sub abortfile { my $mysync = shift @ARG ; my $pid = shift @ARG ; my $abortfile ; if ( $mysync->{ abort } ) { $abortfile = $mysync->{ pidfile } . "abort$pid" ; } else { $abortfile = $mysync->{ pidfile } . "abort$PROCESS_ID" ; } return $abortfile ; } sub tests_kill_zero { note( 'Entering tests_kill_zero()' ) ; SKIP: { if ( 'MSWin32' eq $OSNAME ) { skip( 'Tests tests_kill_zero avoided on Windows', 8 ) ; } is( 1, kill( 'ZERO', $PROCESS_ID ), "kill ZERO : myself $PROCESS_ID => 1" ) ; is( 2, kill( 'ZERO', $PROCESS_ID, $PROCESS_ID ), "kill ZERO : myself $PROCESS_ID $PROCESS_ID => 2" ) ; if ( (-e '/.dockerenv' ) or ( 0 == $EFFECTIVE_USER_ID) ) { is( 1, kill( 'ZERO', 1 ), "kill ZERO : pid 1 => 1 (docker context or root)" ) ; is( 2, kill( 'ZERO', $PROCESS_ID, 1 ), "kill ZERO : myself + pid 1, $PROCESS_ID 1 => 2 (docker context or root)" ) ; } else { is( 0, kill( 'ZERO', 1 ), "kill ZERO : pid 1 => 0 (non root)" ) ; is( 1, kill( 'ZERO', $PROCESS_ID, 1 ), "kill ZERO : myself + pid 1, $PROCESS_ID 1 => 1 (one is non root)" ) ; } my $pid_1 = fork( ) ; if ( $pid_1 ) { # parent } else { # child sleep 3 ; exit ; } my $pid_2 ; $pid_2 = fork( ) ; if ( $pid_2 ) { # I am the parent ok( defined( $pid_2 ), "kill_zero: initial fork ok. I am the parent $PROCESS_ID" ) ; ok( $pid_2 , "kill_zero: initial fork ok, child pid is $pid_2" ) ; is( 3, kill( 'ZERO', $PROCESS_ID, $pid_2, $pid_1 ), "kill ZERO : myself $PROCESS_ID and child $pid_2 and brother $pid_1 => 3" ) ; is( $pid_2, waitpid( $pid_2, 0 ), "kill_zero: child $pid_2 no more there => waitpid return $pid_2" ) ; } else { # I am the child note( 'This one fails under Windows, kill ZERO returns 0 instead of 2' ) ; is( 2, kill( 'ZERO', $PROCESS_ID, $pid_1 ), "kill ZERO : myself child $PROCESS_ID brother $pid_1 => 2" ) ; myprint( "I am the child pid $PROCESS_ID, Exiting\n" ) ; exit ; } wait( ) ; # End of SKIP block } note( 'Leaving tests_kill_zero()' ) ; return ; } sub tests_killpid_by_parent { note( 'Entering tests_killpid_by_parent()' ) ; SKIP: { if ( 'MSWin32' eq $OSNAME ) { skip( 'Tests tests_killpid_by_parent avoided on Windows', 7 ) ; } is( undef, killpid( ), 'killpid: no args => undef' ) ; note( "killpid: trying to kill myself pid $PROCESS_ID, hope I will not succeed" ) ; is( undef, killpid( $PROCESS_ID ), 'killpid: myself => undef' ) ; local $SIG{'QUIT'} = sub { myprint "GOT SIG QUIT! I am PID $PROCESS_ID. Exiting\n" ; exit ; } ; my $pid ; $pid = fork( ) ; if ( $pid ) { # I am the parent ok( defined( $pid ), "killpid: initial fork ok. I am the parent $PROCESS_ID" ) ; ok( $pid , "killpid: initial fork ok, child pid is $pid" ) ; is( 2, kill( 'ZERO', $PROCESS_ID, $pid ), "kill ZERO : myself $PROCESS_ID and child $pid => 2" ) ; is( 1, killpid( $pid ), "killpid: child $pid killed => 1" ) ; is( -1, waitpid( $pid, 0 ), "killpid: child $pid no more there => waitpid return -1" ) ; } else { # I am the child myprint( "I am the child pid $PROCESS_ID, sleeping 1 + 3 seconds then kill myself\n" ) ; sleep 1 ; myprint( "I am the child pid $PROCESS_ID, slept 1 second, should be killed by my parent now, PPID " . mygetppid( ) . "\n" ) ; sleep 3 ; # this test should not be run. If it happens => failure. ok( 0 == 1, "killpid: child pid $PROCESS_ID not dead => failure" ) ; myprint( "I am the child pid $PROCESS_ID, killing myself failure... Exiting\n" ) ; exit ; } # End of SKIP block } note( 'Leaving tests_killpid_by_parent()' ) ; return ; } sub tests_killpid_by_brother { note( 'Entering tests_killpid_by_brother()' ) ; SKIP: { if ( 'MSWin32' eq $OSNAME ) { skip( 'Tests tests_killpid_by_brother avoided on Windows', 2 ) ; } local $SIG{'QUIT'} = sub { myprint "GOT SIG QUIT! I am PID $PROCESS_ID. Exiting\n" ; exit ; } ; my $pid_parent = $PROCESS_ID ; myprint( "I am the parent pid $pid_parent\n" ) ; my $pid_1 = fork( ) ; if ( $pid_1 ) { # parent } else { # child #while ( 1 ) { } ; sleep 2 ; sleep 2 ; # this test should not be run. If it happens => failure. # Well under Windows this always fails, shit! ok( 0 == 1 or ( 'MSWin32' eq $OSNAME ) , "killpid: child pid $PROCESS_ID killing by brother but not dead => failure" ) ; myprint( "I am the child pid $PROCESS_ID, killing by brother failed... Exiting\n" ) ; exit ; } my $pid_2 ; $pid_2 = fork( ) ; if ( $pid_2 ) { # parent } else { # I am the child myprint( "I am the child pid $PROCESS_ID, my brother has pid $pid_1\n" ) ; is( 1, killpid( $pid_1 ), "killpid: brother $pid_1 killed => 1" ) ; sleep 2 ; exit ; } #sleep 1 ; is( $pid_1, waitpid( $pid_1, 0), "I am the parent $PROCESS_ID waitpid _1( $pid_1 )" ) ; is( $pid_2, waitpid( $pid_2, 0 ), "I am the parent $PROCESS_ID waitpid _2( $pid_2 )" ) ; # End of SKIP block } note( 'Leaving tests_killpid_by_brother()' ) ; return ; } sub killpid { my $pidtokill = shift ; if ( ! $pidtokill ) { myprint( "No process to kill.\n" ) ; return ; } if ( $PROCESS_ID == $pidtokill ) { myprint( "I will not kill myself pid $PROCESS_ID via killpid. Sractch it!\n" ) ; return ; } # First ask for suicide if ( kill( 'ZERO', $pidtokill ) or ( 'MSWin32' eq $OSNAME ) ) { myprint( "Sending signal QUIT to PID $pidtokill \n" ) ; kill 'QUIT', $pidtokill ; sleep 3 ; waitpid( $pidtokill, WNOHANG) ; }else{ myprint( "Can not send signal kill ZERO to PID $pidtokill.\n" ) ; return ; } #while ( waitpid( $pidtokill, WNOHANG) > 0 ) { } ; # Then murder if ( kill( 'ZERO', $pidtokill ) or ( 'MSWin32' eq $OSNAME ) ) { myprint( "Sending signal KILL to PID $pidtokill \n" ) ; kill 'KILL', $pidtokill ; sleep 1 ; waitpid( $pidtokill, WNOHANG) ; }else{ myprint( "Process PID $pidtokill ended.\n" ) ; return 1; } # Well ... if ( kill( 'ZERO', $pidtokill ) or ( 'xMSWin32' eq $OSNAME ) ) { myprint( "Process PID $pidtokill seems still there. Can not do much.\n" ) ; return ; }else{ myprint( "Process PID $pidtokill ended.\n" ) ; return 1; } return ; } sub tests_abort { note( 'Entering tests_abort()' ) ; # Well, the abort behavior is tested by test.sh is( undef, abort( ), 'abort: no args => undef' ) ; note( 'Leaving tests_abort()' ) ; return ; } sub abort { my $mysync = shift @ARG ; myprint( "In abort\n" ) ; if ( not $mysync ) { return ; } if ( ! -r $mysync->{pidfile} ) { myprint( "In abort: Can not read pidfile $mysync->{pidfile}\n" ) ; return ; } my $pidtokill = firstline( $mysync->{pidfile} ) ; if ( ! $pidtokill ) { myprint( "In abort: No process to abort in $mysync->{pidfile}\n" ) ; return ; } if ( ! match_a_pid_number( $pidtokill ) ) { myprint( "In abort: pid $pidtokill in $mysync->{pidfile} is not a pid number\n" ) ; return ; } if ( $mysync->{abortbyfile} ) { abortbyfile( $mysync, $pidtokill ) ; } else { killpid( $pidtokill ) ; } return ; } sub abortbyfile { my $mysync = shift @ARG ; my $pidtokill = shift @ARG ; my $abortfile = abortfile( $mysync, $pidtokill ) ; myprint( "touching $abortfile\n" ) ; touch( $abortfile ) ; return ; } sub tests_under_docker_context { note( 'Entering tests_under_docker_context()' ) ; is( undef, under_docker_context( ), 'under_docker_context: no args => undef' ) ; my $mysync = { } ; $mysync->{ dockercontext } = 1 ; is( 1, under_docker_context( $mysync ), 'under_docker_context: --dockercontext => 1' ) ; $mysync->{ dockercontext } = 0 ; is( 0, under_docker_context( $mysync ), 'under_docker_context: --nodockercontext => 0' ) ; $mysync = { } ; # Is not it a stupid test? if ( under_docker_context( $mysync ) ) { is( 1, under_docker_context( $mysync ), 'under_docker_context: docker context => 1' ) ; } else { is( 0, under_docker_context( $mysync ), 'under_docker_context: not docker context => 0' ) ; } note( 'Leaving tests_under_docker_context()' ) ; return ; } sub under_docker_context { my $mysync = shift ; if ( ! defined $mysync ) { return ; } if ( defined $mysync->{ dockercontext } ) { return( $mysync->{ dockercontext } ) ; } if ( -e '/.dockerenv' ) { return 1 ; } else { return 0 ; } return ; } sub docker_context { my $mysync = shift ; if ( ! under_docker_context( $mysync ) ) { return ; } output( $mysync, "Docker context detected with the file /.dockerenv\n" ) ; # No pidfile by default $mysync->{ pidfile } = defined( $mysync->{ pidfile } ) ? $mysync->{ pidfile } : q{} ; # No log by default if ( defined( $mysync->{ log } ) ) { output( $mysync, "Logging in Docker context. Be sure you added access to it with a mount or similar. See https://docs.docker.com/storage/volumes/\n" ) ; } else { output( $mysync, "No log by default in Docker context. Use --log to trigger logging to the logfile.\n" ) ; $mysync->{ log } = 0 ; } # In case something is written relatively to . my $tmp_dir = "/var/tmp/uid_$EFFECTIVE_USER_ID" ; mkpath( $tmp_dir ) ; # silly? No. it is for imapsync --version being ok. do_valid_directory( $tmp_dir ) ; output( $mysync, "Changing current directory to $tmp_dir\n" ) ; chdir $tmp_dir ; return ; } sub cgibegin { my $mysync = shift ; if ( ! under_cgi_context( $mysync ) ) { return ; } require CGI ; CGI->import( qw( -no_debug -utf8 ) ) ; require CGI::Carp ; CGI::Carp->import( qw( fatalsToBrowser ) ) ; $mysync->{cgi} = CGI->new( ) ; return ; } sub tests_under_cgi_context { note( 'Entering tests_under_cgi_context()' ) ; # $ENV{SERVER_SOFTWARE} = 'under imapsync' ; do { # Not in cgi context delete local $ENV{SERVER_SOFTWARE} ; is( undef, under_cgi_context( ), 'under_cgi_context: SERVER_SOFTWARE unset => not in cgi context' ) ; } ; do { # In cgi context local $ENV{SERVER_SOFTWARE} = 'under imapsync' ; is( 1, under_cgi_context( ), 'under_cgi_context: SERVER_SOFTWARE set => in cgi context' ) ; } ; do { # Not in cgi context delete local $ENV{SERVER_SOFTWARE} ; is( undef, under_cgi_context( ), 'under_cgi_context: SERVER_SOFTWARE unset => not in cgi context' ) ; } ; do { # In cgi context local $ENV{SERVER_SOFTWARE} = 'under imapsync' ; is( 1, under_cgi_context( ), 'under_cgi_context: SERVER_SOFTWARE set => in cgi context' ) ; } ; note( 'Leaving tests_under_cgi_context()' ) ; return ; } sub under_cgi_context { my $mysync = shift ; # Under cgi context if ( $ENV{SERVER_SOFTWARE} ) { return 1 ; } # Not in cgi context return ; } sub cgibuildheader { my $mysync = shift ; if ( ! under_cgi_context( $mysync ) ) { return ; } my $imapsync_runs = $mysync->{cgi}->cookie( 'imapsync_runs' ) || 0 ; my $cookie = $mysync->{cgi}->cookie( -name => 'imapsync_runs', -value => 1 + $imapsync_runs, -expires => '+20y', -path => '/cgi-bin/imapsync', ) ; my $httpheader ; if ( $mysync->{ abort } ) { $httpheader = $mysync->{cgi}->header( -type => 'text/plain; charset=UTF-8', -status => '200 OK to abort syncing IMAP boxes' . ". Here is " . hostname(), ) ; }elsif( $mysync->{ loaddelay } ) { # https://tools.ietf.org/html/rfc2616#section-10.5.4 # 503 Service Unavailable # The server is currently unable to handle the request due to a temporary overloading or maintenance of the server. $httpheader = $mysync->{cgi}->header( -type => 'text/plain; charset=UTF-8', -status => '503 Service Unavailable' . ". Be back in $mysync->{ loaddelay } min. Load on " . hostname() . " is $mysync->{ loadavg }", ) ; }else{ $httpheader = $mysync->{cgi}->header( -type => 'text/plain; charset=UTF-8', -status => '200 OK to sync IMAP boxes' . ". Load on " . hostname() . " is $mysync->{ loadavg }", -cookie => $cookie, ) ; } output_start( $mysync, $httpheader ) ; return ; } sub cgiload { # Exit on heavy load in CGI context my $mysync = shift ; if ( ! under_cgi_context( $mysync ) ) { return ; } if ( $mysync->{ abort } ) { return ; } # keep going to abort since some ressources will be free soon if ( $mysync->{ loaddelay } ) { $mysync->{nb_errors}++ ; exit_clean( $mysync, $EX_UNAVAILABLE, "Server is on heavy load. Be back in $mysync->{ loaddelay } min. Load is $mysync->{ loadavg }\n" ) ; } return ; } sub tests_set_umask { note( 'Entering tests_set_umask()' ) ; my $save_umask = umask ; my $mysync = {} ; if ( 'MSWin32' eq $OSNAME ) { is( undef, set_umask( $mysync ), "set_umask: set failure to $UMASK_PARANO on MSWin32" ) ; }else{ is( 1, set_umask( $mysync ), "set_umask: set to $UMASK_PARANO" ) ; } umask $save_umask ; note( 'Leaving tests_set_umask()' ) ; return ; } sub set_umask { my $mysync = shift ; my $previous_umask = umask_str( ) ; my $new_umask = umask_str( $UMASK_PARANO ) ; output( $mysync, "Umask set with $new_umask (was $previous_umask)\n" ) ; if ( $new_umask eq $UMASK_PARANO ) { return 1 ; } return ; } sub tests_umask_str { note( 'Entering tests_umask_str()' ) ; my $save_umask = umask ; is( umask_str( ), umask_str( ), 'umask_str: no parameters => idopotent' ) ; is( my $save_umask_str = umask_str( ), umask_str( ), 'umask_str: no parameters => idopotent + save' ) ; is( '0000', umask_str( q{ } ), 'umask_str: q{ } => 0000' ) ; is( '0000', umask_str( q{} ), 'umask_str: q{} => 0000' ) ; is( '0000', umask_str( '0000' ), 'umask_str: 0000 => 0000' ) ; is( '0000', umask_str( '0' ), 'umask_str: 0 => 0000' ) ; is( '0200', umask_str( '0200' ), 'umask_str: 0200 => 0200' ) ; is( '0400', umask_str( '0400' ), 'umask_str: 0400 => 0400' ) ; is( '0600', umask_str( '0600' ), 'umask_str: 0600 => 0600' ) ; SKIP: { if ( 'MSWin32' eq $OSNAME ) { skip( 'Tests success only for Unix', 6 ) ; } is( '0100', umask_str( '0100' ), 'umask_str: 0100 => 0100' ) ; is( '0001', umask_str( '0001' ), 'umask_str: 0001 => 0001' ) ; is( '0777', umask_str( '0777' ), 'umask_str: 0777 => 0777' ) ; is( '0777', umask_str( '00777' ), 'umask_str: 00777 => 0777' ) ; is( '0777', umask_str( ' 777 ' ), 'umask_str: 777 => 0777' ) ; is( "$UMASK_PARANO", umask_str( $UMASK_PARANO ), "umask_str: UMASK_PARANO $UMASK_PARANO => $UMASK_PARANO" ) ; } is( $save_umask_str, umask_str( $save_umask_str ), 'umask_str: restore with str' ) ; is( $save_umask, umask, 'umask_str: umask is restored, controlled by direct umask' ) ; is( $save_umask, umask $save_umask, 'umask_str: umask is restored by direct umask' ) ; is( $save_umask, umask, 'umask_str: umask initial controlled by direct umask' ) ; note( 'Leaving tests_umask_str()' ) ; return ; } sub umask_str { my $value = shift ; if ( defined $value ) { umask oct( $value ) ; } my $current = umask ; return( sprintf( '%#04o', $current ) ) ; } sub tests_umask { note( 'Entering tests_umask()' ) ; my $save_umask ; is( umask, umask, 'umask: umask is umask' ) ; is( $save_umask = umask, umask, "umask: umask is umask again + save it: $save_umask" ) ; is( $save_umask, umask oct(0000), 'umask: umask 0000' ) ; is( oct(0000), umask, 'umask: umask is now 0000' ) ; is( oct(0000), umask oct(777), 'umask: umask 0777 call, previous 0000' ) ; SKIP: { if ( 'MSWin32' eq $OSNAME ) { skip( 'Tests success only for Unix', 2 ) ; } is( oct(777), umask, 'umask: umask is now 0777' ) ; is( oct(777), umask $save_umask, "umask: umask $save_umask restore inital value, previous 0777" ) ; } ok( defined umask $save_umask, "umask: umask $save_umask restore inital value, previous defined" ) ; is( $save_umask, umask, 'umask: umask is umask restored' ) ; note( 'Leaving tests_umask()' ) ; return ; } sub buggyflagsregex { # From /X analyse # cut -d: -f1 Error_112_all_syncs.txt | xargs egrep -oih 'Invalid system flag [^( ]+' | sort | uniq -c | sort -g my @buggyflagsregex = ( 's/\\\\RECEIPTCHECKED|\\\\Indexed|\\\\X-EON-HAS-ATTACHMENT|\\\\UNSEEN|\\\\ATTACHED|\\\\X-HAS-ATTACH|\\\\FORWARDED|\\\\FORWARD|\\\\X-FORWARDED|\\\\\$FORWARDED|\\\\PRIORITY|\\\\READRCPT//g' ) ; return( @buggyflagsregex ) ; } sub cgisetcontext { my $mysync = shift ; if ( ! under_cgi_context( $mysync ) ) { return ; } output( $mysync, "Under cgi context\n" ) ; set_umask( $mysync ) ; # Remove all content in unsafe evaled options @{ $mysync->{ regextrans2 } } = ( ) ; @{ $mysync->{ regexflag } } = buggyflagsregex( ) ; @regexmess = ( ) ; @skipmess = ( ) ; @pipemess = ( ) ; $delete2foldersonly = undef ; $delete2foldersbutnot = undef ; $maxlinelengthcmd = undef ; # Set safe default values (I hope...) #$mysync->{pidfile} = 'imapsync.pid' ; $mysync->{ pidfilelocking } = 1 ; $mysync->{ errorsmax } = $ERRORS_MAX_CGI ; $modulesversion = 0 ; $mysync->{ releasecheck } = defined $mysync->{ releasecheck } ? $mysync->{ releasecheck } : 1 ; $usecache = 0 ; $mysync->{ showpasswords } = 0 ; $mysync->{ acc1 }->{ debugimap } = 0 ; $mysync->{ acc2 }->{ debugimap } = 0 ; $mysync->{ acc1 }->{ reconnectretry } = $DEFAULT_NB_RECONNECT_PER_IMAP_COMMAND ; $mysync->{ acc2 }->{ reconnectretry } = $DEFAULT_NB_RECONNECT_PER_IMAP_COMMAND ; $pipemesscheck = 0 ; $mysync->{ hashfile } = $CGI_HASHFILE ; my $hashsynclocal = hashsynclocal( $mysync ) || die "Can not get hashsynclocal. Exiting\n" ; if ( $ENV{ 'NET_SERVER_SOFTWARE' } and ( $ENV{ 'NET_SERVER_SOFTWARE' } =~ /Net::Server::HTTP/ ) ) { # under local webserver $cgidir = q{.} ; } else { $cgidir = $CGI_TMPDIR_TOP . '/' . $hashsynclocal ; } -d $cgidir or mkpath $cgidir or die "Can not create $cgidir: $OS_ERROR\n" ; $mysync->{ tmpdir } = $cgidir ; $mysync->{ logdir } = '' ; chdir $cgidir or die "Can not cd to $cgidir: $OS_ERROR\n" ; cgioutputenvcontext( $mysync ) ; $mysync->{ debug } and output( $mysync, 'Current directory is ' . getcwd( ) . "\n" ) ; $mysync->{ debug } and output( $mysync, 'Real user id is ' . getpwuid_any_os( $REAL_USER_ID ) . " (uid $REAL_USER_ID)\n" ) ; $mysync->{ debug } and output( $mysync, 'Effective user id is ' . getpwuid_any_os( $EFFECTIVE_USER_ID ). " (euid $EFFECTIVE_USER_ID)\n" ) ; $mysync->{ skipemptyfolders } = defined $mysync->{ skipemptyfolders } ? $mysync->{ skipemptyfolders } : 1 ; # Out of memory with messages over 1 GB ? $mysync->{ maxsize } = defined $mysync->{ maxsize } ? $mysync->{ maxsize } : 1_000_000_000 ; # tail -f behaviour on by default $mysync->{ tail } = defined $mysync->{ tail } ? $mysync->{ tail } : 1 ; # not sure it's for good @useheader = qw( Message-Id Received ) ; # addheader on by default $mysync->{ addheader } = defined $mysync->{ addheader } ? $mysync->{ addheader } : 1 ; # sync duplicates by default in cgi context $mysync->{ syncduplicates } = defined $mysync->{ syncduplicates } ? $mysync->{ syncduplicates } : 1 ; # log the logfile name by default in cgi context $mysync->{ loglogfile } = defined $mysync->{ loglogfile } ? $mysync->{ loglogfile } : 1 ; return ; } sub cgioutputenvcontext { my $mysync = shift @ARG ; for my $envvar ( qw( REMOTE_ADDR REMOTE_HOST HTTP_REFERER HTTP_USER_AGENT SERVER_SOFTWARE SERVER_PORT HTTP_COOKIE ) ) { my $envval = $ENV{ $envvar } || q{} ; if ( $envval ) { output( $mysync, "$envvar is $envval\n" ) } ; } return ; } sub announcelogfile { my $mysync = shift ; if ( $mysync->{ log } ) { myprint( "Log file is $mysync->{ logfile } ( to change it, use --logfile path ; or use --nolog to turn off logging )\n" ) ; loglogfile( $mysync ) ; } else { myprint( "No log file because of option --nolog\n" ) ; } return ; } sub loglogfile { my $mysync = shift ; if ( ! $mysync->{ loglogfile } ) { return ; } if ( ! $mysync->{ log } ) { return ; } my $cwd = getcwd( ) ; my $absolutelogfilepath ; # Fixme: add case when the logfile name is already absolute $absolutelogfilepath = "$cwd/$mysync->{ logfile }" ; my $loglogfilename = '../list_all_logs_auto.txt' ; myprint( "Writing log file name $absolutelogfilepath to $loglogfilename\n" ) ; if ( open( my $fh, '>>', $loglogfilename ) ) { print $fh "$absolutelogfilepath\n" ; close $fh ; } else { myprint( "Could not open loglogfile $loglogfilename $!\n" ) ; } return ; } sub checkselectable { my $mysync = shift ; if ( $mysync->{ checkselectable } ) { my @h1_folders_wanted_selectable ; myprint( "Host1: Checking wanted folders are selectable. Use --nocheckselectable to avoid this check.\n" ) ; foreach my $folder ( @{ $mysync->{ h1_folders_wanted } } ) { ( $mysync->{ debug } or $mysync->{ debugfolders } ) and myprint( "Checking $folder is selectable on host1\n" ) ; # It does an imap command LIST "" $folder and then search for no \Noselect if ( ! $mysync->{ imap1 }->selectable( $folder ) ) { myprint( "Host1: warning! ignoring folder $folder because it is not selectable\n" ) ; }else { push @h1_folders_wanted_selectable, $folder ; } } @{ $mysync->{ h1_folders_wanted } } = @h1_folders_wanted_selectable ; ( $mysync->{ debug } or $mysync->{ debugfolders } ) and myprint( 'Host1: checking folders took ', timenext( $mysync ), " s\n" ) ; } else { myprint( "Host1: Not checking that wanted folders are selectable. Use --checkselectable to force this check.\n" ) ; } return ; } sub setcheckselectable { my $mysync = shift ; my $h1_folders_wanted_nb = scalar @{ $mysync->{ h1_folders_wanted } } ; # 152 because 98% of host1 accounts have less than 152 folders on /X service. # command to get this value: # datamash_file_op_index G_Host1_Nb_folders.txt perc:98 4 %16.1f if ( ! defined $mysync->{ checkselectable } ) { if ( 152 >= $h1_folders_wanted_nb ) { $mysync->{ checkselectable } = 1 ; }else{ myprint( "Host1: Not checking that $h1_folders_wanted_nb wanted folders are selectable. Use --checkselectable to force this check.\n" ) ; $mysync->{ checkselectable } = 0 ; } } return ; } sub debugsleep { my $mysync = shift @ARG ; if ( defined $mysync->{debugsleep} ) { myprint( "Info: sleeping $mysync->{debugsleep}s\n" ) ; sleep $mysync->{debugsleep} ; } return ; } sub tests_foldersize { note( 'Entering tests_foldersize()' ) ; is( undef, foldersize( ), 'foldersize: no args => undef' ) ; #is_deeply( {}, {}, 'foldersize: a hash is a hash' ) ; #is_deeply( [], [], 'foldersize: an array is an array' ) ; note( 'Leaving tests_foldersize()' ) ; return ; } # Globals: # $fetch_hash_set # sub foldersize { # size of one folder my ( $mysync, $side, $imap, $search_cmd, $abletosearch, $folder ) = @ARG ; if ( ! all_defined( $mysync, $side, $imap, $folder ) ) { return ; } # FTGate is RFC buggy with EXAMINE it does not act as SELECT #if ( ! $imap->examine( $folder ) ) { if ( ! $imap->select( $folder ) ) { my $error = join q{}, "$side Folder $folder: Could not select: ", $imap->LastError, "\n" ; errors_incr( $mysync, $error ) ; return ; } if ( $imap->IsUnconnected( ) ) { return ; } my $hash_ref = { } ; my @msgs = select_msgs( $imap, undef, $search_cmd, $abletosearch, $folder ) ; my $nb_msgs = scalar @msgs ; my $biggest_in_folder = 0 ; @{ $hash_ref }{ @msgs } = ( undef ) if @msgs ; my $stot = 0 ; if ( $imap->IsUnconnected( ) ) { return ; } if ( $nb_msgs > 0 and @msgs ) { if ( $abletosearch ) { if ( ! $imap->fetch_hash( \@msgs, 'RFC822.SIZE', $hash_ref) ) { my $error = "$side failure with fetch_hash: $EVAL_ERROR\n" ; errors_incr( $mysync, $error ) ; return ; } } else { my $fetch_hash_uids = $fetch_hash_set || "1:*" ; if ( ! $imap->fetch_hash( $fetch_hash_uids, 'RFC822.SIZE', $hash_ref ) ) { my $error = "$side failure with fetch_hash: $EVAL_ERROR\n" ; errors_incr( $mysync, $error ) ; return ; } } for ( keys %{ $hash_ref } ) { my $size = $hash_ref->{ $_ }->{ 'RFC822.SIZE' } ; if ( defined $size ) { $stot += $size ; $biggest_in_folder = max( $biggest_in_folder, $size ) ; } } } return( $stot, $nb_msgs, $biggest_in_folder ) ; } # The old subroutine that performed just one side at a time. # Still here for a while, until confident with sub foldersize_diff_compute() sub foldersizes { my ( $mysync, $side, $imap, $search_cmd, $abletosearch, @folders ) = @_ ; my $total_size = 0 ; my $total_nb = 0 ; my $biggest_in_all = 0 ; my $nb_folders = scalar @folders ; my $ct_folders = 0 ; # folder counter. myprint( "++++ Calculating sizes of $nb_folders folders on $side\n" ) ; foreach my $folder ( @folders ) { my $stot = 0 ; my $nb_msgs = 0 ; my $biggest_in_folder = 0 ; $ct_folders++ ; myprintf( "$side folder %7s %-35s", "$ct_folders/$nb_folders", jux_utf8( $folder ) ) ; if ( 'Host2' eq $side and not exists $mysync->{h2_folders_all_UPPER}{ uc $folder } ) { myprint( " does not exist yet\n") ; next ; } if ( 'Host1' eq $side and not exists $h1_folders_all{ $folder } ) { myprint( " does not exist\n" ) ; next ; } last if $imap->IsUnconnected( ) ; ( $stot, $nb_msgs, $biggest_in_folder ) = foldersize( $mysync, $side, $imap, $search_cmd, $abletosearch, $folder ) ; myprintf( ' Size: %9s', $stot ) ; myprintf( ' Messages: %5s', $nb_msgs ) ; myprintf( " Biggest: %9s\n", $biggest_in_folder ) ; $total_size += $stot ; $total_nb += $nb_msgs ; $biggest_in_all = max( $biggest_in_all, $biggest_in_folder ) ; } myprintf( "%s Nb folders: %11s folders\n", $side, $nb_folders ) ; myprintf( "%s Nb messages: %11s messages\n", $side, $total_nb ) ; myprintf( "%s Total size: %11s bytes (%s)\n", $side, $total_size, bytes_display_string_bin( $total_size ) ) ; myprintf( "%s Biggest message: %11s bytes (%s)\n", $side, $biggest_in_all, bytes_display_string_bin( $biggest_in_all ) ) ; myprintf( "%s Time spent on sizing: %11.1f seconds\n", $side, timenext( $mysync ) ) ; return( $total_nb, $total_size ) ; } sub foldersize_diff_present { my $mysync = shift ; my $folder1 = shift ; my $folder2 = shift ; my $counter_str = shift ; my $force = shift ; my $values1_str ; my $values2_str ; if ( ! defined $mysync->{ folder1 }->{ $folder1 }->{ size } || $force ) { foldersize_diff_compute( $mysync, $folder1, $folder2, $force ) ; } # again, but this time it means no availaible data. if ( defined $mysync->{ folder1 }->{ $folder1 }->{ size } ) { $values1_str = sprintf( "Size: %9s Messages: %5s Biggest: %9s\n", $mysync->{ folder1 }->{ $folder1 }->{ size }, $mysync->{ folder1 }->{ $folder1 }->{ nb_msgs }, $mysync->{ folder1 }->{ $folder1 }->{ biggest }, ) ; } else { $values1_str = " does not exist\n" ; } if ( defined $mysync->{ folder2 }->{ $folder2 }->{ size } ) { $values2_str = sprintf( "Size: %9s Messages: %5s Biggest: %9s\n", $mysync->{ folder2 }->{ $folder2 }->{ size }, $mysync->{ folder2 }->{ $folder2 }->{ nb_msgs }, $mysync->{ folder2 }->{ $folder2 }->{ biggest }, ) ; } else { $values2_str = " does not exist yet\n" ; } myprintf( "Host1 folder %7s %-35s %s", "$counter_str", jux_utf8( $folder1 ), $values1_str ) ; myprintf( "Host2 folder %7s %-35s %s", "$counter_str", jux_utf8( $folder2 ), $values2_str ) ; myprintf( "Host2-Host1 %7s %-35s %9s %5s %9s\n\n", "", "", $mysync->{ folder1 }->{ $folder1 }->{ size_diff }, $mysync->{ folder1 }->{ $folder1 }->{ nb_msgs_diff }, $mysync->{ folder1 }->{ $folder1 }->{ biggest_diff }, ) ; return ; } sub foldersize_diff_compute { my $mysync = shift ; my $folder1 = shift ; my $folder2 = shift ; my $force = shift ; my ( $size_1, $nb_msgs_1, $biggest_1 ) ; # memoization if ( exists $h1_folders_all{ $folder1 } && ( ! defined $mysync->{ folder1 }->{ $folder1 }->{ size } || $force ) ) { #myprint( "foldersize folder1 $h1_folders_all{ $folder1 }\n" ) ; ( $size_1, $nb_msgs_1, $biggest_1 ) = foldersize( $mysync, 'Host1', $mysync->{ imap1 }, $mysync->{ search1 }, $mysync->{ abletosearch1 }, $folder1 ) ; $mysync->{ folder1 }->{ $folder1 }->{ size } = $size_1 ; $mysync->{ folder1 }->{ $folder1 }->{ nb_msgs } = $nb_msgs_1 ; $mysync->{ folder1 }->{ $folder1 }->{ biggest } = $biggest_1 ; } else { $size_1 = $mysync->{ folder1 }->{ $folder1 }->{ size } ; $nb_msgs_1 = $mysync->{ folder1 }->{ $folder1 }->{ nb_msgs } ; $biggest_1 = $mysync->{ folder1 }->{ $folder1 }->{ biggest } ; } my ( $size_2, $nb_msgs_2, $biggest_2 ) ; if ( exists $mysync->{ h2_folders_all_UPPER }{ uc $folder2 } && ( ! defined $mysync->{ folder2 }->{ $folder2 }->{ size } || $force ) ) { #myprint( "foldersize folder2\n" ) ; ( $size_2, $nb_msgs_2, $biggest_2 ) = foldersize( $mysync, 'Host2', $mysync->{ imap2 }, $mysync->{ search2 }, $mysync->{ abletosearch2 }, $folder2 ) ; $mysync->{ folder2 }->{ $folder2 }->{ size } = $size_2 ; $mysync->{ folder2 }->{ $folder2 }->{ nb_msgs } = $nb_msgs_2 ; $mysync->{ folder2 }->{ $folder2 }->{ biggest } = $biggest_2 ; } else { $size_2 = $mysync->{ folder2 }->{ $folder2 }->{ size } ; $nb_msgs_2 = $mysync->{ folder2 }->{ $folder2 }->{ nb_msgs } ; $biggest_2 = $mysync->{ folder2 }->{ $folder2 }->{ biggest } ; } my $size_diff = diff( $size_2, $size_1 ) ; my $nb_msgs_diff = diff( $nb_msgs_2, $nb_msgs_1 ) ; my $biggest_diff = diff( $biggest_2, $biggest_1 ) ; $mysync->{ folder1 }->{ $folder1 }->{ size_diff } = $size_diff ; $mysync->{ folder1 }->{ $folder1 }->{ nb_msgs_diff } = $nb_msgs_diff ; $mysync->{ folder1 }->{ $folder1 }->{ biggest_diff } = $biggest_diff ; # It's redundant but easier to access later $mysync->{ folder2 }->{ $folder2 }->{ size_diff } = $size_diff ; $mysync->{ folder2 }->{ $folder2 }->{ nb_msgs_diff } = $nb_msgs_diff ; $mysync->{ folder2 }->{ $folder2 }->{ biggest_diff } = $biggest_diff ; return ; } sub diff { my $x = shift ; my $y = shift ; $x ||= 0 ; $y ||= 0 ; return $x - $y ; } sub add { my $x = shift ; my $y = shift ; $x ||= 0 ; $y ||= 0 ; return $x + $y ; } sub tests_checknoabletosearch { note( 'Entering checknoabletosearch()' ) ; is( undef, checknoabletosearch( ), 'checknoabletosearch: no args => undef' ) ; note( 'Leaving checknoabletosearch()' ) ; return ; } sub checknoabletosearch { # call example: checknoabletosearch( $sync, $sync->{ imap1 }, 'INBOX', 'Host1' ) ; # output: # * undef if something is not ok to decide # * 1 if SEARCH ALL failed my( $mysync, $imap, $folder, $HostX ) = @ARG ; if ( ! all_defined( $mysync, $imap, $folder, $HostX ) ) { return ; } myprint( "$HostX: checking if SEARCH ALL works on $folder\n" ) ; if ( ! select_folder( $mysync, $imap, $folder, $HostX ) ) { myprint( "$HostX: can not SELECT folder [$folder]\n" ) ; return ; } my $count_from_select = count_from_select( $imap->History ) ; myprint( "$HostX: folder [$folder] has $count_from_select messages mentioned by SELECT\n" ) ; my $msgs_all = $imap->messages( ) ; if ( ! $msgs_all ) { myprint( "$HostX: can not SEARCH ALL folder [$folder]\n" ) ; myprint( "$HostX: ", $imap->LastError(), "\n" ) ; return 1 ; } my $count_from_search_all = scalar( @{ $msgs_all } ) ; myprint( "$HostX: folder [$folder] has $count_from_search_all messages found by SEARCH ALL\n" ) ; if ( $count_from_select == $count_from_search_all ) { myprint( "$HostX: folder [$folder] has the same messages count ($count_from_select) by SELECT and SEARCH ALL\n" ) ; } else { myprint( "$HostX: Warning, folder [$folder] has not the same count by SELECT ($count_from_select) and SEARCH ALL ($count_from_search_all)\n" ) ; return 1 ; } return ; } sub foldersizes_diff_list { my $mysync = shift ; my $force = shift ; my @folders = @{ $mysync->{h1_folders_wanted} } ; my $nb_folders = scalar @folders ; my $ct_folders = 0 ; # folder counter. foreach my $folder1 ( @folders ) { $ct_folders++ ; my $counter_str = "$ct_folders/$nb_folders" ; my $folder2 = imap2_folder_name( $mysync, $folder1 ) ; foldersize_diff_present( $mysync, $folder1, $folder2, $counter_str, $force ) ; } return ; } sub foldersizes_total { my $mysync = shift ; my @folders_1 = @{ $mysync->{h1_folders_wanted} } ; my @folders_2 = @h2_folders_from_1_wanted ; my $nb_folders_1 = scalar( @folders_1 ) ; my $nb_folders_2 = scalar( @folders_2 ) ; my ( $total_size_1, $total_nb_1, $biggest_in_all_1 ) = ( 0, 0, 0 ) ; my ( $total_size_2, $total_nb_2, $biggest_in_all_2 ) = ( 0, 0, 0 ) ; foreach my $folder1 ( @folders_1 ) { $total_size_1 = add( $total_size_1, $mysync->{ folder1 }->{ $folder1 }->{ size } ) ; $total_nb_1 = add( $total_nb_1, $mysync->{ folder1 }->{ $folder1 }->{ nb_msgs } ) ; $biggest_in_all_1 = max( $biggest_in_all_1 , $mysync->{ folder1 }->{ $folder1 }->{ biggest } ) ; } foreach my $folder2 ( @folders_2 ) { $total_size_2 = add( $total_size_2, $mysync->{ folder2 }->{ $folder2 }->{ size } ) ; $total_nb_2 = add( $total_nb_2, $mysync->{ folder2 }->{ $folder2 }->{ nb_msgs } ) ; $biggest_in_all_2 = max( $biggest_in_all_2 , $mysync->{ folder2 }->{ $folder2 }->{ biggest } ) ; } myprintf( "Host1 Nb folders: %11s folders\n", $nb_folders_1 ) ; myprintf( "Host2 Nb folders: %11s folders\n", $nb_folders_2 ) ; myprint( "\n" ) ; myprintf( "Host1 Nb messages: %11s messages\n", $total_nb_1 ) ; myprintf( "Host2 Nb messages: %11s messages\n", $total_nb_2 ) ; myprint( "\n" ) ; myprintf( "Host1 Total size: %11s bytes (%s)\n", $total_size_1, bytes_display_string_bin( $total_size_1 ) ) ; myprintf( "Host2 Total size: %11s bytes (%s)\n", $total_size_2, bytes_display_string_bin( $total_size_2 ) ) ; myprint( "\n" ) ; myprintf( "Host1 Biggest message: %11s bytes (%s)\n", $biggest_in_all_1, bytes_display_string_bin( $biggest_in_all_1 ) ) ; myprintf( "Host2 Biggest message: %11s bytes (%s)\n", $biggest_in_all_2, bytes_display_string_bin( $biggest_in_all_2 ) ) ; myprint( "\n" ) ; myprintf( "Time spent on sizing: %11.1f seconds\n", timenext( $mysync ) ) ; my @total_1_2 = ( $total_nb_1, $total_size_1, $total_nb_2, $total_size_2 ) ; return @total_1_2 ; } sub foldersizesatend_old { my $mysync = shift ; timenext( $mysync ) ; return if ( $mysync->{imap1}->IsUnconnected( ) ) ; return if ( $mysync->{imap2}->IsUnconnected( ) ) ; # Get all folders on host2 again since new were created @h2_folders_all = sort $mysync->{imap2}->folders(); for ( @h2_folders_all ) { $h2_folders_all{ $_ } = 1 ; $mysync->{h2_folders_all_UPPER}{ uc $_ } = 1 ; } ; ( $h1_nb_msg_end, $h1_bytes_end ) = foldersizes( $mysync, 'Host1', $mysync->{imap1}, $mysync->{ search1 }, $mysync->{abletosearch1}, @{ $mysync->{h1_folders_wanted} } ) ; ( $h2_nb_msg_end, $h2_bytes_end ) = foldersizes( $mysync, 'Host2', $mysync->{imap2}, $mysync->{ search2 }, $mysync->{abletosearch2}, @h2_folders_from_1_wanted ) ; if ( not all_defined( $h1_nb_msg_end, $h1_bytes_end, $h2_nb_msg_end, $h2_bytes_end ) ) { my $error = "Failure getting foldersizes, final differences will not be calculated\n" ; errors_incr( $mysync, $error ) ; } return ; } sub foldersizesatend { my $mysync = shift ; timenext( $mysync ) ; return if ( $mysync->{imap1}->IsUnconnected( ) ) ; return if ( $mysync->{imap2}->IsUnconnected( ) ) ; # Get all folders on host2 again since new were created @h2_folders_all = sort $mysync->{imap2}->folders(); for ( @h2_folders_all ) { $h2_folders_all{ $_ } = 1 ; $mysync->{h2_folders_all_UPPER}{ uc $_ } = 1 ; } ; foldersizes_diff_list( $mysync, $FORCE ) ; ( $h1_nb_msg_end, $h1_bytes_end, $h2_nb_msg_end, $h2_bytes_end ) = foldersizes_total( $mysync ) ; if ( not all_defined( $h1_nb_msg_end, $h1_bytes_end, $h2_nb_msg_end, $h2_bytes_end ) ) { my $error = "Failure getting foldersizes, final differences will not be calculated\n" ; errors_incr( $mysync, $error ) ; } return ; } sub foldersizes_at_the_beggining { my $mysync = shift ; myprint( << 'END_SIZE' ) ; Folders sizes before the synchronization. You can remove foldersizes listings by using "--nofoldersizes" and "--nofoldersizesatend" but then you will also lose the ETA (Estimation Time of Arrival) given after each message copy. END_SIZE foldersizes_diff_list( $mysync ) ; ( $mysync->{ h1_nb_msg_start }, $mysync->{ h1_bytes_start }, $mysync->{ h2_nb_msg_start }, $mysync->{ h2_bytes_start } ) = foldersizes_total( $mysync ) ; if ( not all_defined( $mysync->{ h1_nb_msg_start }, $mysync->{ h1_bytes_start }, $mysync->{ h2_nb_msg_start }, $mysync->{ h2_bytes_start } ) ) { my $error = "Failure getting foldersizes, ETA and final diff will not be displayed\n" ; errors_incr( $mysync, $error ) ; $mysync->{ foldersizes } = 0 ; $mysync->{ foldersizesatend } = 0 ; return ; } my $h2_bytes_limit = $mysync->{ acc2 }->{quota_limit_bytes} || 0 ; if ( $h2_bytes_limit and ( $h2_bytes_limit < $mysync->{ h1_bytes_start } ) ) { my $quota_percent = mysprintf( '%.0f', $NUMBER_100 * $mysync->{ h1_bytes_start } / $h2_bytes_limit ) ; my $error = "Host2: Quota limit will be exceeded! Over $quota_percent % ( $mysync->{ h1_bytes_start } bytes / $h2_bytes_limit bytes )\n" ; errors_incr( $mysync, $error ) ; } return ; } # Globals: # @h2_folders_from_1_wanted sub foldersizes_at_the_beggining_old { my $mysync = shift ; myprint( << 'END_SIZE' ) ; Folders sizes before the synchronization. You can remove foldersizes listings by using "--nofoldersizes" and "--nofoldersizesatend" but then you will also lose the ETA (Estimation Time of Arrival) given after each message copy. END_SIZE ( $mysync->{ h1_nb_msg_start }, $mysync->{ h1_bytes_start } ) = foldersizes( $mysync, 'Host1', $mysync->{imap1}, $mysync->{ search1 }, $mysync->{abletosearch1}, @{ $mysync->{h1_folders_wanted} } ) ; ( $mysync->{ h2_nb_msg_start }, $mysync->{ h2_bytes_start } ) = foldersizes( $mysync, 'Host2', $mysync->{imap2}, $mysync->{ search2 }, $mysync->{abletosearch2}, @h2_folders_from_1_wanted ) ; if ( not all_defined( $mysync->{ h1_nb_msg_start }, $mysync->{ h1_bytes_start }, $mysync->{ h2_nb_msg_start }, $mysync->{ h2_bytes_start } ) ) { my $error = "Failure getting foldersizes, ETA and final diff will not be displayed\n" ; errors_incr( $mysync, $error ) ; $mysync->{ foldersizes } = 0 ; $mysync->{ foldersizesatend } = 0 ; return ; } my $h2_bytes_limit = $mysync->{ acc2 }->{quota_limit_bytes} || 0 ; if ( $h2_bytes_limit and ( $h2_bytes_limit < $mysync->{ h1_bytes_start } ) ) { my $quota_percent = mysprintf( '%.0f', $NUMBER_100 * $mysync->{ h1_bytes_start } / $h2_bytes_limit ) ; my $error = "Host2: Quota limit will be exceeded! Over $quota_percent % ( $mysync->{ h1_bytes_start } bytes / $h2_bytes_limit bytes )\n" ; errors_incr( $mysync, $error ) ; } return ; } sub tests_total_bytes_max_reached { note( 'Entering tests_total_bytes_max_reached()' ) ; is( undef, total_bytes_max_reached( ), 'total_bytes_max_reached: no args => undef' ) ; my $mysync = {} ; is( undef, total_bytes_max_reached( $mysync ), 'total_bytes_max_reached: no exitwhenover => undef' ) ; $mysync->{ exitwhenover } = 300 ; is( undef, total_bytes_max_reached( $mysync ), 'total_bytes_max_reached: exitwhenover 300 but no total_bytes_transferred => undef' ) ; $mysync->{ total_bytes_transferred } = 200 ; is( undef, total_bytes_max_reached( $mysync ), 'total_bytes_max_reached: exitwhenover 300 but total_bytes_transferred 200 => undef' ) ; $mysync->{ total_bytes_transferred } = 400 ; is( 1, total_bytes_max_reached( $mysync ), 'total_bytes_max_reached: exitwhenover 300 but total_bytes_transferred 400 => 1' ) ; note( 'Leaving tests_total_bytes_max_reached()' ) ; return ; } sub total_bytes_max_reached { my $mysync = shift ; if ( ! defined $mysync ) { return ; } if ( ! $mysync->{ exitwhenover } ) { return ; } if ( ! $mysync->{ total_bytes_transferred } ) { return ; } if ( $mysync->{ total_bytes_transferred } >= $mysync->{ exitwhenover } ) { my $error = "Maximum bytes transferred reached, $mysync->{total_bytes_transferred} >= $mysync->{ exitwhenover }, ending sync\n" ; errors_incr( $mysync, $error ) ; return( 1 ) ; } return ; } sub tests_mock_capability { note( 'Entering tests_mock_capability()' ) ; my $myimap ; ok( $myimap = mock_capability( ), 'mock_capability: (1) no args => a Test::MockObject' ) ; ok( $myimap->isa( 'Test::MockObject' ), 'mock_capability: (2) no args => a Test::MockObject' ) ; is( undef, $myimap->capability( ), 'mock_capability: (3) no args => capability undef' ) ; ok( mock_capability( $myimap ), 'mock_capability: (1) one arg => MockObject' ) ; is( undef, $myimap->capability( ), 'mock_capability: (2) one arg OO style => capability undef' ) ; ok( mock_capability( $myimap, $NUMBER_123456 ), 'mock_capability: (1) two args 123456 => capability 123456' ) ; is( $NUMBER_123456, $myimap->capability( ), 'mock_capability: (2) two args 123456 => capability 123456' ) ; ok( mock_capability( $myimap, 'ABCD' ), 'mock_capability: (1) two args ABCD => capability ABCD' ) ; is( 'ABCD', $myimap->capability( ), 'mock_capability: (2) two args ABCD => capability ABCD' ) ; ok( mock_capability( $myimap, [ 'ABCD' ] ), 'mock_capability: (1) two args [ ABCD ] => capability [ ABCD ]' ) ; is_deeply( [ 'ABCD' ], $myimap->capability( ), 'mock_capability: (2) two args [ ABCD ] => capability [ ABCD ]' ) ; ok( mock_capability( $myimap, [ 'ABC', 'DEF' ] ), 'mock_capability: (1) two args [ ABC, DEF ] => capability [ ABC, DEF ]' ) ; is_deeply( [ 'ABC', 'DEF' ], $myimap->capability( ), 'mock_capability: (2) two args [ ABC, DEF ] => capability capability [ ABC, DEF ]' ) ; ok( mock_capability( $myimap, 'ABC', 'DEF' ), 'mock_capability: (1) two args ABC, DEF => capability [ ABC, DEF ]' ) ; is_deeply( [ 'ABC', 'DEF' ], [ $myimap->capability( ) ], 'mock_capability: (2) two args ABC, DEF => capability capability [ ABC, DEF ]' ) ; ok( mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=123456' ), 'mock_capability: (1) two args IMAP4rev1, APPENDLIMIT=123456 => capability [ IMAP4rev1, APPENDLIMIT=123456 ]' ) ; is_deeply( [ 'IMAP4rev1', 'APPENDLIMIT=123456' ], [ $myimap->capability( ) ], 'mock_capability: (2) two args IMAP4rev1, APPENDLIMIT=123456 => capability capability [ IMAP4rev1, APPENDLIMIT=123456 ]' ) ; note( 'Leaving tests_mock_capability()' ) ; return ; } sub sig_install_toggle_sleep { my $mysync = shift ; if ( 'MSWin32' ne $OSNAME ) { #myprint( "sig_install( $mysync, \&toggle_sleep, 'USR1' )\n" ) ; sig_install( $mysync, 'toggle_sleep', 'USR1' ) ; } #myprint( "Leaving sig_install_toggle_sleep\n" ) ; return ; } sub mock_capability { my $myimap = shift ; my @has_capability_value = @ARG ; my ( $has_capability_value ) = @has_capability_value ; if ( ! $myimap ) { require_ok( "Test::MockObject" ) ; $myimap = Test::MockObject->new( ) ; } $myimap->mock( 'capability', sub { return wantarray ? @has_capability_value : $has_capability_value ; } ) ; return $myimap ; } sub tests_capability_of { note( 'Entering tests_capability_of()' ) ; is( undef, capability_of( ), 'capability_of: no args => undef' ) ; my $myimap ; is( undef, capability_of( $myimap ), 'capability_of: undef => undef' ) ; $myimap = mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=123456' ) ; is( undef, capability_of( $myimap, 'CACA' ), 'capability_of: two args unknown capability => undef' ) ; is( $NUMBER_123456, capability_of( $myimap, 'APPENDLIMIT' ), 'capability_of: two args APPENDLIMIT 123456 => 123456 yeah!' ) ; note( 'Leaving tests_capability_of()' ) ; return ; } sub capability_of { my $imap = shift || return ; my $capability_keyword = shift || return ; my @capability = $imap->capability ; if ( ! @capability ) { return ; } my $capability_value = search_in_array( $capability_keyword, @capability ) ; return $capability_value ; } sub tests_search_in_array { note( 'Entering tests_search_in_array()' ) ; is( undef, search_in_array( 'KA' ), 'search_in_array: no array => undef ' ) ; is( 'VA', search_in_array( 'KA', ( 'KA=VA' ) ), 'search_in_array: KA KA=VA => VA ' ) ; is( 'VA', search_in_array( 'KA', ( 'KA=VA', 'KB=VB' ) ), 'search_in_array: KA KA=VA KB=VB => VA ' ) ; is( 'VB', search_in_array( 'KB', ( 'KA=VA', 'KB=VB' ) ), 'search_in_array: KA=VA KB=VB => VB ' ) ; note( 'Leaving tests_search_in_array()' ) ; return ; } sub search_in_array { my ( $key, @array ) = @ARG ; foreach my $item ( @array ) { if ( $item =~ /([^=]+)=(.*)/ ) { if ( $1 eq $key ) { return $2 ; } } } return ; } sub tests_appendlimit_from_capability { note( 'Entering tests_appendlimit_from_capability()' ) ; is( undef, appendlimit_from_capability( ), 'appendlimit_from_capability: no args => undef' ) ; my $myimap ; is( undef, appendlimit_from_capability( $myimap ), 'appendlimit_from_capability: undef arg => undef' ) ; $myimap = mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=123456' ) ; # Normal behavior is( $NUMBER_123456, appendlimit_from_capability( $myimap ), 'appendlimit_from_capability: APPENDLIMIT=123456 => 123456' ) ; # Not a number $myimap = mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=ABC' ) ; is( undef, appendlimit_from_capability( $myimap ), 'appendlimit_from_capability: not a number => undef' ) ; note( 'Leaving tests_appendlimit_from_capability()' ) ; return ; } sub appendlimit_from_capability { my $myimap = shift ; if ( ! $myimap ) { myprint( "Warn: no imap with call to appendlimit_from_capability\n" ) ; return ; } #myprint( Data::Dumper->Dump( [ \$myimap ] ) ) ; my $appendlimit = capability_of( $myimap, 'APPENDLIMIT' ) ; #myprint( "has_capability APPENDLIMIT $appendlimit\n" ) ; if ( is_integer( $appendlimit ) ) { return $appendlimit ; } return ; } sub tests_appendlimit { note( 'Entering tests_appendlimit()' ) ; is( undef, appendlimit( ), 'appendlimit: no args => undef' ) ; my $mysync = { } ; is( undef, appendlimit( $mysync ), 'appendlimit: no imap2 => undef' ) ; my $myimap ; $myimap = mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=123456' ) ; $mysync->{ imap2 } = $myimap ; is( 123456, appendlimit( $mysync ), 'appendlimit: imap2 with APPENDLIMIT=123456 => 123456' ) ; note( 'Leaving tests_appendlimit()' ) ; return ; } sub appendlimit { my $mysync = shift || return ; my $myimap = $mysync->{ imap2 } ; my $appendlimit = appendlimit_from_capability( $myimap ) ; if ( defined $appendlimit ) { myprint( "Host2: found APPENDLIMIT=$appendlimit in CAPABILITY (use --appendlimit xxxx to override this automatic setting)\n" ) ; return $appendlimit ; } return ; } sub tests_maxsize_setting { note( 'Entering tests_maxsize_setting()' ) ; is( undef, maxsize_setting( ), 'maxsize_setting: no args => undef' ) ; my $mysync ; is( undef, maxsize_setting( $mysync ), 'maxsize_setting: undef arg => undef' ) ; $mysync = { } ; $mysync->{ maxsize } = $NUMBER_123456 ; # --maxsize alone is( $NUMBER_123456, maxsize_setting( $mysync ), 'maxsize_setting: --maxsize 123456 alone => 123456' ) ; $mysync = { } ; my $myimap ; $myimap = mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=654321' ) ; $mysync->{ imap2 } = $myimap ; # APPENDLIMIT alone is( $NUMBER_654321, maxsize_setting( $mysync ), 'maxsize_setting: APPENDLIMIT 654321 alone => 654321' ) ; is( $NUMBER_654321, $mysync->{ maxsize }, 'maxsize_setting: APPENDLIMIT 654321 alone => maxsize 654321' ) ; # APPENDLIMIT with --appendlimit => --appendlimit wins $mysync->{ appendlimit } = $NUMBER_123456 ; is( $NUMBER_123456, maxsize_setting( $mysync ), 'maxsize_setting: APPENDLIMIT 654321 + --appendlimit 123456 => 123456' ) ; is( $NUMBER_123456, $mysync->{ maxsize }, 'maxsize_setting: APPENDLIMIT 654321 + --appendlimit 123456 => maxsize 123456' ) ; # Fresh $mysync = { } ; $mysync->{ imap2 } = $myimap = mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=654321' ) ; # Case: "APPENDLIMIT >= --maxsize" => maxsize. $mysync->{ maxsize } = $NUMBER_123456 ; is( $NUMBER_123456, maxsize_setting( $mysync ), 'maxsize_setting: APPENDLIMIT 654321 --maxsize 123456 => 123456' ) ; # Case: "APPENDLIMIT < --maxsize" => APPENDLIMIT. # Fresh $mysync = { } ; $mysync->{ imap2 } = $myimap = mock_capability( $myimap, 'IMAP4rev1', 'APPENDLIMIT=123456' ) ; $mysync->{ maxsize } = $NUMBER_654321 ; is( $NUMBER_123456, maxsize_setting( $mysync ), 'maxsize_setting: APPENDLIMIT 123456 --maxsize 654321 => 123456 ' ) ; # Now --truncmess stuff note( 'Leaving tests_maxsize_setting()' ) ; return ; } # Three variables to take account of # appendlimit (given by --appendlimit or CAPABILITY...) # maxsize # truncmess sub maxsize_setting { my $mysync = shift || return ; if ( defined $mysync->{ appendlimit } ) { myprint( "Host2: Getting appendlimit from --appendlimit $mysync->{ appendlimit }\n" ) ; } else { $mysync->{ appendlimit } = appendlimit( $mysync ) ; } if ( all_defined( $mysync->{ appendlimit }, $mysync->{ maxsize } ) ) { my $min_maxsize_appendlimit = min( $mysync->{ maxsize }, $mysync->{ appendlimit } ) ; myprint( "Host2: Setting maxsize to $min_maxsize_appendlimit (min of --maxsize $mysync->{ maxsize } and appendlimit $mysync->{ appendlimit }\n" ) ; $mysync->{ maxsize } = $min_maxsize_appendlimit ; return $mysync->{ maxsize } ; } elsif ( defined $mysync->{ appendlimit } ) { myprint( "Host2: Setting maxsize to appendlimit $mysync->{ appendlimit }\n" ) ; $mysync->{ maxsize } = $mysync->{ appendlimit } ; return $mysync->{ maxsize } ; }elsif ( defined $mysync->{ maxsize } ) { return $mysync->{ maxsize } ; }else { return ; } } sub all_defined { if ( not @ARG ) { return 0 ; } foreach my $elem ( @ARG ) { if ( not defined $elem ) { return 0 ; } } return 1 ; } sub tests_all_defined { note( 'Entering tests_all_defined()' ) ; is( 0, all_defined( ), 'all_defined: no param => 0' ) ; is( 0, all_defined( () ), 'all_defined: void list => 0' ) ; is( 0, all_defined( undef ), 'all_defined: undef => 0' ) ; is( 0, all_defined( undef, undef ), 'all_defined: undef => 0' ) ; is( 0, all_defined( 1, undef ), 'all_defined: 1 undef => 0' ) ; is( 0, all_defined( undef, 1 ), 'all_defined: undef 1 => 0' ) ; is( 1, all_defined( 1, 1 ), 'all_defined: 1 1 => 1' ) ; is( 1, all_defined( (1, 1) ), 'all_defined: (1 1) => 1' ) ; note( 'Leaving tests_all_defined()' ) ; return ; } sub tests_hashsynclocal { note( 'Entering tests_hashsynclocal()' ) ; my $mysync = { host1 => q{}, user1 => q{}, password1 => q{}, host2 => q{}, user2 => q{}, password2 => q{}, } ; is( undef, hashsynclocal( $mysync ), 'hashsynclocal: no hashfile name' ) ; $mysync->{ hashfile } = q{} ; is( undef, hashsynclocal( $mysync ), 'hashsynclocal: empty hashfile name' ) ; $mysync->{ hashfile } = './noexist/rrr' ; is( undef, hashsynclocal( $mysync ), 'hashsynclocal: no exists hashfile dir' ) ; SKIP: { if ( 'MSWin32' eq $OSNAME or '0' eq $EFFECTIVE_USER_ID ) { skip( 'Tests only for non-root Unix', 1 ) ; } $mysync->{ hashfile } = '/rrr' ; is( undef, hashsynclocal( $mysync ), 'hashsynclocal: permission denied' ) ; } ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'hashsynclocal: mkpath W/tmp/tests/' ) ; $mysync->{ hashfile } = 'W/tmp/tests/imapsync_hash' ; ok( ! -e 'W/tmp/tests/imapsync_hash' || unlink 'W/tmp/tests/imapsync_hash', 'hashsynclocal: unlink W/tmp/tests/imapsync_hash' ) ; ok( ! -e 'W/tmp/tests/imapsync_hash', 'hashsynclocal: verify there is no W/tmp/tests/imapsync_hash' ) ; is( 'ecdeb4ede672794d173da4e08c52b8ee19b7d252', hashsynclocal( $mysync, 'mukksyhpmbixkxkpjlqivmlqsulpictj' ), 'hashsynclocal: creating/reading W/tmp/tests/imapsync_hash' ) ; # A second time now is( 'ecdeb4ede672794d173da4e08c52b8ee19b7d252', hashsynclocal( $mysync ), 'hashsynclocal: reading W/tmp/tests/imapsync_hash second time => same' ) ; note( 'Leaving tests_hashsynclocal()' ) ; return ; } sub hashsynclocal { my $mysync = shift ; my $hashkey = shift ; # Optional, only there for tests my $hashfile = $mysync->{ hashfile } ; $hashfile = createhashfileifneeded( $hashfile, $hashkey ) ; if ( ! $hashfile ) { return ; } $hashkey = firstline( $hashfile ) ; if ( ! $hashkey ) { myprint( "No hashkey!\n" ) ; return ; } my $hashsynclocal = hashsync( $mysync, $hashkey ) ; return( $hashsynclocal ) ; } sub tests_hashsync { note( 'Entering tests_hashsync()' ) ; is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hashsync( ), 'hashsync: no args' ) ; is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hashsync( {}, q{} ), 'hashsync: empty args' ) ; my $mysync ; $mysync->{ host1 } = 'zzz' ; is( 'e86a28a3611c1e7bbaf8057cd00ae122781a11fe', hashsync( $mysync, q{} ), 'hashsync: host1 zzz => ' ) ; is( '6a7b451ac99eab1531ad8e6cd544b32420c552ac', hashsync( $mysync, q{A} ), 'hashsync: host1 zzz => ' ) ; $mysync->{ host2 } = 'zzz' ; is( '15959573e4a86763253a7aedb1a2b0c60d133dc2', hashsync( $mysync, q{} ), 'hashsync: + host2 zzz => ' ) ; is( 'b8d4ab541b209c75928528020ca28ee43488bd8f', hashsync( $mysync, 'A' ), 'hashsync: + hashkey A => ' ) ; $mysync = undef ; is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hashsync( $mysync, q{} ), 'hashsync: undef $mysync' ) ; $mysync->{ password1 } = 'abcd' ; is( 'afa29ab8534495251ac8346a985717c54bc49c26', hashsync( $mysync, q{} ), 'hashsync: password1: abcd' ) ; # A user reported a massive failure on /X (Thomas V. 21/04/2020 à 21:41 Subject: Error) # "Wide character in subroutine entry at /usr/local/lib/perl5/site_perl/Digest/HMAC.pm" # I can reproduce it now # The eval is there to avoid a complete crash # this one is fatal so it is commented # is( 'f1a3f3dac3f137fd658027c11678b895f773ce55', 1 / 0 , 'hashsync: 1 / 0 fatal' ) ; my $eval ; # this one is not fatal is( undef, $eval = eval { 1 / 0 } , 'hashsync: 1/0 not fatal' ) ; # this one neither $mysync->{ password1 } = 'Ö' ; is( 'f1a3f3dac3f137fd658027c11678b895f773ce55', $eval = eval { hashsync( $mysync, q{} ) } , 'hashsync: password1: Ö with eval' ) ; $mysync->{ password1 } = 'Ö' ; is( 'f1a3f3dac3f137fd658027c11678b895f773ce55', hashsync( $mysync, q{} ), 'hashsync: password1: Ö without eval' ) ; $mysync->{ password1 } = qq{\x{00D6}} ; is( 'bb5bfb461e79ecd3dbc6ade2aabb52d22fa8be1a', $eval = eval { hashsync( $mysync, q{} ) }, 'hashsync: password1: \x{00D6}' ) ; # print qq{1 00D6:Ö\n} ; print encode_utf8( qq{2 00D6:Ö\n} ) ; print qq{3 00D6:\x{00D6}\n} ; print encode_utf8( qq{4 00D6:\x{00D6}\n} ) ; print qq{5 6536:æ”¶\n} ; print encode_utf8( qq{6 6536:æ”¶\n} ) ; # the next one prints "Wide character in print at ./imapsync line xxxx" print qq{7 6536:\x{6536}\n} ; print encode_utf8( qq{8 6536:\x{6536}\n} ) ; $mysync->{ password1 } = qq{æ”¶} ; is( '4199f02773d1cd5599b1a8f2d024bdceb8b48e0b', hashsync( $mysync, q{} ), 'hashsync: password1: æ”¶' ) ; $mysync->{ password1 } = qq{\x{6536}} ; is( '4199f02773d1cd5599b1a8f2d024bdceb8b48e0b', $eval = eval{ hashsync( $mysync, q{} ) }, 'hashsync: password1: \x{6536} with eval' ) ; # No side effect. $mysync->{ password1 } = 'abcd' ; is( 'afa29ab8534495251ac8346a985717c54bc49c26', hashsync( $mysync, q{} ), 'hashsync: password1: abcd again' ) ; note( 'Leaving tests_hashsync()' ) ; return ; } sub hashsync { my $mysync = shift ; my $hashkey = shift ; my $mystring = join( q{}, $mysync->{ host1 } || q{}, $mysync->{ user1 } || q{}, $mysync->{ password1 } || q{}, $mysync->{ host2 } || q{}, $mysync->{ user2 } || q{}, $mysync->{ password2 } || q{}, ) ; #my $hashsync = hmac_sha1_hex( $mystring, $hashkey ) ; my $hashsync = hmac_sha1_hex_robust( $mystring, $hashkey ) ; #myprint( "$hashsync\n" ) ; return( $hashsync ) ; } sub tests_hmac_sha1_hex { note( 'Entering tests_hmac_sha1_hex()' ) ; is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hmac_sha1_hex( ), 'hmac_sha1_hex: no args => fbdb1d1b18aa6c08324b7d64b71fb76370690e1d' ) ; is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hmac_sha1_hex( '' ), 'hmac_sha1_hex: empty string => fbdb1d1b18aa6c08324b7d64b71fb76370690e1d' ) ; is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hmac_sha1_hex( '', '' ), 'hmac_sha1_hex: empty strings => fbdb1d1b18aa6c08324b7d64b71fb76370690e1d' ) ; is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hmac_sha1_hex( '', '', 'caca' ), 'hmac_sha1_hex: empty strings + caca => fbdb1d1b18aa6c08324b7d64b71fb76370690e1d' ) ; # Good is( 'f1a3f3dac3f137fd658027c11678b895f773ce55', hmac_sha1_hex( 'Ö' ), 'hmac_sha1_hex: Ö => f1a3f3dac3f137fd658027c11678b895f773ce55' ) ; is( 'f1a3f3dac3f137fd658027c11678b895f773ce55', hmac_sha1_hex( encode_utf8(qq{\x{00D6}}) ), 'hmac_sha1_hex: encode_utf8 \x{00D6} => f1a3f3dac3f137fd658027c11678b895f773ce55' ) ; # Bad is( 'fe8dc3b9ba3e8850bb4a7b070b2279e911003af2', hmac_sha1_hex( encode_utf8( 'Ö' ) ), 'hmac_sha1_hex: encode_utf8 Ö => fe8dc3b9ba3e8850bb4a7b070b2279e911003af2' ) ; is( 'bb5bfb461e79ecd3dbc6ade2aabb52d22fa8be1a', hmac_sha1_hex( qq{\x{00D6}} ), 'hmac_sha1_hex: qq{\x{00D6}} => bb5bfb461e79ecd3dbc6ade2aabb52d22fa8be1a' ) ; # Good is( 'a6fda2a6acdd74630b20aac0c68716048ecd0333', hmac_sha1_hex( 'A' ), 'hmac_sha1_hex: A => a6fda2a6acdd74630b20aac0c68716048ecd0333' ) ; is( 'a6fda2a6acdd74630b20aac0c68716048ecd0333', hmac_sha1_hex( encode_utf8(qq{\x{0041}}) ), 'hmac_sha1_hex: encode_utf8 \x{0041} => a6fda2a6acdd74630b20aac0c68716048ecd0333' ) ; is( 'a6fda2a6acdd74630b20aac0c68716048ecd0333', hmac_sha1_hex( encode_utf8( 'A' ) ), 'hmac_sha1_hex: encode_utf8 A => a6fda2a6acdd74630b20aac0c68716048ecd0333' ) ; is( 'a6fda2a6acdd74630b20aac0c68716048ecd0333', hmac_sha1_hex( qq{\x{0041}} ), 'hmac_sha1_hex: \x{0041} => a6fda2a6acdd74630b20aac0c68716048ecd0333' ) ; # Good is( '36c54f255b575a2db58921d116b37c8af94c08cd', hmac_sha1_hex( 'A', 'B' ), 'hmac_sha1_hex: A B => 36c54f255b575a2db58921d116b37c8af94c08cd' ) ; is( '36c54f255b575a2db58921d116b37c8af94c08cd', hmac_sha1_hex( encode_utf8(qq{\x{0041}}), 'B' ), 'hmac_sha1_hex: encode_utf8 \x{0041} B => 36c54f255b575a2db58921d116b37c8af94c08cd' ) ; is( '36c54f255b575a2db58921d116b37c8af94c08cd', hmac_sha1_hex( encode_utf8( 'A' ), 'B' ), 'hmac_sha1_hex: encode_utf8 A B => 36c54f255b575a2db58921d116b37c8af94c08cd' ) ; is( '36c54f255b575a2db58921d116b37c8af94c08cd', hmac_sha1_hex( qq{\x{0041}}, 'B' ), 'hmac_sha1_hex: \x{0041} B => 36c54f255b575a2db58921d116b37c8af94c08cd' ) ; # http://unicode.scarfboy.com/?s=U%2B6536 # Good is( '4199f02773d1cd5599b1a8f2d024bdceb8b48e0b', hmac_sha1_hex( 'æ”¶' ), 'hmac_sha1_hex: æ”¶ => 4199f02773d1cd5599b1a8f2d024bdceb8b48e0b' ) ; is( '4199f02773d1cd5599b1a8f2d024bdceb8b48e0b', hmac_sha1_hex( encode_utf8(qq{\x{6536}}) ), 'hmac_sha1_hex: encode_utf8 \x{6536} => 4199f02773d1cd5599b1a8f2d024bdceb8b48e0b' ) ; # Bad is( 'e82217119628ad03e659cc89671d05ea4cee7238', hmac_sha1_hex( encode_utf8( 'æ”¶' ) ), 'hmac_sha1_hex: encode_utf8 æ”¶ => e82217119628ad03e659cc89671d05ea4cee7238' ) ; # Very very bad, perl dies... #is( '4199f02773d1cd5599b1a8f2d024bdceb8b48e0b', hmac_sha1_hex( qq{\x{6536}} ), 'hmac_sha1_hex: \x{6536} => 4199f02773d1cd5599b1a8f2d024bdceb8b48e0b' ) ; # Ok but well, bad indeed is( undef, my $eval = eval{ hmac_sha1_hex( qq{\x{6536}} ) }, 'hmac_sha1_hex: \x{6536} => undef' ) ; note( 'Leaving tests_hmac_sha1_hex()' ) ; return ; } sub tests_hmac_sha1_hex_robust { note( 'Entering tests_hmac_sha1_hex_robust()' ) ; is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hmac_sha1_hex_robust( ), 'hmac_sha1_hex_robust: no args => fbdb1d1b18aa6c08324b7d64b71fb76370690e1d' ) ; is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hmac_sha1_hex_robust( '' ), 'hmac_sha1_hex_robust: empty string => fbdb1d1b18aa6c08324b7d64b71fb76370690e1d' ) ; is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hmac_sha1_hex_robust( '', '' ), 'hmac_sha1_hex_robust: empty strings => fbdb1d1b18aa6c08324b7d64b71fb76370690e1d' ) ; is( 'fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', hmac_sha1_hex_robust( '', '', 'caca' ), 'hmac_sha1_hex_robust: empty strings + caca => fbdb1d1b18aa6c08324b7d64b71fb76370690e1d' ) ; # Good is( 'f1a3f3dac3f137fd658027c11678b895f773ce55', hmac_sha1_hex_robust( 'Ö' ), 'hmac_sha1_hex_robust: Ö => f1a3f3dac3f137fd658027c11678b895f773ce55' ) ; is( 'f1a3f3dac3f137fd658027c11678b895f773ce55', hmac_sha1_hex_robust( encode_utf8(qq{\x{00D6}}) ), 'hmac_sha1_hex_robust: encode_utf8 \x{00D6} => f1a3f3dac3f137fd658027c11678b895f773ce55' ) ; # Bad is( 'fe8dc3b9ba3e8850bb4a7b070b2279e911003af2', hmac_sha1_hex_robust( encode_utf8( 'Ö' ) ), 'hmac_sha1_hex_robust: encode_utf8 Ö => fe8dc3b9ba3e8850bb4a7b070b2279e911003af2' ) ; is( 'bb5bfb461e79ecd3dbc6ade2aabb52d22fa8be1a', hmac_sha1_hex_robust( qq{\x{00D6}} ), 'hmac_sha1_hex_robust: qq{\x{00D6}} => bb5bfb461e79ecd3dbc6ade2aabb52d22fa8be1a' ) ; # Good is( 'a6fda2a6acdd74630b20aac0c68716048ecd0333', hmac_sha1_hex_robust( 'A' ), 'hmac_sha1_hex_robust: A => a6fda2a6acdd74630b20aac0c68716048ecd0333' ) ; is( 'a6fda2a6acdd74630b20aac0c68716048ecd0333', hmac_sha1_hex_robust( encode_utf8(qq{\x{0041}}) ), 'hmac_sha1_hex_robust: encode_utf8 \x{0041} => a6fda2a6acdd74630b20aac0c68716048ecd0333' ) ; is( 'a6fda2a6acdd74630b20aac0c68716048ecd0333', hmac_sha1_hex_robust( encode_utf8( 'A' ) ), 'hmac_sha1_hex_robust: encode_utf8 A => a6fda2a6acdd74630b20aac0c68716048ecd0333' ) ; is( 'a6fda2a6acdd74630b20aac0c68716048ecd0333', hmac_sha1_hex_robust( qq{\x{0041}} ), 'hmac_sha1_hex_robust: \x{0041} => a6fda2a6acdd74630b20aac0c68716048ecd0333' ) ; # Good is( '36c54f255b575a2db58921d116b37c8af94c08cd', hmac_sha1_hex_robust( 'A', 'B' ), 'hmac_sha1_hex_robust: A B => 36c54f255b575a2db58921d116b37c8af94c08cd' ) ; is( '36c54f255b575a2db58921d116b37c8af94c08cd', hmac_sha1_hex_robust( encode_utf8(qq{\x{0041}}), 'B' ), 'hmac_sha1_hex_robust: encode_utf8 \x{0041} B => 36c54f255b575a2db58921d116b37c8af94c08cd' ) ; is( '36c54f255b575a2db58921d116b37c8af94c08cd', hmac_sha1_hex_robust( encode_utf8( 'A' ), 'B' ), 'hmac_sha1_hex_robust: encode_utf8 A B => 36c54f255b575a2db58921d116b37c8af94c08cd' ) ; is( '36c54f255b575a2db58921d116b37c8af94c08cd', hmac_sha1_hex_robust( qq{\x{0041}}, 'B' ), 'hmac_sha1_hex_robust: \x{0041} B => 36c54f255b575a2db58921d116b37c8af94c08cd' ) ; # http://unicode.scarfboy.com/?s=U%2B6536 # Good is( '4199f02773d1cd5599b1a8f2d024bdceb8b48e0b', hmac_sha1_hex_robust( 'æ”¶' ), 'hmac_sha1_hex_robust: æ”¶ => 4199f02773d1cd5599b1a8f2d024bdceb8b48e0b' ) ; is( '4199f02773d1cd5599b1a8f2d024bdceb8b48e0b', hmac_sha1_hex_robust( encode_utf8(qq{\x{6536}}) ), 'hmac_sha1_hex_robust: encode_utf8 \x{6536} => 4199f02773d1cd5599b1a8f2d024bdceb8b48e0b' ) ; # Bad is( 'e82217119628ad03e659cc89671d05ea4cee7238', hmac_sha1_hex_robust( encode_utf8( 'æ”¶' ) ), 'hmac_sha1_hex_robust: encode_utf8 æ”¶ => e82217119628ad03e659cc89671d05ea4cee7238' ) ; # Good is( '4199f02773d1cd5599b1a8f2d024bdceb8b48e0b', hmac_sha1_hex_robust( qq{\x{6536}} ), 'hmac_sha1_hex_robust: \x{6536} => 4199f02773d1cd5599b1a8f2d024bdceb8b48e0b' ) ; # Good again is( '4199f02773d1cd5599b1a8f2d024bdceb8b48e0b', my $eval = eval{ hmac_sha1_hex_robust( qq{\x{6536}} ) }, 'hmac_sha1_hex_robust: \x{6536} => undef' ) ; note( 'Leaving tests_hmac_sha1_hex_robust()' ) ; return ; } sub hmac_sha1_hex_robust { my $string = shift ; my $val ; if ( defined( $val = eval{ hmac_sha1_hex( $string, @ARG ) } ) ) { return $val ; } elsif( defined( $val = eval{ hmac_sha1_hex( encode_utf8( $string ), @ARG ) } ) ) { return $val ; } else { return ; } } sub tests_createhashfileifneeded { note( 'Entering tests_createhashfileifneeded()' ) ; is( undef, createhashfileifneeded( ), 'createhashfileifneeded: no parameters => undef' ) ; note( 'Leaving tests_createhashfileifneeded()' ) ; return ; } sub createhashfileifneeded { my $hashfile = shift ; my $hashkey = shift || rand32( ) ; # no name if ( ! $hashfile ) { return ; } # already there if ( -e -r $hashfile ) { return $hashfile ; } # not creatable if ( ! -w dirname( $hashfile ) ) { return ; } # creatable open my $FILE_HANDLE, '>', $hashfile or do { myprint( "Could not open $hashfile for writing. Check permissions or disk space." ) ; return ; } ; myprint( "Writing random hashkey in $hashfile, once for all times\n" ) ; print $FILE_HANDLE $hashkey ; close $FILE_HANDLE ; # Should be there now if ( -e -r $hashfile ) { return $hashfile ; } # unknown failure return ; } sub tests_rand32 { note( 'Entering tests_rand32()' ) ; my $string = rand32( ) ; myprint( "$string\n" ) ; is( 32, length( $string ), 'rand32: 32 characters long' ) ; is( 32, length( rand32( ) ), 'rand32: 32 characters long, another one' ) ; note( 'Leaving tests_rand32()' ) ; return ; } sub rand32 { my @chars = ( "a".."z" ) ; my $string; $string .= $chars[rand @chars] for 1..32 ; return $string ; } sub imap_id_stuff { my $mysync = shift ; if ( not $mysync->{id} ) { return ; } ; $mysync->{h1_imap_id} = imap_id( $mysync, $mysync->{imap1}, 'Host1' ) ; #myprint( 'Host1: ' . $mysync->{h1_imap_id} ) ; $mysync->{h2_imap_id} = imap_id( $mysync, $mysync->{imap2}, 'Host2' ) ; #myprint( 'Host2: ' . $mysync->{h2_imap_id} ) ; return ; } sub imap_id { my ( $mysync, $imap, $Side ) = @_ ; if ( not $mysync->{id} ) { return q{} ; } ; $Side ||= q{} ; my $imap_id_response = q{} ; if ( not $imap->has_capability( 'ID' ) ) { $imap_id_response = 'No ID capability' ; myprint( "$Side: No ID capability\n" ) ; }else{ my $id_inp = imapsync_id( $mysync, { side => lc $Side } ) ; myprint( "\n$Side: found ID capability. Sending/receiving ID, presented in raw IMAP for now.\n" . "In order to avoid sending/receiving ID, use option --noid\n" ) ; my $debug_before = $imap->Debug( ) ; $imap->Debug( 1 ) ; my $id_out = $imap->tag_and_run( 'ID ' . $id_inp ) ; #my $id_out = $imap->tag_and_run( 'ID NIL' ) ; myprint( "\n" ) ; $imap->Debug( $debug_before ) ; #$imap_id_response = Data::Dumper->Dump( [ $id_out ], [ 'IMAP_ID' ] ) ; } return( $imap_id_response ) ; } sub imapsync_id { my $mysync = shift ; my $overhashref = shift ; # See http://tools.ietf.org/html/rfc2971.html my $imapsync_id = { } ; my $imapsync_id_lamiral = { name => 'imapsync', version => imapsync_version( $mysync ), os => $OSNAME, vendor => 'Gilles LAMIRAL', 'support-url' => 'https://imapsync.lamiral.info/', # Example of date-time: 19-Sep-2015 08:56:07 date => date_from_rcs( q{$Date: 2022/01/12 21:28:37 $ } ), } ; my $imapsync_id_github = { name => 'imapsync', version => imapsync_version( $mysync ), os => $OSNAME, vendor => 'github', 'support-url' => 'https://github.com/imapsync/imapsync', date => date_from_rcs( q{$Date: 2022/01/12 21:28:37 $ } ), } ; $imapsync_id = $imapsync_id_lamiral ; #$imapsync_id = $imapsync_id_github ; my %mix = ( %{ $imapsync_id }, %{ $overhashref } ) ; my $imapsync_id_str = format_for_imap_arg( \%mix ) ; #myprint( "$imapsync_id_str\n" ) ; return( $imapsync_id_str ) ; } sub tests_imapsync_id { note( 'Entering tests_imapsync_id()' ) ; my $mysync ; ok( '("name" "imapsync" "version" "111" "os" "beurk" "vendor" "Gilles LAMIRAL" "support-url" "https://imapsync.lamiral.info/" "date" "22-12-1968" "side" "host1")' eq imapsync_id( $mysync, { version => 111, os => 'beurk', date => '22-12-1968', side => 'host1' } ), 'tests_imapsync_id override' ) ; note( 'Leaving tests_imapsync_id()' ) ; return ; } sub format_for_imap_arg { my $ref = shift ; my $string = q{} ; my %terms = %{ $ref } ; my @terms = ( ) ; if ( not ( %terms ) ) { return( 'NIL' ) } ; # sort like in RFC then add extra key/values foreach my $key ( qw( name version os os-version vendor support-url address date command arguments environment) ) { if ( $terms{ $key } ) { push @terms, $key, $terms{ $key } ; delete $terms{ $key } ; } } push @terms, %terms ; $string = '(' . ( join q{ }, map { '"' . $_ . '"' } @terms ) . ')' ; return( $string ) ; } sub tests_format_for_imap_arg { note( 'Entering tests_format_for_imap_arg()' ) ; ok( 'NIL' eq format_for_imap_arg( { } ), 'format_for_imap_arg empty hash ref' ) ; ok( '("name" "toto")' eq format_for_imap_arg( { name => 'toto' } ), 'format_for_imap_arg { name => toto }' ) ; ok( '("name" "toto" "key" "val")' eq format_for_imap_arg( { name => 'toto', key => 'val' } ), 'format_for_imap_arg 2 x key val' ) ; note( 'Leaving tests_format_for_imap_arg()' ) ; return ; } sub quota { my ( $mysync, $imap, $side ) = @_ ; my %side = ( h1 => 'Host1', h2 => 'Host2', ) ; my $Side = $side{ $side } ; my $debug_before = $imap->Debug( ) ; $imap->Debug( 1 ) ; if ( not $imap->has_capability( 'QUOTA' ) ) { myprint( "$Side: No QUOTA capability found, skipping it.\n" ) ; $imap->Debug( $debug_before ) ; return ; } ; myprint( "\n$Side: QUOTA capability found, presented in raw IMAP on next lines\n" ) ; my $getquotaroot = $imap->getquotaroot( 'INBOX' ) ; # Gmail INBOX quotaroot is "" but with it Mail::IMAPClient does a literal GETQUOTA {2} \n "" #$imap->quota( 'ROOT' ) ; #$imap->quota( '""' ) ; myprint( "\n" ) ; $imap->Debug( $debug_before ) ; my $quota_limit_bytes = quota_extract_storage_limit_in_bytes( $mysync, $getquotaroot ) ; my $quota_current_bytes = quota_extract_storage_current_in_bytes( $mysync, $getquotaroot ) ; $mysync->{$side}->{quota_limit_bytes} = $quota_limit_bytes ; $mysync->{$side}->{quota_current_bytes} = $quota_current_bytes ; my $quota_percent ; if ( $quota_limit_bytes > 0 ) { $quota_percent = mysprintf( '%.2f', $NUMBER_100 * $quota_current_bytes / $quota_limit_bytes ) ; }else{ $quota_percent = 0 ; } myprint( "$Side: Quota current storage is $quota_current_bytes bytes. Limit is $quota_limit_bytes bytes. So $quota_percent % full\n" ) ; if ( $QUOTA_PERCENT_LIMIT < $quota_percent ) { my $error = "$Side: $quota_percent % full: it is time to find a bigger place! ( $quota_current_bytes bytes / $quota_limit_bytes bytes )\n" ; errors_incr( $mysync, $error ) ; } return ; } sub tests_quota_extract_storage_limit_in_bytes { note( 'Entering tests_quota_extract_storage_limit_in_bytes()' ) ; my $mysync = {} ; my $imap_output = [ '* QUOTAROOT "INBOX" "Storage quota" "Messages quota"', '* QUOTA "Storage quota" (STORAGE 1 104857600)', '* QUOTA "Messages quota" (MESSAGE 2 100000)', '5 OK Getquotaroot completed.' ] ; ok( $NUMBER_104_857_600 * $KIBI == quota_extract_storage_limit_in_bytes( $mysync, $imap_output ), 'quota_extract_storage_limit_in_bytes ') ; note( 'Leaving tests_quota_extract_storage_limit_in_bytes()' ) ; return ; } sub quota_extract_storage_limit_in_bytes { my $mysync = shift ; my $imap_output = shift ; my $limit_kb ; $limit_kb = ( map { /.*\(\s*STORAGE\s+\d+\s+(\d+)\s*\)/x ? $1 : () } @{ $imap_output } )[0] ; $limit_kb ||= 0 ; $mysync->{ debug } and myprint( "storage_limit_kb = $limit_kb\n" ) ; return( $KIBI * $limit_kb ) ; } sub tests_quota_extract_storage_current_in_bytes { note( 'Entering tests_quota_extract_storage_current_in_bytes()' ) ; my $mysync = {} ; my $imap_output = [ '* QUOTAROOT "INBOX" "Storage quota" "Messages quota"', '* QUOTA "Storage quota" (STORAGE 1 104857600)', '* QUOTA "Messages quota" (MESSAGE 2 100000)', '5 OK Getquotaroot completed.' ] ; ok( 1*$KIBI == quota_extract_storage_current_in_bytes( $mysync, $imap_output ), 'quota_extract_storage_current_in_bytes: 1 => 1024 ') ; note( 'Leaving tests_quota_extract_storage_current_in_bytes()' ) ; return ; } sub quota_extract_storage_current_in_bytes { my $mysync = shift ; my $imap_output = shift ; my $current_kb ; $current_kb = ( map { /.*\(\s*STORAGE\s+(\d+)\s+\d+\s*\)/x ? $1 : () } @{ $imap_output } )[0] ; $current_kb ||= 0 ; $mysync->{ debug } and myprint( "storage_current_kb = $current_kb\n" ) ; return( $KIBI * $current_kb ) ; } sub automap { my ( $mysync ) = @_ ; if ( $mysync->{automap} ) { myprint( "Turned on automapping folders ( use --noautomap to turn off automapping )\n" ) ; }else{ myprint( "Turned off automapping folders ( use --automap to turn on automapping )\n" ) ; return ; } $mysync->{h1_special} = special_from_folders_hash( $mysync, $mysync->{imap1}, 'Host1' ) ; $mysync->{h2_special} = special_from_folders_hash( $mysync, $mysync->{imap2}, 'Host2' ) ; build_possible_special( $mysync ) ; build_guess_special( $mysync ) ; build_automap( $mysync ) ; return ; } sub build_guess_special { my ( $mysync ) = shift ; foreach my $h1_fold ( sort keys %{ $mysync->{h1_folders_all} } ) { my $special = guess_special( $h1_fold, $mysync->{possible_special}, $mysync->{h1_prefix} ) ; if ( $special ) { $mysync->{h1_special_guessed}{$h1_fold} = $special ; my $already_guessed = $mysync->{h1_special_guessed}{$special} ; if ( $already_guessed ) { myprint( "Host1: $h1_fold not $special because set to $already_guessed\n" ) ; }else{ $mysync->{h1_special_guessed}{$special} = $h1_fold ; } } } foreach my $h2_fold ( sort keys %{ $mysync->{h2_folders_all} } ) { my $special = guess_special( $h2_fold, $mysync->{possible_special}, $mysync->{h2_prefix} ) ; if ( $special ) { $mysync->{h2_special_guessed}{$h2_fold} = $special ; my $already_guessed = $mysync->{h2_special_guessed}{$special} ; if ( $already_guessed ) { myprint( "Host2: $h2_fold not $special because set to $already_guessed\n" ) ; }else{ $mysync->{h2_special_guessed}{$special} = $h2_fold ; } } } return ; } sub guess_special { my( $folder, $possible_special_ref, $prefix ) = @_ ; my $folder_no_prefix = $folder ; $folder_no_prefix =~ s/\Q${prefix}\E//xms ; #$debug and myprint( "folder_no_prefix: $folder_no_prefix\n" ) ; my $guess_special = $possible_special_ref->{ $folder } || $possible_special_ref->{ $folder_no_prefix } || q{} ; return( $guess_special ) ; } sub tests_guess_special { note( 'Entering tests_guess_special()' ) ; my $possible_special_ref = build_possible_special( my $mysync ) ; ok( '\Sent' eq guess_special( 'Sent', $possible_special_ref, q{} ) ,'guess_special: Sent => \Sent' ) ; ok( q{} eq guess_special( 'Blabla', $possible_special_ref, q{} ) ,'guess_special: Blabla => q{}' ) ; ok( '\Sent' eq guess_special( 'INBOX.Sent', $possible_special_ref, 'INBOX.' ) ,'guess_special: INBOX.Sent => \Sent' ) ; ok( '\Sent' eq guess_special( 'IN BOX.Sent', $possible_special_ref, 'IN BOX.' ) ,'guess_special: IN BOX.Sent => \Sent' ) ; note( 'Leaving tests_guess_special()' ) ; return ; } sub build_automap { my $mysync = shift ; $mysync->{ debug } and myprint( "Entering build_automap\n" ) ; foreach my $h1_fold ( @{ $mysync->{h1_folders_wanted} } ) { my $h2_fold ; my $h1_special = $mysync->{h1_special}{$h1_fold} ; my $h1_special_guessed = $mysync->{h1_special_guessed}{$h1_fold} ; # Case 1: special on both sides. if ( $h1_special and exists $mysync->{h2_special}{$h1_special} ) { $h2_fold = $mysync->{h2_special}{$h1_special} ; $mysync->{f1f2auto}{ $h1_fold } = $h2_fold ; next ; } # Case 2: special on host1, not on host2 if ( $h1_special and ( not exists $mysync->{h2_special}{$h1_special} ) and ( exists $mysync->{h2_special_guessed}{$h1_special} ) ) { # special_guessed on host2 $h2_fold = $mysync->{h2_special_guessed}{$h1_special} ; $mysync->{f1f2auto}{ $h1_fold } = $h2_fold ; next ; } # Case 3: no special on host1, special on host2 if ( ( not $h1_special ) and ( $h1_special_guessed ) and ( exists $mysync->{h2_special}{$h1_special_guessed} ) ) { $h2_fold = $mysync->{h2_special}{$h1_special_guessed} ; $mysync->{f1f2auto}{ $h1_fold } = $h2_fold ; next ; } # Case 4: no special on both sides. if ( ( not $h1_special ) and ( $h1_special_guessed ) and ( not exists $mysync->{h2_special}{$h1_special_guessed} ) and ( exists $mysync->{h2_special_guessed}{$h1_special_guessed} ) ) { $h2_fold = $mysync->{h2_special_guessed}{$h1_special_guessed} ; $mysync->{f1f2auto}{ $h1_fold } = $h2_fold ; next ; } } return( $mysync->{f1f2auto} ) ; } # I will not add what there is at: # http://stackoverflow.com/questions/2185391/localized-gmail-imap-folders/2185548#2185548 # because it works well without sub build_possible_special { my $mysync = shift ; my $possible_special = { } ; # All|Archive|Drafts|Flagged|Junk|Sent|Trash $possible_special->{'\All'} = [ 'All', 'All Messages', '&BBIEQQQ1-' ] ; $possible_special->{'\Archive'} = [ 'Archive', 'Archives', '&BBAEQARFBDgEMg-' ] ; $possible_special->{'\Drafts'} = [ 'Drafts', 'DRAFTS', '&BCcENQRABD0EPgQyBDgEOgQ4-', 'Szkice', 'Wersje robocze' ] ; $possible_special->{'\Flagged'} = [ 'Flagged', 'Starred', '&BB8EPgQ8BDUERwQ1BD0EPQRLBDU-' ] ; $possible_special->{'\Junk'} = [ 'Junk', 'junk', 'Spam', 'SPAM', '&BCEEPwQwBDw-', 'Potwierdzony spam', 'Wiadomo&AVs-ci-&AVs-mieci', 'Junk E-Mail', 'Junk Email'] ; $possible_special->{'\Sent'} = [ 'Sent', 'Sent Messages', 'Sent Items', 'Gesendete Elemente', 'Gesendete Objekte', '&AMk-l&AOk-ments envoy&AOk-s', 'E&AwE-le&AwE-ments envoye&AwE-s', 'Envoy&AOk-', 'Objets envoy&AOk-s', 'Elementos enviados', '&kAFP4W4IMH8wojCkMMYw4A-', '&BB4EQgQ,BEAEMAQyBDsENQQ9BD0ESwQ1-', 'Elementy wys&AUI-ane'] ; $possible_special->{'\Trash'} = [ 'Trash', 'TRASH', '&BCMENAQwBDsENQQ9BD0ESwQ1-', '&BBoEPgRABDcEOAQ9BDA-', 'Kosz', 'Deleted Items', 'Deleted Messages' ] ; foreach my $special ( qw( \All \Archive \Drafts \Flagged \Junk \Sent \Trash ) ){ foreach my $possible_folder ( @{ $possible_special->{$special} } ) { $possible_special->{ $possible_folder } = $special ; } ; } $mysync->{possible_special} = $possible_special ; $mysync->{ debug } and myprint( Data::Dumper->Dump( [ $possible_special ], [ 'possible_special' ] ) ) ; return( $possible_special ) ; } sub tests_special_from_folders_hash { note( 'Entering tests_special_from_folders_hash()' ) ; my $mysync = {} ; require_ok( "Test::MockObject" ) ; my $imapT = Test::MockObject->new( ) ; is( undef, special_from_folders_hash( ), 'special_from_folders_hash: no args' ) ; is( undef, special_from_folders_hash( $mysync ), 'special_from_folders_hash: undef args' ) ; is_deeply( {}, special_from_folders_hash( $mysync, $imapT ), 'special_from_folders_hash: $imap void' ) ; $imapT->mock( 'folders_hash', sub { return( [ { name => 'Sent', attrs => [ '\Sent' ] } ] ) } ) ; is_deeply( { Sent => '\Sent', '\Sent' => 'Sent' }, special_from_folders_hash( $mysync, $imapT ), 'special_from_folders_hash: $imap \Sent' ) ; note( 'Leaving tests_special_from_folders_hash()' ) ; return( ) ; } sub special_from_folders_hash { my ( $mysync, $imap, $side ) = @_ ; my %special = ( ) ; if ( ! defined $imap ) { return ; } $side = defined $side ? $side : 'Host?' ; if ( ! $imap->can( 'folders_hash' ) ) { my $error = "$side: To have automagic rfc6154 folder mapping, upgrade Mail::IMAPClient >= 3.34\n" ; errors_incr( $mysync, $error ) ; return( \%special ) ; # empty hash ref } my $folders_hash = $imap->folders_hash( ) ; foreach my $fhash (@{ $folders_hash } ) { my @special = grep { /\\(?:All|Archive|Drafts|Flagged|Junk|Sent|Trash)/x } @{ $fhash->{attrs} } ; if ( @special ) { my $special = $special[0] ; # keep first one. Could be not very good. if ( exists $special{ $special } ) { myprintf( "%s: special %-20s = %s already assigned to %s\n", $side, $fhash->{name}, join( q{ }, @special ), $special{ $special } ) ; }else{ myprintf( "%s: special %-20s = %s\n", $side, $fhash->{name}, join( q{ }, @special ) ) ; $special{ $special } = $fhash->{name} ; $special{ $fhash->{name} } = $special ; # double entry value => key } } } myprint( "\n" ) if ( %special ) ; return( \%special ) ; } sub tests_errors_log { note( 'Entering tests_errors_log()' ) ; is( undef, errors_log( ), 'errors_log: no args => undef' ) ; my $mysync = {} ; is( undef, errors_log( $mysync ), 'errors_log: empty => undef' ) ; is_deeply( [ 'aieaie' ], [ errors_log( $mysync, 'aieaie' ) ], 'errors_log: aieaie => aieaie' ) ; # cumulative is_deeply( [ 'aieaie' ], [ errors_log( $mysync ) ], 'errors_log: nothing more => aieaie' ) ; is_deeply( [ 'aieaie', 'ouille' ], [ errors_log( $mysync, 'ouille' ) ], 'errors_log: ouille => aieaie ouille' ) ; is_deeply( [ 'aieaie', 'ouille' ], [ errors_log( $mysync ) ], 'errors_log: nothing more => aieaie ouille' ) ; note( 'Leaving tests_errors_log()' ) ; return ; } sub errors_log { my ( $mysync, @error ) = @ARG ; if ( ! $mysync->{errors_log} ) { $mysync->{errors_log} = [] ; } if ( @error ) { push @{ $mysync->{errors_log} }, join( q{}, @error ) ; } if ( @{ $mysync->{errors_log} } ) { return @{ $mysync->{errors_log} } ; } else { return ; } } sub tests_comment_of_error_type { note( 'Entering tests_comment_of_error_type()' ) ; is( undef, comment_of_error_type( ), 'comment_of_error_type: no args => undef' ) ; my $mysync = { } ; is( undef, comment_of_error_type( $mysync ), 'comment_of_error_type: undef => undef' ) ; is( "", comment_of_error_type( $mysync, '' ), 'comment_of_error_type: "" => ""' ) ; is( "", comment_of_error_type( $mysync, 'blabla' ), 'comment_of_error_type: blabla => ""' ) ; is( "", comment_of_error_type( $mysync, 'ERR_UNCLASSIFIED' ), 'comment_of_error_type: ERR_UNCLASSIFIED => ""' ) ; like( comment_of_error_type( $mysync, 'ERR_OVERQUOTA' ), qr{100% full}, 'comment_of_error_type: ERR_OVERQUOTA => matches 100% full' ) ; note( 'Leaving tests_comment_of_error_type()' ) ; return ; } sub comment_of_error_type { my $mysync = shift @ARG ; my $error_type = shift @ARG ; if ( ! defined $mysync ) { return ; } if ( ! defined $error_type ) { return ; } my $comment ; if ( exists( $COMMENT_OF_ERR_TYPE{ $error_type } ) ) { $comment = $COMMENT_OF_ERR_TYPE{ $error_type }->( $mysync ) ; } else { $comment = "" ; } return $comment ; } sub tests_error_type { note( 'Entering tests_error_type()' ) ; is( 'ERR_NOTHING_REPORTED', error_type( ), 'error_type: no args => ERR_NOTHING_REPORTED' ) ; is( 'ERR_NOTHING_REPORTED', error_type( '' ), 'error_type: empty string => ERR_NOTHING_REPORTED' ) ; is( 'ERR_UNCLASSIFIED', error_type( 'ERR_UNCLASSIFIED' ), 'error_type: ERR_UNCLASSIFIED => ERR_UNCLASSIFIED' ) ; is( 'ERR_UNCLASSIFIED', error_type( 'aie' ), 'error_type: aie => ERR_UNCLASSIFIED' ) ; is( 'ERR_UNCLASSIFIED', error_type( 'ouille' ), 'error_type: ouille => ERR_UNCLASSIFIED' ) ; is( 'ERR_Host1_FETCH', error_type( 'Message xxx could not be fetched: blabla' ), 'error_type: could not be fetched => ERR_Host1_FETCH' ) ; is( 'ERR_APPEND_SIZE', error_type( 'could not append message xxx: BAD maximum message size exceeded' ), 'error_type: could not append message xxx: BAD maximum message size exceeded => ERR_APPEND_SIZE' ) ; is( 'ERR_OVERQUOTA', error_type( 'Quota limit will be exceeded' ), 'error_type: Quota limit will be exceeded => ERR_OVERQUOTA' ) ; is( 'ERR_APPEND', error_type( 'could not append' ), 'error_type: could not append => ERR_APPEND' ) ; is( 'ERR_CREATE', error_type( 'Could not create folder' ), 'error_type: Could not create folder => ERR_CREATE' ) ; is( 'ERR_SELECT', error_type( 'Could not select: blabla' ), 'error_type: Could not select: blabla => ERR_SELECT' ) ; # #Maximum bytes transferred reached, 423 >= 100, ending sync is( 'ERR_TRANSFER_EXCEEDED', error_type( 'Maximum bytes transferred reached, blabla' ), 'error_type: Maximum bytes transferred reached, blabla => ERR_TRANSFER_EXCEEDED' ) ; # is( 'ERR_CONNECTION_FAILURE_HOST1', error_type( 'Host1 failure: can not open imap connection on host1 [badhostkaka] with user [tata]: Unable to connect to badhostkaka: Invalid argument' ), 'error_type: can not open imap connection on host1 => ERR_CONNECTION_FAILURE_HOST1' ) ; is( 'ERR_CONNECTION_FAILURE_HOST2', error_type( 'Host2 failure: can not open imap connection on host2 [badhostkiki] with user [titi]: Unable to connect to badhostkiki: Invalid argument' ), 'error_type: can not open imap connection on host2 => ERR_CONNECTION_FAILURE_HOST2' ) ; is( 'ERR_APPEND_VIRUS', error_type( 'could not append ( Subject:[For Your Consideration], Date:["29-Nov-2016 03:21:10 -0800"], Size:[5505], Flags:[\Seen] ) to folder INBOX: 275 NO Message refused because it contains a virus' ), 'error_type: could not append ... virus => ERR_APPEND_VIRUS' ) ; is( 'ERR_FLAGS', error_type( 'Host2: flags msg INBOX/957910 could not add flags [PasGlop \PasGlopRe]: 33 NO Error in IMAP command received by server.' ), 'error_type: could not add flags => ERR_FLAGS' ) ; note( 'Leaving tests_error_type()' ) ; return ; } # Could be implemented with https://metacpan.org/pod/Tie::RegexpHash # with just a hash of error regexes as keys and types as values. sub error_type { my $error = shift ; if ( ! defined $error ) { return 'ERR_NOTHING_REPORTED' ; } if ( ! $error ) { return 'ERR_NOTHING_REPORTED' ; } # if ( $error =~ m{Host1 failure: Error login on} ) { return 'ERR_AUTHENTICATION_FAILURE_USER1' } ; if ( $error =~ m{Host2 failure: Error login on} ) { return 'ERR_AUTHENTICATION_FAILURE_USER2' } ; if ( $error =~ m{Host. failure: Can not go to tls encryption on host.} ) { return 'ERR_EXIT_TLS_FAILURE' } ; # if ( $error =~ m{could not be fetched:} ) { return 'ERR_Host1_FETCH' } ; # could not append .*BAD maximum message size exceeded # could not append.*Maximum size of appendable message has been exceeded if ( $error =~ m{could not append .*BAD maximum message size exceeded} ) { return 'ERR_APPEND_SIZE' ; } ; if ( $error =~ m{could not append.*Maximum size of appendable message has been exceeded} ) { return 'ERR_APPEND_SIZE' ; } ; # Could not create folder *[OVERQUOTA] Not enough disk quota # could not append .*[OVERQUOTA] Not enough disk quota # could not append .*[OVERQUOTA] Mailbox is full / Blocks limit exceeded / Inode limit exceeded if ( $error =~ m{OVERQUOTA} ) { return 'ERR_OVERQUOTA' ; } ; if ( $error =~ m{Quota limit will be exceeded} ) { return 'ERR_OVERQUOTA' ; } ; if ( $error =~ m{full: it is time to find a bigger place} ) { return 'ERR_OVERQUOTA' ; } ; # could not append ... to folder INBOX: 276 NO Message refused because it contains a virus if ( $error =~ m{could not append.*virus} ) { return 'ERR_APPEND_VIRUS' ; } ; # could not append .*Write failed 'Broken pipe' # could not append .*timeout waiting .* for data from server # could not append .*BAD Invalid Arguments: Unable to parse message # could not append .*BAD Command Argument Error. 11 # could not append .*NO header limit reached if ( $error =~ m{could not append} ) { return 'ERR_APPEND' ; } ; # could not add flags if ( $error =~ m{could not add flags} ) { return 'ERR_FLAGS' ; } ; # Could not create folder .*Invalid mailbox name if ( $error =~ m{Could not create folder} ) { return 'ERR_CREATE' ; } ; # Could not select:.*NO [NOPERM] Permission denied # Could not select:.*NO Mailbox doesn't exist # Could not select:.*NO [SERVERBUG] Internal error occurred. # Could not select:.*[CANNOT] Mailbox isn't a valid mbox file if ( $error =~ m{Could not select:} ) { return 'ERR_SELECT' ; } ; #Maximum bytes transferred reached, 423 >= 100, ending sync if ( $error =~ m{Maximum bytes transferred reached} ) { return 'ERR_TRANSFER_EXCEEDED' ; } ; if ( $error =~ m{can not open imap connection on host1} ) { return 'ERR_CONNECTION_FAILURE_HOST1' ; } ; if ( $error =~ m{can not open imap connection on host2} ) { return 'ERR_CONNECTION_FAILURE_HOST2' ; } ; # Default is ERR_UNCLASSIFIED return 'ERR_UNCLASSIFIED' ; } sub tests_errorclassify { note( 'Entering tests_errorclassify()' ) ; is( undef, errorclassify( ), 'errorclassify: no args => undef' ) ; is_deeply( { 'ERR_UNCLASSIFIED' => 1 }, errorclassify( 'aie' ), 'errorclassify: aie => { ERR_UNCLASSIFIED => 1 }' ) ; is_deeply( { 'ERR_UNCLASSIFIED' => 2 }, errorclassify( 'aie', 'ouille' ), 'errorclassify: aie ouille => { ERR_UNCLASSIFIED => 2 }' ) ; is_deeply( { 'ERR_UNCLASSIFIED' => 2, 'ERR_NOTHING_REPORTED' => 1 }, errorclassify( 'aie', 'ouille', '' ), 'errorclassify: aie ouille "" => { ERR_UNCLASSIFIED => 2 }' ) ; is_deeply( { 'ERR_UNCLASSIFIED' => 3 }, errorclassify( 'aie', 'ouille', 'aie' ), 'errorclassify: aie ouille aie => { ERR_UNCLASSIFIED => 3 }' ) ; is_deeply( { 'ERR_UNCLASSIFIED' => 1, 'ERR_OVERQUOTA' => 2 }, errorclassify( 'aie', 'OVERQUOTA pipi', 'OVERQUOTA caca' ), 'errorclassify: aie OVERQUOTA OVERQUOTA' ) ; is_deeply( { 'ERR_NOTHING_REPORTED' => 1 }, errorclassify( '' ), 'errorclassify: "" => { ERR_NOTHING_REPORTED => 1 }' ) ; is_deeply( { 'ERR_NOTHING_REPORTED' => 2 }, errorclassify( '', '' ), 'errorclassify: "", "" => { ERR_NOTHING_REPORTED => 1 }' ) ; note( 'Leaving tests_errorclassify()' ) ; return ; } sub errorclassify { my @errors = @ARG ; if ( ! @errors ) { return ; } ; my $error_type_count = { } ; foreach my $error ( @errors ) { my $error_type = error_type( $error ) ; $error_type_count->{ $error_type }++ ; } return $error_type_count ; } sub tests_most_common_error { note( 'Entering tests_most_common_error()' ) ; is( 'ERR_NOTHING_REPORTED', most_common_error( ), 'most_common_error: no args => ERR_NOTHING_REPORTED' ) ; is( 'ERR_NOTHING_REPORTED', most_common_error( {} ), 'most_common_error: empty hash ref => ERR_NOTHING_REPORTED' ) ; is( 'ERR_NOTHING_REPORTED', most_common_error( 'blabla' ), 'most_common_error: not a hash ref => ERR_NOTHING_REPORTED' ) ; is( 'ERR_FOO', most_common_error( { ERR_FOO => 1 } ), 'most_common_error: { ERR_FOO => 1 } => ERR_FOO' ) ; is( 'ERR_BAR', most_common_error( { ERR_FOO => 1, ERR_BAR => 2 } ), 'most_common_error: { ERR_FOO => 1, ERR_BAR => 2 } => ERR_BAR' ) ; is( 'ERR_FOO', most_common_error( { ERR_FOO => 2, ERR_BAR => 1 } ), 'most_common_error: { ERR_FOO => 2, ERR_BAR => 1 } => ERR_FOO' ) ; # exaequo => first lexical wins. ERR_BAR <= ERR_FOO is( 'ERR_BAR', most_common_error( { ERR_FOO => 2, ERR_BAR => 2 } ), 'most_common_error: { ERR_FOO => 2, ERR_BAR => 2 } => ERR_BAR' ) ; is( 'A', most_common_error( { A => 5, B => 5, C => 5 } ), 'most_common_error: { A => 5, B => 5, C => 5 } => A' ) ; is( 'B', most_common_error( { A => 5, B => 6, C => 6 } ), 'most_common_error: { A => 5, B => 6, C => 6 } => B' ) ; is( 'C', most_common_error( { A => 5, B => 5, C => 7 } ), 'most_common_error: { A => 5, B => 5, C => 7 } => C' ) ; is( 'C', most_common_error( { A => 5, B => 6, C => 7 } ), 'most_common_error: { A => 5, B => 5, C => 7 } => C' ) ; note( 'Leaving tests_most_common_error()' ) ; return ; } sub most_common_error { my $errors_counted_ref = shift ; if ( ! defined $errors_counted_ref ) { return 'ERR_NOTHING_REPORTED' ; } if ( 'HASH' ne ref $errors_counted_ref ) { return 'ERR_NOTHING_REPORTED' ; } # empty hash if ( !%{ $errors_counted_ref } ) { return 'ERR_NOTHING_REPORTED' ; } # non empty hash # in case of equality the winner error is the first in alphabetic order my $most_common_error = ( sort { $errors_counted_ref->{$b} <=> $errors_counted_ref->{$a} || $a cmp $b } keys %{$errors_counted_ref} )[0] ; return $most_common_error ; } sub tests_errorsanalyse { note( 'Entering tests_errorsanalyse()' ) ; is( 'ERR_NOTHING_REPORTED', errorsanalyse( ), 'errorsanalyse: no args => ERR_NOTHING_REPORTED' ) ; is( 'ERR_NOTHING_REPORTED', errorsanalyse( ( ) ), 'errorsanalyse: empty list => ERR_NOTHING_REPORTED' ) ; is( 'ERR_UNCLASSIFIED', errorsanalyse( 'aie' ), 'errorsanalyse: aie => ERR_UNCLASSIFIED' ) ; # in case of equality, empty wins is( 'ERR_NOTHING_REPORTED', errorsanalyse( 'aie', '' ), 'errorsanalyse: aie => ERR_UNCLASSIFIED' ) ; is( 'ERR_NOTHING_REPORTED', errorsanalyse( '', 'aie' ), 'errorsanalyse: aie => ERR_UNCLASSIFIED' ) ; is( 'ERR_UNCLASSIFIED', errorsanalyse( 'aie', 'ouille' ), 'errorsanalyse: aie, ouille => ERR_UNCLASSIFIED' ) ; is( 'ERR_UNCLASSIFIED', errorsanalyse( 'aie', 'ouille', '' ), 'errorsanalyse: aie, ouille, "" => ERR_UNCLASSIFIED' ) ; is( 'ERR_UNCLASSIFIED', errorsanalyse( '', 'aie', 'ouille' ), 'errorsanalyse: aie, ouille, "" => ERR_UNCLASSIFIED' ) ; is( 'ERR_NOTHING_REPORTED', errorsanalyse( '' ), 'errorsanalyse: "" => ERR_NOTHING_REPORTED' ) ; is( 'ERR_NOTHING_REPORTED', errorsanalyse( ( '' ) ), 'errorsanalyse: ( "" ) => ERR_NOTHING_REPORTED' ) ; is( 'ERR_NOTHING_REPORTED', errorsanalyse( ( '', '' ) ), 'errorsanalyse: ( "", "" ) => ERR_NOTHING_REPORTED' ) ; note( 'Leaving tests_errorsanalyse()' ) ; return ; } sub errorsanalyse { my @errors = @ARG ; my $errors_types_counted = errorclassify( @errors ) ; my $most_common_error = most_common_error( $errors_types_counted ) ; return $most_common_error ; } sub tests_errorsdump { note( 'Entering tests_errorsdump()' ) ; is( undef, errorsdump( ), 'errorsdump: no args => undef' ) ; is( undef, errorsdump( ( ) ), 'errorsdump: empty list => undef' ) ; is( "Err 1/1: ", errorsdump( '' ), 'errorsdump: one empty string => "Err 1/1: "' ) ; is( "Err 1/1: aieaieaie", errorsdump( 'aieaieaie' ), 'errorsdump: aieaieaie => "Err 1/1: aieaieaie"' ) ; is( "Err 1/2: Aie Err 2/2: Ouille", errorsdump( 'Aie ', 'Ouille' ), 'errorsdump: Aie Ouille => "Err 1/2: Aie Err 2/2: Ouille"' ) ; note( 'Leaving tests_errorsdump()' ) ; return ; } sub errorsdump { if ( ! @ARG ) { return ; } my @errors_log = @ARG ; my $nb_errors = @errors_log ; my $error_num = 0 ; my $errors_list = q{} ; if ( @errors_log ) { foreach my $error ( @errors_log ) { $error_num++ ; $errors_list .= "Err $error_num/$nb_errors: $error" ; } } return( $errors_list ) ; } sub errors_listing { my $mysync = shift ; $mysync->{ most_common_error } = errorsanalyse( errors_log( $mysync ) ) ; my $errors_listing = '' ; if ( $mysync->{ errorsdump } ) { $errors_listing = join( '', "++++ Listing $mysync->{nb_errors} errors encountered during the sync ( avoid this listing with --noerrorsdump ).\n", errorsdump( errors_log( $mysync ) ), ) ; } $errors_listing .= join( '', "The most frequent error is $mysync->{ most_common_error }. ", comment_of_error_type( $mysync, $mysync->{ most_common_error } ), "\n", ) ; return $errors_listing ; } sub errors_incr { my ( $mysync, @error ) = @ARG ; $mysync->{ nb_errors }++ ; if ( @error ) { errors_log( $mysync, @error ) ; myprint( @error ) ; } $mysync->{ errorsmax } ||= $ERRORS_MAX ; if ( $mysync->{ nb_errors } >= $mysync->{ errorsmax } ) { myprint( errorsmax_msg( $mysync ) ) ; myprint( errors_listing( $mysync ) ) ; if ( $mysync->{ errorsdump } ) { # again since errorsdump( ) can be very verbose and masquerade previous warning myprint( errorsmax_msg( $mysync ) ) ; } my $exit_value = exit_value( $mysync, $mysync->{ most_common_error } ) ; exit_clean( $mysync, $exit_value ) ; } return ; } sub errorsmax_msg { my $mysync = shift @ARG ; my $msg = "Maximum number of errors $mysync->{errorsmax} reached " . "( you can change $mysync->{errorsmax} to any value, for example 100 with --errorsmax 100 ). " . "Exiting.\n" ; return $msg ; } sub tests_live_result { note( 'Entering tests_live_result()' ) ; my $nb_errors = shift ; if ( $nb_errors ) { myprint( "Live tests failed with $nb_errors errors\n" ) ; } else { myprint( "Live tests ended successfully\n" ) ; } note( 'Leaving tests_live_result()' ) ; return ; } sub size_filtered_flag { my $mysync = shift ; my $h1_size = shift ; if ( defined $mysync->{ maxsize } and $h1_size >= $mysync->{ maxsize } ) { return( 1 ) ; } if ( defined $minsize and $h1_size <= $minsize ) { return( 1 ) ; } return( 0 ) ; } sub sync_flags_fir { my ( $mysync, $h1_fold, $h1_msg, $h2_fold, $h2_msg, $permanentflags2, $h1_fir_ref, $h2_fir_ref ) = @_ ; if ( not defined $h1_msg ) { return } ; if ( not defined $h2_msg ) { return } ; my $h1_size = $h1_fir_ref->{$h1_msg}->{'RFC822.SIZE'} ; return if size_filtered_flag( $mysync, $h1_size ) ; # used cached flag values for efficiency my $h1_flags = $h1_fir_ref->{ $h1_msg }->{ 'FLAGS' } || q{} ; my $h2_flags = $h2_fir_ref->{ $h2_msg }->{ 'FLAGS' } || q{} ; sync_flags( $mysync, $h1_fold, $h1_msg, $h1_flags, $h2_fold, $h2_msg, $h2_flags, $permanentflags2 ) ; return ; } sub sync_flags_after_copy { # Activated with option --syncflagsaftercopy my( $mysync, $h1_fold, $h1_msg, $h1_flags, $h2_fold, $h2_msg, $permanentflags2 ) = @_ ; if ( my @h2_flags = $mysync->{imap2}->flags( $h2_msg ) ) { my $h2_flags = "@h2_flags" ; ( $mysync->{ debug } or $debugflags ) and myprint( "Host2: msg $h2_fold/$h2_msg flags before sync flags after copy ( $h2_flags )\n" ) ; sync_flags( $mysync, $h1_fold, $h1_msg, $h1_flags, $h2_fold, $h2_msg, $h2_flags, $permanentflags2 ) ; }else{ myprint( "Host2: msg $h2_fold/$h2_msg could not get its flags for sync flags after copy\n" ) ; } return ; } # Globals # $debug # $debugflags # $permanentflags2 sub sync_flags { my( $mysync, $h1_fold, $h1_msg, $h1_flags, $h2_fold, $h2_msg, $h2_flags, $permanentflags2 ) = @_ ; ( $mysync->{ debug } or $debugflags ) and myprint( "Host1: flags init msg $h1_fold/$h1_msg flags( $h1_flags ) Host2 msg $h2_fold/$h2_msg flags( $h2_flags )\n" ) ; $h1_flags = flags_for_host2( $mysync, $h1_flags, $permanentflags2 ) ; $h2_flags = flagscase( $h2_flags ) ; ( $mysync->{ debug } or $debugflags ) and myprint( "Host1: flags filt msg $h1_fold/$h1_msg flags( $h1_flags ) Host2 msg $h2_fold/$h2_msg flags( $h2_flags )\n" ) ; # compare flags - set flags if there a difference my @h1_flags = sort split(q{ }, $h1_flags ); my @h2_flags = sort split(q{ }, $h2_flags ); my $diff = compare_lists( \@h1_flags, \@h2_flags ); $diff and ( $mysync->{ debug } or $debugflags ) and myprint( "Host2: flags msg $h2_fold/$h2_msg replacing h2 flags( $h2_flags ) with h1 flags( $h1_flags )\n" ) ; # This sets flags exactly. So flags can be removed with this. # When you remove a \Seen flag on host1 you want it # to be removed on host2. Just add flags is not what # we need most of the time, so no + like in "+FLAGS.SILENT". if ( not $mysync->{dry} and $diff and not $mysync->{imap2}->store( $h2_msg, "FLAGS.SILENT (@h1_flags)" ) ) { my $error_msg = join q{}, "Host2: flags msg $h2_fold/$h2_msg could not add flags [@h1_flags]: ", $mysync->{imap2}->LastError || q{}, "\n" ; errors_incr( $mysync, $error_msg ) ; } return ; } sub _filter { my $mysync = shift ; my $str = shift or return q{} ; my $sz = $SIZE_MAX_STR ; my $len = length $str ; if ( not $mysync->{ debug } and $len > $sz*2 ) { my $beg = substr $str, 0, $sz ; my $end = substr $str, -$sz, $sz ; $str = $beg . '...' . $end ; } $str =~ s/\012?\015$//x ; return "(len=$len) " . $str ; } sub lost_connection { my( $mysync, $imap, $error_message ) = @_; if ( $imap->IsUnconnected( ) ) { $mysync->{nb_errors}++ ; my $lcomm = $imap->LastIMAPCommand || q{} ; my $einfo = imap_last_error( $imap ) ; # if string is long try reduce to a more reasonable size $lcomm = _filter( $mysync, $lcomm ) ; $einfo = _filter( $mysync, $einfo ) ; myprint( "Failure: last command: $lcomm\n") if ( $mysync->{ debug } && $lcomm) ; myprint( "Failure: lost connection $error_message: ", $einfo, "\n") ; return( 1 ) ; } else{ return( 0 ) ; } } sub imap_last_error { my $imap = shift ; my $einfo = $imap->LastError || @{$imap->History}[$LAST] || q{} ; chomp( $einfo ) ; return( $einfo ) ; } sub tests_max { note( 'Entering tests_max()' ) ; is( 0, max( 0 ), 'max 0 => 0' ) ; is( 1, max( 1 ), 'max 1 => 1' ) ; is( $MINUS_ONE, max( $MINUS_ONE ), 'max -1 => -1') ; is( undef, max( ), 'max no arg => undef' ) ; is( undef, max( undef ), 'undef => undef' ) ; is( undef, max( undef, undef ), 'undef, undef => undef' ) ; is( $NUMBER_100, max( 1, $NUMBER_100 ), 'max 1 100 => 100' ) ; is( $NUMBER_100, max( $NUMBER_100, 1 ), 'max 100 1 => 100' ) ; is( $NUMBER_100, max( $NUMBER_100, $NUMBER_42, 1 ), 'max 100 42 1 => 100' ) ; is( $NUMBER_100, max( $NUMBER_100, '42', 1 ), 'max 100 42 1 => 100' ) ; is( $NUMBER_100, max( '100', '42', 1 ), 'max 100 42 1 => 100' ) ; is( $NUMBER_100, max( $NUMBER_100, 'haha', 1 ), 'max 100 haha 1 => 100') ; is( $NUMBER_100, max( 'bb', $NUMBER_100, 'haha' ), 'max bb 100 haha => 100') ; is( $MINUS_ONE, max( q{}, $MINUS_ONE, 'haha' ), 'max "" -1 haha => -1') ; is( $MINUS_ONE, max( q{}, $MINUS_ONE, $MINUS_TWO ), 'max "" -1 -2 => -1') ; is( $MINUS_ONE, max( 'haha', $MINUS_ONE, $MINUS_TWO ), 'max haha -1 -2 => -1') ; is( 1, max( $MINUS_ONE, 1 ), 'max -1 1 => 1') ; is( 1, max( undef, 1 ), 'max undef 1 => 1' ) ; is( 0, max( undef, 0 ), 'max undef 0 => 0' ) ; is( 'haha', max( 'haha' ), 'max haha => haha') ; is( 'bb', max( 'aa', 'bb' ), 'max aa bb => bb') ; is( 'bb', max( 'bb', 'aa' ), 'max bb aa => bb') ; is( 'bb', max( 'bb', 'aa', 'bb' ), 'max bb aa bb => bb') ; note( 'Leaving tests_max()' ) ; return ; } sub max { my @list = @_ ; return( undef ) if ( 0 == scalar @list ) ; my( @numbers, @notnumbers ) ; foreach my $item ( @list ) { if ( is_number( $item ) ) { push @numbers, $item ; } elsif ( defined $item ) { push @notnumbers, $item ; } } my @sorted ; if ( @numbers ) { @sorted = sort { $a <=> $b } @numbers ; } elsif ( @notnumbers ) { @sorted = sort { $a cmp $b } @notnumbers ; } else { return ; } return( pop @sorted ) ; } sub tests_is_number { note( 'Entering tests_is_number()' ) ; is( undef, is_number( ), 'is_number: no args => undef ' ) ; is( undef, is_number( undef ), 'is_number: undef => undef ' ) ; ok( is_number( 1 ), 'is_number: 1 => 1' ) ; ok( is_number( 1.1 ), 'is_number: 1.1 => 1' ) ; ok( is_number( 0 ), 'is_number: 0 => 1' ) ; ok( is_number( -1 ), 'is_number: -1 => 1' ) ; ok( ! is_number( 1.1.1 ), 'is_number: 1.1.1 => no' ) ; ok( ! is_number( q{} ), 'is_number: q{} => no' ) ; ok( ! is_number( 'haha' ), 'is_number: haha => no' ) ; ok( ! is_number( '0haha' ), 'is_number: 0haha => no' ) ; ok( ! is_number( '2haha' ), 'is_number: 2haha => no' ) ; ok( ! is_number( 'haha2' ), 'is_number: haha2 => no' ) ; note( 'Leaving tests_is_number()' ) ; return ; } sub is_number { my $item = shift ; if ( ! defined $item ) { return ; } if ( $item =~ /\A$RE{num}{real}\Z/ ) { return 1 ; } return ; } sub tests_min { note( 'Entering tests_min()' ) ; is( 0, min( 0 ), 'min 0 => 0' ) ; is( 1, min( 1 ), 'min 1 => 1' ) ; is( $MINUS_ONE, min( $MINUS_ONE ), 'min -1 => -1' ) ; is( undef, min( ), 'min no arg => undef' ) ; is( 1, min( 1, $NUMBER_100 ), 'min 1 100 => 1' ) ; is( 1, min( $NUMBER_100, 1 ), 'min 100 1 => 1' ) ; is( 1, min( $NUMBER_100, $NUMBER_42, 1 ), 'min 100 42 1 => 1' ) ; is( 1, min( $NUMBER_100, '42', 1 ), 'min 100 42 1 => 1' ) ; is( 1, min( '100', '42', 1 ), 'min 100 42 1 => 1' ) ; is( 1, min( $NUMBER_100, 'haha', 1 ), 'min 100 haha 1 => 1') ; is( $MINUS_ONE, min( $MINUS_ONE, 1 ), 'min -1 1 => -1') ; is( 1, min( undef, 1 ), 'min undef 1 => 1' ) ; is( 0, min( undef, 0 ), 'min undef 0 => 0' ) ; is( 1, min( undef, 1 ), 'min undef 1 => 1' ) ; is( 0, min( undef, 2, 0, 1 ), 'min undef, 2, 0, 1 => 0' ) ; is( 'haha', min( 'haha' ), 'min haha => haha') ; is( 'aa', min( 'aa', 'bb' ), 'min aa bb => aa') ; is( 'aa', min( 'bb', 'aa' ), 'min bb aa bb => aa') ; is( 'aa', min( 'bb', 'aa', 'bb' ), 'min bb aa bb => aa') ; note( 'Leaving tests_min()' ) ; return ; } sub min { my @list = @_ ; return( undef ) if ( 0 == scalar @list ) ; my( @numbers, @notnumbers ) ; foreach my $item ( @list ) { if ( is_number( $item ) ) { push @numbers, $item ; }else{ push @notnumbers, $item ; } } my @sorted ; if ( @numbers ) { @sorted = sort { $a <=> $b } @numbers ; }elsif( @notnumbers ) { @sorted = sort { $a cmp $b } @notnumbers ; }else{ return ; } return( shift @sorted ) ; } sub check_lib_version { my $mysync = shift ; $mysync->{ debug } and myprint( "IMAPClient $Mail::IMAPClient::VERSION\n" ) ; if ( '2.2.9' eq $Mail::IMAPClient::VERSION ) { myprint( "imapsync no longer supports Mail::IMAPClient 2.2.9, upgrade it\n" ) ; return 0 ; } else{ # 3.x.x is no longer buggy with imapsync. # 3.30 or currently superior is imposed in the Perl "use Mail::IMAPClient line". return 1 ; } return ; } sub module_version_str { my( $module_name, $module_version ) = @_ ; my $str = mysprintf( "%-20s %s\n", $module_name, $module_version ) ; return( $str ) ; } sub modulesversion { my @list_version; my %modulesversion = ( 'Authen::NTLM' => sub { $Authen::NTLM::VERSION }, 'CGI' => sub { $CGI::VERSION }, 'Compress::Zlib' => sub { $Compress::Zlib::VERSION }, 'Crypt::OpenSSL::RSA' => sub { $Crypt::OpenSSL::RSA::VERSION }, 'Data::Uniqid' => sub { $Data::Uniqid::VERSION }, 'Digest::HMAC_MD5' => sub { $Digest::HMAC_MD5::VERSION }, 'Digest::HMAC_SHA1' => sub { $Digest::HMAC_SHA1::VERSION }, 'Digest::MD5' => sub { $Digest::MD5::VERSION }, 'Encode' => sub { $Encode::VERSION }, 'Encode::IMAPUTF7' => sub { $Encode::IMAPUTF7::VERSION }, 'File::Copy::Recursive' => sub { $File::Copy::Recursive::VERSION }, 'File::Spec' => sub { $File::Spec::VERSION }, 'Getopt::Long' => sub { $Getopt::Long::VERSION }, 'HTML::Entities' => sub { $HTML::Entities::VERSION }, 'IO::Socket' => sub { $IO::Socket::VERSION }, 'IO::Socket::INET' => sub { $IO::Socket::INET::VERSION }, 'IO::Socket::INET6' => sub { $IO::Socket::INET6::VERSION }, 'IO::Socket::IP' => sub { $IO::Socket::IP::VERSION }, 'IO::Socket::SSL' => sub { $IO::Socket::SSL::VERSION }, 'IO::Tee' => sub { $IO::Tee::VERSION }, 'JSON' => sub { $JSON::VERSION }, 'JSON::WebToken' => sub { $JSON::WebToken::VERSION }, 'LWP' => sub { $LWP::VERSION }, 'Mail::IMAPClient' => sub { $Mail::IMAPClient::VERSION }, 'MIME::Base64' => sub { $MIME::Base64::VERSION }, 'Net::Ping' => sub { $Net::Ping::VERSION }, 'Net::SSLeay' => sub { $Net::SSLeay::VERSION }, 'Term::ReadKey' => sub { $Term::ReadKey::VERSION }, 'Test::MockObject' => sub { $Test::MockObject::VERSION }, 'Time::HiRes' => sub { $Time::HiRes::VERSION }, 'Unicode::String' => sub { $Unicode::String::VERSION }, 'URI::Escape' => sub { $URI::Escape::VERSION }, #'Lalala' => sub { $Lalala::VERSION }, ) ; foreach my $module_name ( sort keys %modulesversion ) { # trick from http://www.perlmonks.org/?node_id=152122 my $file_name = $module_name . '.pm' ; $file_name =~s,::,/,xmgs; # Foo::Bar::Baz => Foo/Bar/Baz.pm my $v ; eval { require $file_name ; $v = defined $modulesversion{ $module_name } ? $modulesversion{ $module_name }->() : q{?} ; } or $v = q{Not installed} ; push @list_version, module_version_str( $module_name, $v ) ; } return( @list_version ) ; } sub tests_command_line_nopassword { note( 'Entering tests_command_line_nopassword()' ) ; ok( q{} eq command_line_nopassword(), 'command_line_nopassword void' ); my $mysync = {} ; ok( '--blabla' eq command_line_nopassword( $mysync, '--blabla' ), 'command_line_nopassword --blabla' ); #myprint( command_line_nopassword((qw{ --password1 secret1 })), "\n" ) ; ok( '--password1 MASKED' eq command_line_nopassword( $mysync, qw{ --password1 secret1}), 'command_line_nopassword --password1' ); ok( '--blabla --password1 MASKED --blibli' eq command_line_nopassword( $mysync, qw{ --blabla --password1 secret1 --blibli } ), 'command_line_nopassword --password1 --blibli' ); $mysync->{showpasswords} = 1 ; ok( q{} eq command_line_nopassword(), 'command_line_nopassword void' ); ok( '--blabla' eq command_line_nopassword( $mysync, '--blabla'), 'command_line_nopassword --blabla' ); #myprint( command_line_nopassword((qw{ --password1 secret1 })), "\n" ) ; ok( '--password1 secret1' eq command_line_nopassword( $mysync, qw{ --password1 secret1} ), 'command_line_nopassword --password1' ); ok( '--blabla --password1 secret1 --blibli' eq command_line_nopassword( $mysync, qw{ --blabla --password1 secret1 --blibli } ), 'command_line_nopassword --password1 --blibli' ); note( 'Leaving tests_command_line_nopassword()' ) ; return ; } # Construct a command line copy with passwords replaced by MASKED. sub command_line_nopassword { my $mysync = shift @ARG ; my @argv = @ARG ; my @argv_nopassword ; if ( $mysync->{ cmdcgi } ) { @argv_nopassword = mask_password_value( @{ $mysync->{ cmdcgi } } ) ; return( "@argv_nopassword" ) ; } if ( $mysync->{showpasswords} ) { return( "@argv" ) ; } @argv_nopassword = mask_password_value( @argv ) ; return("@argv_nopassword") ; } sub mask_password_value { my @argv = @ARG ; my @argv_nopassword ; while ( @argv ) { my $arg = shift @argv ; # option name or value if ( $arg =~ m/-password[12]/x ) { shift @argv ; # password value push @argv_nopassword, $arg, 'MASKED' ; # option name and fake value }else{ push @argv_nopassword, $arg ; # same option or value } } return @argv_nopassword ; } sub tests_get_stdin_masked { note( 'Entering tests_get_stdin_masked()' ) ; is( q{}, get_stdin_masked( ), 'get_stdin_masked: no args' ) ; is( q{}, get_stdin_masked( 'Please ENTER: ' ), 'get_stdin_masked: ENTER' ) ; note( 'Leaving tests_get_stdin_masked()' ) ; return ; } ####################################################### # The issue is that prompt() does not prompt the prompt # when the program is used like # { sleep 2 ; echo blablabla ; } | ./imapsync ...--host1 lo --user1 tata --host2 lo --user2 titi # use IO::Prompter ; sub get_stdin_masked { my $prompt = shift || 'Say something: ' ; local @ARGV = () ; my $input = prompt( -prompt => $prompt, -echo => '*', ) ; #myprint( "You said: $input\n" ) ; return $input ; } sub ask_for_password_new { my $prompt = shift ; my $password = get_stdin_masked( $prompt ) ; return $password ; } ######################################################### sub ask_for_password { my $prompt = shift ; myprint( $prompt ) ; Term::ReadKey::ReadMode( 2 ) ; ## no critic (InputOutput::ProhibitExplicitStdin) my $password = ; chomp $password ; myprint( "\nGot it\n" ) ; Term::ReadKey::ReadMode( 0 ) ; return $password ; } # Have to refactor get_password1() get_password2() # to have only get_password() and two calls sub get_password1 { my $mysync = shift ; $mysync->{ password1 } || $mysync->{ passfile1 } || 'PREAUTH' eq $mysync->{ acc1 }->{ authmech } || 'EXTERNAL' eq $mysync->{ acc1 }->{ authmech } || $ENV{IMAPSYNC_PASSWORD1} || do { myprint( << 'FIN_PASSFILE' ) ; If you are afraid of giving password on the command line arguments, you can put the password of user1 in a file named file1 and use "--passfile1 file1" instead of typing it. Then give this file restrictive permissions with the command "chmod 600 file1". An other solution is to set the environment variable IMAPSYNC_PASSWORD1 FIN_PASSFILE my $user = $mysync->{ acc1 }->{ authuser } || $mysync->{ user1 } ; my $host = $mysync->{ host1 } ; my $prompt = "What's the password for $user" . ' at ' . "$host? (not visible while you type, then enter RETURN) " ; $mysync->{password1} = ask_for_password( $prompt ) ; } ; if ( defined $mysync->{ passfile1 } ) { if ( ! -e -r $mysync->{ passfile1 } ) { myprint( "Failure: file from parameter --passfile1 $mysync->{ passfile1 } does not exist or is not readable\n" ) ; $mysync->{nb_errors}++ ; exit_clean( $mysync, $EX_NOINPUT ) ; } # passfile1 readable $mysync->{password1} = firstline ( $mysync->{ passfile1 } ) ; return ; } if ( $ENV{IMAPSYNC_PASSWORD1} ) { $mysync->{password1} = $ENV{IMAPSYNC_PASSWORD1} ; return ; } return ; } sub get_password2 { my $mysync = shift ; $mysync->{password2} || $mysync->{ passfile2 } || 'PREAUTH' eq $mysync->{ acc2 }->{ authmech } || 'EXTERNAL' eq $mysync->{ acc2 }->{ authmech } || $ENV{IMAPSYNC_PASSWORD2} || do { myprint( << 'FIN_PASSFILE' ) ; If you are afraid of giving password on the command line arguments, you can put the password of user2 in a file named file2 and use "--passfile2 file2" instead of typing it. Then give this file restrictive permissions with the command "chmod 600 file2". An other solution is to set the environment variable IMAPSYNC_PASSWORD2 FIN_PASSFILE my $user = $mysync->{ acc2 }->{ authuser } || $mysync->{ user2 } ; my $host = $mysync->{ host2 } ; my $prompt = "What's the password for $user" . ' at ' . "$host? (not visible while you type, then enter RETURN) " ; $mysync->{password2} = ask_for_password( $prompt ) ; } ; if ( defined $mysync->{ passfile2 } ) { if ( ! -e -r $mysync->{ passfile2 } ) { myprint( "Failure: file from parameter --passfile2 $mysync->{ passfile2 } does not exist or is not readable\n" ) ; $mysync->{nb_errors}++ ; exit_clean( $mysync, $EX_NOINPUT ) ; } # passfile2 readable $mysync->{password2} = firstline ( $mysync->{ passfile2 } ) ; return ; } if ( $ENV{IMAPSYNC_PASSWORD2} ) { $mysync->{password2} = $ENV{IMAPSYNC_PASSWORD2} ; return ; } return ; } sub remove_tmp_files { my $mysync = shift or return ; $mysync->{pidfile} or return ; if ( -e $mysync->{pidfile} ) { myprint( "Removing pidfile $mysync->{pidfile}\n" ) ; unlink $mysync->{pidfile} ; } if ( -e $mysync->{abortfile} ) { myprint( "Removing pidfile $mysync->{abortfile}\n" ) ; unlink $mysync->{abortfile} ; } return ; } sub cleanup_before_exit { my $mysync = shift ; remove_tmp_files( $mysync ) ; if ( $mysync->{imap1} and $mysync->{imap1}->IsConnected() ) { myprint( "Disconnecting from host1 $mysync->{ host1 } user1 $mysync->{ user1 }\n" ) ; $mysync->{imap1}->logout( ) ; } if ( $mysync->{imap2} and $mysync->{imap2}->IsConnected() ) { myprint( "Disconnecting from host2 $mysync->{ host2 } user2 $mysync->{ user2 }\n" ) ; $mysync->{imap2}->logout( ) ; } if ( $mysync->{log} ) { myprint( "Log file is $mysync->{logfile} ( to change it, use --logfile filepath ; or use --nolog to turn off logging )\n" ) ; } else { myprint( "No log file because of option --nolog\n" ) ; } if ( $mysync->{log} and $mysync->{logfile_handle} ) { #print( "Closing $mysync->{ logfile }\n" ) ; teefinish( $mysync ) ; } return ; } sub tests_exit_value { note( 'Entering tests_exit_value()' ) ; is( $EXIT_CATCH_ALL, exit_value( ), 'exit_value: no args => EXIT_CATCH_ALL' ) ; my $mysync = { } ; is( $EXIT_CATCH_ALL, exit_value( $mysync ), 'exit_value: undef => EXIT_CATCH_ALL' ) ; is( $EXIT_CATCH_ALL, exit_value( $mysync, 'Blabla_unknown' ), 'exit_value: Blabla => EXIT_CATCH_ALL' ) ; is( $EXIT_CATCH_ALL, exit_value( $mysync, '' ), 'exit_value: empty => EXIT_CATCH_ALL' ) ; is( $EXIT_OVERQUOTA, exit_value( $mysync, 'ERR_OVERQUOTA' ), 'exit_value: ERR_OVERQUOTA => EXIT_OVERQUOTA' ) ; is( $EXIT_TRANSFER_EXCEEDED, exit_value( $mysync, 'ERR_TRANSFER_EXCEEDED' ), 'exit_value: ERR_TRANSFER_EXCEEDED => EXIT_TRANSFER_EXCEEDED' ) ; note( 'Leaving tests_exit_value()' ) ; return ; } sub exit_value { my $mysync = shift @ARG ; my $most_common_error = shift @ARG ; if ( ! defined $most_common_error ) { return $EXIT_CATCH_ALL ; } my $exit_value = $EXIT_VALUE_OF_ERR_TYPE{ $most_common_error } || $EXIT_CATCH_ALL ; return $exit_value ; } sub exit_most_errors { my $mysync = shift @ARG ; myprint( errors_listing( $mysync ) ) ; my $exit_value = exit_value( $mysync, $mysync->{ most_common_error } ) ; exit_clean( $mysync, $exit_value ) ; return ; } sub exit_clean { my $mysync = shift @ARG ; my $status = shift @ARG ; my @messages = @ARG ; if ( @messages ) { myprint( @messages ) ; } myprint( "Exiting with return value $status ($EXIT_TXT{$status}) $mysync->{nb_errors}/$mysync->{errorsmax} nb_errors/max_errors PID $PROCESS_ID\n" ) ; cleanup_before_exit( $mysync ) ; exit $status ; } sub missing_option { my $mysync = shift ; my $option = shift ; $mysync->{nb_errors}++ ; exit_clean( $mysync, $EX_USAGE, "$option option is mandatory, for help run $PROGRAM_NAME --help\n" ) ; return ; } sub catch_ignore { my $mysync = shift ; my $signame = shift ; my $sigcounter = ++$mysync->{ sigcounter }{ $signame } ; myprint( "\nGot a signal $signame (my PID is $PROCESS_ID my PPID is ", getppid( ), "). Received $sigcounter $signame signals so far. Thanks!\n" ) ; do_and_print_stats( $mysync ) ; return ; } sub catch_exit { my $mysync = shift ; my $signame = shift || q{} ; if ( $signame ) { myprint( "\nGot a signal $signame (my PID is $PROCESS_ID my PPID is ", getppid( ), "). Asked to terminate\n" ) ; if ( $mysync->{can_do_stats} ) { do_and_print_stats( $mysync ) ; myprint( "Ended by a signal $signame (my PID is $PROCESS_ID my PPID is ", getppid( ), "). I am asked to terminate immediately.\n" ) ; } myprint( "You should resynchronize those accounts by running a sync again,\n", "since some messages and entire folders might still be missing on host2.\n" ) ; ## no critic (RequireLocalizedPunctuationVars) # Well, restore default action does not work well $SIG{ $signame } = 'DEFAULT'; # restore default action #$SIG{ 'TERM' } = 'DEFAULT'; # restore default action # kill myself with $signame # https://www.cons.org/cracauer/sigint.html myprint( "Killing myself with signal $signame\n" ) ; #cleanup_before_exit( $mysync ) ; kill( $signame, $PROCESS_ID ) ; #kill( 'TERM', $PROCESS_ID ) ; #sleep 1 ; #while ( 1 ) { } ; $mysync->{nb_errors}++ ; exit_clean( $mysync, $EXIT_BY_SIGNAL, "Still there after killing myself with signal $signame...\n" ) ; } else { $mysync->{nb_errors}++ ; exit_clean( $mysync, $EXIT_BY_SIGNAL, "Exiting in catch_exit with no signal...\n" ) ; } return ; } sub catch_print { my $mysync = shift ; my $signame = shift ; my $sigcounter = ++$mysync->{ sigcounter }{ $signame } ; myprint( "\nGot a signal $signame (my PID is $PROCESS_ID my PPID is ", getppid( ), "). Received $sigcounter $signame signals so far. Thanks!\n" ) ; return ; } sub here_twice { my $mysync = shift ; my $now = time ; my $previous = $mysync->{lastcatch} || 0 ; $mysync->{lastcatch} = $now ; if ( $INTERVAL_TO_EXIT >= $now - $previous ) { return $TRUE ; }else{ return $FALSE ; } } sub catch_reconnect { my $mysync = shift ; my $signame = shift ; if ( here_twice( $mysync ) ) { myprint( "Got two signals $signame within $INTERVAL_TO_EXIT seconds. Exiting...\n" ) ; catch_exit( $mysync, $signame ) ; }else{ myprint( "\nGot a signal $signame (my PID is $PROCESS_ID my PPID is ", getppid( ), ")\n", "Hit 2 ctr-c within 2 seconds to exit the program\n", "Hit only 1 ctr-c to reconnect to both imap servers\n", ) ; myprint( "For now only one signal $signame within $INTERVAL_TO_EXIT seconds.\n" ) ; if ( ! defined $mysync->{imap1} ) { return ; } if ( ! defined $mysync->{imap2} ) { return ; } myprint( "Info: reconnecting to host1 imap server $mysync->{host1}\n" ) ; $mysync->{imap1}->State( Mail::IMAPClient::Unconnected ) ; $mysync->{imap1}->{IMAPSYNC_RECONNECT_COUNT} += 1 ; if ( $mysync->{imap1}->reconnect( ) ) { myprint( "Info: reconnected to host1 imap server $mysync->{host1}\n" ) ; } else { $mysync->{nb_errors}++ ; exit_clean( $mysync, $EXIT_CONNECTION_FAILURE ) ; } myprint( "Info: reconnecting to host2 imap server\n" ) ; $mysync->{imap2}->State( Mail::IMAPClient::Unconnected ) ; $mysync->{imap2}->{IMAPSYNC_RECONNECT_COUNT} += 1 ; if ( $mysync->{imap2}->reconnect( ) ) { myprint( "Info: reconnected to host2 imap server $mysync->{host2}\n" ) ; } else { $mysync->{nb_errors}++ ; exit_clean( $mysync, $EXIT_CONNECTION_FAILURE ) ; } myprint( "Info: reconnected to both imap servers\n" ) ; } return ; } sub install_signals { my $mysync = shift ; if ( under_docker_context( $mysync ) ) { # output( $mysync, "Under docker context so leaving signals as they are\n" ) ; output( $mysync, "Under docker context so installing only signals to exit\n" ) ; @{ $mysync->{ sigexit } } = ( defined( $mysync->{ sigexit } ) ) ? @{ $mysync->{ sigexit } } : ( 'INT', 'QUIT', 'TERM' ) ; sig_install( $mysync, 'catch_exit', @{ $mysync->{ sigexit } } ) ; } else { # Unix signals @{ $mysync->{ sigexit } } = ( defined( $mysync->{ sigexit } ) ) ? @{ $mysync->{ sigexit } } : ( 'QUIT', 'TERM' ) ; @{ $mysync->{ sigreconnect } } = ( defined( $mysync->{ sigreconnect } ) ) ? @{ $mysync->{ sigreconnect } } : ( 'INT' ) ; @{ $mysync->{ sigprint } } = ( defined( $mysync->{ sigprint } ) ) ? @{ $mysync->{ sigprint } } : ( 'HUP' ) ; @{ $mysync->{ sigignore } } = ( defined( $mysync->{ sigignore } ) ) ? @{ $mysync->{ sigignore } } : ( ) ; #local %SIG = %SIG ; sig_install( $mysync, 'catch_exit', @{ $mysync->{ sigexit } } ) ; sig_install( $mysync, 'catch_reconnect', @{ $mysync->{ sigreconnect } } ) ; sig_install( $mysync, 'catch_print', @{ $mysync->{ sigprint } } ) ; # --sigignore can override sigexit, sigreconnect and sigprint (for the same signals only) sig_install( $mysync, 'catch_ignore', @{ $mysync->{ sigignore } } ) ; # remove/add sleeping mechanism when receiving USR1 signal (except on Win32) sig_install_toggle_sleep( $mysync ) ; } return ; } sub tests_reconnect_12_if_needed { note( 'Entering tests_reconnect_12_if_needed()' ) ; my $mysync ; $mysync->{imap1} = Mail::IMAPClient->new( ) ; $mysync->{imap2} = Mail::IMAPClient->new( ) ; $mysync->{imap1}->Server( 'test1.lamiral.info' ) ; $mysync->{imap2}->Server( 'test2.lamiral.info' ) ; is( 2, reconnect_12_if_needed( $mysync ), 'reconnect_12_if_needed: test1&test2 .lamiral.info => 1' ) ; is( 1, $mysync->{imap1}->{IMAPSYNC_RECONNECT_COUNT}, 'reconnect_12_if_needed: test1.lamiral.info IMAPSYNC_RECONNECT_COUNT => 1' ) ; is( 1, $mysync->{imap2}->{IMAPSYNC_RECONNECT_COUNT}, 'reconnect_12_if_needed: test2.lamiral.info IMAPSYNC_RECONNECT_COUNT => 1' ) ; note( 'Leaving tests_reconnect_12_if_needed()' ) ; return ; } sub reconnect_12_if_needed { my $mysync = shift ; #return 2 ; if ( ! reconnect_if_needed( $mysync->{imap1} ) ) { return ; } if ( ! reconnect_if_needed( $mysync->{imap2} ) ) { return ; } # both were good return 2 ; } sub tests_reconnect_if_needed { note( 'Entering tests_reconnect_if_needed()' ) ; my $myimap ; is( undef, reconnect_if_needed( ), 'reconnect_if_needed: no args => undef' ) ; is( undef, reconnect_if_needed( $myimap ), 'reconnect_if_needed: undef arg => undef' ) ; $myimap = Mail::IMAPClient->new( ) ; $myimap->Debug( 1 ) ; is( undef, reconnect_if_needed( $myimap ), 'reconnect_if_needed: empty new Mail::IMAPClient => undef' ) ; $myimap->Server( 'test.lamiral.info' ) ; is( 1, reconnect_if_needed( $myimap ), 'reconnect_if_needed: test.lamiral.info => 1' ) ; is( 1, $myimap->{IMAPSYNC_RECONNECT_COUNT}, 'reconnect_if_needed: test.lamiral.info IMAPSYNC_RECONNECT_COUNT => 1' ) ; note( 'Leaving tests_reconnect_if_needed()' ) ; return ; } sub reconnect_if_needed { # return undef upon failure. # return 1 upon connection success, with or without reconnection. my $imap = shift ; if ( ! defined $imap ) { return ; } if ( ! $imap->Server( ) ) { return ; } if ( $imap->IsUnconnected( ) ) { $imap->{IMAPSYNC_RECONNECT_COUNT} += 1 ; if ( $imap->reconnect( ) ) { return 1 ; } }else{ return 1 ; } # A last forced one $imap->State( Mail::IMAPClient::Unconnected ) ; $imap->reconnect( ) ; $imap->{IMAPSYNC_RECONNECT_COUNT} += 1 ; if ( $imap->noop ) { # NOOP is ok return 1 ; } return ; } sub justconnect { my $mysync = shift ; my $justconnect1 = justconnect1( $sync ) ; my $justconnect2 = justconnect2( $sync ) ; return "$justconnect1 $justconnect2"; } sub justconnect1 { my $mysync = shift ; if ( $mysync->{host1} ) { myprint( "Host1: Will just connect to $mysync->{host1} without login\n" ) ; $mysync->{imap1} = connect_imap( $mysync->{host1}, $mysync->{port1}, $mysync->{ssl1}, $mysync->{tls1}, $mysync->{ acc1 } ) ; imap_id( $mysync, $mysync->{imap1}, $mysync->{ acc1 }->{ Side } ) ; $mysync->{imap1}->logout( ) ; return $mysync->{host1} ; } return q{} ; } sub justconnect2 { my $mysync = shift ; if ( $mysync->{host2} ) { myprint( "Host2: Will just connect to $mysync->{host2} without login\n" ) ; $mysync->{imap2} = connect_imap( $mysync->{host2}, $mysync->{port2}, $mysync->{ssl2}, $mysync->{tls2}, $mysync->{ acc2 } ) ; imap_id( $mysync, $mysync->{imap2}, $mysync->{ acc2 }->{ Side } ) ; $mysync->{imap2}->logout( ) ; return $mysync->{host2} ; } return q{} ; } sub skip_macosx { #return ; # hostname is sometimes "macosx.polarhome.com" sometimes "macosx" return( ( ( 'macosx.polarhome.com' eq hostname( ) ) || ( 'macosx' eq hostname( ) ) ) && ( 'darwin' eq $OSNAME ) ) ; } sub skip_macosx_binary { #return ; return( skip_macosx( ) && ( $PROGRAM_NAME =~ m{imapsync_bin_Darwin} ) ) ; } sub tests_mailimapclient_connect { note( 'Entering tests_mailimapclient_connect()' ) ; my $imap ; # ipv4 ok( $imap = Mail::IMAPClient->new( ), 'mailimapclient_connect ipv4: new' ) ; is( 'Mail::IMAPClient', ref( $imap ), 'mailimapclient_connect ipv4: ref is Mail::IMAPClient' ) ; # Mail::IMAPClient 3.40 die on this... So we skip it, thanks to "mature" IO::Socket::IP # Mail::IMAPClient 3.42 is ok so this test is back. is( undef, $imap->connect( ), 'mailimapclient_connect ipv4: connect with no server => failure' ) ; is( 'test.lamiral.info', $imap->Server( 'test.lamiral.info' ), 'mailimapclient_connect ipv4: setting Server(test.lamiral.info)' ) ; is( 1, $imap->Debug( 1 ), 'mailimapclient_connect ipv4: setting Debug( 1 )' ) ; is( 143, $imap->Port( 143 ), 'mailimapclient_connect ipv4: setting Port( 143 )' ) ; is( 10, $imap->Timeout( 10 ), 'mailimapclient_connect ipv4: setting Timeout( 10 )' ) ; like( ref( $imap->connect( ) ), qr/IO::Socket::INET|IO::Socket::IP/, 'mailimapclient_connect ipv4: connect to test.lamiral.info' ) ; like( $imap->logout( ), qr/Mail::IMAPClient/, 'mailimapclient_connect ipv4: logout' ) ; is( undef, undef $imap, 'mailimapclient_connect ipv4: free variable' ) ; # ipv4 + ssl ok( $imap = Mail::IMAPClient->new( ), 'mailimapclient_connect ipv4 + ssl: new' ) ; is( 'test.lamiral.info', $imap->Server( 'test.lamiral.info' ), 'mailimapclient_connect ipv4 + ssl: setting Server(test.lamiral.info)' ) ; is( 1, $imap->Debug( 1 ), 'mailimapclient_connect ipv4 + ssl: setting Debug( 1 )' ) ; ok( $imap->Ssl( [ SSL_verify_mode => SSL_VERIFY_NONE, SSL_cipher_list => 'DEFAULT:!DH' ] ), 'mailimapclient_connect ipv4 + ssl: setting Ssl( SSL_VERIFY_NONE )' ) ; is( 993, $imap->Port( 993 ), 'mailimapclient_connect ipv4 + ssl: setting Port( 993 )' ) ; like( ref( $imap->connect( ) ), qr/IO::Socket::SSL/, 'mailimapclient_connect ipv4 + ssl: connect to test.lamiral.info' ) ; like( $imap->logout( ), qr/Mail::IMAPClient/, 'mailimapclient_connect ipv4 + ssl: logout in ssl does not cause failure' ) ; is( undef, undef $imap, 'mailimapclient_connect ipv4 + ssl: free variable' ) ; # ipv6 + ssl ok( $imap = Mail::IMAPClient->new( ), 'mailimapclient_connect ipv6 + ssl: new' ) ; is( 'petiteipv6.lamiral.info', $imap->Server( 'petiteipv6.lamiral.info' ), 'mailimapclient_connect ipv6 + ssl: setting Server petiteipv6.lamiral.info' ) ; is( 10, $imap->Timeout( 10 ), 'mailimapclient_connect ipv6: setting Timeout( 10 )' ) ; ok( $imap->Ssl( [ SSL_verify_mode => SSL_VERIFY_NONE, SSL_cipher_list => 'DEFAULT:!DH' ] ), 'mailimapclient_connect ipv6 + ssl: setting Ssl( SSL_VERIFY_NONE )' ) ; is( 993, $imap->Port( 993 ), 'mailimapclient_connect ipv6 + ssl: setting Port( 993 )' ) ; SKIP: { if ( 'CUILLERE' eq hostname() or skip_macosx() or -e '/.dockerenv' or 'pcHPDV7-HP' eq hostname() ) { skip( 'Tests avoided on CUILLERE/pcHPDV7-HP/macosx.polarhome.com/docker cannot do ipv6', 4 ) ; } is( 1, $imap->Debug( 1 ), 'mailimapclient_connect ipv4 + ssl: setting Debug( 1 )' ) ; # It sounds stupid but it avoids failures on the next test about $imap->connect is( '2a01:e34:ecde:70d0:223:54ff:fec2:36d7', resolv( 'petiteipv6.lamiral.info' ), 'resolv: petiteipv6.lamiral.info => 2a01:e34:ecde:70d0:223:54ff:fec2:36d7' ) ; like( ref( $imap->connect( ) ), qr/IO::Socket::SSL/, 'mailimapclient_connect ipv6 + ssl: connect to petiteipv6.lamiral.info' ) ; # This one is ok on petite, not on ks2, do not know why, so commented. like( ref( $imap->logout( ) ), qr/Mail::IMAPClient/, 'mailimapclient_connect ipv6 + ssl: logout in ssl is ok on petiteipv6.lamiral.info' ) ; } is( undef, undef $imap, 'mailimapclient_connect ipv6 + ssl: free variable' ) ; note( 'Leaving tests_mailimapclient_connect()' ) ; return ; } sub tests_mailimapclient_connect_bug { note( 'Entering tests_mailimapclient_connect_bug()' ) ; my $imap ; # ipv6 ok( $imap = Mail::IMAPClient->new( ), 'mailimapclient_connect_bug ipv6: new' ) ; is( 'ks6ipv6.lamiral.info', $imap->Server( 'ks6ipv6.lamiral.info' ), 'mailimapclient_connect_bug ipv6: setting Server(ks6ipv6.lamiral.info)' ) ; is( 143, $imap->Port( 143 ), 'mailimapclient_connect_bug ipv6: setting Port( 993 )' ) ; SKIP: { if ( 'CUILLERE' eq hostname() or skip_macosx() or -e '/.dockerenv' or 'pcHPDV7-HP' eq hostname() ) { skip( 'Tests avoided on CUILLERE/pcHPDV7-HP/macosx.polarhome.com/docker cannot do ipv6', 1 ) ; } like( ref( $imap->connect( ) ), qr/IO::Socket::INET/, 'mailimapclient_connect_bug ipv6: connect to ks6ipv6.lamiral.info' ) or diag( 'mailimapclient_connect_bug ipv6: ', $imap->LastError( ), $!, ) ; } #is( $imap->logout( ), undef, 'mailimapclient_connect_bug ipv6: logout in ssl causes failure' ) ; is( undef, undef $imap, 'mailimapclient_connect_bug ipv6: free variable' ) ; note( 'Leaving tests_mailimapclient_connect_bug()' ) ; return ; } sub tests_connect_socket { note( 'Entering tests_connect_socket()' ) ; is( undef, connect_socket( ), 'connect_socket: no args' ) ; my $socket ; my $imap ; SKIP: { if ( 'CUILLERE' eq hostname() or skip_macosx() or -e '/.dockerenv' or 'pcHPDV7-HP' eq hostname() ) { skip( 'Tests avoided on CUILLERE/pcHPDV7-HP/macosx.polarhome.com/docker cannot do ipv6', 2 ) ; } $socket = IO::Socket::INET6->new( PeerAddr => 'ks6ipv6.lamiral.info', PeerPort => 143, ) ; ok( $imap = connect_socket( $socket ), 'connect_socket: ks6ipv6.lamiral.info port 143 IO::Socket::INET6' ) ; #$imap->Debug( 1 ) ; # myprint( $imap->capability( ) ) ; if ( $imap ) { $imap->logout( ) ; } $IO::Socket::SSL::DEBUG = 4 ; $socket = IO::Socket::SSL->new( PeerHost => 'ks6ipv6.lamiral.info', PeerPort => 993, SSL_verify_mode => SSL_VERIFY_NONE, SSL_cipher_list => 'DEFAULT:!DH', ) ; # myprint( $socket ) ; ok( $imap = connect_socket( $socket ), 'connect_socket: ks6ipv6.lamiral.info port 993 IO::Socket::SSL' ) ; #$imap->Debug( 1 ) ; # myprint( $imap->capability( ) ) ; # $socket->close( ) ; if ( $imap ) { $socket->close( ) ; } #$socket->close(SSL_no_shutdown => 1) ; #$imap->logout( ) ; #myprint( "\n" ) ; #$imap->logout( ) ; } note( 'Leaving tests_connect_socket()' ) ; return ; } sub connect_socket { my( $socket ) = @ARG ; if ( ! defined $socket ) { return ; } my $host = $socket->peerhost( ) ; my $port = $socket->peerport( ) ; #print "socket->peerhost: ", $socket->peerhost( ), "\n" ; #print "socket->peerport: ", $socket->peerport( ), "\n" ; my $imap = Mail::IMAPClient->new( ) ; $imap->Socket( $socket ) ; my $banner = $imap->Results()->[0] ; #myprint( "banner: $banner" ) ; return $imap ; } sub tests_probe_imapssl { note( 'Entering tests_probe_imapssl()' ) ; is( undef, probe_imapssl( ), 'probe_imapssl: no args => undef' ) ; is( undef, probe_imapssl( 'unknown' ), 'probe_imapssl: unknown => undef' ) ; note( "hostname is: ", hostname() ) ; SKIP: { if ( 'CUILLERE' eq hostname() or skip_macosx() or -e '/.dockerenv' or 'pcHPDV7-HP' eq hostname() ) { skip( 'Tests avoided on CUILLERE or pcHPDV7-HP or Mac or docker: cannot do ipv6', 0 ) ; } # fed up with this one #like( probe_imapssl( 'ks6ipv6.lamiral.info' ), qr/^\* OK/, 'probe_imapssl: ks6ipv6.lamiral.info matches "* OK"' ) ; } ; # It sounds stupid but it avoids failures on the next test about $imap->connect ok( resolv( 'imap.gmail.com' ), 'resolv: imap.gmail.com => something' ) ; like( probe_imapssl( 'imap.gmail.com' ), qr/^\* OK/, 'probe_imapssl: imap.gmail.com matches "* OK"' ) ; like( probe_imapssl( 'test1.lamiral.info' ), qr/^\* OK/, 'probe_imapssl: test1.lamiral.info matches "* OK"' ) ; note( 'Leaving tests_probe_imapssl()' ) ; return ; } sub probe_imapssl { my $host = shift ; if ( ! $host ) { return ; } $sync->{ debug } and $IO::Socket::SSL::DEBUG = 4 ; my $socket = IO::Socket::SSL->new( PeerHost => $host, PeerPort => $IMAP_SSL_PORT, SSL_verifycn_scheme => 'imap', SSL_verify_mode => $SSL_VERIFY_POLICY, SSL_cipher_list => 'DEFAULT:!DH', ) ; if ( ! $socket ) { return ; } $sync->{ debug } and print "socket: $socket\n" ; my $banner ; $socket->sysread( $banner, 65_536 ) ; $sync->{ debug } and print "banner: $banner" ; $socket->close( ) ; return $banner ; } sub connect_imap { my( $host, $port, $ssl, $tls, $acc ) = @_ ; my $imap = Mail::IMAPClient->new( ) ; if ( $ssl ) { set_ssl( $imap, $acc ) } $imap->Server( $host ) ; $imap->Port( $port ) ; $imap->Debug( $acc->{ debugimap } ) ; $imap->Timeout( $acc->{ timeout } ) ; #$imap->Keepalive( $acc->{ keepalive } ) ; my $side = lc $acc->{ Side } ; myprint( "$acc->{ Side }: connecting on $side [$host] port [$port]\n" ) ; if ( ! $imap->connect( ) ) { $sync->{nb_errors}++ ; exit_clean( $sync, $EXIT_CONNECTION_FAILURE, "$acc->{ Side }: Can not open imap connection on [$host]: ", $imap->LastError, " $OS_ERROR\n" ) ; } myprint( "$acc->{ Side } IP address: ", $imap->Socket->peerhost(), "\n" ) ; my $banner = $imap->Results()->[0] ; myprint( "$acc->{ Side } banner: $banner" ) ; myprint( "$acc->{ Side } capability: ", join(q{ }, @{ $imap->capability() || [] }), "\n" ) ; if ( $tls ) { set_tls( $imap, $acc ) ; if ( ! $imap->starttls( ) ) { $sync->{nb_errors}++ ; exit_clean( $sync, $EXIT_TLS_FAILURE, "$acc->{ Side }: Can not go to tls encryption on $side [$host]:", $imap->LastError, "\n" ) ; } myprint( "$acc->{ Side }: Socket successfully converted to SSL\n" ) ; } return( $imap ) ; } sub tests_compress_ssl { note( 'Entering tests_compress_ssl()' ) ; SKIP: { if ( skip_macosx( ) ) { skip( 'Tests avoided on host polarhome macosx, no clue "ssl3_get_server_certificate:certificate verify failed"', 12 ) ; } else { my $myimap ; my $acc = {} ; $acc->{ Side } = 'HostK' ; $acc->{ authmech } = 'LOGIN' ; $acc->{ debugimap } = 1 ; $acc->{ compress } = 1 ; $acc->{ N } = 'K' ; ok( $myimap = login_imap( 'test1.lamiral.info', 993, 'test1', 'secret1', 1, undef, 1, 100, $acc, {}, ), 'acc_compress_imap: test1.lamiral.info test1 ssl' ) ; ok( defined( $myimap ) && $myimap->IsAuthenticated( ), 'acc_compress_imap: test1.lamiral.info test1 ssl IsAuthenticated' ) ; is( $acc->{ imap }, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info ok" ) ; is( undef, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info 2nd nok" ) ; ok( $myimap = login_imap( 'test1.lamiral.info', 143, 'test1', 'secret1', 0, undef, 1, 100, $acc, {}, ), 'acc_compress_imap: test1.lamiral.info test1 tls' ) ; ok( $myimap && $myimap->IsAuthenticated( ), 'acc_compress_imap: test1.lamiral.info test1 tls IsAuthenticated' ) ; is( $acc->{ imap }, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info tls ok" ) ; is( undef, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info tls 2nd nok" ) ; # Third, no compression $acc->{ compress } = 0 ; ok( $myimap = login_imap( 'test1.lamiral.info', 993, 'test1', 'secret1', 1, undef, 1, 100, $acc, {}, ), 'acc_compress_imap: test1.lamiral.info test1 ssl' ) ; ok( defined( $myimap ) && $myimap->IsAuthenticated( ), 'acc_compress_imap: test1.lamiral.info test1 ssl IsAuthenticated' ) ; is( undef, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info off ok" ) ; is( undef, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info 2nd off ok" ) ; } } note( 'Leaving tests_compress_ssl()' ) ; return ; } sub tests_compress { note( 'Entering tests_compress()' ) ; my $myimap ; my $acc = {} ; $acc->{ Side } = 'HostK' ; $acc->{ authmech } = 'LOGIN' ; $acc->{ debugimap } = 1 ; $acc->{ compress } = 1 ; $acc->{ N } = 'K' ; ok( $myimap = login_imap( 'test1.lamiral.info', 143, 'test1', 'secret1', 0, 0, 1, 100, $acc, {}, ), 'acc_compress_imap: test1.lamiral.info test1' ) ; ok( defined( $myimap ) && $myimap->IsAuthenticated( ), 'acc_compress_imap: test1.lamiral.info test1 IsAuthenticated' ) ; is( $acc->{ imap }, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info ok" ) ; is( undef, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info 2nd nok" ) ; ok( $myimap = login_imap( 'test1.lamiral.info', 143, 'test1', 'secret1', 0, 0, 1, 100, $acc, {}, ), 'acc_compress_imap: test1.lamiral.info test1 tls' ) ; ok( $myimap && $myimap->IsAuthenticated( ), 'acc_compress_imap: test1.lamiral.info test1 tls IsAuthenticated' ) ; is( $acc->{ imap }, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info tls ok" ) ; is( undef, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info tls 2nd nok" ) ; # Third, no compression $acc->{ compress } = 0 ; ok( $myimap = login_imap( 'test1.lamiral.info', 143, 'test1', 'secret1', 0, 0, 1, 100, $acc, {}, ), 'acc_compress_imap: test1.lamiral.info test1 ssl' ) ; ok( defined( $myimap ) && $myimap->IsAuthenticated( ), 'acc_compress_imap: test1.lamiral.info test1 ssl IsAuthenticated' ) ; is( undef, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info off ok" ) ; is( undef, acc_compress_imap( $acc ), "acc_compress_imap: test1.lamiral.info 2nd off ok" ) ; note( 'Leaving tests_compress()' ) ; return ; } sub acc_compress_imap { my $acc = shift ; if ( ! defined( $acc ) ) { return ; } my $ret ; my $imap = $acc->{ imap } ; if ( ! defined $imap ) { return ; } if ( $imap && $acc->{ compress } ) { myprint( "$acc->{ Side }: Trying to turn imap compression on. Use --nocompress" . $acc->{ N } . " to avoid compression on " . lc( $acc->{ Side } ) . "\n" ) ; if ( $ret = $imap->compress() ) { myprint( "$acc->{ Side }: Compression is on now\n" ) ; } else { myprint( "$acc->{ Side }: Failed to turn compression on\n" ) ; } } else { myprint( "$acc->{ Side }: Compression is off. Use --compress" . $acc->{ N } . " to allow compression on " . lc( $acc->{ Side } ) . "\n" ) ; } # $ret is $acc->{ imap } on success, undef on failure or when there is nothing to do. return $ret ; } sub tests_login_imap { note( 'Entering tests_login_imap()' ) ; is( undef, login_imap( ), 'login_imap: no args => undef' ) ; SKIP: { if ( skip_macosx( ) ) { skip( 'Tests avoided only on binary on host polarhome macosx, no clue "ssl3_get_server_certificate:certificate verify failed"', 15 ) ; } else{ my $myimap ; my $acc = {} ; $acc->{ Side } = 'HostK' ; $acc->{ authmech } = 'LOGIN' ; #$IO::Socket::SSL::DEBUG = 4 ; # Each month (trimester?): # echo | openssl s_client -crlf -connect test1.lamiral.info:993 # ... # certificate has expired # Fix: ssh root@test1.lamiral.info 'apt update && apt upgrade && /etc/init.d/dovecot restart' # # or # echo | openssl s_client -crlf -connect test1.lamiral.info:993 # ... # Verify return code: 9 (certificate is not yet valid) # Fix: /etc/init.d/openntpd restart # 2021_09_04 done ok( $myimap = login_imap( 'test1.lamiral.info', 993, 'test1', 'secret1', 1, undef, 1, 100, $acc, {}, ), 'login_imap: test1.lamiral.info test1 ssl' ) ; ok( defined( $myimap ) && $myimap->IsAuthenticated( ), 'login_imap: test1.lamiral.info test1 ssl IsAuthenticated' ) ; is( $myimap, $acc->{ imap }, "login_imap: acc->{ imap } ok test1 ssl") ; ok( $myimap = login_imap( 'test1.lamiral.info', 143, 'test1', 'secret1', 0, undef, 1, 100, $acc, {}, ), 'login_imap: test1.lamiral.info test1 tls' ) ; ok( $myimap && $myimap->IsAuthenticated( ), 'login_imap: test1.lamiral.info test1 tls IsAuthenticated' ) ; is( $myimap, $acc->{ imap }, "login_imap: acc->{ imap } ok test1 tls") ; #$IO::Socket::SSL::DEBUG = 4 ; $acc->{sslargs} = { SSL_version => 'SSLv2' } ; # SSLv2 not supported is( undef, $myimap = login_imap( 'test1.lamiral.info', 143, 'test1', 'secret1', 0, undef, 1, 100, $acc, {}, ), 'login_imap: test1.lamiral.info test1 tls SSLv2 not supported' ) ; #SSL_verify_mode => 1 #SSL_version => 'TLSv1_1' is( undef, $acc->{ imap }, "login_imap: acc->{ imap } test1 tls error => undef") ; # I have left ? exit_clean to be replaced by errors_incr( $mysync, 'error message' ) # 1 in login_imap() my $mysync = {} ; $acc = {} ; $acc->{ Side } = 'Host2' ; $acc->{ authmech } = 'LOGIN' ; is( undef, login_imap( 'noresol.lamiral.info', 143, 'test1', 'secret1', 0, undef, 1, 100, $acc, $mysync, ), 'login_imap: noresol.lamiral.info undef' ) ; is( 'ERR_CONNECTION_FAILURE_HOST2', errorsanalyse( errors_log( $mysync ) ), 'login_imap: Host2 noresol.lamiral.info => ERR_CONNECTION_FAILURE_HOST2' ) ; is( undef, $acc->{ imap }, "login_imap: acc->{ imap } noresol error => undef") ; # authentication failure for user2 $mysync = {} ; is( undef, login_imap( 'test1.lamiral.info', 143, 'test1', 'Ce crétin', 0, undef, 1, 100, $acc, $mysync, ), 'login_imap: user2 bad passord => undef' ) ; is( 'ERR_AUTHENTICATION_FAILURE_USER2', errorsanalyse( errors_log( $mysync ) ), 'login_imap: Host2 bad password => ERR_AUTHENTICATION_FAILURE_USER2' ) ; # authentication failure for user1 $mysync = {} ; $acc = {} ; $acc->{ Side } = 'Host1' ; $acc->{ authmech } = 'LOGIN' ; is( undef, login_imap( 'test1.lamiral.info', 143, 'test1', 'Ce crétin', 0, undef, 1, 100, $acc, $mysync, ), 'login_imap: user1 bad passord => undef' ) ; is( 'ERR_AUTHENTICATION_FAILURE_USER1', errorsanalyse( errors_log( $mysync ) ), 'login_imap: Host1 bad password => ERR_AUTHENTICATION_FAILURE_USER1' ) ; } } note( 'Leaving tests_login_imap()' ) ; return ; } sub oauthgenerateaccess { if ( "petite" eq hostname() ) { myprint( "oauthgenerateaccess\n" ) ; my @output = backtick( 'cd oauth2 && pwd && ./generate_gmail_token imapsync.gl0@gmail.com' ) ; myprint( @output ) ; } return ; } sub tests_login_imap_oauth { note( 'Entering tests_login_imap_oauth()' ) ; oauthgenerateaccess() ; SKIP: { if ( skip_macosx_binary( ) ) { skip( 'Tests avoided only on binary on host polarhome macosx, no clue "ssl3_get_server_certificate:certificate verify failed"', 6 ) ; } else { my $mysync ; my $acc ; # oauthdirect authentication failure for user2 $mysync = {} ; $acc = {} ; $acc->{ oauthdirect } = 'caca2' ; $acc->{ debugimap } = 1 ; $mysync->{ showpasswords } = 1 ; $acc->{ Side } = 'Host2' ; $acc->{ authmech } = 'QQQ' ; is( undef, login_imap( 'imap.gmail.com', 993, 'test1', 'Ce crétin', 1, undef, 1, 100, $acc, $mysync, ), 'login_imap: user2 bad oauthdirect => undef' ) ; is( 'ERR_AUTHENTICATION_FAILURE_USER2', errorsanalyse( errors_log( $mysync ) ), 'login_imap: Host2 bad oauthdirect => ERR_AUTHENTICATION_FAILURE_USER2' ) ; # oauthdirect authentication failure for user1 $mysync = {} ; $acc = {} ; $acc->{ Side } = 'Host1' ; $acc->{ oauthdirect } = 'caca1' ; $acc->{ debugimap } = 1 ; $mysync->{ showpasswords } = 1 ; $acc->{ authmech } = 'QQQ' ; is( undef, login_imap( 'imap.gmail.com', 993, 'test1', 'Ce crétin', 1, undef, 1, 100, $acc, $mysync, ), 'login_imap: user1 bad oauthdirect => undef' ) ; is( 'ERR_AUTHENTICATION_FAILURE_USER1', errorsanalyse( errors_log( $mysync ) ), 'login_imap: Host1 bad oauthdirect => ERR_AUTHENTICATION_FAILURE_USER1' ) ; # oauthdirect authentication failure for user1 $mysync = {} ; $acc = {} ; $acc->{ Side } = 'Host1' ; $acc->{ oauthdirect } = '' ; $acc->{ debugimap } = 1 ; $mysync->{ showpasswords } = 1 ; $acc->{ authmech } = 'QQQ' ; is( undef, login_imap( 'imap.gmail.com', 993, 'test1', 'Ce crétin', 1, undef, 1, 100, $acc, $mysync, ), 'login_imap: user1 bad oauthdirect => undef' ) ; is( 'ERR_AUTHENTICATION_FAILURE_USER1', errorsanalyse( errors_log( $mysync ) ), 'login_imap: Host1 no oauthdirect value => ERR_AUTHENTICATION_FAILURE_USER1' ) ; } } # oauthdirect authentication success for user1 SKIP: { if ( ! -r 'oauth2/D_oauth2_oauthdirect_imapsync.gl0@gmail.com.txt' ) { skip( 'oauthdirect: no oauthdirect file', 6 ) ; } my $myimap ; my $mysync = {} ; my $acc = {} ; $acc->{ Side } = 'Host1' ; $acc->{ oauthdirect } = 'oauth2/D_oauth2_oauthdirect_imapsync.gl0@gmail.com.txt' ; $acc->{ debugimap } = 1 ; $mysync->{ showpasswords } = 1 ; $acc->{ authmech } = 'QQQ' ; isa_ok( $myimap = login_imap( 'imap.gmail.com', 993, 'user_useless', 'password_useless', 1, undef, 1, 100, $acc, $mysync, ), 'Mail::IMAPClient', 'login_imap: user1 good oauthdirect => Mail::IMAPClient' ) ; ok( defined( $myimap ) && $myimap->IsAuthenticated( ), 'login_imap: gmail oauth2 oauthdirect IsAuthenticated' ) ; ok( defined( $myimap ) && $myimap->logout( ), 'login_imap: gmail oauth2 oauthdirect logout' ) ; ok( defined( $myimap ) && ! $myimap->IsAuthenticated( ), 'login_imap: gmail oauth2 oauthdirect not IsAuthenticated after logout' ) ; ok( defined( $myimap ) && $myimap->reconnect( ), 'login_imap: gmail oauth2 oauthdirect reconnect ok' ) ; ok( defined( $myimap ) && $myimap->IsAuthenticated( ), 'login_imap: gmail oauth2 oauthdirect IsAuthenticated after reconnect' ) ; } # oauthaccesstoken authentication success for user1 SKIP: { if ( ! -r 'oauth2/D_oauth2_access_token_imapsync.gl0@gmail.com.txt' ) { skip( 'oauthaccesstoken: no access_token file', 6 ) ; } my $myimap ; my $mysync = {} ; my $acc = {} ; $acc->{ Side } = 'Host1' ; $acc->{ oauthaccesstoken } = 'oauth2/D_oauth2_access_token_imapsync.gl0@gmail.com.txt' ; $acc->{ debugimap } = 1 ; $mysync->{ showpasswords } = 1 ; $acc->{ authmech } = 'QQQ' ; isa_ok( $myimap = login_imap( 'imap.gmail.com', 993, 'imapsync.gl0@gmail.com', 'password_useless', 1, undef, 1, 100, $acc, $mysync, ), 'Mail::IMAPClient', 'login_imap: user1 good oauthaccesstoken => Mail::IMAPClient' ) ; ok( defined( $myimap ) && $myimap->IsAuthenticated( ), 'login_imap: gmail oauth2 oauthaccesstoken IsAuthenticated' ) ; ok( defined( $myimap ) && $myimap->logout( ), 'login_imap: gmail oauth2 oauthaccesstoken logout' ) ; ok( defined( $myimap ) && ! $myimap->IsAuthenticated( ), 'login_imap: gmail oauth2 oauthaccesstoken not IsAuthenticated after logout' ) ; ok( defined( $myimap ) && $myimap->reconnect( ), 'login_imap: gmail oauth2 oauthaccesstoken reconnect ok' ) ; ok( defined( $myimap ) && $myimap->IsAuthenticated( ), 'login_imap: gmail oauth2 oauthaccesstoken IsAuthenticated after reconnect' ) ; } note( 'Leaving tests_login_imap_oauth()' ) ; return ; } sub login_imap { my @allargs = @_ ; my( $host, $port, $user, $password, $ssl, $tls, $uid, $split, $acc, $mysync ) = @allargs ; $acc->{ imap } = undef ; if ( ! all_defined( $host, $port, $user, $acc->{ Side } ) ) { return ; } my $side = lc $acc->{ Side } ; myprint( "$acc->{ Side }: connecting and login on $side [$host] port [$port] with user [$user]\n" ) ; my $imap = init_imap( @allargs ) ; if ( ! $imap->connect() ) { my $error = "$acc->{ Side } failure: can not open imap connection on $side [$host] with user [$user]: " . $imap->LastError . " $OS_ERROR\n" ; errors_incr( $mysync, $error ) ; return ; } myprint( "$acc->{ Side } IP address: ", $imap->Socket->peerhost(), "\n" ) ; my $banner = $imap->Results()->[0] ; myprint( "$acc->{ Side } banner: $banner" ) ; myprint( "$acc->{ Side } capability before authentication: ", join(q{ }, @{ $imap->capability() || [] }), "\n" ) ; if ( (! $ssl) and (! defined $tls ) and $imap->has_capability( 'STARTTLS' ) ) { myprint( "$acc->{ Side }: going to ssl because STARTTLS is in CAPABILITY. Use --notls1 or --notls2 to avoid that behavior\n" ) ; $tls = 1 ; } #myprint( Data::Dumper->Dump( [ @allargs ] ) ) ; if ( $tls ) { set_tls( $imap, $acc ) ; if ( ! $imap->starttls( ) ) { my $error = "$acc->{ Side } failure: Can not go to tls encryption on $side [$host]: " . $imap->LastError . "\n" ; errors_incr( $mysync, $error ) ; return ; } myprint( "$acc->{ Side }: Socket successfully converted to SSL\n" ) ; } if ( $acc->{ authmech } eq 'PREAUTH' ) { if ( $imap->IsAuthenticated( ) ) { $imap->Socket ; myprintf("%s: Assuming PREAUTH for %s\n", $acc->{ Side }, $imap->Server ) ; }else{ $mysync->{nb_errors}++ ; exit_clean( $mysync, $EXIT_AUTHENTICATION_FAILURE, "$acc->{ Side } failure: error login on $side [$host] with user [$user] auth [PREAUTH]\n" ) ; } } if ( authenticate_imap( $imap, @allargs ) ) { myprint( "$acc->{ Side }: success login on [$host] with user [$user] auth [$acc->{ authmech }] or [LOGIN]\n" ) ; $acc->{ imap } = $imap ; return( $imap ) ; } else { # The errors are already printed myprint( "$acc->{ Side }: failed login on [$host] with user [$user] auth [$acc->{ authmech }]\n" ) ; return ; } } sub init_imap { my( $host, $port, $user, $password, $ssl, $tls, $uid, $split, $acc, $mysync ) = @_ ; my ( $imap ) ; $imap = Mail::IMAPClient->new() ; if ( $mysync->{ tee } ) { # Well, it does not change anything, does it? # It does when suppressing the hack with *STDERR $imap->Debug_fh( $mysync->{ tee } ) ; } if ( $ssl ) { set_ssl( $imap, $acc ) } if ( $tls ) { } # can not do set_tls() here because connect() will directly do a STARTTLS $imap->Clear( 1 ) ; $imap->Server( $host ) ; $imap->Port( $port ) ; $imap->Fast_io( $acc->{ fastio } ) ; $imap->Buffer( $buffersize || $DEFAULT_BUFFER_SIZE ) ; $imap->Uid( $uid ) ; $imap->Peek( 1 ) ; $imap->Debug( $acc->{ debugimap } ) ; if ( $mysync->{ showpasswords } ) { $imap->Showcredentials( 1 ) ; } if ( defined( $acc->{ timeout } ) ) { $imap->Timeout( $acc->{ timeout } ) ; } if ( defined $acc->{ keepalive } ) { $imap->Keepalive( $acc->{ keepalive } ) ; } if ( defined $acc->{ reconnectretry } ) { $imap->Reconnectretry( $acc->{ reconnectretry } ) ; } $imap->{IMAPSYNC_RECONNECT_COUNT} = 0 ; $imap->Ignoresizeerrors( $allowsizemismatch ) ; $split and $imap->Maxcommandlength( $SPLIT_FACTOR * $split ) ; return( $imap ) ; } sub authenticate_imap { my( $imap, $host, $port, $user, $password, $ssl, $tls, $uid, $split, $acc, $mysync ) = @_ ; check_capability( $imap, $acc->{ authmech }, $acc->{ Side } ) ; $imap->User( $user ) ; if ( defined $acc->{ domain } ) { $imap->Domain( $acc->{ domain } ) ; $mysync->{ debug } and myprint( "Domain: $acc->{ domain }\n" ) ; } $imap->Authuser( $acc->{ authuser } ) ; $imap->Password( $password ) ; if ( 'X-MASTERAUTH' eq $acc->{ authmech } ) { xmasterauth( $imap ) ; return 1 ; } if ( defined $acc->{ oauthdirect } ) { $acc->{ authmech } = 'XOAUTH2 direct' ; return( oauthdirect( $mysync, $acc, $imap, $host, $user ) ) ; } if ( defined $acc->{ oauthaccesstoken } ) { $acc->{ authmech } = 'XOAUTH2 accesstoken' ; return( oauthaccesstoken( $mysync, $acc, $imap, $host, $user ) ) ; } if ( $acc->{ proxyauth } ) { $imap->Authmechanism(q{}) ; $imap->User( $acc->{ authuser } ) ; } else { $imap->Authmechanism( $acc->{ authmech } ) unless ( $acc->{ authmech } eq 'LOGIN' or $acc->{ authmech } eq 'PREAUTH' ) ; } $imap->Authcallback(\&xoauth2) if ( 'XOAUTH2' eq $acc->{ authmech } ) ; $imap->Authcallback(\&plainauth) if ( ( 'PLAIN' eq $acc->{ authmech } ) or ( 'EXTERNAL' eq $acc->{ authmech } ) ) ; unless ( $acc->{ authmech } eq 'PREAUTH' or $imap->login( ) ) { my $info = "$acc->{ Side } failure: Error login on [$host] with user [$user] auth" ; my $einfo = imap_last_error( $imap ) ; my $error = "$info [$acc->{ authmech }]: $einfo\n" ; if ( ( $acc->{ authmech } eq 'LOGIN' ) or $imap->IsUnconnected( ) or $acc->{ authuser } ) { $acc->{ authuser } ||= "" ; myprint( "$acc->{ Side } info: authmech [$acc->{ authmech }] user [$user] authuser [$acc->{ authuser }] IsUnconnected [", $imap->IsUnconnected( ), "]\n" ) ; errors_incr( $mysync, $error ) ; return ; }else{ errors_incr( $mysync, $error ) ; } # It is not secure to try plain text LOGIN when another authmech failed # but I do it anyway. This behavior is optional as option --notrylogin will skip it. if ( $mysync->{ trylogin } ) { myprint( "$acc->{ Side } info: trying LOGIN Auth mechanism on [$host] with user [$user]. Use option --notrylogin to avoid this second chance to login via LOGIN auth\n" ) ; $imap->Authmechanism(q{}) ; if ( ! $imap->login( ) ) { failure_login( $mysync, $acc, 'LOGIN', $imap, $host, $user ) ; return ; } else { myprint( "$acc->{ Side }: success login on [$host] with user [$user] auth [LOGIN] after [$acc->{ authmech }] failure\n" ) ; } } else { myprint( "$acc->{ Side } info: not trying LOGIN Auth mechanism on [$host] with user [$user]. Use option --trylogin to have this second chance to login via LOGIN auth\n" ) ; return ; } } if ( $acc->{ proxyauth } ) { if ( ! $imap->proxyauth( $user ) ) { failure_proxyauth( $mysync, $acc, $acc->{ authmech }, $imap, $host, $user ) ; return ; } } return 1; } sub failure_login { my( $mysync, $acc, $authmech, $imap, $host, $user ) = @ARG ; my $info = "$acc->{ Side } failure: Error login on [$host] with user [$user] auth" ; my $einfo = imap_last_error( $imap ) ; my $error = "$info [$authmech]: $einfo\n" ; errors_incr( $mysync, $error ) ; return ; } # failure_login and failure_proxyauth function are similar but # variable $error so no factoring sub failure_proxyauth { my( $mysync, $acc, $authmech, $imap, $host, $user ) = @ARG ; my $info = "$acc->{ Side } failure: Error login on [$host] with user [$user] auth" ; my $einfo = imap_last_error( $imap ) ; my $error = "$info [$authmech] using proxy-login as [$acc->{ authuser }]: $einfo\n" ; errors_incr( $mysync, $error ) ; return ; } sub oauthdirect { my( $mysync, $acc, $imap, $host, $user ) = @_ ; my $oauthdirect_str ; if ( -f -r $acc->{ oauthdirect } ) { $oauthdirect_str = firstline( $acc->{ oauthdirect } ) ; } else { $oauthdirect_str = $acc->{ oauthdirect } || 'Please define oauthdirect value' ; } $imap->Authmechanism( 'XOAUTH2' ) ; $imap->Authcallback( sub { return $oauthdirect_str } ) ; #if ( $imap->authenticate('XOAUTH2', sub { return $oauthdirect_str } ) ) if ( $imap->login( ) ) { return 1 ; } else { failure_login( $mysync, $acc, $acc->{ authmech }, $imap, $host, $user ) ; return ; } return ; } sub oauthaccesstoken { my( $mysync, $acc, $imap, $host, $user ) = @_ ; my $oauthaccesstoken_str ; if ( -f -r $acc->{ oauthaccesstoken } ) { $oauthaccesstoken_str = firstline( $acc->{ oauthaccesstoken } ) ; } else { $oauthaccesstoken_str = $acc->{ oauthaccesstoken } || 'Please define oauthaccesstoken value' ; } my $oauth_string = "user=" . $user . "\x01auth=Bearer ". $oauthaccesstoken_str . "\x01\x01" ; #myprint "oauth_string: $oauth_string\n" ; my $oauth_string_base64 = encode_base64( $oauth_string , '' ) ; #myprint "oauth_string_base64: $oauth_string_base64\n" ; my $oauthdirect_str = $oauth_string_base64 ; $imap->Authmechanism( 'XOAUTH2' ) ; $imap->Authcallback( sub { return $oauthdirect_str } ) ; #if ( $imap->authenticate('XOAUTH2', sub { return $oauthdirect_str } ) ) if ( $imap->login( ) ) { return 1 ; } else { failure_login( $mysync, $acc, $acc->{ authmech }, $imap, $host, $user ) ; return ; } return ; } sub check_capability { my( $imap, $authmech, $Side ) = @_ ; if ( $imap->has_capability( "AUTH=$authmech" ) or $imap->has_capability( $authmech ) ) { myprintf("%s: %s says it has CAPABILITY for AUTHENTICATE %s\n", $Side, $imap->Server, $authmech) ; return ; } if ( $authmech eq 'LOGIN' ) { # Well, the warning is so common and useless that I prefer to remove it # No more "... says it has NO CAPABILITY for AUTHENTICATE LOGIN" return ; } myprintf( "%s: %s says it has NO CAPABILITY for AUTHENTICATE %s\n", $Side, $imap->Server, $authmech ) ; if ( $authmech eq 'PLAIN' ) { myprint( "$Side: frequently PLAIN is only supported with SSL, try --ssl or --tls options\n" ) ; } return ; } sub set_ssl { my ( $imap, $acc ) = @_ ; # SSL_version can be # SSLv3 SSLv2 SSLv23 SSLv23:!SSLv2 (last one is the default in IO-Socket-SSL-1.953) # my $sslargs_hash = $acc->{sslargs} ; my $sslargs_default = { SSL_verify_mode => $SSL_VERIFY_POLICY, SSL_verifycn_scheme => 'imap', SSL_cipher_list => 'DEFAULT:!DH', } ; # initiate with default values my %sslargs_mix = %{ $sslargs_default } ; # now override with passed values @sslargs_mix{ keys %{ $sslargs_hash } } = values %{ $sslargs_hash } ; # remove keys with undef values foreach my $key ( keys %sslargs_mix ) { delete $sslargs_mix{ $key } if ( not defined $sslargs_mix{ $key } ) ; } # back to an ARRAY my @sslargs_mix = %sslargs_mix ; #myprint( Data::Dumper->Dump( [ $sslargs_hash, $sslargs_default, \%sslargs_mix, \@sslargs_mix ] ) ) ; $imap->Ssl( \@sslargs_mix ) ; return ; } sub set_tls { my ( $imap, $acc ) = @_ ; my $sslargs_hash = $acc->{sslargs} ; my $sslargs_default = { SSL_verify_mode => $SSL_VERIFY_POLICY, SSL_cipher_list => 'DEFAULT:!DH', } ; #myprint( Data::Dumper->Dump( [ $acc, $sslargs_hash, $sslargs_default ] ) ) ; # initiate with default values my %sslargs_mix = %{ $sslargs_default } ; # now override with passed values @sslargs_mix{ keys %{ $sslargs_hash } } = values %{ $sslargs_hash } ; # remove keys with undef values foreach my $key ( keys %sslargs_mix ) { delete $sslargs_mix{ $key } if ( not defined $sslargs_mix{ $key } ) ; } # back to an ARRAY my @sslargs_mix = %sslargs_mix ; $imap->Starttls( \@sslargs_mix ) ; return ; } sub plainauth { my $code = shift; my $imap = shift; my $string = mysprintf("%s\x00%s\x00%s", $imap->User, defined $imap->Authuser ? $imap->Authuser : "", $imap->Password); return encode_base64("$string", q{}); } # Copy from https://github.com/imapsync/imapsync/pull/25/files # Changes "use" pragmas to "require". # The openssl system call shall be replaced by pure Perl and # https://metacpan.org/pod/Crypt::OpenSSL::PKCS12 # Now the Joaquin Lopez code: # # Used this as an example: https://gist.github.com/gsainio/6322375 # # And this as a reference: https://developers.google.com/accounts/docs/OAuth2ServiceAccount # (note there is an http/rest tab, where the real info is hidden away... went on a witch hunt # until I noticed that...) # # This is targeted at gmail to maintain compatibility after google's oauth1 service is deactivated # on May 5th, 2015: https://developers.google.com/gmail/oauth_protocol # If there are other oauth2 implementations out there, this would need to be modified to be # compatible # # This is a good guide on setting up the google api/apps side of the equation: # http://www.limilabs.com/blog/oauth2-gmail-imap-service-account # # 2016/05/27: Updated to support oauth/key data in the .json files Google now defaults to # when creating gmail service accounts. They're easier to work with since they neither # requiring decrypting nor specifying the oauth2 client id separately. # # If the password arg ends in .json, it will assume this new json method, otherwise it # will fallback to the "oauth client id;.p12" format it was previously using. sub xoauth2 { require JSON::WebToken ; require LWP::UserAgent ; require HTML::Entities ; require JSON ; require JSON::WebToken::Crypt::RSA ; require Crypt::OpenSSL::PKCS12; require Crypt::OpenSSL::RSA ; require Encode::Byte ; require IO::Socket::SSL ; my $code = shift; my $imap = shift; my ($iss,$key); if( $imap->Password =~ /^(.*\.json)$/x ) { my $json = JSON->new( ) ; my $filename = $1; $sync->{ debug } and myprint( "XOAUTH2 json file: $filename\n" ) ; my $FILE ; if ( ! open( $FILE, '<', $filename ) ) { $sync->{nb_errors}++ ; exit_clean( $sync, $EXIT_AUTHENTICATION_FAILURE, "error [$filename]: $OS_ERROR\n" ) ; } my $jsonfile = $json->decode( join q{}, <$FILE> ) ; close $FILE ; $iss = $jsonfile->{client_id}; $key = $jsonfile->{private_key}; $sync->{ debug } and myprint( "Service account: $iss\n"); $sync->{ debug } and myprint( "Private key:\n$key\n"); } else { # Get iss (service account address), keyfile name, and keypassword if necessary ( $iss, my $keyfile, my $keypass ) = $imap->Password =~ /([\-\d\w\@\.]+);([a-zA-Z0-9 \_\-\.\/]+);?(.*)?/x ; # Assume key password is google default if not provided $keypass = 'notasecret' if not $keypass; $sync->{ debug } and myprint( "Service account: $iss\nKey file: $keyfile\nKey password: $keypass\n"); # Get private key from p12 file my $pkcs12 = Crypt::OpenSSL::PKCS12->new_from_file($keyfile); $key = $pkcs12->private_key($keypass); $sync->{ debug } and myprint( "Private key:\n$key\n"); } # Create jwt of oauth2 request my $time = time ; my $jwt = JSON::WebToken->encode( { 'iss' => $iss, # service account 'scope' => 'https://mail.google.com/', 'aud' => 'https://www.googleapis.com/oauth2/v3/token', 'exp' => $time + $DEFAULT_EXPIRATION_TIME_OAUTH2_PK12, 'iat' => $time, 'prn' => $imap->User # user to auth as }, $key, 'RS256', {'typ' => 'JWT'} ); # Crypt::OpenSSL::RSA needed here. # Post oauth2 request my $ua = LWP::UserAgent->new( ) ; $ua->env_proxy( ) ; my $response = $ua->post('https://www.googleapis.com/oauth2/v3/token', { grant_type => HTML::Entities::encode_entities('urn:ietf:params:oauth:grant-type:jwt-bearer'), assertion => $jwt } ) ; unless( $response->is_success( ) ) { $sync->{nb_errors}++ ; exit_clean( $sync, $EXIT_AUTHENTICATION_FAILURE, $response->code, "\n", $response->content, "\n" ) ; }else{ $sync->{ debug } and myprint( $response->content ) ; } # access_token in response is what we need my $data = JSON::decode_json( $response->content ) ; # format as oauth2 auth data my $xoauth2_string = encode_base64( 'user=' . $imap->User . "\1auth=Bearer " . $data->{access_token} . "\1\1", q{} ) ; $sync->{ debug } and myprint( "XOAUTH2 String: $xoauth2_string\n"); return($xoauth2_string); } sub xmasterauth { # This is Kerio auth admin # This code comes from # https://github.com/imapsync/imapsync/pull/53/files my $imap = shift ; my $user = $imap->User( ) ; my $password = $imap->Password( ) ; my $authmech = 'X-MASTERAUTH' ; my @challenge = $imap->tag_and_run( $authmech, "+" ) ; if ( not defined $challenge[0] ) { $sync->{nb_errors}++ ; exit_clean( $sync, $EXIT_AUTHENTICATION_FAILURE, "Failure authenticate with $authmech: ", $imap->LastError, "\n" ) ; return ; # hahaha! } $sync->{ debug } and myprint( "X-MASTERAUTH challenge: [@challenge]\n" ) ; $challenge[1] =~ s/^\+ |^\s+|\s+$//g ; if ( ! $imap->_imap_command( { addcrlf => 1, addtag => 0, tag => $imap->Count }, md5_hex( $challenge[1] . $password ) ) ) { $sync->{nb_errors}++ ; exit_clean( $sync, $EXIT_AUTHENTICATION_FAILURE, "Failure authenticate with $authmech: ", $imap->LastError, "\n" ) ; } if ( ! $imap->tag_and_run( 'X-SETUSER ' . $user ) ) { $sync->{nb_errors}++ ; exit_clean( $sync, $EXIT_AUTHENTICATION_FAILURE, "Failure authenticate with $authmech: ", "X-SETUSER ", $imap->LastError, "\n" ) ; } $imap->State( Mail::IMAPClient::Authenticated ) ; # I comment this state because "Selected" state is usually done by SELECT or EXAMINE imap commands # $imap->State( Mail::IMAPClient::Selected ) ; return ; } sub keepalive1 { my $mysync = shift ; $mysync->{ acc1 }->{ keepalive } = defined $mysync->{ acc1 }->{ keepalive } ? $mysync->{ acc1 }->{ keepalive } : 1 ; if ( $mysync->{ acc1 }->{ keepalive } ) { myprint( "Host1: imap connection keepalive is on on host1. Use --nokeepalive1 to disable it.\n" ) ; } else { myprint( "Host1: imap connection keepalive is off on host1. Use --keepalive1 to enable it.\n" ) ; } } sub keepalive2 { my $mysync = shift ; $mysync->{ acc2 }->{ keepalive } = defined $mysync->{ acc2 }->{ keepalive } ? $mysync->{ acc2 }->{ keepalive } : 1 ; if ( $mysync->{ acc2 }->{ keepalive } ) { myprint( "Host2: imap connection keepalive is on on host2. Use --nokeepalive2 to disable it.\n" ) ; } else { myprint( "Host2: imap connection keepalive is off on host2. Use --keepalive2 to enable it.\n" ) ; } } sub banner_imapsync { my $mysync = shift @ARG ; my @argv = @ARG ; my $banner_imapsync = join q{}, q{$RCSfile: imapsync,v $ }, q{$Revision: 2.178 $ }, q{$Date: 2022/01/12 21:28:37 $ }, "\n", "Command line used, run by $EXECUTABLE_NAME:\n", "$PROGRAM_NAME ", command_line_nopassword( $mysync, @argv ), "\n" ; return( $banner_imapsync ) ; } sub tests_do_valid_directory { note( 'Entering tests_do_valid_directory()' ) ; is( 1, do_valid_directory( '.'), 'do_valid_directory: . good' ) ; is( 1, do_valid_directory( './W/tmp/tests/valid/sub'), 'do_valid_directory: ./W/tmp/tests/valid/sub good' ) ; Readonly my $NB_UNIX_tests_do_valid_directory_non_root => 2 ; diag( "OSNAME=$OSNAME EFFECTIVE_USER_ID=$EFFECTIVE_USER_ID" ) ; SKIP: { skip( 'Tests only for non roor user', $NB_UNIX_tests_do_valid_directory_non_root ) if ( '0' eq $EFFECTIVE_USER_ID ) ; diag( 'The "Error / is not writable" is on purpose' ) ; ok( 0 == do_valid_directory( '/'), 'do_valid_directory: / bad' ) ; diag( 'The "Error permission denied" on /noway is on purpose' ) ; ok( 0 == do_valid_directory( '/noway'), 'do_valid_directory: /noway bad' ) ; } note( 'Leaving tests_do_valid_directory()' ) ; return ; } sub do_valid_directory { my $dir = shift @ARG ; # all good => return ok. return( 1 ) if ( -d $dir and -r _ and -w _ ) ; # exist but bad if ( -e $dir and not -d _ ) { myprint( "Error: $dir exists but is not a directory\n" ) ; return( 0 ) ; } if ( -e $dir and not -w _ ) { my $sb = stat $dir ; myprintf( "Error: directory %s is not writable for user %s, permissions are %04o and owner is %s ( uid %s )\n", $dir, getpwuid_any_os( $EFFECTIVE_USER_ID ), ($sb->mode & oct($PERMISSION_FILTER) ), getpwuid_any_os( $sb->uid ), $sb->uid( ) ) ; return( 0 ) ; } # Trying to create it myprint( "Creating directory $dir (current directory is " . getcwd( ) . ")\n" ) ; if ( ! eval { mkpath( $dir ) } ) { myprint( "$EVAL_ERROR" ) if ( $EVAL_ERROR ) ; } return( 1 ) if ( -d $dir and -r _ and -w _ ) ; return( 0 ) ; } sub tests_match_a_pid_number { note( 'Entering tests_match_a_pid_number()' ) ; is( undef, match_a_pid_number( ), 'match_a_pid_number: no args => undef' ) ; is( undef, match_a_pid_number( q{} ), 'match_a_pid_number: "" => undef' ) ; is( undef, match_a_pid_number( 'lalala' ), 'match_a_pid_number: lalala => undef' ) ; is( 1, match_a_pid_number( 1 ), 'match_a_pid_number: 1 => 1' ) ; is( 1, match_a_pid_number( 123 ), 'match_a_pid_number: 123 => 1' ) ; is( 1, match_a_pid_number( -123 ), 'match_a_pid_number: -123 => 1' ) ; is( 1, match_a_pid_number( '123' ), 'match_a_pid_number: "123" => 1' ) ; is( 1, match_a_pid_number( '-123' ), 'match_a_pid_number: "-123" => 1' ) ; is( undef, match_a_pid_number( 'a123' ), 'match_a_pid_number: a123 => undef' ) ; is( undef, match_a_pid_number( '-a123' ), 'match_a_pid_number: -a123 => undef' ) ; is( 1, match_a_pid_number( 99999 ), 'match_a_pid_number: 99999 => 1' ) ; is( 1, match_a_pid_number( -99999 ), 'match_a_pid_number: -99999 => 1' ) ; is( undef, match_a_pid_number( 0 ), 'match_a_pid_number: 0 => undef' ) ; is( 1, match_a_pid_number( 100000 ), 'match_a_pid_number: 100000 => 1' ) ; is( 1, match_a_pid_number( 123456 ), 'match_a_pid_number: 123456 => 1' ) ; is( undef, match_a_pid_number( '-0' ), 'match_a_pid_number: "-0" => undef' ) ; is( 1, match_a_pid_number( -100000 ), 'match_a_pid_number: -100000 => 1' ) ; is( 1, match_a_pid_number( -123456 ), 'match_a_pid_number: -123456 => 1' ) ; is( 1, match_a_pid_number( 2**22 ), 'match_a_pid_number: 2**22 => 1' ) ; is( undef, match_a_pid_number( 2**22 + 1 ), 'match_a_pid_number: 2**22 + 1 => undef' ) ; is( undef, match_a_pid_number( 4194304 + 1 ), 'match_a_pid_number: 2**22 + 1 = 4194305 => undef' ) ; note( 'Leaving tests_match_a_pid_number()' ) ; return ; } sub match_a_pid_number { my $pid = shift @ARG ; if ( ! defined $pid ) { return ; } #print "$pid\n" ; if ( ! match( $pid, '^-?\d+$' ) ) { return ; } #print "$pid\n" ; # can be negative on Windows #if ( 0 > $pid ) { return ; } #if ( 65535 < $pid ) { return ; } if ( 2**22 < abs( $pid ) ) { return ; } if ( 0 == abs( $pid ) ) { return ; } return 1 ; } sub tests_remove_pidfile_not_running { note( 'Entering tests_remove_pidfile_not_running()' ) ; ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'remove_pidfile_not_running: mkpath W/tmp/tests/' ) ; is( undef, remove_pidfile_not_running( ), 'remove_pidfile_not_running: no args => undef' ) ; is( undef, remove_pidfile_not_running( './W' ), 'remove_pidfile_not_running: a dir => undef' ) ; is( undef, remove_pidfile_not_running( 'noexists' ), 'remove_pidfile_not_running: noexists => undef' ) ; is( 1, touch( 'W/tmp/tests/empty.pid' ), 'remove_pidfile_not_running: prepa empty W/tmp/tests/empty.pid' ) ; is( undef, remove_pidfile_not_running( 'W/tmp/tests/empty.pid' ), 'remove_pidfile_not_running: W/tmp/tests/empty.pid => undef' ) ; is( 'lalala', string_to_file( 'lalala', 'W/tmp/tests/lalala.pid' ), 'remove_pidfile_not_running: prepa W/tmp/tests/lalala.pid' ) ; is( undef, remove_pidfile_not_running( 'W/tmp/tests/lalala.pid' ), 'remove_pidfile_not_running: W/tmp/tests/lalala.pid => undef' ) ; is( '55555', string_to_file( '55555', 'W/tmp/tests/notrunning.pid' ), 'remove_pidfile_not_running: prepa W/tmp/tests/notrunning.pid' ) ; is( 1, remove_pidfile_not_running( 'W/tmp/tests/notrunning.pid' ), 'remove_pidfile_not_running: W/tmp/tests/notrunning.pid => 1' ) ; is( $PROCESS_ID, string_to_file( $PROCESS_ID, 'W/tmp/tests/running.pid' ), 'remove_pidfile_not_running: prepa W/tmp/tests/running.pid' ) ; is( undef, remove_pidfile_not_running( 'W/tmp/tests/running.pid' ), 'remove_pidfile_not_running: W/tmp/tests/running.pid => undef' ) ; note( 'Leaving tests_remove_pidfile_not_running()' ) ; return ; } sub remove_pidfile_not_running { # my $pid_filename = shift @ARG ; #myprint( "In remove_pidfile_not_running $pid_filename\n" ) ; if ( ! $pid_filename ) { myprint( "No variable pid_filename\n" ) ; return } ; if ( ! -e $pid_filename ) { myprint( "File $pid_filename does not exist\n" ) ; return ; } #myprint( "Still In remove_pidfile_not_running $pid_filename\n" ) ; if ( ! -f $pid_filename ) { myprint( "File $pid_filename is not a file\n" ) ; return } ; my $pid = firstline( $pid_filename ) ; if ( ! match_a_pid_number( $pid ) ) { myprint( "In remove_pidfile_not_running: pid $pid in $pid_filename is not a pid number\n" ) ; return } ; # can't kill myself => do nothing if ( ! kill 'ZERO', $PROCESS_ID ) { myprint( "Can not kill ZERO myself $PROCESS_ID\n" ) ; return } ; # can't kill ZERO the pid => it is gone or own by another user => remove pidfile if ( ! kill 'ZERO', $pid ) { myprint( "Removing old $pid_filename since its PID $pid is not running anymore (oo-killed?)\n" ) ; if ( unlink $pid_filename ) { myprint( "Removed old $pid_filename\n" ) ; return 1 ; }else{ myprint( "Could not remove old $pid_filename because $!\n" ) ; return ; } } myprint( "Another imapsync process $pid is running as says pidfile $pid_filename\n" ) ; return ; } sub tests_tail { note( 'Entering tests_tail()' ) ; ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'tail: mkpath W/tmp/tests/' ) ; ok( ( ! -e 'W/tmp/tests/tail.pid' || unlink 'W/tmp/tests/tail.pid' ), 'tail: unlink W/tmp/tests/tail.pid' ) ; ok( ( ! -e 'W/tmp/tests/tail.txt' || unlink 'W/tmp/tests/tail.txt' ), 'tail: unlink W/tmp/tests/tail.txt' ) ; is( undef, tail( ), 'tail: no args => undef' ) ; my $mysync ; is( undef, tail( $mysync ), 'tail: no pidfile => undef' ) ; $mysync->{pidfile} = 'W/tmp/tests/tail.pid' ; is( undef, tail( $mysync ), 'tail: no pidfilelocking => undef' ) ; $mysync->{pidfilelocking} = 1 ; is( undef, tail( $mysync ), 'tail: pidfile no exists => undef' ) ; my $pidandlog = "33333\nW/tmp/tests/tail.txt\n" ; is( $pidandlog, string_to_file( $pidandlog, $mysync->{pidfile} ), 'tail: put pid 33333 and tail.txt in pidfile' ) ; is( undef, tail( $mysync ), 'tail: logfile to tail no exists => undef' ) ; my $tailcontent = "L1\nL2\nL3\nL4\nL5\n" ; is( $tailcontent, string_to_file( $tailcontent, 'W/tmp/tests/tail.txt' ), 'tail: put L1\nL2\nL3\nL4\nL5\n in W/tmp/tests/tail.txt' ) ; is( undef, tail( $mysync ), 'tail: fake pid in pidfile + tail off => 1' ) ; $mysync->{ tail } = 1 ; is( 1, tail( $mysync ), 'tail: fake pid in pidfile + tail on=> 1' ) ; # put my own pid, won't do tail $pidandlog = "$PROCESS_ID\nW/tmp/tests/tail.txt\n" ; is( $pidandlog, string_to_file( $pidandlog, $mysync->{pidfile} ), 'tail: put my own PID in pidfile' ) ; is( undef, tail( $mysync ), 'tail: my own pid in pidfile => undef' ) ; note( 'Leaving tests_tail()' ) ; return ; } sub tail { # return undef on failures # return 1 on success my $mysync = shift ; # no tail when aborting! if ( $mysync->{ abort } ) { return ; } my $pidfile = $mysync->{pidfile} ; my $lock = $mysync->{pidfilelocking} ; my $tail = $mysync->{tail} ; if ( ! $pidfile ) { return ; } if ( ! $lock ) { return ; } if ( ! $tail ) { return ; } if ( ! -e $pidfile ) { return ; } my $pidtotail = firstline( $pidfile ) ; if ( ! $pidtotail ) { return ; } # It should not happen but who knows... if ( $pidtotail eq $PROCESS_ID ) { return ; } my $filetotail = secondline( $pidfile ) ; if ( ! $filetotail ) { return ; } if ( ! -r $filetotail ) { #myprint( "Error: can not read $filetotail\n" ) ; return ; } myprint( "Doing a tail -f on $filetotail for processus pid $pidtotail until it is finished.\n" ) ; my $file = File::Tail->new( name => $filetotail, nowait => 1, interval => 1, tail => 1, adjustafter => 2 ); my $moretimes = 200 ; # print one line at least my $line = $file->read ; myprint( $line ) ; while ( isrunning( $pidtotail, \$moretimes ) and defined( $line = $file->read ) ) { myprint( $line ); sleep( 0.02 ) ; } return 1 ; } sub isrunning { my $pidtocheck = shift ; my $moretimes_ref = shift ; if ( kill 'ZERO', $pidtocheck ) { #myprint( "$pidtocheck running\n" ) ; return 1 ; } elsif ( $$moretimes_ref >= 0 ) { # continue to consider it running $$moretimes_ref-- ; return 1 ; } else { myprint( "Tailed processus $pidtocheck ended\n" ) ; return ; } } sub tests_write_pidfile { note( 'Entering tests_write_pidfile()' ) ; my $mysync ; is( 1, write_pidfile( ), 'write_pidfile: no args => 1' ) ; # no pidfile => ok $mysync->{pidfile} = q{} ; is( 1, write_pidfile( $mysync ), 'write_pidfile: no pidfile => undef' ) ; # The pidfile path is bad => failure $mysync->{pidfile} = '/no/no/no.pid' ; is( undef, write_pidfile( $mysync ), 'write_pidfile: no permission for /no/no/no.pid, no lock => undef' ) ; $mysync->{pidfilelocking} = 1 ; is( undef, write_pidfile( $mysync ), 'write_pidfile: no permission for /no/no/no.pid + lock => undef' ) ; $mysync->{pidfile} = 'W/tmp/tests/test.pid' ; ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'write_pidfile: mkpath W/tmp/tests/' ) ; is( 1, touch( $mysync->{pidfile} ), 'write_pidfile: lock prepa' ) ; $mysync->{pidfilelocking} = 0 ; is( 1, write_pidfile( $mysync ), 'write_pidfile: W/tmp/tests/test.pid + no lock => 1' ) ; is( $PROCESS_ID, firstline( 'W/tmp/tests/test.pid' ), "write_pidfile: W/tmp/tests/test.pid contains $PROCESS_ID" ) ; is( q{}, secondline( 'W/tmp/tests/test.pid' ), "write_pidfile: W/tmp/tests/test.pid contains no second line" ) ; $mysync->{pidfilelocking} = 1 ; is( undef, write_pidfile( $mysync ), 'write_pidfile: W/tmp/tests/test.pid + lock => undef' ) ; $mysync->{pidfilelocking} = 0 ; $mysync->{ logfile } = 'rrrr.txt' ; is( 1, write_pidfile( $mysync ), 'write_pidfile: W/tmp/tests/test.pid + no lock + logfile => 1' ) ; is( $PROCESS_ID, firstline( 'W/tmp/tests/test.pid' ), "write_pidfile: + no lock + logfile W/tmp/tests/test.pid contains $PROCESS_ID" ) ; is( q{rrrr.txt}, secondline( 'W/tmp/tests/test.pid' ), "write_pidfile: + no lock + logfile W/tmp/tests/test.pid contains rrrr.txt" ) ; note( 'Leaving tests_write_pidfile()' ) ; return ; } sub write_pidfile { # returns undef if something is considered fatal # returns 1 otherwise #myprint( "In write_pidfile\n" ) ; if ( ! @ARG ) { return 1 ; } my $mysync = shift @ARG ; # Do not write the pid file if the current process goal is to abort the process designed by the pid file if ( $mysync->{ abort } ) { return 1 ; } # my $pid_filename = $mysync->{ pidfile } ; my $lock = $mysync->{ pidfilelocking } ; if ( ! $pid_filename ) { myprint( "PID file is unset ( to set it, use --pidfile filepath ; to avoid it use --pidfile \"\" )\n" ) ; return( 1 ) ; } myprint( "PID file is $pid_filename ( to change it, use --pidfile filepath ; to avoid it use --pidfile \"\" )\n" ) ; if ( -e $pid_filename and $lock ) { myprint( "$pid_filename already exists, another imapsync may be curently running. Aborting imapsync.\n" ) ; return ; } if ( -e $pid_filename ) { myprint( "$pid_filename already exists, overwriting it ( use --pidfilelocking to avoid concurrent runs )\n" ) ; } my $pid_string = "$PROCESS_ID\n" ; my $pid_message = "Writing my PID $PROCESS_ID in $pid_filename\n" ; if ( $mysync->{ logfile } ) { $pid_string .= "$mysync->{ logfile }\n" ; $pid_message .= "Writing also my logfile name in $pid_filename : $mysync->{ logfile }\n" ; } if ( open my $FILE_HANDLE, '>', $pid_filename ) { myprint( $pid_message ) ; print $FILE_HANDLE $pid_string ; close $FILE_HANDLE ; return( 1 ) ; } else { myprint( "Could not open $pid_filename for writing. Check permissions or disk space: $OS_ERROR\n" ) ; return ; } } sub fix_Inbox_INBOX_mapping { my( $h1_all, $h2_all ) = @_ ; my $regex = q{} ; SWITCH: { if ( exists $h1_all->{INBOX} and exists $h2_all->{INBOX} ) { $regex = q{} ; last SWITCH ; } ; if ( exists $h1_all->{Inbox} and exists $h2_all->{Inbox} ) { $regex = q{} ; last SWITCH ; } ; if ( exists $h1_all->{INBOX} and exists $h2_all->{Inbox} ) { $regex = q{s/^INBOX$/Inbox/x} ; last SWITCH ; } ; if ( exists $h1_all->{Inbox} and exists $h2_all->{INBOX} ) { $regex = q{s/^Inbox$/INBOX/x} ; last SWITCH ; } ; } ; return( $regex ) ; } sub tests_fix_Inbox_INBOX_mapping { note( 'Entering tests_fix_Inbox_INBOX_mapping()' ) ; my( $h1_all, $h2_all ) ; $h1_all = { 'INBOX' => q{} } ; $h2_all = { 'INBOX' => q{} } ; ok( q{} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: INBOX INBOX' ) ; $h1_all = { 'Inbox' => q{} } ; $h2_all = { 'Inbox' => q{} } ; ok( q{} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: Inbox Inbox' ) ; $h1_all = { 'INBOX' => q{} } ; $h2_all = { 'Inbox' => q{} } ; ok( q{s/^INBOX$/Inbox/x} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: INBOX Inbox' ) ; $h1_all = { 'Inbox' => q{} } ; $h2_all = { 'INBOX' => q{} } ; ok( q{s/^Inbox$/INBOX/x} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: Inbox INBOX' ) ; $h1_all = { 'INBOX' => q{} } ; $h2_all = { 'rrrrr' => q{} } ; ok( q{} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: INBOX rrrrrr' ) ; $h1_all = { 'rrrrr' => q{} } ; $h2_all = { 'Inbox' => q{} } ; ok( q{} eq fix_Inbox_INBOX_mapping( $h1_all, $h2_all ), 'fix_Inbox_INBOX_mapping: rrrrr Inbox' ) ; note( 'Leaving tests_fix_Inbox_INBOX_mapping()' ) ; return ; } sub jux_utf8_list { my @s_inp = @_ ; my $s_out = q{} ; foreach my $s ( @s_inp ) { $s_out .= jux_utf8( $s ) . "\n" ; } return( $s_out ) ; } sub tests_jux_utf8_list { note( 'Entering tests_jux_utf8_list()' ) ; use utf8 ; is( q{}, jux_utf8_list( ), 'jux_utf8_list: void' ) ; is( "[]\n", jux_utf8_list( q{} ), 'jux_utf8_list: empty string' ) ; is( "[INBOX]\n", jux_utf8_list( 'INBOX' ), 'jux_utf8_list: INBOX' ) ; is( "[&ANY-] = [Ö]\n", jux_utf8_list( '&ANY-' ), 'jux_utf8_list: [&ANY-] = [Ö]' ) ; note( 'Leaving tests_jux_utf8_list()' ) ; return( 0 ) ; } # editing utf8 can be tricky without an utf8 editor sub tests_jux_utf8_old { note( 'Entering tests_jux_utf8_old()' ) ; no utf8 ; is( '[]', jux_utf8_old( q{} ), 'jux_utf8_old: void => []' ) ; is( '[INBOX]', jux_utf8_old( 'INBOX'), 'jux_utf8_old: INBOX => [INBOX]' ) ; is( '[&ZTZO9nux-] = [æ”¶ä»¶ç®±]', jux_utf8_old( '&ZTZO9nux-'), 'jux_utf8_old: => [&ZTZO9nux-] = [æ”¶ä»¶ç®±]' ) ; is( '[&ANY-] = [Ö]', jux_utf8_old( '&ANY-'), 'jux_utf8_old: &ANY- => [&ANY-] = [Ö]' ) ; # +BD8EQAQ1BDQEOwQ+BDM- SHOULD stay as is! is( '[+BD8EQAQ1BDQEOwQ+BDM-] = [предлог]', jux_utf8_old( '+BD8EQAQ1BDQEOwQ+BDM-' ), 'jux_utf8_old: => [+BD8EQAQ1BDQEOwQ+BDM-] = [предлог]' ) ; is( '[&BB8EQAQ+BDUEOgRC-] = [Проект]', jux_utf8_old( '&BB8EQAQ+BDUEOgRC-' ), 'jux_utf8_old: => [&BB8EQAQ+BDUEOgRC-] = [Проект]' ) ; note( 'Leaving tests_jux_utf8_old()' ) ; return ; } sub jux_utf8_old { # juxtapose utf8 at the right if different my ( $s_utf7 ) = shift ; my ( $s_utf8 ) = imap_utf7_decode_old( $s_utf7 ) ; if ( $s_utf7 eq $s_utf8 ) { #myprint( "[$s_utf7]\n" ) ; return( "[$s_utf7]" ) ; }else{ #myprint( "[$s_utf7] = [$s_utf8]\n" ) ; return( "[$s_utf7] = [$s_utf8]" ) ; } } # Copied from http://cpansearch.perl.org/src/FABPOT/Unicode-IMAPUtf7-2.01/lib/Unicode/IMAPUtf7.pm # and then fixed with # https://rt.cpan.org/Public/Bug/Display.html?id=11172 sub imap_utf7_decode_old { my ( $s ) = shift ; # Algorithm # On remplace , par / dans les BASE 64 (, entre & et -) # On remplace les &, non suivi d'un - par + # On remplace les &- par & $s =~ s/&([^,&\-]*),([^,\-&]*)\-/&$1\/$2\-/xg ; $s =~ s/&(?!\-)/\+/xg ; $s =~ s/&\-/&/xg ; return( Unicode::String::utf7( $s )->utf8 ) ; } sub tests_jux_utf8 { note( 'Entering tests_jux_utf8()' ) ; #no utf8 ; use utf8 ; #binmode STDOUT, ":encoding(UTF-8)" ; binmode STDERR, ":encoding(UTF-8)" ; # This test is because the binary can fail on it, a PAR.pm issue. # The failure was with the underlying Encode::IMAPUTF7 module line 66 release 1.05 # Was solved by including Encode in imapsync and using "pp -x". ok( find_encoding( "UTF-16BE"), 'jux_utf8: Encode::find_encoding: UTF-16BE' ) ; # is( '[]', jux_utf8( q{} ), 'jux_utf8: void => []' ) ; is( '[INBOX]', jux_utf8( 'INBOX'), 'jux_utf8: INBOX => [INBOX]' ) ; is( '[&ANY-] = [Ö]', jux_utf8( '&ANY-'), 'jux_utf8: &ANY- => [&ANY-] = [Ö]' ) ; # +BD8EQAQ1BDQEOwQ+BDM- must stay as is is( '[+BD8EQAQ1BDQEOwQ+BDM-]', jux_utf8( '+BD8EQAQ1BDQEOwQ+BDM-' ), 'jux_utf8: => [+BD8EQAQ1BDQEOwQ+BDM-] = [+BD8EQAQ1BDQEOwQ+BDM-]' ) ; is( '[&BB8EQAQ+BDUEOgRC-] = [Проект]', jux_utf8( '&BB8EQAQ+BDUEOgRC-' ), 'jux_utf8: => [&BB8EQAQ+BDUEOgRC-] = [Проект]' ) ; is( '[R&AOk-ponses 1200+1201+1202] = [Réponses 1200+1201+1202]', jux_utf8( q{R&AOk-ponses 1200+1201+1202} ), 'jux_utf8: [R&AOk-ponses 1200+1201+1202] = [Réponses 1200+1201+1202]' ) ; my $str = Encode::IMAPUTF7::encode("IMAP-UTF-7", 'Réponses 1200+1201+1202' ) ; is( '[R&AOk-ponses 1200+1201+1202] = [Réponses 1200+1201+1202]', jux_utf8( $str ), "jux_utf8: [$str] = [Réponses 1200+1201+1202]" ) ; is( '[INBOX.&AOkA4ADnAPk-&-*] = [INBOX.éà çù&*]', jux_utf8( 'INBOX.&AOkA4ADnAPk-&-*' ), "jux_utf8: [INBOX.&AOkA4ADnAPk-&-*] = [INBOX.éà çù&*]" ) ; is( '[&ZTZO9nux-] = [æ”¶ä»¶ç®±]', jux_utf8( '&ZTZO9nux-'), 'jux_utf8: => [&ZTZO9nux-] = [æ”¶ä»¶ç®±]' ) ; # # is( '[!Old Emails]', jux_utf8( '!Old Emails'), 'jux_utf8: !Old Emails => [!Old Emails]' ) ; is( '[2006 Budget & Fcst]', jux_utf8( '2006 Budget & Fcst'), 'jux_utf8: 2006 Budget & Fcst => [2006 Budget & Fcst]' ) ; note( 'Leaving tests_jux_utf8()' ) ; return ; } sub jux_utf8 { #use utf8 ; # juxtapose utf8 at the right if different my ( $s_utf7 ) = shift ; my ( $s_utf8 ) = imap_utf7_decode( $s_utf7 ) ; if ( $s_utf7 eq $s_utf8 ) { #myprint( "[$s_utf7]\n" ) ; return( "[$s_utf7]" ) ; }else{ #myprint( "[$s_utf7] = [$s_utf8]\n" ) ; return( "[$s_utf7] = [$s_utf8]" ) ; } } sub imap_utf7_decode { #use utf8 ; my ( $s ) = shift ; return( Encode::IMAPUTF7::decode("IMAP-UTF-7", $s ) ) ; } sub imap_utf7_encode { #use utf8 ; my ( $s ) = shift ; return( Encode::IMAPUTF7::encode("IMAP-UTF-7", $s ) ) ; } sub imap_utf7_encode_old { my ( $s ) = @_ ; $s = Unicode::String::utf8( $s )->utf7 ; $s =~ s/\+([^\/&\-]*)\/([^\/\-&]*)\-/\+$1,$2\-/xg ; $s =~ s/&/&\-/xg ; $s =~ s/\+([^+\-]+)?\-/&$1\-/xg ; return( $s ) ; } sub select_folder { my ( $mysync, $imap, $folder, $hostside ) = @_ ; if ( ! $imap->select( $folder ) ) { my $error = join q{}, "$hostside folder $folder: Could not select: ", $imap->LastError, "\n" ; errors_incr( $mysync, $error ) ; return( 0 ) ; }else{ # ok select succeeded return( 1 ) ; } } sub examine_folder { my ( $mysync, $imap, $folder, $hostside ) = @_ ; if ( ! $imap->examine( $folder ) ) { my $error = join q{}, "$hostside folder $folder: Could not examine: ", $imap->LastError, "\n" ; errors_incr( $mysync, $error ) ; return( 0 ) ; }else{ # ok select succeeded return( 1 ) ; } } sub count_from_select { my @lines = @ARG ; my $count ; foreach my $line ( @lines ) { #myprint( "line = [$line]\n" ) ; if ( $line =~ m/^\*\s+(\d+)\s+EXISTS/x ) { $count = $1 ; return( $count ) ; } } return( undef ) ; } sub create_folder_old { my $mysync = shift @ARG ; my( $imap, $h2_fold, $h1_fold ) = @ARG ; myprint( "Creating (old way) folder [$h2_fold] on host2\n" ) ; if ( ( 'INBOX' eq uc $h2_fold ) and ( $imap->exists( $h2_fold ) ) ) { myprint( "Folder [$h2_fold] already exists\n" ) ; return( 1 ) ; } if ( ! $mysync->{dry} ){ if ( ! $imap->create( $h2_fold ) ) { my $error = join q{}, "Could not create folder [$h2_fold] from [$h1_fold]: ", $imap->LastError( ), "\n" ; errors_incr( $mysync, $error ) ; # success if folder exists ("already exists" error) return( 1 ) if $imap->exists( $h2_fold ) ; # failure since create failed return( 0 ) ; }else{ #create succeeded myprint( "Created ( the old way ) folder [$h2_fold] on host2\n" ) ; return( 1 ) ; } }else{ # dry mode, no folder so many imap will fail, assuming failure myprint( "Created ( the old way ) folder [$h2_fold] on host2 $mysync->{dry_message}\n" ) ; return( 0 ) ; } } sub create_folder { my $mysync = shift @ARG ; my( $myimap2 , $h2_fold , $h1_fold ) = @ARG ; my( @parts , $parent ) ; if ( $myimap2->IsUnconnected( ) ) { myprint( "Host2: Unconnected state\n" ) ; return( 0 ) ; } if ( $create_folder_old ) { return( create_folder_old( $mysync, $myimap2 , $h2_fold , $h1_fold ) ) ; } # $imap->exists() calls $imap->status() that does an IMAP STATUS folder myprint( "Creating folder [$h2_fold] on host2\n" ) ; if ( ( 'INBOX' eq uc $h2_fold ) and ( $myimap2->exists( $h2_fold ) ) ) { myprint( "Folder [$h2_fold] already exists\n" ) ; return( 1 ) ; } if ( $mixfolders and $myimap2->exists( $h2_fold ) ) { myprint( "Folder [$h2_fold] already exists (--nomixfolders is not set)\n" ) ; return( 1 ) ; } if ( ( not $mixfolders ) and ( $myimap2->exists( $h2_fold ) ) ) { myprint( "Folder [$h2_fold] already exists and --nomixfolders is set\n" ) ; return( 0 ) ; } @parts = split /\Q$mysync->{ h2_sep }\E/x, $h2_fold ; pop @parts ; $parent = join $mysync->{ h2_sep }, @parts ; $parent =~ s/^\s+|\s+$//xg ; if ( ( $parent ne q{} ) and ( ! $myimap2->exists( $parent ) ) ) { create_folder( $mysync, $myimap2 , $parent , $h1_fold ) ; } if ( ! $mysync->{dry} ) { if ( ! $myimap2->create( $h2_fold ) ) { my $error = join q{}, "Could not create folder [$h2_fold] from [$h1_fold]: " , $myimap2->LastError( ), "\n" ; errors_incr( $mysync, $error ) ; # success if folder exists ("already exists" error) or selectable if ( $myimap2->exists( $h2_fold ) or select_folder( $mysync, $myimap2, $h2_fold, 'Host2' ) ) { return( 1 ) ; } # failure since create failed + not exist + not selectable return( 0 ) ; }else{ #create succeeded myprint( "Created folder [$h2_fold] on host2\n" ) ; return( 1 ) ; } }else{ # dry mode, no folder so many imap will fail, assuming failure myprint( "Created folder [$h2_fold] on host2 $mysync->{dry_message}\n" ) ; if ( ! $mysync->{ justfolders } ) { myprint( "Since --dry mode is on and folder [$h2_fold] on host2 does not exist yet, syncing messages will not be simulated.\n" . "To simulate message syncing, use --justfolders without --dry to first create the missing folders then rerun the --dry sync.\n" ) ; # The messages that could be transferred are counted and the number is given at the end. } return( 0 ) ; } } sub tests_folder_routines { note( 'Entering tests_folder_routines()' ) ; ok( !is_requested_folder('folder_foo'), 'is_requested_folder folder_foo 1' ); ok( add_to_requested_folders('folder_foo'), 'add_to_requested_folders folder_foo' ); ok( is_requested_folder('folder_foo'), 'is_requested_folder folder_foo 2' ); ok( !is_requested_folder('folder_NO_EXIST'), 'is_requested_folder folder_NO_EXIST' ); is_deeply( [ 'folder_foo' ], [ remove_from_requested_folders( 'folder_foo' ) ], 'removed folder_foo => folder_foo' ) ; ok( !is_requested_folder('folder_foo'), 'is_requested_folder folder_foo 3' ); my @f ; ok( @f = add_to_requested_folders('folder_bar', 'folder_toto'), "add result: @f" ); ok( is_requested_folder('folder_bar'), 'is_requested_folder 4' ); ok( is_requested_folder('folder_toto'), 'is_requested_folder 5' ); ok( remove_from_requested_folders('folder_toto'), 'remove_from_requested_folders: ' ); ok( !is_requested_folder('folder_toto'), 'is_requested_folder 6' ); is_deeply( [ 'folder_bar' ], [ remove_from_requested_folders('folder_bar') ], 'remove_from_requested_folders: empty' ) ; ok( 0 == compare_lists( [ sort_requested_folders( ) ], [] ), 'sort_requested_folders: all empty' ) ; ok( add_to_requested_folders( 'A_99', 'M_55', 'Z_11' ), 'add_to_requested_folders M_55 Z_11' ); ok( 0 == compare_lists( [ sort_requested_folders( ) ], [ 'A_99', 'M_55', 'Z_11' ] ), 'sort_requested_folders: middle' ) ; @folderfirst = ( 'Z_11' ) ; ok( 0 == compare_lists( [ sort_requested_folders( ) ], [ 'Z_11', 'A_99', 'M_55' ] ), 'sort_requested_folders: first+middle' ) ; is_deeply( [ 'Z_11', 'A_99', 'M_55' ], [ sort_requested_folders( ) ], 'sort_requested_folders: first+middle is_deeply' ) ; @folderlast = ( 'A_99' ) ; ok( 0 == compare_lists( [ sort_requested_folders( ) ], [ 'Z_11', 'M_55', 'A_99' ] ), 'sort_requested_folders: first+middle+last 1' ) ; ok( add_to_requested_folders('M_55', 'M_44',), 'add_to_requested_folders M_55 M_44' ) ; ok( 0 == compare_lists( [ sort_requested_folders( ) ], [ 'Z_11', 'M_44', 'M_55', 'A_99'] ), 'sort_requested_folders: first+middle+last 2' ) ; ok( add_to_requested_folders('A_88', 'Z_22',), 'add_to_requested_folders A_88 Z_22' ) ; @folderfirst = qw( Z_22 Z_11 ) ; @folderlast = qw( A_99 A_88 ) ; ok( 0 == compare_lists( [ sort_requested_folders( ) ], [ 'Z_22', 'Z_11', 'M_44', 'M_55', 'A_99', 'A_88' ] ), 'sort_requested_folders: first+middle+last 3' ) ; undef @folderfirst ; undef @folderlast ; note( 'Leaving tests_folder_routines()' ) ; return ; } sub sort_requested_folders { my @requested_folders_sorted = () ; $sync->{ debug } and myprint "folderfirst: @folderfirst\n" ; my @folderfirst_requested = remove_from_requested_folders( @folderfirst ) ; #myprint "folderfirst_requested: @folderfirst_requested\n" ; my @folderlast_requested = remove_from_requested_folders( @folderlast ) ; my @middle = sort keys %requested_folder ; @requested_folders_sorted = ( @folderfirst_requested, @middle, @folderlast_requested ) ; $sync->{ debug } and myprint "requested_folders_sorted: @requested_folders_sorted\n" ; add_to_requested_folders( @requested_folders_sorted ) ; return( @requested_folders_sorted ) ; } sub is_requested_folder { my ( $folder ) = @_; return( defined $requested_folder{ $folder } ) ; } sub add_to_requested_folders { my @wanted_folders = @_ ; foreach my $folder ( @wanted_folders ) { ++$requested_folder{ $folder } ; } return( keys %requested_folder ) ; } sub tests_remove_from_requested_folders { note( 'Entering tests_remove_from_requested_folders()' ) ; is( undef, undef, 'remove_from_requested_folders: undef is undef' ) ; is_deeply( [], [ remove_from_requested_folders( ) ], 'remove_from_requested_folders: no args' ) ; %requested_folder = ( 'F1' => 1, ) ; is_deeply( [], [ remove_from_requested_folders( ) ], 'remove_from_requested_folders: remove nothing among F1 => nothing' ) ; is_deeply( [], [ remove_from_requested_folders( 'Fno' ) ], 'remove_from_requested_folders: remove Fno among F1 => nothing' ) ; is_deeply( [ 'F1' ], [ remove_from_requested_folders( 'F1' ) ], 'remove_from_requested_folders: remove F1 among F1 => F1' ) ; is_deeply( { }, { %requested_folder }, 'remove_from_requested_folders: remove F1 among F1 => %requested_folder emptied' ) ; %requested_folder = ( 'F1' => 1, 'F2' => 1, ) ; is_deeply( [], [ remove_from_requested_folders( ) ], 'remove_from_requested_folders: remove nothing among F1 F2 => nothing' ) ; is_deeply( [], [ remove_from_requested_folders( 'Fno' ) ], 'remove_from_requested_folders: remove Fno among F1 F2 => nothing' ) ; is_deeply( [ 'F1' ], [ remove_from_requested_folders( 'F1' ) ], 'remove_from_requested_folders: remove F1 among F1 F2 => F1' ) ; is_deeply( { 'F2' => 1 }, { %requested_folder }, 'remove_from_requested_folders: remove F1 among F1 F2 => %requested_folder F2' ) ; is_deeply( [], [ remove_from_requested_folders( 'F1' ) ], 'remove_from_requested_folders: remove F1 among F2 => nothing' ) ; is_deeply( [ 'F2' ], [ remove_from_requested_folders( 'F1', 'F2' ) ], 'remove_from_requested_folders: remove F1 F2 among F2 => F2' ) ; is_deeply( {}, { %requested_folder }, 'remove_from_requested_folders: remove F1 among F1 F2 => %requested_folder F2' ) ; %requested_folder = ( 'F1' => 1, 'F2' => 1, 'F3' => 1, ) ; is_deeply( [ 'F1', 'F2' ], [ remove_from_requested_folders( 'F1', 'F2' ) ], 'remove_from_requested_folders: remove F1 F2 among F1 F2 F3 => F1 F2' ) ; is_deeply( { 'F3' => 1 }, { %requested_folder }, 'remove_from_requested_folders: remove F1 F2 among F1 F2 F3 => %requested_folder F3' ) ; undef %requested_folder ; note( 'Leaving tests_remove_from_requested_folders()' ) ; return ; } sub remove_from_requested_folders { my @unwanted_folders = @_ ; my @removed_folders = () ; foreach my $folder ( @unwanted_folders ) { if ( exists $requested_folder{ $folder } ) { delete $requested_folder{ $folder } ; push @removed_folders, $folder ; } } return( @removed_folders ) ; } sub compare_lists { my ($list_1_ref, $list_2_ref) = @_; return($MINUS_ONE) if ((not defined $list_1_ref) and defined $list_2_ref); return(0) if ((not defined $list_1_ref) and not defined $list_2_ref); # end if no list return(1) if (not defined $list_2_ref); # end if only one list if (not ref $list_1_ref ) {$list_1_ref = [$list_1_ref]}; if (not ref $list_2_ref ) {$list_2_ref = [$list_2_ref]}; my $last_used_indice = $MINUS_ONE; ELEMENT: foreach my $indice ( 0 .. $#{ $list_1_ref } ) { $last_used_indice = $indice ; # End of list_2 return 1 if ($indice > $#{ $list_2_ref } ) ; my $element_list_1 = $list_1_ref->[$indice] ; my $element_list_2 = $list_2_ref->[$indice] ; my $balance = $element_list_1 cmp $element_list_2 ; next ELEMENT if ($balance == 0) ; return $balance ; } # each element equal until last indice of list_1 return $MINUS_ONE if ($last_used_indice < $#{ $list_2_ref } ) ; # same size, each element equal return 0 ; } sub tests_compare_lists { note( 'Entering tests_compare_lists()' ) ; my $empty_list_ref = []; ok( 0 == compare_lists() , 'compare_lists, no args'); ok( 0 == compare_lists(undef) , 'compare_lists, undef = nothing'); ok( 0 == compare_lists(undef, undef) , 'compare_lists, undef = undef'); ok($MINUS_ONE == compare_lists(undef , []) , 'compare_lists, undef < []'); ok($MINUS_ONE == compare_lists(undef , [1]) , 'compare_lists, undef < [1]'); ok($MINUS_ONE == compare_lists(undef , [0]) , 'compare_lists, undef < [0]'); ok(+1 == compare_lists([]) , 'compare_lists, [] > nothing'); ok(+1 == compare_lists([], undef) , 'compare_lists, [] > undef'); ok( 0 == compare_lists([] , []) , 'compare_lists, [] = []'); ok($MINUS_ONE == compare_lists([] , [1]) , 'compare_lists, [] < [1]'); ok(+1 == compare_lists([1] , []) , 'compare_lists, [1] > []'); ok( 0 == compare_lists( [1], 1 ) , 'compare_lists, [1] = 1 ') ; ok( 0 == compare_lists( 1 , [1] ) , 'compare_lists, 1 = [1]') ; ok( 0 == compare_lists( 1 , 1 ) , 'compare_lists, 1 = 1 ') ; ok( $MINUS_ONE == compare_lists( 0 , 1 ) , 'compare_lists, 0 < 1 ') ; ok( $MINUS_ONE == compare_lists( $MINUS_ONE , 0 ) , 'compare_lists, -1 < 0 ') ; ok( $MINUS_ONE == compare_lists( 1 , 2 ) , 'compare_lists, 1 < 2 ') ; ok( +1 == compare_lists( 2 , 1 ) , 'compare_lists, 2 > 1 ') ; ok( 0 == compare_lists([1,2], [1,2]) , 'compare_lists, [1,2] = [1,2]' ) ; ok($MINUS_ONE == compare_lists([1], [1,2]) , 'compare_lists, [1] < [1,2]' ) ; ok(+1 == compare_lists([2], [1,2]) , 'compare_lists, [2] > [1,2]' ) ; ok($MINUS_ONE == compare_lists([1], [1,1]) , 'compare_lists, [1] < [1,1]' ) ; ok(+1 == compare_lists([1, 1], [1]) , 'compare_lists, [1, 1] > [1]' ) ; ok( 0 == compare_lists([1 .. $NUMBER_20_000] , [1 .. $NUMBER_20_000]) , 'compare_lists, [1..20_000] = [1..20_000]' ) ; ok($MINUS_ONE == compare_lists([1], [2]) , 'compare_lists, [1] < [2]') ; ok( 0 == compare_lists([2], [2]) , 'compare_lists, [0] = [2]') ; ok(+1 == compare_lists([2], [1]) , 'compare_lists, [2] > [1]') ; ok($MINUS_ONE == compare_lists(['a'], ['b']) , 'compare_lists, ["a"] < ["b"]') ; ok( 0 == compare_lists(['a'], ['a']) , 'compare_lists, ["a"] = ["a"]') ; ok( 0 == compare_lists(['ab'], ['ab']) , 'compare_lists, ["ab"] = ["ab"]') ; ok(+1 == compare_lists(['b'], ['a']) , 'compare_lists, ["b"] > ["a"]') ; ok($MINUS_ONE == compare_lists(['a'], ['aa']) , 'compare_lists, ["a"] < ["aa"]') ; ok($MINUS_ONE == compare_lists(['a'], ['a', 'a']), 'compare_lists, ["a"] < ["a", "a"]') ; ok( 0 == compare_lists([split q{ }, 'a b' ], ['a', 'b']), 'compare_lists, split') ; ok( 0 == compare_lists([sort split q{ }, 'b a' ], ['a', 'b']), 'compare_lists, sort split') ; note( 'Leaving tests_compare_lists()' ) ; return ; } sub guess_prefix { my @foldernames = @_ ; my $prefix_guessed = q{} ; foreach my $folder ( @foldernames ) { next if ( $folder =~ m{^INBOX$}xi ) ; # no guessing from INBOX if ( $folder !~ m{^INBOX}xi ) { $prefix_guessed = q{} ; # prefix empty guessed last ; } if ( $folder =~ m{^(INBOX(?:\.|\/))}xi ) { $prefix_guessed = $1 ; # prefix Inbox/ or INBOX. guessed } } return( $prefix_guessed ) ; } sub tests_guess_prefix { note( 'Entering tests_guess_prefix()' ) ; is( guess_prefix( ), q{}, 'guess_prefix: no args => empty string' ) ; is( q{} , guess_prefix( 'INBOX' ), 'guess_prefix: INBOX alone' ) ; is( q{} , guess_prefix( 'Inbox' ), 'guess_prefix: Inbox alone' ) ; is( q{} , guess_prefix( 'INBOX' ), 'guess_prefix: INBOX alone' ) ; is( 'INBOX/' , guess_prefix( 'INBOX', 'INBOX/Junk' ), 'guess_prefix: INBOX INBOX/Junk' ) ; is( 'INBOX.' , guess_prefix( 'INBOX', 'INBOX.Junk' ), 'guess_prefix: INBOX INBOX.Junk' ) ; is( 'Inbox/' , guess_prefix( 'Inbox', 'Inbox/Junk' ), 'guess_prefix: Inbox Inbox/Junk' ) ; is( 'Inbox.' , guess_prefix( 'Inbox', 'Inbox.Junk' ), 'guess_prefix: Inbox Inbox.Junk' ) ; is( 'INBOX/' , guess_prefix( 'INBOX', 'INBOX/Junk', 'INBOX/rrr' ), 'guess_prefix: INBOX INBOX/Junk INBOX/rrr' ) ; is( q{} , guess_prefix( 'INBOX', 'INBOX/Junk', 'INBOX/rrr', 'zzz' ), 'guess_prefix: INBOX INBOX/Junk INBOX/rrr zzz' ) ; is( q{} , guess_prefix( 'INBOX', 'Junk' ), 'guess_prefix: INBOX Junk' ) ; is( q{} , guess_prefix( 'INBOX', 'Junk' ), 'guess_prefix: INBOX Junk' ) ; note( 'Leaving tests_guess_prefix()' ) ; return ; } sub get_prefix { my( $imap, $prefix_in, $prefix_opt, $Side, $folders_ref ) = @_ ; my( $prefix_out, $prefix_guessed ) ; ( $sync->{ debug } or $sync->{debugfolders} ) and myprint( "$Side: Getting prefix\n" ) ; $prefix_guessed = guess_prefix( @{ $folders_ref } ) ; myprint( "$Side: guessing prefix from folder listing: [$prefix_guessed]\n" ) ; ( $sync->{ debug } or $sync->{debugfolders} ) and myprint( "$Side: Calling namespace capability\n" ) ; if ( $imap->has_capability( 'namespace' ) ) { my $r_namespace = $imap->namespace( ) ; $prefix_out = $r_namespace->[0][0][0] ; myprint( "$Side: prefix given by NAMESPACE: [$prefix_out]\n" ) ; if ( defined $prefix_in ) { myprint( "$Side: but using [$prefix_in] given by $prefix_opt\n" ) ; $prefix_out = $prefix_in ; return( $prefix_out ) ; }else{ # all good return( $prefix_out ) ; } } else{ if ( defined $prefix_in ) { myprint( "$Side: using [$prefix_in] given by $prefix_opt\n" ) ; $prefix_out = $prefix_in ; return( $prefix_out ) ; }else{ myprint( "$Side: No NAMESPACE capability so using guessed prefix [$prefix_guessed]\n", help_to_guess_prefix( $imap, $prefix_opt ) ) ; return( $prefix_guessed ) ; } } return ; } sub guess_separator { my @foldernames = @_ ; #return( undef ) unless ( @foldernames ) ; my $sep_guessed ; my %counter ; foreach my $folder ( @foldernames ) { $counter{'/'}++ while ( $folder =~ m{/}xg ) ; # count / $counter{'.'}++ while ( $folder =~ m{\.}xg ) ; # count . $counter{'\\\\'}++ while ( $folder =~ m{(\\){2}}xg ) ; # count \\ $counter{'\\'}++ while ( $folder =~ m{[^\\](\\){1}(?=[^\\])}xg ) ; # count \ } my @race_sorted = sort { $counter{ $b } <=> $counter{ $a } } keys %counter ; $sync->{ debug } and myprint( "@foldernames\n@race_sorted\n", %counter, "\n" ) ; $sep_guessed = shift @race_sorted || $LAST_RESSORT_SEPARATOR ; # / when nothing found. return( $sep_guessed ) ; } sub tests_guess_separator { note( 'Entering tests_guess_separator()' ) ; ok( '/' eq guess_separator( ), 'guess_separator: no args' ) ; ok( '/' eq guess_separator( 'abcd' ), 'guess_separator: abcd' ) ; ok( '/' eq guess_separator( 'a/b/c.d' ), 'guess_separator: a/b/c.d' ) ; ok( '.' eq guess_separator( 'a.b/c.d' ), 'guess_separator: a.b/c.d' ) ; ok( '\\\\' eq guess_separator( 'a\\\\b\\\\c.c\\\\d/e/f' ), 'guess_separator: a\\\\b\\\\c.c\\\\d/e/f' ) ; ok( '\\' eq guess_separator( 'a\\b\\c.c\\d/e/f' ), 'guess_separator: a\\b\\c.c\\d/e/f' ) ; ok( '\\' eq guess_separator( 'a\\b' ), 'guess_separator: a\\b' ) ; ok( '\\' eq guess_separator( 'a\\b\\c' ), 'guess_separator: a\\b\\c' ) ; note( 'Leaving tests_guess_separator()' ) ; return ; } sub get_separator { my( $imap, $sep_in, $sep_opt, $Side, $folders_ref ) = @_ ; my( $sep_out, $sep_guessed ) ; ( $sync->{ debug } or $sync->{debugfolders} ) and myprint( "$Side: Getting separator\n" ) ; $sep_guessed = guess_separator( @{ $folders_ref } ) ; myprint( "$Side: guessing separator from folder listing: [$sep_guessed]\n" ) ; ( $sync->{ debug } or $sync->{debugfolders} ) and myprint( "$Side: calling namespace capability\n" ) ; if ( $imap->has_capability( 'namespace' ) ) { $sep_out = $imap->separator( ) ; if ( defined $sep_out ) { myprint( "$Side: separator given by NAMESPACE: [$sep_out]\n" ) ; if ( defined $sep_in ) { myprint( "$Side: but using [$sep_in] given by $sep_opt\n" ) ; $sep_out = $sep_in ; return( $sep_out ) ; }else{ return( $sep_out ) ; } }else{ if ( defined $sep_in ) { myprint( "$Side: NAMESPACE request failed but using [$sep_in] given by $sep_opt\n" ) ; $sep_out = $sep_in ; return( $sep_out ) ; }else{ myprint( "$Side: NAMESPACE request failed so using guessed separator [$sep_guessed]\n", help_to_guess_sep( $imap, $sep_opt ) ) ; return( $sep_guessed ) ; } } } else { if ( defined $sep_in ) { myprint( "$Side: No NAMESPACE capability but using [$sep_in] given by $sep_opt\n" ) ; $sep_out = $sep_in ; return( $sep_out ) ; }else{ myprint( "$Side: No NAMESPACE capability, so using guessed separator [$sep_guessed]\n", help_to_guess_sep( $imap, $sep_opt ) ) ; return( $sep_guessed ) ; } } return ; } sub help_to_guess_sep { my( $imap, $sep_opt ) = @_ ; my $help_to_guess_sep = "You can set the separator character with the $sep_opt option,\n" . "the complete listing of folders may help you to find it\n" . folders_list_to_help( $imap ) ; return( $help_to_guess_sep ) ; } sub help_to_guess_prefix { my( $imap, $prefix_opt ) = @_ ; my $help_to_guess_prefix = "You can set the prefix namespace with the $prefix_opt option,\n" . "the folowing listing of folders may help you to find it:\n" . folders_list_to_help( $imap ) ; return( $help_to_guess_prefix ) ; } sub folders_list_to_help { my( $imap ) = shift ; my @folders = $imap->folders ; my $listing = join q{}, map { "[$_]\n" } @folders ; return( $listing ) ; } # Globals are $sync @h1_folders_all @h2_folders_all $prefix1 $prefix2 sub private_folders_separators_and_prefixes { # what are the private folders separators and prefixes for each server ? ( $sync->{ debug } or $sync->{debugfolders} ) and myprint( "Getting separators\n" ) ; $sync->{ h1_sep } = get_separator( $sync->{imap1}, $sync->{ sep1 }, '--sep1', 'Host1', \@h1_folders_all ) ; $sync->{ h2_sep } = get_separator( $sync->{imap2}, $sync->{ sep2 }, '--sep2', 'Host2', \@h2_folders_all ) ; $sync->{ h1_prefix } = get_prefix( $sync->{imap1}, $prefix1, '--prefix1', 'Host1', \@h1_folders_all ) ; $sync->{ h2_prefix } = get_prefix( $sync->{imap2}, $prefix2, '--prefix2', 'Host2', \@h2_folders_all ) ; myprint( "Host1: separator and prefix: [$sync->{ h1_sep }][$sync->{ h1_prefix }]\n" ) ; myprint( "Host2: separator and prefix: [$sync->{ h2_sep }][$sync->{ h2_prefix }]\n" ) ; return ; } sub subfolder1 { my $mysync = shift ; my $subfolder1 = sanitize_subfolder( $mysync->{ subfolder1 } ) ; if ( $subfolder1 ) { # turns off automap myprint( "Turning off automapping folders because of --subfolder1\n" ) ; $mysync->{ automap } = undef ; myprint( "Sanitizing subfolder1: [$mysync->{ subfolder1 }] => [$subfolder1]\n" ) ; $mysync->{ subfolder1 } = $subfolder1 ; if ( ! add_subfolder1_to_folderrec( $mysync ) ) { $mysync->{nb_errors}++ ; exit_clean( $mysync, $EXIT_SUBFOLDER1_NO_EXISTS, "subfolder1 $subfolder1 does not exist\n" ) ; } } else { $mysync->{ subfolder1 } = undef ; } return ; } sub subfolder2 { my $mysync = shift ; my $subfolder2 = sanitize_subfolder( $mysync->{ subfolder2 } ) ; if ( $subfolder2 ) { # turns off automap myprint( "Turning off automapping folders because of --subfolder2\n" ) ; $mysync->{ automap } = undef ; myprint( "Sanitizing subfolder2: [$mysync->{ subfolder2 }] => [$subfolder2]\n" ) ; $mysync->{ subfolder2 } = $subfolder2 ; set_regextrans2_for_subfolder2( $mysync ) ; } else { $mysync->{ subfolder2 } = undef ; } return ; } sub tests_sanitize_subfolder { note( 'Entering tests_sanitize_subfolder()' ) ; is( undef, sanitize_subfolder( ), 'sanitize_subfolder: no args => undef' ) ; is( undef, sanitize_subfolder( q{} ), 'sanitize_subfolder: empty => undef' ) ; is( undef, sanitize_subfolder( ' ' ), 'sanitize_subfolder: blank => undef' ) ; is( undef, sanitize_subfolder( ' ' ), 'sanitize_subfolder: blanks => undef' ) ; is( 'abcd', sanitize_subfolder( 'abcd' ), 'sanitize_subfolder: abcd => abcd' ) ; is( 'ab cd', sanitize_subfolder( ' ab cd ' ), 'sanitize_subfolder: " ab cd " => "ab cd"' ) ; is( 'abcd', sanitize_subfolder( q{a&~b#\\c[]=d;} ), 'sanitize_subfolder: "a&~b#\\c[]=d;" => "abcd"' ) ; is( 'aA.b-_ 8c/dD', sanitize_subfolder( 'aA.b-_ 8c/dD' ), 'sanitize_subfolder: aA.b-_ 8c/dD => aA.b-_ 8c/dD' ) ; note( 'Leaving tests_sanitize_subfolder()' ) ; return ; } sub sanitize_subfolder { my $subfolder = shift ; if ( ! $subfolder ) { return ; } # Remove edging blanks $subfolder =~ s,^ +| +$,,g ; # Keep only abcd...ABCD...0123... and -_./ $subfolder =~ tr,-_a-zA-Z0-9./ ,,cd ; # A blank subfolder is not a subfolder if ( ! $subfolder ) { return ; } else { return $subfolder ; } } sub tests_sanitize_host { note( 'Entering tests_sanitize_host()' ) ; is( undef, sanitize_host( ), 'sanitize_host: no args => undef' ) ; is( '', sanitize_host( '' ), 'sanitize_host: empty => empty' ) ; is( 'imap.example.org', sanitize_host( 'imap.example.org' ), 'sanitize_host: imap.example.org => imap.example.org' ) ; is( 'imap.example.org', sanitize_host( ' imap.example.org' ), 'sanitize_host: imap.example.org 1 => imap.example.org' ) ; is( 'imap.example.org', sanitize_host( 'imap.example.org ' ), 'sanitize_host: imap.example.org 2 => imap.example.org' ) ; is( 'imap.example.org', sanitize_host( 'imap.exam ple.org' ), 'sanitize_host: imap.example.org 3 => imap.example.org' ) ; is( 'imap.example.org', sanitize_host( ' imap.exam ple.org ' ), 'sanitize_host: imap.example.org 4 => imap.example.org' ) ; is( 'imap.example.org', sanitize_host( 'imap.exa/mple.org/' ), 'sanitize_host: imap.example.org/ => imap.example.org' ) ; note( 'Leaving tests_sanitize_host()' ) ; return ; } sub sanitize_host { my $host = shift ; if ( ! defined $host ) { return ; } $host =~ tr{ /}{}d ; return $host ; } sub tests_add_subfolder1_to_folderrec { note( 'Entering tests_add_subfolder1_to_folderrec()' ) ; is( undef, add_subfolder1_to_folderrec( ), 'add_subfolder1_to_folderrec: undef => undef' ) ; is_deeply( [], [ add_subfolder1_to_folderrec( ) ], 'add_subfolder1_to_folderrec: no args => empty array' ) ; @folderrec = () ; my $mysync = {} ; is_deeply( [ ], [ add_subfolder1_to_folderrec( $mysync ) ], 'add_subfolder1_to_folderrec: empty => empty array' ) ; is_deeply( [ ], [ @folderrec ], 'add_subfolder1_to_folderrec: empty => empty folderrec' ) ; $mysync->{ subfolder1 } = 'SUBI' ; $h1_folders_all{ 'SUBI' } = 1 ; $mysync->{ h1_prefix } = 'INBOX/' ; is_deeply( [ 'SUBI' ], [ add_subfolder1_to_folderrec( $mysync ) ], 'add_subfolder1_to_folderrec: SUBI => SUBI' ) ; is_deeply( [ 'SUBI' ], [ @folderrec ], 'add_subfolder1_to_folderrec: SUBI => folderrec SUBI ' ) ; @folderrec = () ; $mysync->{ subfolder1 } = 'SUBO' ; is_deeply( [ ], [ add_subfolder1_to_folderrec( $mysync ) ], 'add_subfolder1_to_folderrec: SUBO no exists => empty array' ) ; is_deeply( [ ], [ @folderrec ], 'add_subfolder1_to_folderrec: SUBO no exists => empty folderrec' ) ; $h1_folders_all{ 'INBOX/SUBO' } = 1 ; is_deeply( [ 'INBOX/SUBO' ], [ add_subfolder1_to_folderrec( $mysync ) ], 'add_subfolder1_to_folderrec: SUBO + INBOX/SUBO exists => INBOX/SUBO' ) ; is_deeply( [ 'INBOX/SUBO' ], [ @folderrec ], 'add_subfolder1_to_folderrec: SUBO + INBOX/SUBO exists => INBOX/SUBO folderrec' ) ; note( 'Leaving tests_add_subfolder1_to_folderrec()' ) ; return ; } sub add_subfolder1_to_folderrec { my $mysync = shift ; if ( ! $mysync || ! $mysync->{ subfolder1 } ) { return ; } my $subfolder1 = $mysync->{ subfolder1 } ; my $subfolder1_extended = $mysync->{ h1_prefix } . $subfolder1 ; if ( exists $h1_folders_all{ $subfolder1 } ) { myprint( qq{Acting like --folderrec "$subfolder1"\n} ) ; push @folderrec, $subfolder1 ; } elsif ( exists $h1_folders_all{ $subfolder1_extended } ) { myprint( qq{Acting like --folderrec "$subfolder1_extended"\n} ) ; push @folderrec, $subfolder1_extended ; } else { myprint( qq{Nor folder "$subfolder1" nor "$subfolder1_extended" exists on host1\n} ) ; } return @folderrec ; } sub set_regextrans2_for_subfolder2 { my $mysync = shift ; unshift @{ $mysync->{ regextrans2 } }, q(s,^$mysync->{ h2_prefix }(.*),$mysync->{ h2_prefix }$mysync->{ subfolder2 }$mysync->{ h2_sep }$1,), q(s,^INBOX$,$mysync->{ h2_prefix }$mysync->{ subfolder2 }$mysync->{ h2_sep }INBOX,), q(s,^($mysync->{ h2_prefix }){2},$mysync->{ h2_prefix },); #myprint( "@{ $mysync->{ regextrans2 } }\n" ) ; return ; } # Looks like no globals here sub tests_imap2_folder_name { note( 'Entering tests_imap2_folder_name()' ) ; my $mysync = {} ; $mysync->{ h1_prefix } = q{} ; $mysync->{ h2_prefix } = q{} ; $mysync->{ h1_sep } = '/'; $mysync->{ h2_sep } = '.'; $mysync->{ debug } and myprint( <<"EOS" prefix1: [$mysync->{ h1_prefix }] prefix2: [$mysync->{ h2_prefix }] sep1: [$sync->{ h1_sep }] sep2: [$sync->{ h2_sep }] EOS ) ; $mysync->{ fixslash2 } = 0 ; is( q{INBOX}, imap2_folder_name( $mysync, q{} ), 'imap2_folder_name: empty string' ) ; is( 'blabla', imap2_folder_name( $mysync, 'blabla' ), 'imap2_folder_name: blabla' ) ; is('spam.spam', imap2_folder_name( $mysync, 'spam/spam' ), 'imap2_folder_name: spam/spam' ) ; is( 'spam/spam', imap2_folder_name( $mysync, 'spam.spam' ), 'imap2_folder_name: spam.spam') ; is( 'spam.spam/spam', imap2_folder_name( $mysync, 'spam/spam.spam' ), 'imap2_folder_name: spam/spam.spam' ) ; is( 's pam.spam/sp am', imap2_folder_name( $mysync, 's pam/spam.sp am' ), 'imap2_folder_name: s pam/spam.sp am' ) ; $mysync->{f1f2h}{ 'auto' } = 'moto' ; is( 'moto', imap2_folder_name( $mysync, 'auto' ), 'imap2_folder_name: auto' ) ; $mysync->{f1f2h}{ 'auto/auto' } = 'moto x 2' ; is( 'moto x 2', imap2_folder_name( $mysync, 'auto/auto' ), 'imap2_folder_name: auto/auto' ) ; @{ $mysync->{ regextrans2 } } = ( 's,/,X,g' ) ; is( q{INBOX}, imap2_folder_name( $mysync, q{} ), 'imap2_folder_name: empty string [s,/,X,g]' ) ; is( 'blabla', imap2_folder_name( $mysync, 'blabla' ), 'imap2_folder_name: blabla [s,/,X,g]' ) ; is('spam.spam', imap2_folder_name( $mysync, 'spam/spam'), 'imap2_folder_name: spam/spam [s,/,X,g]'); is('spamXspam', imap2_folder_name( $mysync, 'spam.spam'), 'imap2_folder_name: spam.spam [s,/,X,g]'); is('spam.spamXspam', imap2_folder_name( $mysync, 'spam/spam.spam'), 'imap2_folder_name: spam/spam.spam [s,/,X,g]'); @{ $mysync->{ regextrans2 } } = ( 's, ,_,g' ) ; is('blabla', imap2_folder_name( $mysync, 'blabla'), 'imap2_folder_name: blabla [s, ,_,g]'); is('bla_bla', imap2_folder_name( $mysync, 'bla bla'), 'imap2_folder_name: blabla [s, ,_,g]'); @{ $mysync->{ regextrans2 } } = ( q{s,(.*),\U$1,} ) ; is( 'BLABLA', imap2_folder_name( $mysync, 'blabla' ), q{imap2_folder_name: blabla [s,\U(.*)\E,$1,]} ) ; $mysync->{ fixslash2 } = 1 ; @{ $mysync->{ regextrans2 } } = ( ) ; is(q{INBOX}, imap2_folder_name( $mysync, q{}), 'imap2_folder_name: empty string'); is('blabla', imap2_folder_name( $mysync, 'blabla'), 'imap2_folder_name: blabla'); is('spam.spam', imap2_folder_name( $mysync, 'spam/spam'), 'imap2_folder_name: spam/spam -> spam.spam'); is('spam_spam', imap2_folder_name( $mysync, 'spam.spam'), 'imap2_folder_name: spam.spam -> spam_spam'); is('spam.spam_spam', imap2_folder_name( $mysync, 'spam/spam.spam'), 'imap2_folder_name: spam/spam.spam -> spam.spam_spam'); is('s pam.spam_spa m', imap2_folder_name( $mysync, 's pam/spam.spa m'), 'imap2_folder_name: s pam/spam.spa m -> s pam.spam_spa m'); $mysync->{ h1_sep } = '.'; $mysync->{ h2_sep } = '/'; is( q{INBOX}, imap2_folder_name( $mysync, q{}), 'imap2_folder_name: empty string'); is('blabla', imap2_folder_name( $mysync, 'blabla'), 'imap2_folder_name: blabla'); is('spam.spam', imap2_folder_name( $mysync, 'spam/spam'), 'imap2_folder_name: spam/spam -> spam.spam'); is('spam/spam', imap2_folder_name( $mysync, 'spam.spam'), 'imap2_folder_name: spam.spam -> spam/spam'); is('spam.spam/spam', imap2_folder_name( $mysync, 'spam/spam.spam'), 'imap2_folder_name: spam/spam.spam -> spam.spam/spam'); $mysync->{ fixslash2 } = 0 ; $mysync->{ h1_prefix } = q{ }; is( 'spam.spam/spam', imap2_folder_name( $mysync, 'spam/spam.spam' ), 'imap2_folder_name: spam/spam.spam -> spam.spam/spam' ) ; is( 'spam.spam/spam', imap2_folder_name( $mysync, ' spam/spam.spam' ), 'imap2_folder_name: spam/spam.spam -> spam.spam/spam' ) ; $mysync->{ h1_sep } = '.' ; $mysync->{ h2_sep } = '/' ; $mysync->{ h1_prefix } = 'INBOX.' ; $mysync->{ h2_prefix } = q{} ; @{ $mysync->{ regextrans2 } } = ( q{s,(.*),\U$1,} ) ; is( 'BLABLA', imap2_folder_name( $mysync, 'blabla' ), 'imap2_folder_name: blabla' ) ; is( 'TEST/TEST/TEST/TEST', imap2_folder_name( $mysync, 'INBOX.TEST.test.Test.tesT' ), 'imap2_folder_name: INBOX.TEST.test.Test.tesT' ) ; @{ $mysync->{ regextrans2 } } = ( q{s,(.*),\L$1,} ) ; is( 'test/test/test/test', imap2_folder_name( $mysync, 'INBOX.TEST.test.Test.tesT' ), 'imap2_folder_name: INBOX.TEST.test.Test.tesT' ) ; # INBOX $mysync = {} ; $mysync->{ h1_prefix } = q{Pf1.} ; $mysync->{ h2_prefix } = q{Pf2/} ; $mysync->{ h1_sep } = '.'; $mysync->{ h2_sep } = '/'; # #$mysync->{ debug } = 1 ; is( 'Pf2/F1/F2/F3', imap2_folder_name( $mysync, 'F1.F2.F3' ), 'imap2_folder_name: F1.F2.F3 -> Pf2/F1/F2/F3' ) ; is( 'Pf2/F1/INBOX', imap2_folder_name( $mysync, 'F1.INBOX' ), 'imap2_folder_name: F1.INBOX -> Pf2/F1/INBOX' ) ; is( 'INBOX', imap2_folder_name( $mysync, 'INBOX' ), 'imap2_folder_name: INBOX -> INBOX' ) ; is( 'Pf2/F1/F2/F3', imap2_folder_name( $mysync, 'Pf1.F1.F2.F3' ), 'imap2_folder_name: Pf1.F1.F2.F3 -> Pf2/F1/F2/F3' ) ; is( 'Pf2/F1/INBOX', imap2_folder_name( $mysync, 'Pf1.F1.INBOX' ), 'imap2_folder_name: Pf1.F1.INBOX -> Pf2/F1/INBOX' ) ; is( 'INBOX', imap2_folder_name( $mysync, 'Pf1.INBOX' ), 'imap2_folder_name: Pf1.INBOX -> INBOX' ) ; # not Pf2/INBOX: Yes I can! # subfolder2 $mysync = {} ; $mysync->{ h1_prefix } = q{} ; $mysync->{ h2_prefix } = q{} ; $mysync->{ h1_sep } = '/'; $mysync->{ h2_sep } = '.'; set_regextrans2_for_subfolder2( $mysync ) ; $mysync->{ subfolder2 } = 'S1.S2' ; is( 'S1.S2.F1.F2.F3', imap2_folder_name( $mysync, 'F1/F2/F3' ), 'imap2_folder_name: F1/F2/F3 -> S1.S2.F1.F2.F3' ) ; is( 'S1.S2.INBOX', imap2_folder_name( $mysync, 'INBOX' ), 'imap2_folder_name: F1/F2/F3 -> S1.S2.INBOX' ) ; $mysync = {} ; $mysync->{ h1_prefix } = q{Pf1/} ; $mysync->{ h2_prefix } = q{Pf2.} ; $mysync->{ h1_sep } = '/'; $mysync->{ h2_sep } = '.'; #$mysync->{ debug } = 1 ; set_regextrans2_for_subfolder2( $mysync ) ; $mysync->{ subfolder2 } = 'Pf2.S1.S2' ; is( 'Pf2.S1.S2.F1.F2.F3', imap2_folder_name( $mysync, 'F1/F2/F3' ), 'imap2_folder_name: F1/F2/F3 -> Pf2.S1.S2.F1.F2.F3' ) ; is( 'Pf2.S1.S2.INBOX', imap2_folder_name( $mysync, 'INBOX' ), 'imap2_folder_name: INBOX -> Pf2.S1.S2.INBOX' ) ; is( 'Pf2.S1.S2.F1.F2.F3', imap2_folder_name( $mysync, 'Pf1/F1/F2/F3' ), 'imap2_folder_name: F1/F2/F3 -> Pf2.S1.S2.F1.F2.F3' ) ; is( 'Pf2.S1.S2.INBOX', imap2_folder_name( $mysync, 'Pf1/INBOX' ), 'imap2_folder_name: INBOX -> Pf2.S1.S2.INBOX' ) ; # subfolder1 # scenario as the reverse of the previous tests, separators point of vue $mysync = {} ; $mysync->{ h1_prefix } = q{Pf1.} ; $mysync->{ h2_prefix } = q{Pf2/} ; $mysync->{ h1_sep } = '.'; $mysync->{ h2_sep } = '/'; #$mysync->{ debug } = 1 ; $mysync->{ subfolder1 } = 'S1.S2' ; is( 'Pf2/F1/F2/F3', imap2_folder_name( $mysync, 'S1.S2.F1.F2.F3' ), 'imap2_folder_name: S1.S2.F1.F2.F3 -> Pf2/F1/F2/F3' ) ; is( 'Pf2/F1/F2/F3', imap2_folder_name( $mysync, 'Pf1.S1.S2.F1.F2.F3' ), 'imap2_folder_name: Pf1.S1.S2.F1.F2.F3 -> Pf2/F1/F2/F3' ) ; is( 'INBOX', imap2_folder_name( $mysync, 'S1.S2.INBOX' ), 'imap2_folder_name: S1.S2.INBOX -> INBOX' ) ; is( 'INBOX', imap2_folder_name( $mysync, 'S1.S2' ), 'imap2_folder_name: S1.S2 -> INBOX' ) ; is( 'INBOX', imap2_folder_name( $mysync, 'S1.S2.' ), 'imap2_folder_name: S1.S2. -> INBOX' ) ; is( 'INBOX', imap2_folder_name( $mysync, 'Pf1.S1.S2.INBOX' ), 'imap2_folder_name: Pf1.S1.S2.INBOX -> INBOX' ) ; is( 'INBOX', imap2_folder_name( $mysync, 'Pf1.S1.S2' ), 'imap2_folder_name: Pf1.S1.S2 -> INBOX' ) ; is( 'INBOX', imap2_folder_name( $mysync, 'Pf1.S1.S2.' ), 'imap2_folder_name: Pf1.S1.S2. -> INBOX' ) ; $mysync->{ subfolder1 } = 'S1.S2.' ; is( 'Pf2/F1/F2/F3', imap2_folder_name( $mysync, 'S1.S2.F1.F2.F3' ), 'imap2_folder_name: S1.S2.F1.F2.F3 -> Pf2/F1/F2/F3' ) ; is( 'Pf2/F1/F2/F3', imap2_folder_name( $mysync, 'Pf1.S1.S2.F1.F2.F3' ), 'imap2_folder_name: Pf1.S1.S2.F1.F2.F3 -> Pf2/F1/F2/F3' ) ; is( 'INBOX', imap2_folder_name( $mysync, 'S1.S2.INBOX' ), 'imap2_folder_name: S1.S2.INBOX -> INBOX' ) ; is( 'INBOX', imap2_folder_name( $mysync, 'S1.S2' ), 'imap2_folder_name: S1.S2 -> INBOX' ) ; is( 'INBOX', imap2_folder_name( $mysync, 'S1.S2.' ), 'imap2_folder_name: S1.S2. -> INBOX' ) ; is( 'INBOX', imap2_folder_name( $mysync, 'Pf1.S1.S2.INBOX' ), 'imap2_folder_name: Pf1.S1.S2.INBOX -> INBOX' ) ; is( 'INBOX', imap2_folder_name( $mysync, 'Pf1.S1.S2' ), 'imap2_folder_name: Pf1.S1.S2 -> INBOX' ) ; is( 'INBOX', imap2_folder_name( $mysync, 'Pf1.S1.S2.' ), 'imap2_folder_name: Pf1.S1.S2. -> INBOX' ) ; # subfolder1 # scenario as Gmail $mysync = {} ; $mysync->{ h1_prefix } = q{} ; $mysync->{ h2_prefix } = q{} ; $mysync->{ h1_sep } = '/'; $mysync->{ h2_sep } = '/'; #$mysync->{ debug } = 1 ; $mysync->{ subfolder1 } = 'S1/S2' ; is( 'F1/F2/F3', imap2_folder_name( $mysync, 'S1/S2/F1/F2/F3' ), 'imap2_folder_name: S1/S2/F1/F2/F3 -> F1/F2/F3' ) ; is( 'INBOX', imap2_folder_name( $mysync, 'S1/S2/INBOX' ), 'imap2_folder_name: S1/S2/INBOX -> INBOX' ) ; is( 'INBOX', imap2_folder_name( $mysync, 'S1/S2' ), 'imap2_folder_name: S1/S2 -> INBOX' ) ; is( 'INBOX', imap2_folder_name( $mysync, 'S1/S2/' ), 'imap2_folder_name: S1/S2/ -> INBOX' ) ; $mysync->{ subfolder1 } = 'S1/S2/' ; is( 'F1/F2/F3', imap2_folder_name( $mysync, 'S1/S2/F1/F2/F3' ), 'imap2_folder_name: S1/S2/F1/F2/F3 -> F1/F2/F3' ) ; is( 'INBOX', imap2_folder_name( $mysync, 'S1/S2/INBOX' ), 'imap2_folder_name: S1/S2/INBOX -> INBOX' ) ; is( 'INBOX', imap2_folder_name( $mysync, 'S1/S2' ), 'imap2_folder_name: S1/S2 -> INBOX' ) ; is( 'INBOX', imap2_folder_name( $mysync, 'S1/S2/' ), 'imap2_folder_name: S1/S2/ -> INBOX' ) ; note( 'Leaving tests_imap2_folder_name()' ) ; return ; } # Global variables to remove: # None? sub imap2_folder_name { my $mysync = shift ; my ( $h1_fold ) = shift ; my ( $h2_fold ) ; if ( $mysync->{f1f2h}{ $h1_fold } ) { $h2_fold = $mysync->{f1f2h}{ $h1_fold } ; ( $mysync->{ debug } or $mysync->{debugfolders} ) and myprint( "f1f2 [$h1_fold] -> [$h2_fold]\n" ) ; return( $h2_fold ) ; } if ( $mysync->{f1f2auto}{ $h1_fold } ) { $h2_fold = $mysync->{f1f2auto}{ $h1_fold } ; ( $mysync->{ debug } or $mysync->{debugfolders} ) and myprint( "automap [$h1_fold] -> [$h2_fold]\n" ) ; return( $h2_fold ) ; } if ( $mysync->{ subfolder1 } ) { my $esc_h1_sep = "\\" . $mysync->{ h1_sep } ; # case where subfolder1 has the sep1 at the end, then remove it my $part_to_removed = remove_last_char_if_is( $mysync->{ subfolder1 }, $mysync->{ h1_sep } ) ; # remove the subfolder1 part and the sep1 if present after $h1_fold =~ s{$part_to_removed($esc_h1_sep)?}{} ; #myprint( "h1_fold=$h1_fold\n" ) ; } if ( ( q{} eq $h1_fold ) or ( $mysync->{ h1_prefix } eq $h1_fold ) ) { $h1_fold = 'INBOX' ; } $h2_fold = prefix_seperator_invertion( $mysync, $h1_fold ) ; $h2_fold = regextrans2( $mysync, $h2_fold ) ; return( $h2_fold ) ; } sub tests_remove_last_char_if_is { note( 'Entering tests_remove_last_char_if_is()' ) ; is( undef, remove_last_char_if_is( ), 'remove_last_char_if_is: no args => undef' ) ; is( q{}, remove_last_char_if_is( q{} ), 'remove_last_char_if_is: empty => empty' ) ; is( q{}, remove_last_char_if_is( q{}, 'Z' ), 'remove_last_char_if_is: empty Z => empty' ) ; is( q{}, remove_last_char_if_is( 'Z', 'Z' ), 'remove_last_char_if_is: Z Z => empty' ) ; is( 'abc', remove_last_char_if_is( 'abcZ', 'Z' ), 'remove_last_char_if_is: abcZ Z => abc' ) ; is( 'abcY', remove_last_char_if_is( 'abcY', 'Z' ), 'remove_last_char_if_is: abcY Z => abcY' ) ; note( 'Leaving tests_remove_last_char_if_is()' ) ; return ; } sub remove_last_char_if_is { my $string = shift ; my $char = shift ; if ( ! defined $string ) { return ; } if ( ! defined $char ) { return $string ; } my $last_char = substr $string, -1 ; if ( $char eq $last_char ) { chop $string ; return $string ; } else { return $string ; } } sub tests_prefix_seperator_invertion { note( 'Entering tests_prefix_seperator_invertion()' ) ; is( undef, prefix_seperator_invertion( ), 'prefix_seperator_invertion: no args => undef' ) ; is( q{}, prefix_seperator_invertion( undef, q{} ), 'prefix_seperator_invertion: empty string => empty string' ) ; is( 'lalala', prefix_seperator_invertion( undef, 'lalala' ), 'prefix_seperator_invertion: lalala => lalala' ) ; is( 'lal/ala', prefix_seperator_invertion( undef, 'lal/ala' ), 'prefix_seperator_invertion: lal/ala => lal/ala' ) ; is( 'lal.ala', prefix_seperator_invertion( undef, 'lal.ala' ), 'prefix_seperator_invertion: lal.ala => lal.ala' ) ; is( '////', prefix_seperator_invertion( undef, '////' ), 'prefix_seperator_invertion: //// => ////' ) ; is( '.....', prefix_seperator_invertion( undef, '.....' ), 'prefix_seperator_invertion: ..... => .....' ) ; my $mysync = { h1_prefix => q{}, h2_prefix => q{}, h1_sep => '/', h2_sep => '/', } ; is( q{}, prefix_seperator_invertion( $mysync, q{} ), 'prefix_seperator_invertion: $mysync empty string => empty string' ) ; is( 'lalala', prefix_seperator_invertion( $mysync, 'lalala' ), 'prefix_seperator_invertion: $mysync lalala => lalala' ) ; is( 'lal/ala', prefix_seperator_invertion( $mysync, 'lal/ala' ), 'prefix_seperator_invertion: $mysync lal/ala => lal/ala' ) ; is( 'lal.ala', prefix_seperator_invertion( $mysync, 'lal.ala' ), 'prefix_seperator_invertion: $mysync lal.ala => lal.ala' ) ; is( '////', prefix_seperator_invertion( $mysync, '////' ), 'prefix_seperator_invertion: $mysync //// => ////' ) ; is( '.....', prefix_seperator_invertion( $mysync, '.....' ), 'prefix_seperator_invertion: $mysync ..... => .....' ) ; $mysync = { h1_prefix => 'PPP', h2_prefix => 'QQQ', h1_sep => 's', h2_sep => 't', } ; is( q{QQQ}, prefix_seperator_invertion( $mysync, q{} ), 'prefix_seperator_invertion: PPPQQQst empty string => QQQ' ) ; is( 'QQQlalala', prefix_seperator_invertion( $mysync, 'lalala' ), 'prefix_seperator_invertion: PPPQQQst lalala => QQQlalala' ) ; is( 'QQQlal/ala', prefix_seperator_invertion( $mysync, 'lal/ala' ), 'prefix_seperator_invertion: PPPQQQst lal/ala => QQQlal/ala' ) ; is( 'QQQlal.ala', prefix_seperator_invertion( $mysync, 'lal.ala' ), 'prefix_seperator_invertion: PPPQQQst lal.ala => QQQlal.ala' ) ; is( 'QQQ////', prefix_seperator_invertion( $mysync, '////' ), 'prefix_seperator_invertion: PPPQQQst //// => QQQ////' ) ; is( 'QQQ.....', prefix_seperator_invertion( $mysync, '.....' ), 'prefix_seperator_invertion: PPPQQQst ..... => QQQ.....' ) ; is( 'QQQPlalala', prefix_seperator_invertion( $mysync, 'PPPPlalala' ), 'prefix_seperator_invertion: PPPQQQst PPPPlalala => QQQPlalala' ) ; is( 'QQQ', prefix_seperator_invertion( $mysync, 'PPP' ), 'prefix_seperator_invertion: PPPQQQst PPP => QQQ' ) ; is( 'QQQttt', prefix_seperator_invertion( $mysync, 'sss' ), 'prefix_seperator_invertion: PPPQQQst sss => QQQttt' ) ; is( 'QQQt', prefix_seperator_invertion( $mysync, 's' ), 'prefix_seperator_invertion: PPPQQQst s => QQQt' ) ; is( 'QQQtAAAtBBB', prefix_seperator_invertion( $mysync, 'PPPsAAAsBBB' ), 'prefix_seperator_invertion: PPPQQQst PPPsAAAsBBB => QQQtAAAtBBB' ) ; note( 'Leaving tests_prefix_seperator_invertion()' ) ; return ; } # Global variables to remove: sub prefix_seperator_invertion { my $mysync = shift ; my $h1_fold = shift ; my $h2_fold ; if ( not defined $h1_fold ) { return ; } my $my_h1_prefix = $mysync->{ h1_prefix } || q{} ; my $my_h2_prefix = $mysync->{ h2_prefix } || q{} ; my $my_h1_sep = $mysync->{ h1_sep } || '/' ; my $my_h2_sep = $mysync->{ h2_sep } || '/' ; # first we remove the prefix $h1_fold =~ s/^\Q$my_h1_prefix\E//x ; ( $mysync->{ debug } or $mysync->{debugfolders} ) and myprint( "removed host1 prefix: [$h1_fold]\n" ) ; $h2_fold = separator_invert( $mysync, $h1_fold, $my_h1_sep, $my_h2_sep ) ; ( $mysync->{ debug } or $mysync->{debugfolders} ) and myprint( "inverted separators: [$h2_fold]\n" ) ; # Adding the prefix supplied by namespace or the --prefix2 option # except for INBOX or Inbox if ( $h2_fold !~ m/^INBOX$/xi ) { $h2_fold = $my_h2_prefix . $h2_fold ; } ( $mysync->{ debug } or $mysync->{debugfolders} ) and myprint( "added host2 prefix: [$h2_fold]\n" ) ; return( $h2_fold ) ; } sub tests_separator_invert { note( 'Entering tests_separator_invert()' ) ; my $mysync = {} ; $mysync->{ fixslash2 } = 0 ; ok( not( defined separator_invert( ) ), 'separator_invert: no args' ) ; ok( not( defined separator_invert( q{} ) ), 'separator_invert: not enough args' ) ; ok( not( defined separator_invert( q{}, q{} ) ), 'separator_invert: not enough args' ) ; ok( q{} eq separator_invert( $mysync, q{}, q{}, q{} ), 'separator_invert: 3 empty strings' ) ; ok( 'lalala' eq separator_invert( $mysync, 'lalala', q{}, q{} ), 'separator_invert: empty separator' ) ; ok( 'lalala' eq separator_invert( $mysync, 'lalala', '/', '/' ), 'separator_invert: same separator /' ) ; ok( 'lal/ala' eq separator_invert( $mysync, 'lal/ala', '/', '/' ), 'separator_invert: same separator / 2' ) ; ok( 'lal.ala' eq separator_invert( $mysync, 'lal/ala', '/', '.' ), 'separator_invert: separators /.' ) ; ok( 'lal/ala' eq separator_invert( $mysync, 'lal.ala', '.', '/' ), 'separator_invert: separators ./' ) ; ok( 'la.l/ala' eq separator_invert( $mysync, 'la/l.ala', '.', '/' ), 'separator_invert: separators ./' ) ; ok( 'l/al.ala' eq separator_invert( $mysync, 'l.al/ala', '/', '.' ), 'separator_invert: separators /.' ) ; $mysync->{ fixslash2 } = 1 ; ok( 'l_al.ala' eq separator_invert( $mysync, 'l.al/ala', '/', '.' ), 'separator_invert: separators /.' ) ; note( 'Leaving tests_separator_invert()' ) ; return ; } # Global variables to remove: # sub separator_invert { my( $mysync, $h1_fold, $h1_separator, $h2_separator ) = @_ ; return( undef ) if ( not all_defined( $mysync, $h1_fold, $h1_separator, $h2_separator ) ) ; # The separator we hope we'll never encounter: 00000000 == 0x00 my $o_sep = "\000" ; my $h2_fold = $h1_fold ; $h2_fold =~ s,\Q$h2_separator,$o_sep,xg ; $h2_fold =~ s,\Q$h1_separator,$h2_separator,xg ; $h2_fold =~ s,\Q$o_sep,$h1_separator,xg ; $h2_fold =~ s,/,_,xg if( $mysync->{ fixslash2 } and '/' ne $h2_separator and '/' eq $h1_separator ) ; return( $h2_fold ) ; } sub regextrans2 { my( $mysync, $h2_fold ) = @_ ; # Transforming the folder name by the --regextrans2 option(s) foreach my $regextrans2 ( @{ $mysync->{ regextrans2 } } ) { my $h2_fold_before = $h2_fold ; my $ret = eval "\$h2_fold =~ $regextrans2 ; 1 " ; ( $mysync->{ debug } or $mysync->{debugfolders} ) and myprint( "[$h2_fold_before] -> [$h2_fold] using regextrans2 [$regextrans2]\n" ) ; if ( not ( defined $ret ) or $EVAL_ERROR ) { $mysync->{nb_errors}++ ; exit_clean( $mysync, $EX_USAGE, "error: eval regextrans2 '$regextrans2': $EVAL_ERROR\n" ) ; } } return( $h2_fold ) ; } sub tests_decompose_regex { note( 'Entering tests_decompose_regex()' ) ; ok( 1, 'decompose_regex 1' ) ; ok( 0 == compare_lists( [ q{}, q{} ], [ decompose_regex( q{} ) ] ), 'decompose_regex empty string' ) ; ok( 0 == compare_lists( [ '.*', 'lala' ], [ decompose_regex( 's/.*/lala/' ) ] ), 'decompose_regex s/.*/lala/' ) ; note( 'Leaving tests_decompose_regex()' ) ; return ; } sub decompose_regex { my $regex = shift ; my( $left_part, $right_part ) ; ( $left_part, $right_part ) = $regex =~ m{^s/((?:[^/]|\\/)+)/((?:[^/]|\\/)+)/}x; return( q{}, q{} ) if not $left_part ; return( $left_part, $right_part ) ; } sub tests_timenext { note( 'Entering tests_timenext()' ) ; is( undef, timenext( ), 'timenext: no args => undef' ) ; my $mysync ; is( undef, timenext( $mysync ), 'timenext: undef => undef' ) ; $mysync = {} ; ok( time - timenext( $mysync ) <= 1e-02, 'timenext: defined first time => ~ time' ) ; ok( timenext( $mysync ) <= 1e-02, 'timenext: second time => less than 1e-02' ) ; ok( timenext( $mysync ) <= 1e-02, 'timenext: third time => less than 1e-02' ) ; note( 'Leaving tests_timenext()' ) ; return ; } sub timenext { my $mysync = shift ; if ( ! defined $mysync ) { return ; } my ( $timenow, $timediff ) ; $mysync->{ timebefore } ||= 0; # epoch... $timenow = time ; $timediff = $timenow - $mysync->{ timebefore } ; $mysync->{ timebefore } = $timenow ; # myprint( "timenext: $timediff\n" ) ; return( $timediff ) ; } sub tests_timesince { note( 'Entering tests_timesince()' ) ; ok( timesince( time - 1 ) - 1 <= 1e-02, 'timesince: time - 1 => <= 1 + 1e-02' ) ; ok( timesince( time ) <= 1e-02, 'timesince: time => <= 1e-02' ) ; ok( timesince( ) - time <= 1e-02, 'timesince: no args => <= time + 1e-02' ) ; note( 'Leaving tests_timesince()' ) ; return ; } sub timesince { my $timeinit = shift || 0 ; my ( $timenow, $timediff ) ; $timenow = time ; $timediff = $timenow - $timeinit ; # Often used in a division so no 0 but a nano seconde. return( max( $timediff, min( 1e-09, $timediff ) ) ) ; } sub tests_regexflags { note( 'Entering tests_regexflags()' ) ; my $mysync = {} ; ok( q{} eq regexflags( $mysync, q{} ), 'regexflags, null string q{}' ) ; ok( q{\Seen NonJunk $Spam} eq regexflags( $mysync, q{\Seen NonJunk $Spam} ), q{regexflags, nothing to do} ) ; @{ $mysync->{ regexflag } } = ('I am BAD' ) ; ok( not ( defined regexflags( $mysync, q{} ) ), 'regexflags, bad regex' ) ; @{ $mysync->{ regexflag } } = ( 's/NonJunk//g' ) ; ok( q{\Seen $Spam} eq regexflags( $mysync, q{\Seen NonJunk $Spam} ), q{regexflags, remove NonJunk: 's/NonJunk//g'} ) ; @{ $mysync->{ regexflag } } = ( q{s/\$Spam//g} ) ; ok( q{\Seen NonJunk } eq regexflags( $mysync, q{\Seen NonJunk $Spam} ), q{regexflags, remove $Spam: 's/\$Spam//g'} ) ; @{ $mysync->{ regexflag } } = ( 's/\\\\Seen//g' ) ; ok( q{ NonJunk $Spam} eq regexflags( $mysync, q{\Seen NonJunk $Spam} ), q{regexflags, remove \Seen: 's/\\\\\\\\Seen//g'} ) ; @{ $mysync->{ regexflag } } = ( 's/(\s|^)[^\\\\]\w+//g' ) ; ok( q{\Seen \Middle \End} eq regexflags( $mysync, q{\Seen NonJunk \Middle $Spam \End} ), q{regexflags: only \word among \Seen NonJunk \Middle $Spam \End} ) ; ok( q{ \Seen \Middle \End1} eq regexflags( $mysync, q{Begin \Seen NonJunk \Middle $Spam \End1 End} ), q{regexflags: only \word among Begin \Seen NonJunk \Middle $Spam \End1 End} ) ; @{ $mysync->{ regexflag } } = ( q{s/.*?(Keep1|Keep2|Keep3)/$1 /g} ) ; ok( 'Keep1 Keep2 ReB' eq regexflags( $mysync, 'ReA Keep1 REM Keep2 ReB' ), 'Keep only regex' ) ; ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'REM REM Keep1 Keep2' ), 'Keep only regex' ) ; ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'Keep1 REM REM Keep2' ), 'Keep only regex' ) ; ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'REM Keep1 REM REM Keep2' ), 'Keep only regex' ) ; ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'Keep1 Keep2' ), 'Keep only regex' ) ; ok( 'Keep1 ' eq regexflags( $mysync, 'REM Keep1' ), 'Keep only regex' ) ; @{ $mysync->{ regexflag } } = ( q{s/(Keep1|Keep2|Keep3) (?!(Keep1|Keep2|Keep3)).*/$1 /g} ) ; ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'Keep1 Keep2 ReB' ), 'Keep only regex' ) ; ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'Keep1 Keep2 REM REM REM' ), 'Keep only regex' ) ; ok( 'Keep2 ' eq regexflags( $mysync, 'Keep2 REM REM REM' ), 'Keep only regex' ) ; @{ $mysync->{ regexflag } } = ( q{s/.*?(Keep1|Keep2|Keep3)/$1 /g}, 's/(Keep1|Keep2|Keep3) (?!(Keep1|Keep2|Keep3)).*/$1 /g' ) ; ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'REM Keep1 REM Keep2 REM' ), 'Keep only regex' ) ; ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'Keep1 REM Keep2 REM' ), 'Keep only regex' ) ; ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'REM Keep1 Keep2 REM' ), 'Keep only regex' ) ; ok( 'Keep1 Keep2 ' eq regexflags( $mysync, 'REM Keep1 REM Keep2' ), 'Keep only regex' ) ; ok( 'Keep1 Keep2 Keep3 ' eq regexflags( $mysync, 'REM Keep1 REM Keep2 REM REM Keep3 REM' ), 'Keep only regex' ) ; ok( 'Keep1 ' eq regexflags( $mysync, 'REM REM Keep1 REM REM REM ' ), 'Keep only regex' ) ; ok( 'Keep1 Keep3 ' eq regexflags( $mysync, 'RE1 Keep1 RE2 Keep3 RE3 RE4 RE5 ' ), 'Keep only regex' ) ; @{ $mysync->{ regexflag } } = ( 's/(.*)/$1 jrdH8u/' ) ; ok('REM REM REM REM REM jrdH8u' eq regexflags( $mysync, 'REM REM REM REM REM' ), q{Add jrdH8u 's/(.*)/\$1 jrdH8u/'} ) ; @{ $mysync->{ regexflag } } = ('s/jrdH8u *//' ); ok('REM REM REM REM REM ' eq regexflags( $mysync, 'REM REM REM REM REM jrdH8u' ), q{Remove jrdH8u s/jrdH8u *//} ) ; @{ $mysync->{ regexflag } } = ( 's/.*?(?:(\\\\(?:Answered|Flagged|Deleted|Seen|Draft)\s?)|$)/defined($1)?$1:q()/eg' ); ok( '\\Deleted \\Answered ' eq regexflags( $mysync, 'Blabla \$Junk \\Deleted machin \\Answered truc' ), 'Keep only regex: Exchange case (Phil)' ) ; ok( q{} eq regexflags( $mysync, q{} ), 'Keep only regex: Exchange case, null string (Phil)' ) ; ok( q{} eq regexflags( $mysync, 'Blabla $Junk machin truc' ), 'Keep only regex: Exchange case, no accepted flags (Phil)' ) ; ok('\\Deleted \\Answered \\Draft \\Flagged ' eq regexflags( $mysync, '\\Deleted \\Answered \\Draft \\Flagged ' ), 'Keep only regex: Exchange case (Phil)' ) ; @{ $mysync->{ regexflag } } = ( 's/\\\\Flagged//g' ) ; is('\Deleted \Answered \Draft ', regexflags( $mysync, '\\Deleted \\Answered \\Draft \\Flagged ' ), 'regexflags: remove \Flagged 1' ) ; is('\\Deleted \\Answered \\Draft', regexflags( $mysync, '\\Deleted \\Flagged \\Answered \\Draft' ), 'regexflags: remove \Flagged 2' ) ; # I didn't understand why it gives \F # https://perldoc.perl.org/perlrebackslash.html # \F Foldcase till \E. Not in []. # https://perldoc.perl.org/functions/fc.html # \F Not available in old Perl so I comment the test # @{ $mysync->{ regexflag } } = ( 's/\\Flagged/X/g' ) ; #is('\Deleted FX \Answered \FX \Draft \FX', #regexflags( '\Deleted Flagged \Answered \Flagged \Draft \Flagged' ), # 'regexflags: remove \Flagged 3 mistery...' ) ; $mysync->{ regexflag } = [ ] ; $mysync->{ filterbuggyflags } = 1 ; filterbuggyflags( $mysync ) ; is( '\Deleted \Answered \Draft \Flagged', regexflags( $mysync, '\\Deleted \\Answered \\RECEIPTCHECKED \\Draft \\Indexed \\Flagged' ), 'regexflags: remove famous /X 1' ) ; is( '\\Deleted \\Flagged \\Answered \\Draft', regexflags( $mysync, '\\Deleted \\RECEIPTCHECKED \\Flagged \\Answered \\Indexed \\Draft' ), 'regexflags: remove famous /X 2' ) ; is( '\ ', '\\ ', 'regexflags: \ is \\ ' ) ; is( '\\ ', '\\ ', 'regexflags: \\ is \\ ' ) ; is( '\\ \ ', '\ \\ ', 'regexflags: \\ \ is \ \\ ' ) ; note( 'Leaving tests_regexflags()' ) ; return ; } sub regexflags { my $mysync = shift ; my $flags = shift ; foreach my $regexflag ( @{ $mysync->{ regexflag } } ) { my $flags_orig = $flags ; $debugflags and myprint( "eval \$flags =~ $regexflag\n" ) ; my $ret = eval "\$flags =~ $regexflag ; 1 " ; $debugflags and myprint( "regexflag $regexflag [$flags_orig] -> [$flags]\n" ) ; if( not ( defined $ret ) or $EVAL_ERROR ) { myprint( "Error: eval regexflag '$regexflag': $EVAL_ERROR\n" ) ; return( undef ) ; } } return( $flags ) ; } sub filterbuggyflags { my $mysync = shift ; if ( $mysync->{ filterbuggyflags } ) { unshift @{ $mysync->{ regexflag } }, buggyflagsregex( ) ; } return ; } sub tests_remove_doublequotes_if_any { note( 'Entering tests_remove_doublequotes_if_any()' ) ; # the number of tests is stupid here is( undef, remove_doublequotes_if_any( ), 'remove_doublequotes_if_any: no args => undef' ) ; is( q{}, remove_doublequotes_if_any( q{} ), 'remove_doublequotes_if_any: empty string => empty string' ) ; is( q{}, remove_doublequotes_if_any( q{""} ), 'remove_doublequotes_if_any: double-quotes => empty string' ) ; is( q{}, remove_doublequotes_if_any( q{"""} ), 'remove_doublequotes_if_any: double-quotes => empty string' ) ; is( q{}, remove_doublequotes_if_any( q{"""} ), 'remove_doublequotes_if_any: double-quotes => empty string' ) ; is( q{toto}, remove_doublequotes_if_any( q{"toto"} ), 'remove_doublequotes_if_any: "toto" => toto' ) ; is( q{toto}, remove_doublequotes_if_any( q{toto} ), 'remove_doublequotes_if_any: toto => toto' ) ; is( q{toto}, remove_doublequotes_if_any( q{to"to} ), 'remove_doublequotes_if_any: to"to => toto' ) ; is( q{toto}, remove_doublequotes_if_any( q{toto"} ), 'remove_doublequotes_if_any: toto" => toto' ) ; is( q{toto}, remove_doublequotes_if_any( q{"toto} ), 'remove_doublequotes_if_any: "toto => toto' ) ; is( q{toto}, remove_doublequotes_if_any( q{"to"to} ), 'remove_doublequotes_if_any: "to"to => toto' ) ; is( q{toto}, remove_doublequotes_if_any( q{to"to"} ), 'remove_doublequotes_if_any: to"to" => toto' ) ; is( q{toto}, remove_doublequotes_if_any( q{to\"to} ), 'remove_doublequotes_if_any: to\"to => toto' ) ; is( q{toto}, remove_doublequotes_if_any( q{toto\"} ), 'remove_doublequotes_if_any: toto\" => toto' ) ; is( q{toto}, remove_doublequotes_if_any( q{\"toto} ), 'remove_doublequotes_if_any: \"toto => toto' ) ; is( q{toto}, remove_doublequotes_if_any( q{\"to\"to} ), 'remove_doublequotes_if_any: \"to\"to => toto' ) ; is( q{toto}, remove_doublequotes_if_any( q{to\"to\"} ), 'remove_doublequotes_if_any: to\"to" => toto' ) ; note( 'Leaving tests_remove_doublequotes_if_any()' ) ; return ; } sub remove_doublequotes_if_any { my $string = shift ; if ( ! defined $string ) { return ; } $string =~ s/\\\"//g ; $string =~ tr/"//d ; return $string ; } # No globals here sub acls_sync { # https://tools.ietf.org/html/rfc4314 # Standard Rights: # https://tools.ietf.org/html/rfc4314#section-2.1 my( $mysync, $h1_fold, $h2_fold ) = @_ ; if ( $mysync->{ syncacls } ) { my $h1_hash = $mysync->{imap1}->getacl($h1_fold) or myprint( "Host1: Could not getacl for $h1_fold: $EVAL_ERROR\n" ) ; my $h2_hash = $mysync->{imap2}->getacl($h2_fold) or myprint( "Host2: Could not getacl for $h2_fold: $EVAL_ERROR\n" ) ; my %users = map { ($_, 1) } ( keys %{ $h1_hash} , keys %{ $h2_hash } ) ; foreach my $user (sort keys %users ) { my $h1_acl = remove_doublequotes_if_any( $h1_hash->{$user} ) || '' ; my $h2_acl = remove_doublequotes_if_any( $h2_hash->{$user} ) || '' ; myprint( "Host1: user $user has acl [$h1_acl] on host1\n" ) ; myprint( "Host2: user $user has acl [$h2_acl] on host2\n" ) ; # removes surrounding double-quotes if any my $user_no_quotes = remove_doublequotes_if_any( $user ) ; if ( $h1_hash->{$user} && $h2_hash->{$user} && $h1_hash->{$user} eq $h2_hash->{$user} ) { myprint( "Host2: user $user_no_quotes has already the same acl, no need to set it.\n" ) ; next ; } myprint( "Host2: setting acl for folder $h2_fold user $user_no_quotes acl $h1_acl $mysync->{dry_message}\n" ) ; unless ( $mysync->{dry} ) { $mysync->{imap2}->setacl( $h2_fold, $user_no_quotes, $h1_acl ) or myprint( "Could not set acl for user $user_no_quotes on host2: $EVAL_ERROR\n" ) ; } } } return ; } sub tests_permanentflags { note( 'Entering tests_permanentflags()' ) ; my $mysync = { } ; ok( q{} eq permanentflags( $mysync, ' * OK [PERMANENTFLAGS (\* \Draft \Answered)] Limited' ), 'permanentflags \*' ) ; ok( '\Draft \Answered' eq permanentflags( $mysync, ' * OK [PERMANENTFLAGS (\Draft \Answered)] Limited' ), 'permanentflags \Draft \Answered' ) ; ok( '\Draft \Answered' eq permanentflags( $mysync, 'Blabla', ' * OK [PERMANENTFLAGS (\Draft \Answered)] Limited', 'Blabla' ), 'permanentflags \Draft \Answered' ) ; ok( q{} eq permanentflags( $mysync, 'Blabla' ), 'permanentflags nothing' ) ; note( 'Leaving tests_permanentflags()' ) ; return ; } sub permanentflags { my $mysync = shift ; my @lines = @_ ; foreach my $line (@lines) { if ( $line =~ m{\[PERMANENTFLAGS\s\(([^)]+?)\)\]}x ) { ( $debugflags or $sync->{ debug } ) and myprint( "permanentflags: $line" ) ; my $permanentflags = $1 ; if ( $permanentflags =~ m{\\\*}x ) { $permanentflags = q{} ; } return( $permanentflags ) ; } ; } return( q{} ) ; } sub tests_flags_filter { note( 'Entering tests_flags_filter()' ) ; ok( '\Seen' eq flags_filter('\Seen', '\Draft \Seen \Answered'), 'flags_filter ' ); ok( q{} eq flags_filter('\Seen', '\Draft \Answered'), 'flags_filter ' ); ok( '\Seen' eq flags_filter('\Seen', '\Seen'), 'flags_filter ' ); ok( '\Seen' eq flags_filter('\Seen', ' \Seen '), 'flags_filter ' ); ok( '\Seen \Draft' eq flags_filter('\Seen \Draft', '\Draft \Seen \Answered'), 'flags_filter ' ); ok( '\Seen \Draft' eq flags_filter('\Seen \Draft', ' \Draft \Seen \Answered '), 'flags_filter ' ); note( 'Leaving tests_flags_filter()' ) ; return ; } sub flags_filter { my( $flags, $allowed_flags ) = @_ ; my @flags = split /\s+/x, $flags ; my %allowed_flags = map { $_ => 1 } split q{ }, $allowed_flags ; my @flags_out = map { exists $allowed_flags{$_} ? $_ : () } @flags ; my $flags_out = join q{ }, @flags_out ; return( $flags_out ) ; } sub tests_flagscase { note( 'Entering tests_flagscase()' ) ; ok( '\Seen' eq flagscase( '\Seen' ), 'flagscase: \Seen -> \Seen' ) ; ok( '\Seen' eq flagscase( '\SEEN' ), 'flagscase: \SEEN -> \Seen' ) ; ok( '\Seen \Draft' eq flagscase( '\SEEN \DRAFT' ), 'flagscase: \SEEN \DRAFT -> \Seen \Draft' ) ; ok( '\Draft \Seen' eq flagscase( '\DRAFT \SEEN' ), 'flagscase: \DRAFT \SEEN -> \Draft \Seen' ) ; ok( '\Draft LALA \Seen' eq flagscase( '\DRAFT LALA \SEEN' ), 'flagscase: \DRAFT LALA \SEEN -> \Draft LALA \Seen' ) ; ok( '\Draft lala \Seen' eq flagscase( '\DRAFT lala \SEEN' ), 'flagscase: \DRAFT lala \SEEN -> \Draft lala \Seen' ) ; note( 'Leaving tests_flagscase()' ) ; return ; } sub flagscase { my $flags = shift ; my @flags = split /\s+/x, $flags ; my %rfc_flags = map { $_ => 1 } split q{ }, '\Answered \Flagged \Deleted \Seen \Draft' ; my @flags_out = map { exists $rfc_flags{ ucsecond( lc $_ ) } ? ucsecond( lc $_ ) : $_ } @flags ; my $flags_out = join q{ }, @flags_out ; return( $flags_out ) ; } sub tests_flags_for_host2 { note( 'Entering tests_flags_for_host2()' ) ; is( undef, flags_for_host2( ), 'flags_for_host2: no args => undef' ) ; my $mysync ; is( undef, flags_for_host2( $mysync ), 'flags_for_host2: undef => undef' ) ; $mysync = { } ; is( undef, flags_for_host2( $mysync ), 'flags_for_host2: nothing => undef' ) ; is( q{}, flags_for_host2( $mysync, '' ), 'flags_for_host2: no flags => empty string' ) ; is( q{}, flags_for_host2( $mysync, '\Recent' ), 'flags_for_host2: \Recent => empty string' ) ; is( q{\Seen}, flags_for_host2( $mysync, '\Recent \Seen' ), 'flags_for_host2: \Recent \Seen => \Seen' ) ; is( q{\Deleted \Seen}, flags_for_host2( $mysync, '\Deleted \Recent \Seen' ), 'flags_for_host2: \Deleted \Recent \Seen => \Deleted \Seen' ) ; $mysync->{ flagscase } = 0 ; is( q{\DELETED \Seen}, flags_for_host2( $mysync, '\DELETED \Seen' ), 'flags_for_host2: flagscase = 0 \DELETED \Seen => \DELETED \Seen' ) ; $mysync->{ flagscase } = 1 ; is( q{\Deleted \Seen}, flags_for_host2( $mysync, '\DELETED \Seen' ), 'flags_for_host2: flagscase = 1 \DELETED \Seen => \Deleted \Seen' ) ; $mysync->{ filterflags } = 0 ; is( q{\Seen \Blabla}, flags_for_host2( $mysync, '\Seen \Blabla', '\Seen \Junk' ), 'flags_for_host2: filterflags = 0 \Seen \Blabla among \Seen \Junk => \Seen \Blabla' ) ; $mysync->{ filterflags } = 1 ; is( q{\Seen}, flags_for_host2( $mysync, '\Seen \Blabla', '\Seen \Junk' ), 'flags_for_host2: filterflags = 1 \Seen \Blabla among \Seen \Junk => \Seen' ) ; $mysync->{ filterflags } = 1 ; is( q{\Seen \Blabla}, flags_for_host2( $mysync, '\Seen \Blabla', '' ), 'flags_for_host2: filterflags = 1 \Seen \Blabla among "" => \Seen \Blabla' ) ; note( 'Leaving tests_flags_for_host2()' ) ; return ; } sub flags_for_host2 { my $mysync = shift ; my $h1_flags = shift ; my $permanentflags2 = shift ; if ( ! all_defined( $mysync, $h1_flags ) ) { return ; } ; # RFC 2060: This flag can not be altered by any client $h1_flags =~ s@\\Recent\s?@@xgi ; my $h1_flags_re ; if ( $mysync->{ regexflag } and defined( $h1_flags_re = regexflags( $mysync, $h1_flags ) ) ) { $h1_flags = $h1_flags_re ; } if ( $mysync->{ flagscase } ) { $h1_flags = flagscase( $h1_flags ) ; } if ( $permanentflags2 and $mysync->{ filterflags } ) { $h1_flags = flags_filter( $h1_flags, $permanentflags2 ) ; } return( $h1_flags ) ; } sub ucsecond { my $string = shift ; my $output ; return( $string ) if ( 1 >= length $string ) ; $output = ( substr( $string, 0, 1) ) . ( uc substr $string, 1, 1 ) . ( substr $string, 2 ) ; #myprint( "UUU $string -> $output\n" ) ; return( $output ) ; } sub tests_ucsecond { note( 'Entering tests_ucsecond()' ) ; ok( 'aBcde' eq ucsecond( 'abcde' ), 'ucsecond: abcde -> aBcde' ) ; ok( 'ABCDE' eq ucsecond( 'ABCDE' ), 'ucsecond: ABCDE -> ABCDE' ) ; ok( 'ABCDE' eq ucsecond( 'AbCDE' ), 'ucsecond: AbCDE -> ABCDE' ) ; ok( 'ABCde' eq ucsecond( 'AbCde' ), 'ucsecond: AbCde -> ABCde' ) ; ok( 'A' eq ucsecond( 'A' ), 'ucsecond: A -> A' ) ; ok( 'AB' eq ucsecond( 'Ab' ), 'ucsecond: Ab -> AB' ) ; ok( '\B' eq ucsecond( '\b' ), 'ucsecond: \b -> \B' ) ; ok( '\Bcde' eq ucsecond( '\bcde' ), 'ucsecond: \bcde -> \Bcde' ) ; note( 'Leaving tests_ucsecond()' ) ; return ; } sub select_msgs { my ( $imap, $msgs_all_hash_ref, $search_cmd, $abletosearch, $folder ) = @_ ; my ( @msgs ) ; if ( $abletosearch ) { @msgs = select_msgs_by_search( $imap, $msgs_all_hash_ref, $search_cmd, $folder ) ; }else{ @msgs = select_msgs_by_fetch( $imap, $msgs_all_hash_ref, $search_cmd, $folder ) ; } return( @msgs ) ; } sub select_msgs_by_search { my ( $imap, $msgs_all_hash_ref, $search_cmd, $folder ) = @_ ; my ( @msgs, @msgs_all ) ; # Need to have the whole list in msgs_all_hash_ref # without calling messages() several times. # Need all messages list to avoid deleting useful cache part # in case of --search or --minage or --maxage if ( ( defined $msgs_all_hash_ref and $usecache ) or ( not defined $maxage and not defined $minage and not defined $search_cmd ) ) { $debugdev and myprint( "Calling messages()\n" ) ; @msgs_all = $imap->messages( ) ; return if ( $#msgs_all == 0 && !defined $msgs_all[0] ) ; if ( defined $msgs_all_hash_ref ) { @{ $msgs_all_hash_ref }{ @msgs_all } = () ; } # return all messages if ( not defined $maxage and not defined $minage and not defined $search_cmd ) { return( @msgs_all ) ; } } if ( defined $search_cmd ) { @msgs = $imap->search( $search_cmd ) ; return( @msgs ) ; } # we are here only if $maxage or $minage is defined @msgs = select_msgs_by_age( $imap ) ; return( @msgs ); } sub select_msgs_by_fetch { my ( $imap, $msgs_all_hash_ref, $search_cmd, $folder ) = @_ ; my ( @msgs, @msgs_all, %fetch ) ; # Need to have the whole list in msgs_all_hash_ref # without calling messages() several times. # Need all messages list to avoid deleting useful cache part # in case of --search or --minage or --maxage $debugdev and myprint( "Calling fetch_hash()\n" ) ; my $fetch_hash_uids = $fetch_hash_set || "1:*" ; %fetch = %{$imap->fetch_hash( $fetch_hash_uids, 'INTERNALDATE' ) } ; @msgs_all = sort { $a <=> $b } keys %fetch ; $debugdev and myprint( "Done fetch_hash()\n" ) ; return if ( $#msgs_all == 0 && !defined $msgs_all[0] ) ; if ( defined $msgs_all_hash_ref ) { @{ $msgs_all_hash_ref }{ @msgs_all } = () ; } # return all messages if ( not defined $maxage and not defined $minage and not defined $search_cmd ) { return( @msgs_all ) ; } if ( defined $search_cmd ) { myprint( "Warning: strange to see --search with --noabletosearch, an error can happen\n" ) ; @msgs = $imap->search( $search_cmd ) ; return( @msgs ) ; } # we are here only if $maxage or $minage is defined my( @max, @min, $maxage_epoch, $minage_epoch ) ; if ( defined $maxage ) { $maxage_epoch = $timestart_int - $NB_SECONDS_IN_A_DAY * $maxage ; } if ( defined $minage ) { $minage_epoch = $timestart_int - $NB_SECONDS_IN_A_DAY * $minage ; } foreach my $msg ( @msgs_all ) { my $idate = $fetch{ $msg }->{'INTERNALDATE'} ; #myprint( "$idate\n" ) ; if ( defined $maxage and ( epoch( $idate ) >= $maxage_epoch ) ) { push @max, $msg ; } if ( defined $minage and ( epoch( $idate ) <= $minage_epoch ) ) { push @min, $msg ; } } @msgs = msgs_from_maxmin( \@max, \@min ) ; return( @msgs ) ; } sub select_msgs_by_age { my( $imap ) = @_ ; my( @max, @min, @msgs, @inter, @union ) ; if ( defined $maxage ) { @max = $imap->sentsince( $timestart_int - $NB_SECONDS_IN_A_DAY * $maxage ) ; } if ( defined $minage ) { @min = $imap->sentbefore( $timestart_int - $NB_SECONDS_IN_A_DAY * $minage ) ; } @msgs = msgs_from_maxmin( \@max, \@min ) ; return( @msgs ) ; } sub msgs_from_maxmin { my( $max_ref, $min_ref ) = @_ ; my( @max, @min, @msgs, @inter, @union ) ; @max = @{ $max_ref } ; @min = @{ $min_ref } ; SWITCH: { if ( not ( defined $minage or defined $maxage ) ) { return ; } unless( defined $minage ) { @msgs = @max ; last SWITCH } ; unless( defined $maxage ) { @msgs = @min ; last SWITCH } ; my ( %union, %inter ) ; foreach my $m ( @min, @max ) { $union{ $m }++ && $inter{ $m }++ } @inter = sort { $a <=> $b } keys %inter ; @union = sort { $a <=> $b } keys %union ; # normal case if ( $minage <= $maxage ) { @msgs = @inter ; last SWITCH } ; # just exclude messages between if ( $minage > $maxage ) { @msgs = @union ; last SWITCH } ; } return( @msgs ) ; } sub tests_msgs_from_maxmin { note( 'Entering tests_msgs_from_maxmin()' ) ; my @msgs ; # no maxage nor minage @msgs = msgs_from_maxmin( [ '1', '2' ], [ '2', '3' ] ) ; is_deeply( [ ], \@msgs , 'msgs_from_maxmin: no maxage nor minage => empty result' ) ; # maxage alone $maxage = $NUMBER_200 ; @msgs = msgs_from_maxmin( [ '1', '2' ], [ '2', '3' ] ) ; is_deeply( [ '1', '2' ], \@msgs , 'msgs_from_maxmin: maxage++' ) ; # maxage > minage -> intersection $minage = $NUMBER_100 ; @msgs = msgs_from_maxmin( [ '1', '2' ], [ '2', '3' ] ) ; is_deeply( [ '2' ], \@msgs , 'msgs_from_maxmin: -maxage++minage-' ) ; # maxage < minage -> union $minage = $NUMBER_300 ; @msgs = msgs_from_maxmin( [ '1', '2' ], [ '2', '3' ] ) ; is_deeply( [ '1', '2', '3' ], \@msgs, 'msgs_from_maxmin: ++maxage-minage++' ) ; # minage alone $maxage = undef ; @msgs = msgs_from_maxmin( [ '1', '2' ], [ '2', '3' ] ) ; is_deeply( [ '2', '3' ], \@msgs, 'msgs_from_maxmin: ++minage-' ) ; note( 'Leaving tests_msgs_from_maxmin()' ) ; return ; } sub tests_info_date_from_uid { note( 'Entering tests_info_date_from_uid()' ) ; note( 'Leaving tests_info_date_from_uid()' ) ; return ; } sub info_date_from_uid { #my $first_uid = $msgs_all[ 0 ] ; #my $first_idate = $fetch{ $first_uid }->{'INTERNALDATE'} ; #my $first_epoch = epoch( $first_idate ) ; #my $first_days = ( $timestart_int - $first_epoch ) / $NB_SECONDS_IN_A_DAY ; #myprint( "\nOldest msg has UID $first_uid INTERNALDATE $first_idate EPOCH $first_epoch DAYS AGO $first_days\n" ) ; } sub lastuid { my $imap = shift ; my $folder = shift ; my $lastuid_guess = shift ; my $lastuid ; # rfc3501: The only reliable way to identify recent messages is to # look at message flags to see which have the \Recent flag # set, or to do a SEARCH RECENT. # SEARCH RECENT doesn't work this way on courrier. my @recent_messages ; # SEARCH RECENT for each transfer can be expensive with a big folder # Call commented for now #@recent_messages = $imap->recent( ) ; #myprint( "Recent: @recent_messages\n" ) ; my $max_recent ; $max_recent = max( @recent_messages ) ; if ( defined $max_recent and ($lastuid_guess <= $max_recent ) ) { $lastuid = $max_recent ; }else{ $lastuid = $lastuid_guess } return( $lastuid ) ; } sub size_filtered { my( $h1_size, $h1_msg, $h1_fold, $h2_fold ) = @_ ; $h1_size = 0 if ( ! $h1_size ) ; # null if empty or undef if ( defined $sync->{ maxsize } and $h1_size > $sync->{ maxsize } ) { myprint( "msg $h1_fold/$h1_msg skipped ($h1_size exceeds maxsize limit $sync->{ maxsize } bytes)\n" ) ; $sync->{ total_bytes_skipped } += $h1_size; $sync->{ nb_msg_skipped } += 1; return( 1 ) ; } if ( defined $minsize and $h1_size <= $minsize ) { myprint( "msg $h1_fold/$h1_msg skipped ($h1_size smaller than minsize $minsize bytes)\n" ) ; $sync->{ total_bytes_skipped } += $h1_size; $sync->{ nb_msg_skipped } += 1; return( 1 ) ; } return( 0 ) ; } sub message_exists { my( $imap, $msg ) = @_ ; return( 1 ) if not $imap->Uid( ) ; my $search_uid ; ( $search_uid ) = $imap->search( "UID $msg" ) ; #myprint( "$search ? $msg\n" ) ; return( 1 ) if ( $search_uid eq $msg ) ; return( 0 ) ; } # Globals # $sync->{ total_bytes_skipped } # $sync->{ nb_msg_skipped } # $mysync->{ h1_nb_msg_processed } sub stats_update_skip_message { my $mysync = shift ; # to be used my $h1_size = shift ; $mysync->{ total_bytes_skipped } += $h1_size ; $mysync->{ nb_msg_skipped } += 1 ; $mysync->{ h1_nb_msg_processed } +=1 ; return ; } sub copy_message { # copy my ( $mysync, $h1_msg, $h1_fold, $h2_fold, $h1_fir_ref, $permanentflags2, $cache_dir ) = @_ ; ( $mysync->{ debug } or $mysync->{dry} ) and myprint( "msg $h1_fold/$h1_msg copying to $h2_fold $mysync->{dry_message} " . eta( $mysync ) . "\n" ) ; if ( $mysync->{dry1} ) { $mysync->{ h1_nb_msg_processed } +=1 ; $nb_msg_skipped_dry_mode += 1 ; return ; } my $h1_size = $h1_fir_ref->{$h1_msg}->{'RFC822.SIZE'} || 0 ; my $h1_flags = $h1_fir_ref->{$h1_msg}->{'FLAGS'} || q{} ; my $h1_idate = $h1_fir_ref->{$h1_msg}->{'INTERNALDATE'} || q{} ; if ( size_filtered( $h1_size, $h1_msg, $h1_fold, $h2_fold ) ) { $mysync->{ h1_nb_msg_processed } +=1 ; return ; } debugsleep( $mysync ) ; myprint( "- msg $h1_fold/$h1_msg S[$h1_size] F[$h1_flags] I[$h1_idate] has RFC822.SIZE null!\n" ) if ( ! $h1_size ) ; if ( $checkmessageexists and not message_exists( $mysync->{imap1}, $h1_msg ) ) { stats_update_skip_message( $mysync, $h1_size ) ; return ; } myprint( debugmemory( $mysync, " at C1" ) ) ; my ( $string, $string_len ) ; ( $string_len ) = message_for_host2( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, \$string ) ; myprint( debugmemory( $mysync, " at C2" ) ) ; # not defined or empty $string if ( ( not $string ) or ( not $string_len ) ) { myprint( "- msg $h1_fold/$h1_msg skipped.\n" ) ; stats_update_skip_message( $mysync, $h1_size ) ; return ; } # Lines too long (or not enough) => do no copy or fix if ( ( defined $maxlinelength ) or ( defined $minmaxlinelength ) ) { $string = linelengthstuff( $string, $h1_fold, $h1_msg, $string_len, $h1_size, $h1_flags, $h1_idate ) ; if ( not defined $string ) { stats_update_skip_message( $mysync, $h1_size ) ; return ; } } my $h1_date = date_for_host2( $h1_msg, $h1_idate ) ; ( $mysync->{ debug } or $debugflags ) and myprint( "Host1: flags init msg $h1_fold/$h1_msg date [$h1_date] flags [$h1_flags] size [$h1_size]\n" ) ; $h1_flags = flags_for_host2( $mysync, $h1_flags, $permanentflags2 ) ; ( $mysync->{ debug } or $debugflags ) and myprint( "Host1: flags filt msg $h1_fold/$h1_msg date [$h1_date] flags [$h1_flags] size [$h1_size]\n" ) ; $h1_date = undef if ( $h1_date eq q{} ) ; my $new_id = append_message_on_host2( $mysync, \$string, $h1_fold, $h1_msg, $string_len, $h2_fold, $h1_size, $h1_flags, $h1_date, $cache_dir ) ; if ( $new_id and $syncflagsaftercopy ) { sync_flags_after_copy( $mysync, $h1_fold, $h1_msg, $h1_flags, $h2_fold, $new_id, $permanentflags2 ) ; } myprint( debugmemory( $mysync, " at C3" ) ) ; return $new_id ; } sub linelengthstuff { my( $string, $h1_fold, $h1_msg, $string_len, $h1_size, $h1_flags, $h1_idate ) = @_ ; my $maxlinelength_string = max_line_length( $string ) ; $debugmaxlinelength and myprint( "msg $h1_fold/$h1_msg maxlinelength: $maxlinelength_string\n" ) ; if ( ( defined $minmaxlinelength ) and ( $maxlinelength_string <= $minmaxlinelength ) ) { my $subject = subject( $string ) ; $debugdev and myprint( "- msg $h1_fold/$h1_msg skipped S[$h1_size] F[$h1_flags] I[$h1_idate] " . "(Subject:[$subject]) (max line length under minmaxlinelength $minmaxlinelength bytes)\n" ) ; return ; } if ( ( defined $maxlinelength ) and ( $maxlinelength_string > $maxlinelength ) ) { my $subject = subject( $string ) ; if ( $maxlinelengthcmd ) { $string = pipemess( $string, $maxlinelengthcmd ) ; # string undef means something was bad. if ( not ( defined $string ) ) { myprint( "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate] " . "(Subject:[$subject]) could not be successfully transformed by --maxlinelengthcmd option\n" ) ; return ; }else{ return $string ; } } myprint( "- msg $h1_fold/$h1_msg skipped S[$h1_size] F[$h1_flags] I[$h1_idate] " . "(Subject:[$subject]) (line length exceeds maxlinelength $maxlinelength bytes)\n" ) ; return ; } return $string ; } sub message_for_host2 { # global variable list: # @skipmess # @regexmess # @pipemess # $debugcontent # $debug # # API current # # at failure: # * return nothing ( will then be undef or () ) # * $string_ref content is undef or empty # at success: # * return string length ($string_ref content length) # * $string_ref content filled with message # API future # # my ( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ) = @_ ; # abort when missing a parameter if ( ( ! $mysync ) or ( ! $h1_msg ) or ( ! $h1_fold ) or ( ! defined $h1_size ) or ( ! defined $h1_flags) or ( ! defined $h1_idate ) or ( ! $h1_fir_ref) or ( ! $string_ref ) ) { return ; } myprint( debugmemory( $mysync, " at M1" ) ) ; my $string_ok = $mysync->{imap1}->message_to_file( $string_ref, $h1_msg ) ; myprint( debugmemory( $mysync, " at M2" ) ) ; my $string_len = length_ref( $string_ref ) ; unless ( defined $string_ok and $string_len ) { # undef or 0 length my $error = join q{}, "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate] could not be fetched: ", $mysync->{imap1}->LastError || q{}, "\n" ; errors_incr( $mysync, $error ) ; $mysync->{ h1_nb_msg_processed } +=1 ; return ; } if ( @skipmess ) { my $match = skipmess( ${ $string_ref } ) ; # string undef means the eval regex was bad. if ( not ( defined $match ) ) { myprint( "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate]" . " could not be skipped by --skipmess option, bad regex\n" ) ; return ; } if ( $match ) { my $subject = subject( ${ $string_ref } ) ; myprint( "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate]" . " (Subject:[$subject]) skipped by --skipmess\n" ) ; return ; } } if ( @regexmess ) { ${ $string_ref } = regexmess( ${ $string_ref } ) ; # string undef means the eval regex was bad. if ( not ( defined ${ $string_ref } ) ) { myprint( "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate]" . " could not be transformed by --regexmess\n" ) ; return ; } } if ( @pipemess ) { ${ $string_ref } = pipemess( ${ $string_ref }, @pipemess ) ; # string undef means something was bad. if ( not ( defined ${ $string_ref } ) ) { myprint( "- msg $h1_fold/$h1_msg {$string_len} S[$h1_size] F[$h1_flags] I[$h1_idate]" . " could not be successfully transformed by --pipemess option\n" ) ; return ; } } if ( $mysync->{addheader} and defined $h1_fir_ref->{$h1_msg}->{'NO_HEADER'} ) { my $header = add_header( $h1_msg ) ; $mysync->{ debug } and myprint( "msg $h1_fold/$h1_msg adding custom header [$header]\n" ) ; ${ $string_ref } = $header . "\r\n" . ${ $string_ref } ; } if ( ( defined $mysync->{ truncmess } ) and is_integer( $mysync->{ truncmess } ) ) { ${ $string_ref } = truncmess( ${ $string_ref }, $mysync->{ truncmess } ) ; } $string_len = length_ref( $string_ref ) ; $mysync->{ debugcontent } and myprint( debugcontent( $mysync, $string_ref ) ) ; myprint( debugmemory( $mysync, " at M3" ) ) ; return $string_len ; } sub tests_debugcontent { note( 'Entering tests_debugcontent()' ) ; is( undef, debugcontent( ), 'debugcontent: no args => undef' ) ; my $mysync = { } ; is( undef, debugcontent( $mysync ), 'debugcontent: undef => undef' ) ; is( undef, debugcontent( $mysync, 'mm' ), 'debugcontent: undef, mm => undef' ) ; #my $string_ref = \'zztop' ; my $string = '================================================================================ F message content begin next line (2 characters long) mm F message content ended on previous line ================================================================================ ' ; is( $string, debugcontent( $mysync, \'mm' ), 'debugcontent: undef, mm => mm' ) ; note( 'Leaving tests_debugcontent()' ) ; return ; } sub debugcontent { my $mysync = shift @ARG ; if ( ! defined $mysync ) { return ; } my $string_ref = shift @ARG ; if ( ! defined $string_ref ) { return ; } if ( 'SCALAR' ne ref( $string_ref ) ) { return ; } my $string_len = length_ref( $string_ref ) ; my $string = join( '', q{=} x $STD_CHAR_PER_LINE, "\n", "F message content begin next line ($string_len characters long)\n", ${ $string_ref }, "\nF message content ended on previous line\n", q{=} x $STD_CHAR_PER_LINE, "\n", ) ; return $string ; } sub tests_truncmess { note( 'Entering tests_truncmess()' ) ; is( undef, truncmess( ), 'truncmess: no args => undef' ) ; is( 'abc', truncmess( 'abc' ), 'truncmess: abc => abc' ) ; is( 'ab', truncmess( 'abc', 2 ), 'truncmess: abc 2 => ab' ) ; is( 'abc', truncmess( 'abc', 3 ), 'truncmess: abc 3 => abc' ) ; is( 'abc', truncmess( 'abc', 4 ), 'truncmess: abc 4 => abc' ) ; is( '12345', truncmess( "123456789\n", 5 ), 'truncmess: "123456789\n", 5 => 12345' ) ; is( "123456789\n" x 5000, truncmess( "123456789\n" x 100000, 50000 ), 'truncmess: "123456789\n" x 100000, 50000 => "123456789\n" x 5000' ) ; note( 'Leaving tests_truncmess()' ) ; return ; } sub truncmess { my $string = shift ; my $length = shift ; if ( not defined $string ) { return ; } if ( not defined $length ) { return $string ; } $string = substr $string, 0, $length ; return $string ; } sub tests_message_for_host2 { note( 'Entering tests_message_for_host2()' ) ; my ( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ) ; is( undef, message_for_host2( ), q{message_for_host2: no args} ) ; is( undef, message_for_host2( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ), q{message_for_host2: undef args} ) ; require_ok( "Test::MockObject" ) ; my $imapT = Test::MockObject->new( ) ; $mysync->{imap1} = $imapT ; my $string ; $h1_msg = 1 ; $h1_fold = 'FoldFoo'; $h1_size = 9 ; $h1_flags = q{} ; $h1_idate = '10-Jul-2015 09:00:00 +0200' ; $h1_fir_ref = {} ; $string_ref = \$string ; $imapT->mock( 'message_to_file', sub { my ( $imap, $mystring_ref, $msg ) = @_ ; ${$mystring_ref} = 'blablabla' ; return length ${$mystring_ref} ; } ) ; is( 9, message_for_host2( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ), q{message_for_host2: msg 1 == "blablabla", length} ) ; is( 'blablabla', $string, q{message_for_host2: msg 1 == "blablabla", value} ) ; # so far so good # now the --pipemess stuff SKIP: { Readonly my $NB_WIN_tests_message_for_host2 => 0 ; skip( 'Not on MSWin32', $NB_WIN_tests_message_for_host2 ) if ('MSWin32' ne $OSNAME) ; # Windows # "type" command does not accept redirection of STDIN with < # "sort" does } ; SKIP: { Readonly my $NB_UNX_tests_message_for_host2 => 6 ; skip( 'Not on Unix', $NB_UNX_tests_message_for_host2 ) if ('MSWin32' eq $OSNAME) ; # Unix # no change by cat @pipemess = ( 'cat' ) ; is( 9, message_for_host2( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ), q{message_for_host2: --pipemess 'cat', length} ) ; is( 'blablabla', $string, q{message_for_host2: --pipemess 'cat', value} ) ; # failure by false @pipemess = ( 'false' ) ; is( undef, message_for_host2( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ), q{message_for_host2: --pipemess 'false', length} ) ; is( undef, $string, q{message_for_host2: --pipemess 'false', value} ) ; # failure by true since no output @pipemess = ( 'true' ) ; is( undef, message_for_host2( $mysync, $h1_msg, $h1_fold, $h1_size, $h1_flags, $h1_idate, $h1_fir_ref, $string_ref ), q{message_for_host2: --pipemess 'true', length} ) ; is( undef, $string, q{message_for_host2: --pipemess 'true', value} ) ; } note( 'Leaving tests_message_for_host2()' ) ; return ; } sub tests_labels_remove_subfolder1 { note( 'Entering tests_labels_remove_subfolder1()' ) ; is( undef, labels_remove_subfolder1( ), 'labels_remove_subfolder1: no parameters => undef' ) ; is( 'Blabla', labels_remove_subfolder1( 'Blabla' ), 'labels_remove_subfolder1: one parameter Blabla => Blabla' ) ; is( 'Blan blue', labels_remove_subfolder1( 'Blan blue' ), 'labels_remove_subfolder1: one parameter Blan blue => Blan blue' ) ; is( '\Bla "Blan blan" Blabla', labels_remove_subfolder1( '\Bla "Blan blan" Blabla' ), 'labels_remove_subfolder1: one parameter \Bla "Blan blan" Blabla => \Bla "Blan blan" Blabla' ) ; is( 'Bla', labels_remove_subfolder1( 'Subf/Bla', 'Subf' ), 'labels_remove_subfolder1: Subf/Bla Subf => "Bla"' ) ; is( '"\\\\Bla"', labels_remove_subfolder1( '"\\\\Bla"', 'Subf' ), 'labels_remove_subfolder1: "\\\\Bla" Subf => "\\\\Bla"' ) ; is( 'Bla Kii', labels_remove_subfolder1( 'Subf/Bla Subf/Kii', 'Subf' ), 'labels_remove_subfolder1: Subf/Bla Subf/Kii, Subf => "Bla" "Kii"' ) ; is( '"\\\\Bla" Kii', labels_remove_subfolder1( '"\\\\Bla" Subf/Kii', 'Subf' ), 'labels_remove_subfolder1: "\\\\Bla" Subf/Kii Subf => "\\\\Bla" Kii' ) ; is( '"Blan blan"', labels_remove_subfolder1( '"Subf/Blan blan"', 'Subf' ), 'labels_remove_subfolder1: "Subf/Blan blan" Subf => "Blan blan"' ) ; is( '"\\\\Loo" "Blan blan" Kii', labels_remove_subfolder1( '"\\\\Loo" "Subf/Blan blan" Subf/Kii', 'Subf' ), 'labels_remove_subfolder1: "\\\\Loo" "Subf/Blan blan" Subf/Kii + Subf => "\\\\Loo" "Blan blan" Kii' ) ; is( '"\\\\Inbox"', labels_remove_subfolder1( 'Subf/INBOX', 'Subf' ), 'labels_remove_subfolder1: Subf/INBOX + Subf => "\\\\Inbox"' ) ; is( '"\\\\Loo" "Blan blan" Kii "\\\\Inbox"', labels_remove_subfolder1( '"\\\\Loo" "Subf/Blan blan" Subf/Kii Subf/INBOX', 'Subf' ), 'labels_remove_subfolder1: "\\\\Loo" "Subf/Blan blan" Subf/Kii Subf/INBOX + Subf => "\\\\Loo" "Blan blan" Kii "\\\\Inbox"' ) ; note( 'Leaving tests_labels_remove_subfolder1()' ) ; return ; } sub labels_remove_subfolder1 { my $labels = shift ; my $subfolder1 = shift ; if ( not defined $labels ) { return ; } if ( not defined $subfolder1 ) { return $labels ; } my @labels = quotewords('\s+', 1, $labels ) ; #myprint( "@labels\n" ) ; my @labels_subfolder2 ; foreach my $label ( @labels ) { if ( $label =~ m{zzzzzzzzzz} ) { # \Seen \Deleted ... stay the same push @labels_subfolder2, $label ; } else { # Remove surrounding quotes if any, to add them again in case of space $label = join( q{}, quotewords('\s+', 0, $label ) ) ; $label =~ s{$subfolder1/?}{} ; if ( 'INBOX' eq $label ) { push @labels_subfolder2, q{"\\\\Inbox"} ; } elsif ( $label =~ m{\\} ) { push @labels_subfolder2, qq{"\\$label"} ; } elsif ( $label =~ m{ } ) { push @labels_subfolder2, qq{"$label"} ; } else { push @labels_subfolder2, $label ; } } } my $labels_subfolder2 = join( ' ', sort uniq( @labels_subfolder2 ) ) ; return $labels_subfolder2 ; } sub tests_labels_remove_special { note( 'Entering tests_labels_remove_special()' ) ; is( undef, labels_remove_special( ), 'labels_remove_special: no parameters => undef' ) ; is( q{}, labels_remove_special( q{} ), 'labels_remove_special: empty string => empty string' ) ; is( q{}, labels_remove_special( '"\\\\Inbox"' ), 'labels_remove_special:"\\\\Inbox" => empty string' ) ; is( q{}, labels_remove_special( '"\\\\Inbox" "\\\\Starred"' ), 'labels_remove_special:"\\\\Inbox" "\\\\Starred" => empty string' ) ; is( 'Bar Foo', labels_remove_special( 'Foo Bar' ), 'labels_remove_special:Foo Bar => Bar Foo' ) ; is( 'Bar Foo', labels_remove_special( 'Foo Bar "\\\\Inbox"' ), 'labels_remove_special:Foo Bar "\\\\Inbox" => Bar Foo' ) ; note( 'Leaving tests_labels_remove_special()' ) ; return ; } sub labels_remove_special { my $labels = shift ; if ( not defined $labels ) { return ; } my @labels = quotewords('\s+', 1, $labels ) ; myprint( "labels before remove_non_folded: @labels\n" ) ; my @labels_remove_special ; foreach my $label ( @labels ) { if ( $label =~ m{^\"\\\\} ) { # not kept } else { push @labels_remove_special, $label ; } } my $labels_remove_special = join( ' ', sort @labels_remove_special ) ; return $labels_remove_special ; } sub tests_labels_add_subfolder2 { note( 'Entering tests_labels_add_subfolder2()' ) ; is( undef, labels_add_subfolder2( ), 'labels_add_subfolder2: no parameters => undef' ) ; is( 'Blabla', labels_add_subfolder2( 'Blabla' ), 'labels_add_subfolder2: one parameter Blabla => Blabla' ) ; is( 'Blan blue', labels_add_subfolder2( 'Blan blue' ), 'labels_add_subfolder2: one parameter Blan blue => Blan blue' ) ; is( '\Bla "Blan blan" Blabla', labels_add_subfolder2( '\Bla "Blan blan" Blabla' ), 'labels_add_subfolder2: one parameter \Bla "Blan blan" Blabla => \Bla "Blan blan" Blabla' ) ; is( 'Subf/Bla', labels_add_subfolder2( 'Bla', 'Subf' ), 'labels_add_subfolder2: Bla Subf => "Subf/Bla"' ) ; is( 'Subf/\Bla', labels_add_subfolder2( '\\\\Bla', 'Subf' ), 'labels_add_subfolder2: \Bla Subf => \Bla' ) ; is( 'Subf/Bla Subf/Kii', labels_add_subfolder2( 'Bla Kii', 'Subf' ), 'labels_add_subfolder2: Bla Kii Subf => "Subf/Bla" "Subf/Kii"' ) ; is( 'Subf/Kii Subf/\Bla', labels_add_subfolder2( '\\\\Bla Kii', 'Subf' ), 'labels_add_subfolder2: \Bla Kii Subf => \Bla Subf/Kii' ) ; is( '"Subf/Blan blan"', labels_add_subfolder2( '"Blan blan"', 'Subf' ), 'labels_add_subfolder2: "Blan blan" Subf => "Subf/Blan blan"' ) ; is( '"Subf/Blan blan" Subf/Kii Subf/\Loo', labels_add_subfolder2( '\\\\Loo "Blan blan" Kii', 'Subf' ), 'labels_add_subfolder2: \Loo "Blan blan" Kii + Subf => "Subf/Blan blan" Subf/Kii Subf/\Loo' ) ; # "\\Inbox" is special, add to subfolder INBOX also because Gmail will but ... is( '"Subf/\\\\Inbox" Subf/INBOX', labels_add_subfolder2( '"\\\\Inbox"', 'Subf' ), 'labels_add_subfolder2: "\\\\Inbox" Subf => "Subf/\\\\Inbox" Subf/INBOX' ) ; # but not with INBOX folder is( '"Subf/\\\\Inbox"', labels_add_subfolder2( '"\\\\Inbox"', 'Subf', 'INBOX' ), 'labels_add_subfolder2: "\\\\Inbox" Subf INBOX => "Subf/\\\\Inbox"' ) ; # two times => one time is( '"Subf/\\\\Inbox" Subf/INBOX', labels_add_subfolder2( '"\\\\Inbox" "\\\\Inbox"', 'Subf' ), 'labels_add_subfolder2: "\\\\Inbox" "\\\\Inbox" Subf => "Subf/\\\\Inbox"' ) ; is( '"Subf/\\\\Starred"', labels_add_subfolder2( '"\\\\Starred"', 'Subf' ), 'labels_add_subfolder2: "\\\\Starred" Subf => "Subf/\\\\Starred"' ) ; note( 'Leaving tests_labels_add_subfolder2()' ) ; return ; } sub labels_add_subfolder2 { my $labels = shift ; my $subfolder2 = shift ; my $h1_folder = shift || q{} ; if ( not defined $labels ) { return ; } if ( not defined $subfolder2 ) { return $labels ; } # Isn't it messy? if ( 'INBOX' eq $h1_folder ) { $labels .= ' "\\\\Inbox"' ; } my @labels = uniq( quotewords('\s+', 1, $labels ) ) ; myprint( "labels before subfolder2: @labels\n" ) ; my @labels_subfolder2 ; foreach my $label ( @labels ) { # Isn't it more messy? if ( ( q{"\\\\Inbox"} eq $label ) and ( 'INBOX' ne $h1_folder ) ) { if ( $subfolder2 =~ m{ } ) { push @labels_subfolder2, qq{"$subfolder2/INBOX"} ; } else { push @labels_subfolder2, "$subfolder2/INBOX" ; } } if ( $label =~ m{^\"\\\\} ) { # \Seen \Deleted ... stay the same #push @labels_subfolder2, $label ; # Remove surrounding quotes if any, to add them again $label = join( q{}, quotewords('\s+', 0, $label ) ) ; push @labels_subfolder2, qq{"$subfolder2/\\$label"} ; } else { # Remove surrounding quotes if any, to add them again in case of space $label = join( q{}, quotewords('\s+', 0, $label ) ) ; if ( $label =~ m{ } ) { push @labels_subfolder2, qq{"$subfolder2/$label"} ; } else { push @labels_subfolder2, "$subfolder2/$label" ; } } } my $labels_subfolder2 = join( ' ', sort @labels_subfolder2 ) ; return $labels_subfolder2 ; } sub tests_labels { note( 'Entering tests_labels()' ) ; is( undef, labels( ), 'labels: no parameters => undef' ) ; is( undef, labels( undef ), 'labels: undef => undef' ) ; require_ok( "Test::MockObject" ) ; my $myimap = Test::MockObject->new( ) ; $myimap->mock( 'fetch_hash', sub { return( { '1' => { 'X-GM-LABELS' => '\Seen Blabla' } } ) ; } ) ; $myimap->mock( 'Debug' , sub { } ) ; $myimap->mock( 'Unescape', sub { return Mail::IMAPClient::Unescape( @_ ) } ) ; # real one is( undef, labels( $myimap ), 'labels: one parameter => undef' ) ; is( '\Seen Blabla', labels( $myimap, '1' ), 'labels: $mysync UID_1 => \Seen Blabla' ) ; note( 'Leaving tests_labels()' ) ; return ; } sub labels { my ( $myimap, $uid ) = @ARG ; if ( not all_defined( $myimap, $uid ) ) { return ; } my $hash = $myimap->fetch_hash( [ $uid ], 'X-GM-LABELS' ) ; my $labels = $hash->{ $uid }->{ 'X-GM-LABELS' } ; #$labels = $myimap->Unescape( $labels ) ; return $labels ; } sub tests_synclabels { note( 'Entering tests_synclabels()' ) ; is( undef, synclabels( ), 'synclabels: no parameters => undef' ) ; is( undef, synclabels( undef ), 'synclabels: undef => undef' ) ; my $mysync ; is( undef, synclabels( $mysync ), 'synclabels: var undef => undef' ) ; require_ok( "Test::MockObject" ) ; $mysync = {} ; my $myimap1 = Test::MockObject->new( ) ; $myimap1->mock( 'fetch_hash', sub { return( { '1' => { 'X-GM-LABELS' => '\Seen Blabla' } } ) ; } ) ; $myimap1->mock( 'Debug', sub { } ) ; $myimap1->mock( 'Unescape', sub { return Mail::IMAPClient::Unescape( @_ ) } ) ; # real one my $myimap2 = Test::MockObject->new( ) ; $myimap2->mock( 'store', sub { return 1 ; } ) ; $mysync->{imap1} = $myimap1 ; $mysync->{imap2} = $myimap2 ; is( undef, synclabels( $mysync ), 'synclabels: fresh $mysync => undef' ) ; is( undef, synclabels( $mysync, '1' ), 'synclabels: $mysync UID_1 alone => undef' ) ; is( 1, synclabels( $mysync, '1', '2' ), 'synclabels: $mysync UID_1 UID_2 => 1' ) ; note( 'Leaving tests_synclabels()' ) ; return ; } sub synclabels { my( $mysync, $uid1, $uid2 ) = @ARG ; if ( not all_defined( $mysync, $uid1, $uid2 ) ) { return ; } my $myimap1 = $mysync->{ 'imap1' } || return ; my $myimap2 = $mysync->{ 'imap2' } || return ; $mysync->{debuglabels} and $myimap1->Debug( 1 ) ; my $labels1 = labels( $myimap1, $uid1 ) ; $mysync->{debuglabels} and $myimap1->Debug( 0 ) ; $mysync->{debuglabels} and myprint( "Host1 labels: $labels1\n" ) ; if ( $mysync->{ subfolder1 } and $labels1 ) { $labels1 = labels_remove_subfolder1( $labels1, $mysync->{ subfolder1 } ) ; $mysync->{debuglabels} and myprint( "Host1 labels with subfolder1: $labels1\n" ) ; } if ( $mysync->{ subfolder2 } and $labels1 ) { $labels1 = labels_add_subfolder2( $labels1, $mysync->{ subfolder2 } ) ; $mysync->{debuglabels} and myprint( "Host1 labels with subfolder2: $labels1\n" ) ; } my $store ; if ( $labels1 and not $mysync->{ dry } ) { $mysync->{ debuglabels } and $myimap2->Debug( 1 ) ; $store = $myimap2->store( $uid2, "X-GM-LABELS ($labels1)" ) ; $mysync->{ debuglabels } and $myimap2->Debug( 0 ) ; } return $store ; } sub tests_resynclabels { note( 'Entering tests_resynclabels()' ) ; is( undef, resynclabels( ), 'resynclabels: no parameters => undef' ) ; is( undef, resynclabels( undef ), 'resynclabels: undef => undef' ) ; my $mysync ; is( undef, resynclabels( $mysync ), 'resynclabels: var undef => undef' ) ; my ( $h1_fir_ref, $h2_fir_ref ) ; $mysync->{ debuglabels } = 1 ; $h1_fir_ref->{ 11 }->{ 'X-GM-LABELS' } = '\Seen Baa Kii' ; $h2_fir_ref->{ 22 }->{ 'X-GM-LABELS' } = '\Seen Baa Kii' ; # labels are equal is( 1, resynclabels( $mysync, 11, 22, $h1_fir_ref, $h2_fir_ref ), 'resynclabels: $mysync UID_1 UID_2 labels are equal => 1' ) ; # labels are different $h2_fir_ref->{ 22 }->{ 'X-GM-LABELS' } = '\Seen Zuu' ; require_ok( "Test::MockObject" ) ; my $myimap2 = Test::MockObject->new( ) ; $myimap2->mock( 'store', sub { return 1 ; } ) ; $myimap2->mock( 'Debug', sub { } ) ; $mysync->{imap2} = $myimap2 ; is( 1, resynclabels( $mysync, 11, 22, $h1_fir_ref, $h2_fir_ref ), 'resynclabels: $mysync UID_1 UID_2 labels are not equal => store => 1' ) ; note( 'Leaving tests_resynclabels()' ) ; return ; } sub resynclabels { my( $mysync, $uid1, $uid2, $h1_fir_ref, $h2_fir_ref, $h1_folder ) = @ARG ; if ( not all_defined( $mysync, $uid1, $uid2, $h1_fir_ref, $h2_fir_ref ) ) { return ; } my $labels1 = $h1_fir_ref->{ $uid1 }->{ 'X-GM-LABELS' } || q{} ; my $labels2 = $h2_fir_ref->{ $uid2 }->{ 'X-GM-LABELS' } || q{} ; if ( $mysync->{ subfolder1 } and $labels1 ) { $labels1 = labels_remove_subfolder1( $labels1, $mysync->{ subfolder1 } ) ; } if ( $mysync->{ subfolder2 } and $labels1 ) { $labels1 = labels_add_subfolder2( $labels1, $mysync->{ subfolder2 }, $h1_folder ) ; $labels2 = labels_remove_special( $labels2 ) ; } $mysync->{ debuglabels } and myprint( "Host1 labels fixed: $labels1\n" ) ; $mysync->{ debuglabels } and myprint( "Host2 labels : $labels2\n" ) ; my $store ; if ( $labels1 eq $labels2 ) { # no sync needed $mysync->{ debuglabels } and myprint( "Labels are already equal\n" ) ; return 1 ; } elsif ( not $mysync->{ dry } ) { # sync needed $mysync->{debuglabels} and $mysync->{imap2}->Debug( 1 ) ; $store = $mysync->{imap2}->store( $uid2, "X-GM-LABELS ($labels1)" ) ; $mysync->{debuglabels} and $mysync->{imap2}->Debug( 0 ) ; } return $store ; } sub tests_uniq { note( 'Entering tests_uniq()' ) ; is( 0, uniq( ), 'uniq: undef => 0' ) ; is_deeply( [ 'one' ], [ uniq( 'one' ) ], 'uniq: one => one' ) ; is_deeply( [ 'one' ], [ uniq( 'one', 'one' ) ], 'uniq: one one => one' ) ; is_deeply( [ 'one', 'two' ], [ uniq( 'one', 'one', 'two', 'one', 'two' ) ], 'uniq: one one two one two => one two' ) ; note( 'Leaving tests_uniq()' ) ; return ; } sub uniq { my @list = @ARG ; my %seen = ( ) ; my @uniq = ( ) ; foreach my $item ( @list ) { if ( ! $seen{ $item } ) { $seen{ $item } = 1 ; push( @uniq, $item ) ; } } return @uniq ; } sub length_ref { my $string_ref = shift ; my $string_len = defined ${ $string_ref } ? length( ${ $string_ref } ) : q{} ; # length or empty string return $string_len ; } sub tests_length_ref { note( 'Entering tests_length_ref()' ) ; my $notdefined ; is( q{}, length_ref( \$notdefined ), q{length_ref: value not defined} ) ; my $notref ; is( q{}, length_ref( $notref ), q{length_ref: param not a ref} ) ; my $lala = 'lala' ; is( 4, length_ref( \$lala ), q{length_ref: lala length == 4} ) ; is( 4, length_ref( \'lili' ), q{length_ref: lili length == 4} ) ; note( 'Leaving tests_length_ref()' ) ; return ; } sub date_for_host2 { my( $h1_msg, $h1_idate ) = @_ ; my $h1_date = q{} ; if ( $syncinternaldates ) { $h1_date = $h1_idate ; $sync->{ debug } and myprint( "internal date from host1: [$h1_date]\n" ) ; $h1_date = good_date( $h1_date ) ; $sync->{ debug } and myprint( "internal date from host1: [$h1_date] (fixed)\n" ) ; } if ( $idatefromheader ) { $h1_date = $sync->{imap1}->get_header( $h1_msg, 'Date' ) ; $sync->{ debug } and myprint( "header date from host1: [$h1_date]\n" ) ; $h1_date = good_date( $h1_date ) ; $sync->{ debug } and myprint( "header date from host1: [$h1_date] (fixed)\n" ) ; } return( $h1_date ) ; } sub subject { my $string = shift ; my $subject = q{} ; my $header = extract_header( $string ) ; if( $header =~ m/^Subject:[ \t]*([^\n\r]*)\r?$/msx ) { #myprint( "MMM[$1]\n" ) ; $subject = $1 ; } return( $subject ) ; } sub tests_subject { note( 'Entering tests_subject()' ) ; ok( q{} eq subject( q{} ), 'subject: null') ; is( '', subject( 'Subject:' ), 'Subject:') ; is( '', subject( "Subject:\r\n" ), 'Subject:\r\n') ; ok( 'toto le hero' eq subject( 'Subject: toto le hero' ), 'Subject: toto le hero') ; ok( 'toto le hero' eq subject( 'Subject:toto le hero' ), 'Subject:toto le hero') ; ok( 'toto le hero' eq subject( "Subject:toto le hero\r\n" ), 'Subject: toto le hero\r\n') ; my $MESS ; $MESS = <<'EOF'; From: lalala Subject: toto le hero Date: zzzzzz Boogie boogie EOF ok( 'toto le hero' eq subject( $MESS ), 'subject: toto le hero 2') ; $MESS = <<'EOF'; Subject: toto le hero From: lalala Date: zzzzzz Boogie boogie EOF ok( 'toto le hero' eq subject( $MESS ), 'subject: toto le hero 3') ; $MESS = <<'EOF'; From: lalala Subject: cuicui Date: zzzzzz Subject: toto le hero EOF ok( 'cuicui' eq subject( $MESS ), 'subject: cuicui') ; $MESS = <<'EOF'; From: lalala Date: zzzzzz Subject: toto le hero EOF ok( q{} eq subject( $MESS ), 'subject: null but body could') ; $MESS = <<'EOF'; From: lalala Subject: Date: zzzzzz Subject: toto le hero EOF is( '', subject( $MESS ), 'Subject:') ; note( 'Leaving tests_subject()' ) ; return ; } # GlobVar # $h2_uidguess # ... # # sub append_message_on_host2 { my( $mysync, $string_ref, $h1_fold, $h1_msg, $string_len, $h2_fold, $h1_size, $h1_flags, $h1_date, $cache_dir ) = @_ ; myprint( debugmemory( $mysync, " at A1" ) ) ; my $new_id ; if ( ! $mysync->{dry} ) { $new_id = $mysync->{imap2}->append_string( $h2_fold, ${ $string_ref }, $h1_flags, $h1_date ) ; myprint( debugmemory( $mysync, " at A2" ) ) ; if ( ! defined $new_id ){ my $subject = subject( ${ $string_ref } ) ; my $error_imap = $mysync->{imap2}->LastError || q{} ; my $error = "- msg $h1_fold/$h1_msg {$string_len} could not append ( Subject:[$subject], Date:[$h1_date], Size:[$h1_size], Flags:[$h1_flags] ) to folder $h2_fold: $error_imap\n" ; errors_incr( $mysync, $error ) ; $mysync->{ h1_nb_msg_processed } +=1 ; return ; } else{ # good # $new_id is an id if the IMAP server has the # UIDPLUS capability else just a ref if ( $new_id !~ m{^\d+$}x ) { $new_id = lastuid( $mysync->{imap2}, $h2_fold, $h2_uidguess ) ; } if ( $mysync->{ synclabels } ) { synclabels( $mysync, $h1_msg, $new_id ) } $h2_uidguess += 1 ; $mysync->{ total_bytes_transferred } += $string_len ; $mysync->{ nb_msg_transferred } += 1 ; $mysync->{ h1_nb_msg_processed } +=1 ; $mysync->{ biggest_message_transferred } = max( $string_len, $mysync->{ biggest_message_transferred } ) ; my $time_spent = timesince( $mysync->{begin_transfer_time} ) ; my $rate = bytes_display_string_bin( $mysync->{total_bytes_transferred} / $time_spent ) ; my $eta = eta( $mysync ) ; my $amount_transferred = bytes_display_string_bin( $mysync->{total_bytes_transferred} ) ; myprintf( "msg %s/%-19s copied to %s/%-10s %.2f msgs/s %s/s %s copied %s\n", $h1_fold, "$h1_msg {$string_len}", $h2_fold, $new_id, $mysync->{nb_msg_transferred}/$time_spent, $rate, $amount_transferred, $eta ); sleep_if_needed( $mysync ) ; if ( $usecache and $cacheaftercopy and $new_id =~ m{^\d+$}x ) { $debugcache and myprint( "touch $cache_dir/${h1_msg}_$new_id\n" ) ; touch( "$cache_dir/${h1_msg}_$new_id" ) or croak( "Couldn't touch $cache_dir/${h1_msg}_$new_id" ) ; } if ( $mysync->{ delete1 } ) { delete_message_on_host1( $mysync, $h1_fold, $mysync->{ expungeaftereach }, $h1_msg ) ; } #myprint( "PRESS ENTER" ) and my $a = <> ; return( $new_id ) ; } } else{ $nb_msg_skipped_dry_mode += 1 ; $mysync->{ h1_nb_msg_processed } += 1 ; } return ; } sub tests_sleep_if_needed { note( 'Entering tests_sleep_if_needed()' ) ; is( undef, sleep_if_needed( ), 'sleep_if_needed: no args => undef' ) ; my $mysync ; is( undef, sleep_if_needed( $mysync ), 'sleep_if_needed: arg undef => undef' ) ; $mysync->{maxbytespersecond} = 1000 ; is( 0, sleep_if_needed( $mysync ), 'sleep_if_needed: maxbytespersecond only => no sleep => 0' ) ; $mysync->{begin_transfer_time} = time ; # now is( 0, sleep_if_needed( $mysync ), 'sleep_if_needed: begin_transfer_time now => no sleep => 0' ) ; $mysync->{begin_transfer_time} = time - 2 ; # 2 s before is( 0, sleep_if_needed( $mysync ), 'sleep_if_needed: total_bytes_transferred == 0 => no sleep => 0' ) ; $mysync->{total_bytes_transferred} = 2200 ; $mysync->{begin_transfer_time} = time - 2 ; # 2 s before is( '0.20', sleep_if_needed( $mysync ), 'sleep_if_needed: total_bytes_transferred == 2200 since 2s => sleep 0.2s' ) ; is( '0', sleep_if_needed( $mysync ), 'sleep_if_needed: total_bytes_transferred == 2200 since 2+2 == 4s => no sleep' ) ; $mysync->{maxsleep} = 0.1 ; $mysync->{begin_transfer_time} = time - 2 ; # 2 s before again is( '0.10', sleep_if_needed( $mysync ), 'sleep_if_needed: total_bytes_transferred == 4000 since 2s but maxsleep 0.1s => sleep 0.1s' ) ; $mysync->{maxbytesafter} = 4000 ; $mysync->{begin_transfer_time} = time - 2 ; # 2 s before again is( 0, sleep_if_needed( $mysync ), 'sleep_if_needed: maxbytesafter == total_bytes_transferred => no sleep => 0' ) ; note( 'Leaving tests_sleep_if_needed()' ) ; return ; } sub sleep_if_needed { my( $mysync ) = shift ; if ( ! $mysync ) { return ; } # No need to go further if there is no limit set if ( not ( $mysync->{maxmessagespersecond} or $mysync->{maxbytespersecond} ) ) { return ; } $mysync->{maxsleep} = defined $mysync->{maxsleep} ? $mysync->{maxsleep} : $MAX_SLEEP ; # Must be positive $mysync->{maxsleep} = max( 0, $mysync->{maxsleep} ) ; my $time_spent = timesince( $mysync->{begin_transfer_time} ) ; my $sleep_max_messages = sleep_max_messages( $mysync->{nb_msg_transferred}, $time_spent, $mysync->{maxmessagespersecond} ) ; my $maxbytesafter = $mysync->{maxbytesafter} || 0 ; my $total_bytes_transferred = $mysync->{total_bytes_transferred} || 0 ; my $total_bytes_to_consider = $total_bytes_transferred - $maxbytesafter ; #myprint( "maxbytesafter:$maxbytesafter\n" ) ; #myprint( "total_bytes_to_consider:$total_bytes_to_consider\n" ) ; my $sleep_max_bytes = sleep_max_bytes( $total_bytes_to_consider, $time_spent, $mysync->{maxbytespersecond} ) ; my $sleep_max = min( $mysync->{maxsleep}, max( $sleep_max_messages, $sleep_max_bytes ) ) ; $sleep_max = mysprintf( "%.2f", $sleep_max ) ; # round with 2 decimals. if ( $sleep_max > 0 ) { myprint( "sleeping $sleep_max s\n" ) ; sleep $sleep_max ; # Slept return $sleep_max ; } # No sleep return 0 ; } sub sleep_max_messages { # how long we have to sleep to go under max_messages_per_second my( $nb_msg_transferred, $time_spent, $maxmessagespersecond ) = @_ ; if ( ( not defined $maxmessagespersecond ) or $maxmessagespersecond <= 0 ) { return( 0 ) } ; my $sleep = ( $nb_msg_transferred / $maxmessagespersecond ) - $time_spent ; # the sleep must be positive return( max( 0, $sleep ) ) ; } sub tests_sleep_max_messages { note( 'Entering tests_sleep_max_messages()' ) ; ok( 0 == sleep_max_messages( 4, 2, undef ), 'sleep_max_messages: maxmessagespersecond = undef') ; ok( 0 == sleep_max_messages( 4, 2, 0 ), 'sleep_max_messages: maxmessagespersecond = 0') ; ok( 0 == sleep_max_messages( 4, 2, $MINUS_ONE ), 'sleep_max_messages: maxmessagespersecond = -1') ; ok( 0 == sleep_max_messages( 4, 2, 2 ), 'sleep_max_messages: maxmessagespersecond = 2 max reached') ; ok( 2 == sleep_max_messages( 8, 2, 2 ), 'sleep_max_messages: maxmessagespersecond = 2 max over') ; ok( 0 == sleep_max_messages( 2, 2, 2 ), 'sleep_max_messages: maxmessagespersecond = 2 max not reached') ; note( 'Leaving tests_sleep_max_messages()' ) ; return ; } sub sleep_max_bytes { # how long we have to sleep to go under max_bytes_per_second my( $total_bytes_to_consider, $time_spent, $maxbytespersecond ) = @_ ; $total_bytes_to_consider ||= 0 ; $time_spent ||= 0 ; if ( ( not defined $maxbytespersecond ) or $maxbytespersecond <= 0 ) { return( 0 ) } ; #myprint( "total_bytes_to_consider:$total_bytes_to_consider\n" ) ; my $sleep = ( $total_bytes_to_consider / $maxbytespersecond ) - $time_spent ; # the sleep must be positive return( max( 0, $sleep ) ) ; } sub tests_sleep_max_bytes { note( 'Entering tests_sleep_max_bytes()' ) ; ok( 0 == sleep_max_bytes( 4000, 2, undef ), 'sleep_max_bytes: maxbytespersecond == undef => sleep 0' ) ; ok( 0 == sleep_max_bytes( 4000, 2, 0 ), 'sleep_max_bytes: maxbytespersecond = 0 => sleep 0') ; ok( 0 == sleep_max_bytes( 4000, 2, $MINUS_ONE ), 'sleep_max_bytes: maxbytespersecond = -1 => sleep 0') ; ok( 0 == sleep_max_bytes( 4000, 2, 2000 ), 'sleep_max_bytes: maxbytespersecond = 2k max reached sharp => sleep 0') ; ok( 2 == sleep_max_bytes( 8000, 2, 2000 ), 'sleep_max_bytes: maxbytespersecond = 2k max over => sleep a little') ; ok( 0 == sleep_max_bytes( -8000, 2, 2000 ), 'sleep_max_bytes: maxbytespersecond = 2k max not reached => sleep 0') ; ok( 0 == sleep_max_bytes( 2000, 2, 2000 ), 'sleep_max_bytes: maxbytespersecond = 2k max not reached => sleep 0') ; ok( 0 == sleep_max_bytes( -2000, 2, 1000 ), 'sleep_max_bytes: maxbytespersecond = 1k max not reached => sleep 0') ; note( 'Leaving tests_sleep_max_bytes()' ) ; return ; } sub delete_message_on_host1 { my( $mysync, $h1_fold, $expunge, @h1_msg ) = @_ ; if ( ! $mysync->{ delete1 } ) { return ; } if ( ! @h1_msg ) { return ; } delete_messages_on_any( $mysync, $mysync->{ acc1 }, $mysync->{ imap1 }, "Host1: $h1_fold", $expunge, $split1, @h1_msg ) ; return ; } sub tests_operators_and_exclam_precedence { note( 'Entering tests_operators_and_exclam_precedence()' ) ; is( 1, ! 0, 'tests_operators_and_exclam_precedence: ! 0 => 1' ) ; is( "", ! 1, 'tests_operators_and_exclam_precedence: ! 1 => ""' ) ; is( 1, not( 0 ), 'tests_operators_and_exclam_precedence: not( 0 ) => 1' ) ; is( "", not( 1 ), 'tests_operators_and_exclam_precedence: not( 1 ) => ""' ) ; # I wrote those tests to avoid perlcrit "Mixed high and low-precedence booleans" # and change sub delete_messages_on_any() but got 4 more warnings... So now commented. #is( 0, ( ! 0 and 0 ), 'tests_operators_and_exclam_precedence: ! 0 and 0 ) => 0' ) ; #is( 1, ( ! 0 and 1 ), 'tests_operators_and_exclam_precedence: ! 0 and 1 ) => 1' ) ; #is( "", ( ! 1 and 0 ), 'tests_operators_and_exclam_precedence: ! 1 and 0 ) => ""' ) ; #is( "", ( ! 1 and 1 ), 'tests_operators_and_exclam_precedence: ! 1 and 1 ) => ""' ) ; is( 0, ( ! 0 && 0 ), 'tests_operators_and_exclam_precedence: ! 0 && 0 ) => 0' ) ; is( 1, ( ! 0 && 1 ), 'tests_operators_and_exclam_precedence: ! 0 && 1 ) => 1' ) ; is( "", ( ! 1 && 0 ), 'tests_operators_and_exclam_precedence: ! 1 && 0 ) => ""' ) ; is( "", ( ! 1 && 1 ), 'tests_operators_and_exclam_precedence: ! 1 && 1 ) => ""' ) ; is( 2, ( ! 0 && 2 ), 'tests_operators_and_exclam_precedence: ! 0 && 2 ) => 1' ) ; note( 'Leaving tests_operators_and_exclam_precedence()' ) ; return ; } sub delete_messages_on_any { # $acc is not used yet, # my( $mysync, $acc, $imap, $hostX_folder, $expunge, $split, @messages ) = @_ ; my $expunge_message = q{} ; my $dry_message = $mysync->{ dry_message } ; $expunge_message = 'and expunged' if ( $expunge ) ; # "Host1: msg " # $imap->Debug( 1 ) ; my @messages_to_mark_deleted = @messages ; while ( my @messages_part = splice @messages_to_mark_deleted, 0, $split ) { foreach my $message ( @messages_part ) { myprint( "$hostX_folder/$message marking deleted $expunge_message $dry_message\n" ) ; } if ( ! $mysync->{dry} && @messages_part ) { my $nb_deleted = $imap->delete_message( $imap->Range( @messages_part ) ) ; if ( defined $nb_deleted ) { # $nb_deleted is not accurate $acc->{ nb_msg_deleted } += scalar @messages_part ; } else { my $error_imap = $imap->LastError || q{} ; my $error = join( q{}, "$hostX_folder folder, could not delete ", scalar @messages_part, ' messages: ', $error_imap, "\n" ) ; errors_incr( $mysync, $error ) ; } } } if ( $expunge ) { uidexpunge_or_expunge( $mysync, $imap, @messages ) ; } #$imap->Debug( 0 ) ; return ; } sub tests_uidexpunge_or_expunge { note( 'Entering tests_uidexpunge_or_expunge()' ) ; is( undef, uidexpunge_or_expunge( ), 'uidexpunge_or_expunge: no args => undef' ) ; my $mysync ; is( undef, uidexpunge_or_expunge( $mysync ), 'uidexpunge_or_expunge: undef args => undef' ) ; $mysync = {} ; is( undef, uidexpunge_or_expunge( $mysync ), 'uidexpunge_or_expunge: arg empty => undef' ) ; my $imap ; is( undef, uidexpunge_or_expunge( $mysync, $imap ), 'uidexpunge_or_expunge: undef Mail-IMAPClient instance => undef' ) ; require_ok( "Test::MockObject" ) ; $imap = Test::MockObject->new( ) ; is( undef, uidexpunge_or_expunge( $mysync, $imap ), 'uidexpunge_or_expunge: no message (1) to uidexpunge => undef' ) ; my @messages = ( ) ; is( undef, uidexpunge_or_expunge( $mysync, $imap, @messages ), 'uidexpunge_or_expunge: no message (2) to uidexpunge => undef' ) ; @messages = ( '2', '1' ) ; $imap->mock( 'uidexpunge', sub { return ; } ) ; $imap->mock( 'expunge', sub { return ; } ) ; is( undef, uidexpunge_or_expunge( $mysync, $imap, @messages ), 'uidexpunge_or_expunge: uidexpunge failure => expunge failure => undef' ) ; $imap->mock( 'expunge', sub { return 1 ; } ) ; is( 1, uidexpunge_or_expunge( $mysync, $imap, @messages ), 'uidexpunge_or_expunge: uidexpunge failure => expunge ok => 1' ) ; $imap->mock( 'uidexpunge', sub { return 1 ; } ) ; is( 1, uidexpunge_or_expunge( $mysync, $imap, @messages ), 'uidexpunge_or_expunge: messages to uidexpunge ok => 1' ) ; note( 'Leaving tests_uidexpunge_or_expunge()' ) ; return ; } sub uidexpunge_or_expunge { my $mysync = shift ; my $imap = shift ; my @messages = @ARG ; if ( ! $imap ) { return ; } ; if ( ! @messages ) { return ; } ; # Doing uidexpunge my @uidexpunge_result = $imap->uidexpunge( @messages ) ; if ( @uidexpunge_result ) { return 1 ; } # Failure so doing expunge my $expunge_result = $imap->expunge( ) ; if ( $expunge_result ) { return 1 ; } # bad trip return ; } sub eta_print { my $mysync = shift ; if ( my $eta = eta( $mysync ) ) { myprint( "$eta\n" ) ; } return ; } sub tests_eta { note( 'Entering tests_eta()' ) ; is( q{}, eta( ), 'eta: no args => ""' ) ; is( q{}, eta( undef ), 'eta: undef => ""' ) ; my $mysync = {} ; # No foldersizes is( q{}, eta( $mysync ), 'eta: No foldersizes => ""' ) ; $mysync->{ foldersizes } = 1 ; $mysync->{ begin_transfer_time } = time ; # Now $mysync->{ h1_nb_msg_processed } = 0 ; is( "ETA: " . localtimez( time ) . " 0 s 0/0 msgs left", eta( $mysync ), 'eta: no args => ETA: "Now" 0 s 0/0 msgs left' ) ; $mysync->{ h1_nb_msg_processed } = 1 ; $mysync->{ h1_nb_msg_start } = 2 ; is( "ETA: " . localtimez( time ) . " 0 s 1/2 msgs left", eta( $mysync ), 'eta: 1, 1, 2 => ETA: "Now" 0 s 1/2 msgs left' ) ; note( 'Leaving tests_eta()' ) ; return ; } sub eta { my( $mysync ) = shift ; if ( ! $mysync ) { return q{} ; } return( q{} ) if not $mysync->{ foldersizes } ; my $h1_nb_msg_start = $mysync->{ h1_nb_msg_start } ; my $h1_nb_processed = $mysync->{ h1_nb_msg_processed } ; my $nb_msg_transferred = ( $mysync->{dry} ) ? $mysync->{ h1_nb_msg_processed } : $mysync->{ nb_msg_transferred } ; my $time_spent = timesince( $mysync->{ begin_transfer_time } ) ; $h1_nb_processed ||= 0 ; $h1_nb_msg_start ||= 0 ; $time_spent ||= 0 ; my $time_remaining = time_remaining( $time_spent, $h1_nb_processed, $h1_nb_msg_start, $nb_msg_transferred ) ; $mysync->{ debug } and myprint( "time_spent: $time_spent time_remaining: $time_remaining\n" ) ; my $nb_msg_remaining = $h1_nb_msg_start - $h1_nb_processed ; my $eta_date = localtimez( time + $time_remaining ) ; return( mysprintf( 'ETA: %s %1.0f s %s/%s msgs left', $eta_date, $time_remaining, $nb_msg_remaining, $h1_nb_msg_start ) ) ; } sub time_remaining { my( $my_time_spent, $h1_nb_processed, $h1_nb_msg_start, $nb_transferred ) = @_ ; $nb_transferred ||= 1 ; # At least one is done (no division by zero) $h1_nb_processed ||= 0 ; $h1_nb_msg_start ||= $h1_nb_processed ; $my_time_spent ||= 0 ; my $time_remaining = ( $my_time_spent / $nb_transferred ) * ( $h1_nb_msg_start - $h1_nb_processed ) ; return( $time_remaining ) ; } sub tests_time_remaining { note( 'Entering tests_time_remaining()' ) ; # time_spent, nb_processed, nb_to_do_total, nb_transferred is( 0, time_remaining( ), 'time_remaining: no args -> 0' ) ; is( 0, time_remaining( 0, 0, 0, 0 ), 'time_remaining: 0, 0, 0, 0 -> 0' ) ; is( 1, time_remaining( 1, 1, 2, 1 ), 'time_remaining: 1, 1, 2, 1 -> 1' ) ; is( 1, time_remaining( 9, 9, 10, 9 ), 'time_remaining: 9, 9, 10, 9 -> 1' ) ; is( 9, time_remaining( 1, 1, 10, 1 ), 'time_remaining: 1, 1, 10, 1 -> 9' ) ; is( 5, time_remaining( 5, 5, 10, 5 ), 'time_remaining: 5, 5, 10, 5 -> 5' ) ; is( 25, time_remaining( 5, 5, 10, 0 ), 'time_remaining: 5, 5, 10, 0 -> ( 5 / 1 ) * ( 10 - 5) = 25' ) ; is( 25, time_remaining( 5, 5, 10, 1 ), 'time_remaining: 5, 5, 10, 1 -> ( 5 / 1 ) * ( 10 - 5) = 25' ) ; note( 'Leaving tests_time_remaining()' ) ; return ; } sub cache_map { my ( $cache_files_ref, $h1_msgs_ref, $h2_msgs_ref ) = @_; my ( %map1_2, %map2_1, %done2 ) ; my $h1_msgs_hash_ref = { } ; my $h2_msgs_hash_ref = { } ; @{ $h1_msgs_hash_ref }{ @{ $h1_msgs_ref } } = ( ) ; @{ $h2_msgs_hash_ref }{ @{ $h2_msgs_ref } } = ( ) ; foreach my $file ( sort @{ $cache_files_ref } ) { $debugcache and myprint( "C12: $file\n" ) ; ( $uid1, $uid2 ) = match_a_cache_file( $file ) ; if ( exists( $h1_msgs_hash_ref->{ defined $uid1 ? $uid1 : q{} } ) and exists( $h2_msgs_hash_ref->{ defined $uid2 ? $uid2 : q{} } ) ) { # keep only the greatest uid2 # 130_2301 and # 130_231 => keep only 130 -> 2301 # keep only the greatest uid1 # 1601_260 and # 161_260 => keep only 1601 -> 260 my $max_uid2 = max( $uid2, $map1_2{ $uid1 } || $MINUS_ONE ) ; if ( exists $done2{ $max_uid2 } ) { if ( $done2{ $max_uid2 } < $uid1 ) { $map1_2{ $uid1 } = $max_uid2 ; delete $map1_2{ $done2{ $max_uid2 } } ; $done2{ $max_uid2 } = $uid1 ; } }else{ $map1_2{ $uid1 } = $max_uid2 ; $done2{ $max_uid2 } = $uid1 ; } }; } %map2_1 = reverse %map1_2 ; return( \%map1_2, \%map2_1) ; } sub tests_cache_map { note( 'Entering tests_cache_map()' ) ; #$debugcache = 1 ; my @cache_files = qw ( 100_200 101_201 120_220 142_242 143_243 177_277 177_278 177_279 155_255 180_280 181_280 182_280 130_231 130_2301 161_260 1601_260 ) ; my $msgs_1 = [120, 142, 143, 144, 161, 1601, 177, 182, 130 ]; my $msgs_2 = [ 242, 243, 260, 299, 377, 279, 255, 280, 231, 2301 ]; my( $c12, $c21 ) ; ok( ( $c12, $c21 ) = cache_map( \@cache_files, $msgs_1, $msgs_2 ), 'cache_map: 02' ); my $a1 = [ sort { $a <=> $b } keys %{ $c12 } ] ; my $a2 = [ sort { $a <=> $b } keys %{ $c21 } ] ; ok( 0 == compare_lists( [ 130, 142, 143, 177, 182, 1601 ], $a1 ), 'cache_map: 03' ); ok( 0 == compare_lists( [ 242, 243, 260, 279, 280, 2301 ], $a2 ), 'cache_map: 04' ); ok( ! $c12->{161}, 'cache_map: ! 161 -> 260' ); ok( 260 == $c12->{1601}, 'cache_map: 1601 -> 260' ); ok( 2301 == $c12->{130}, 'cache_map: 130 -> 2301' ); #myprint( $c12->{1601}, "\n" ) ; note( 'Leaving tests_cache_map()' ) ; return ; } sub cache_dir_fix { my $cache_dir = shift ; $cache_dir =~ s/([;<>\*\|`&\$!#\(\)\[\]\{\}:'"\\])/\\$1/xg ; #myprint( "cache_dir_fix: $cache_dir\n" ) ; return( $cache_dir ) ; } sub tests_cache_dir_fix { note( 'Entering tests_cache_dir_fix()' ) ; ok( 'lalala' eq cache_dir_fix('lalala'), 'cache_dir_fix: lalala -> lalala' ); ok( 'ii\\\\ii' eq cache_dir_fix('ii\ii'), 'cache_dir_fix: ii\ii -> ii\\\\ii' ); ok( 'ii@ii' eq cache_dir_fix('ii@ii'), 'cache_dir_fix: ii@ii -> ii@ii' ); ok( 'ii@ii\\:ii' eq cache_dir_fix('ii@ii:ii'), 'cache_dir_fix: ii@ii:ii -> ii@ii\\:ii' ); ok( 'i\\\\i\\\\ii' eq cache_dir_fix('i\i\ii'), 'cache_dir_fix: i\i\ii -> i\\\\i\\\\ii' ); ok( 'i\\\\ii' eq cache_dir_fix('i\\ii'), 'cache_dir_fix: i\\ii -> i\\\\\\\\ii' ); ok( '\\\\ ' eq cache_dir_fix('\\ '), 'cache_dir_fix: \\ -> \\\\\ ' ); ok( '\\\\ ' eq cache_dir_fix('\ '), 'cache_dir_fix: \ -> \\\\\ ' ); ok( '\[bracket\]' eq cache_dir_fix('[bracket]'), 'cache_dir_fix: [bracket] -> \[bracket\]' ); note( 'Leaving tests_cache_dir_fix()' ) ; return ; } sub cache_dir_fix_win { my $cache_dir = shift ; $cache_dir =~ s/(\[|\])/[$1]/xg ; #myprint( "cache_dir_fix_win: $cache_dir\n" ) ; return( $cache_dir ) ; } sub tests_cache_dir_fix_win { note( 'Entering tests_cache_dir_fix_win()' ) ; ok( 'lalala' eq cache_dir_fix_win('lalala'), 'cache_dir_fix_win: lalala -> lalala' ); ok( '[[]bracket[]]' eq cache_dir_fix_win('[bracket]'), 'cache_dir_fix_win: [bracket] -> [[]bracket[]]' ); note( 'Leaving tests_cache_dir_fix_win()' ) ; return ; } sub get_cache { my ( $cache_dir, $h1_msgs_ref, $h2_msgs_ref, $h1_msgs_all_hash_ref, $h2_msgs_all_hash_ref ) = @_; $debugcache and myprint( "Entering get_cache\n" ) ; -d $cache_dir or return( undef ); # exit if cache directory doesn't exist $debugcache and myprint( "cache_dir : $cache_dir\n" ) ; if ( 'MSWin32' ne $OSNAME ) { $cache_dir = cache_dir_fix( $cache_dir ) ; }else{ $cache_dir = cache_dir_fix_win( $cache_dir ) ; } $debugcache and myprint( "cache_dir_fix: $cache_dir\n" ) ; my @cache_files = bsd_glob( "$cache_dir/*" ) ; #$debugcache and myprint( "cache_files: [@cache_files]\n" ) ; $debugcache and myprint( 'cache_files: ', scalar @cache_files , " files found\n" ) ; my( $cache_1_2_ref, $cache_2_1_ref ) = cache_map( \@cache_files, $h1_msgs_ref, $h2_msgs_ref ) ; clean_cache( \@cache_files, $cache_1_2_ref, $h1_msgs_all_hash_ref, $h2_msgs_all_hash_ref ) ; $debugcache and myprint( "Exiting get_cache\n" ) ; return( $cache_1_2_ref, $cache_2_1_ref ) ; } sub tests_get_cache { note( 'Entering tests_get_cache()' ) ; ok( not( get_cache('/cache_no_exist') ), 'get_cache: /cache_no_exist' ); ok( ( not -d 'W/tmp/cache/F1/F2' or rmtree( 'W/tmp/cache/F1/F2' ) ), 'get_cache: rmtree W/tmp/cache/F1/F2' ) ; ok( mkpath( 'W/tmp/cache/F1/F2' ), 'get_cache: mkpath W/tmp/cache/F1/F2' ) ; my @test_files_cache = ( qw( W/tmp/cache/F1/F2/100_200 W/tmp/cache/F1/F2/101_201 W/tmp/cache/F1/F2/120_220 W/tmp/cache/F1/F2/142_242 W/tmp/cache/F1/F2/143_243 W/tmp/cache/F1/F2/177_277 W/tmp/cache/F1/F2/177_377 W/tmp/cache/F1/F2/177_777 W/tmp/cache/F1/F2/155_255 ) ) ; ok( touch( @test_files_cache ), 'get_cache: touch W/tmp/cache/F1/F2/...' ) ; # on cache: 100_200 101_201 142_242 143_243 177_277 177_377 177_777 155_255 # on live: my $msgs_1 = [120, 142, 143, 144, 177 ]; my $msgs_2 = [ 242, 243, 299, 377, 777, 255 ]; my $msgs_all_1 = { 120 => 0, 142 => 0, 143 => 0, 144 => 0, 177 => 0 } ; my $msgs_all_2 = { 242 => 0, 243 => 0, 299 => 0, 377 => 0, 777 => 0, 255 => 0 } ; my( $c12, $c21 ) ; ok( ( $c12, $c21 ) = get_cache( 'W/tmp/cache/F1/F2', $msgs_1, $msgs_2, $msgs_all_1, $msgs_all_2 ), 'get_cache: 02' ); my $a1 = [ sort { $a <=> $b } keys %{ $c12 } ] ; my $a2 = [ sort { $a <=> $b } keys %{ $c21 } ] ; ok( 0 == compare_lists( [ 142, 143, 177 ], $a1 ), 'get_cache: 03' ); ok( 0 == compare_lists( [ 242, 243, 777 ], $a2 ), 'get_cache: 04' ); ok( -f 'W/tmp/cache/F1/F2/142_242', 'get_cache: file kept 142_242'); ok( -f 'W/tmp/cache/F1/F2/142_242', 'get_cache: file kept 143_243'); ok( ! -f 'W/tmp/cache/F1/F2/100_200', 'get_cache: file removed 100_200'); ok( ! -f 'W/tmp/cache/F1/F2/101_201', 'get_cache: file removed 101_201'); # test clean_cache executed $maxage = 2 ; ok( touch(@test_files_cache), 'get_cache: touch W/tmp/cache/F1/F2/...' ) ; ok( ( $c12, $c21 ) = get_cache('W/tmp/cache/F1/F2', $msgs_1, $msgs_2, $msgs_all_1, $msgs_all_2 ), 'get_cache: 02' ); ok( -f 'W/tmp/cache/F1/F2/142_242', 'get_cache: file kept 142_242'); ok( -f 'W/tmp/cache/F1/F2/142_242', 'get_cache: file kept 143_243'); ok( ! -f 'W/tmp/cache/F1/F2/100_200', 'get_cache: file NOT removed 100_200'); ok( ! -f 'W/tmp/cache/F1/F2/101_201', 'get_cache: file NOT removed 101_201'); # strange files #$debugcache = 1 ; $maxage = undef ; ok( ( not -d 'W/tmp/cache/rr\uee' or rmtree( 'W/tmp/cache/rr\uee' )), 'get_cache: rmtree W/tmp/cache/rr\uee' ) ; ok( mkpath( 'W/tmp/cache/rr\uee' ), 'get_cache: mkpath W/tmp/cache/rr\uee' ) ; @test_files_cache = ( qw( W/tmp/cache/rr\uee/100_200 W/tmp/cache/rr\uee/101_201 W/tmp/cache/rr\uee/120_220 W/tmp/cache/rr\uee/142_242 W/tmp/cache/rr\uee/143_243 W/tmp/cache/rr\uee/177_277 W/tmp/cache/rr\uee/177_377 W/tmp/cache/rr\uee/177_777 W/tmp/cache/rr\uee/155_255 ) ) ; ok( touch(@test_files_cache), 'get_cache: touch strange W/tmp/cache/...' ) ; # on cache: 100_200 101_201 142_242 143_243 177_277 177_377 177_777 155_255 # on live: $msgs_1 = [120, 142, 143, 144, 177 ] ; $msgs_2 = [ 242, 243, 299, 377, 777, 255 ] ; $msgs_all_1 = { 120 => q{}, 142 => q{}, 143 => q{}, 144 => q{}, 177 => q{} } ; $msgs_all_2 = { 242 => q{}, 243 => q{}, 299 => q{}, 377 => q{}, 777 => q{}, 255 => q{} } ; ok( ( $c12, $c21 ) = get_cache('W/tmp/cache/rr\uee', $msgs_1, $msgs_2, $msgs_all_1, $msgs_all_2), 'get_cache: strange path 02' ); $a1 = [ sort { $a <=> $b } keys %{ $c12 } ] ; $a2 = [ sort { $a <=> $b } keys %{ $c21 } ] ; ok( 0 == compare_lists( [ 142, 143, 177 ], $a1 ), 'get_cache: strange path 03' ); ok( 0 == compare_lists( [ 242, 243, 777 ], $a2 ), 'get_cache: strange path 04' ); ok( -f 'W/tmp/cache/rr\uee/142_242', 'get_cache: strange path file kept 142_242'); ok( -f 'W/tmp/cache/rr\uee/142_242', 'get_cache: strange path file kept 143_243'); ok( ! -f 'W/tmp/cache/rr\uee/100_200', 'get_cache: strange path file removed 100_200'); ok( ! -f 'W/tmp/cache/rr\uee/101_201', 'get_cache: strange path file removed 101_201'); note( 'Leaving tests_get_cache()' ) ; return ; } sub match_a_cache_file { my $file = shift ; my ( $cache_uid1, $cache_uid2 ) ; return( ( undef, undef ) ) if ( ! $file ) ; if ( $file =~ m{(?:^|/)(\d+)_(\d+)$}x ) { $cache_uid1 = $1 ; $cache_uid2 = $2 ; } return( $cache_uid1, $cache_uid2 ) ; } sub tests_match_a_cache_file { note( 'Entering tests_match_a_cache_file()' ) ; my ( $tuid1, $tuid2 ) ; ok( ( $tuid1, $tuid2 ) = match_a_cache_file( ), 'match_a_cache_file: no arg' ) ; ok( ! defined $tuid1 , 'match_a_cache_file: no arg 1' ) ; ok( ! defined $tuid2 , 'match_a_cache_file: no arg 2' ) ; ok( ( $tuid1, $tuid2 ) = match_a_cache_file( q{} ), 'match_a_cache_file: empty arg' ) ; ok( ! defined $tuid1 , 'match_a_cache_file: empty arg 1' ) ; ok( ! defined $tuid2 , 'match_a_cache_file: empty arg 2' ) ; ok( ( $tuid1, $tuid2 ) = match_a_cache_file( '000_000' ), 'match_a_cache_file: 000_000' ) ; ok( '000' eq $tuid1, 'match_a_cache_file: 000_000 1' ) ; ok( '000' eq $tuid2, 'match_a_cache_file: 000_000 2' ) ; ok( ( $tuid1, $tuid2 ) = match_a_cache_file( '123_456' ), 'match_a_cache_file: 123_456' ) ; ok( '123' eq $tuid1, 'match_a_cache_file: 123_456 1' ) ; ok( '456' eq $tuid2, 'match_a_cache_file: 123_456 2' ) ; ok( ( $tuid1, $tuid2 ) = match_a_cache_file( '/tmp/truc/123_456' ), 'match_a_cache_file: /tmp/truc/123_456' ) ; ok( '123' eq $tuid1, 'match_a_cache_file: /tmp/truc/123_456 1' ) ; ok( '456' eq $tuid2, 'match_a_cache_file: /tmp/truc/123_456 2' ) ; ok( ( $tuid1, $tuid2 ) = match_a_cache_file( '/lala123_456' ), 'match_a_cache_file: NO /lala123_456' ) ; ok( ! $tuid1, 'match_a_cache_file: /lala123_456 1' ) ; ok( ! $tuid2, 'match_a_cache_file: /lala123_456 2' ) ; ok( ( $tuid1, $tuid2 ) = match_a_cache_file( 'la123_456' ), 'match_a_cache_file: NO la123_456' ) ; ok( ! $tuid1, 'match_a_cache_file: la123_456 1' ) ; ok( ! $tuid2, 'match_a_cache_file: la123_456 2' ) ; note( 'Leaving tests_match_a_cache_file()' ) ; return ; } sub clean_cache { my ( $cache_files_ref, $cache_1_2_ref, $h1_msgs_all_hash_ref, $h2_msgs_all_hash_ref ) = @_ ; $debugcache and myprint( "Entering clean_cache\n" ) ; $debugcache and myprint( map { "$_ -> " . $cache_1_2_ref->{ $_ } . "\n" } keys %{ $cache_1_2_ref } ) ; foreach my $file ( @{ $cache_files_ref } ) { $debugcache and myprint( "$file\n" ) ; my ( $cache_uid1, $cache_uid2 ) = match_a_cache_file( $file ) ; $debugcache and myprint( "u1: $cache_uid1 u2: $cache_uid2 c12: ", $cache_1_2_ref->{ $cache_uid1 } || q{}, "\n") ; # or ( ! exists( $cache_1_2_ref->{ $cache_uid1 } ) ) # or ( ! ( $cache_uid2 == $cache_1_2_ref->{ $cache_uid1 } ) ) if ( ( not defined $cache_uid1 ) or ( not defined $cache_uid2 ) or ( not exists $h1_msgs_all_hash_ref->{ $cache_uid1 } ) or ( not exists $h2_msgs_all_hash_ref->{ $cache_uid2 } ) ) { $debugcache and myprint( "remove $file\n" ) ; unlink $file or myprint( "$OS_ERROR" ) ; } } $debugcache and myprint( "Exiting clean_cache\n" ) ; return( 1 ) ; } sub tests_clean_cache { note( 'Entering tests_clean_cache()' ) ; ok( ( not -d 'W/tmp/cache/G1/G2' or rmtree( 'W/tmp/cache/G1/G2' )), 'clean_cache: rmtree W/tmp/cache/G1/G2' ) ; ok( mkpath( 'W/tmp/cache/G1/G2' ), 'clean_cache: mkpath W/tmp/cache/G1/G2' ) ; my @test_files_cache = ( qw( W/tmp/cache/G1/G2/100_200 W/tmp/cache/G1/G2/101_201 W/tmp/cache/G1/G2/120_220 W/tmp/cache/G1/G2/142_242 W/tmp/cache/G1/G2/143_243 W/tmp/cache/G1/G2/177_277 W/tmp/cache/G1/G2/177_377 W/tmp/cache/G1/G2/177_777 W/tmp/cache/G1/G2/155_255 ) ) ; ok( touch(@test_files_cache), 'clean_cache: touch W/tmp/cache/G1/G2/...' ) ; ok( -f 'W/tmp/cache/G1/G2/100_200', 'clean_cache: 100_200 before' ); ok( -f 'W/tmp/cache/G1/G2/142_242', 'clean_cache: 142_242 before' ); ok( -f 'W/tmp/cache/G1/G2/177_277', 'clean_cache: 177_277 before' ); ok( -f 'W/tmp/cache/G1/G2/177_377', 'clean_cache: 177_377 before' ); ok( -f 'W/tmp/cache/G1/G2/177_777', 'clean_cache: 177_777 before' ); ok( -f 'W/tmp/cache/G1/G2/155_255', 'clean_cache: 155_255 before' ); my $cache = { 142 => 242, 177 => 777, } ; my $all_1 = { 142 => q{}, 177 => q{}, } ; my $all_2 = { 200 => q{}, 242 => q{}, 777 => q{}, } ; ok( clean_cache( \@test_files_cache, $cache, $all_1, $all_2 ), 'clean_cache: ' ) ; ok( ! -f 'W/tmp/cache/G1/G2/100_200', 'clean_cache: 100_200 after' ); ok( -f 'W/tmp/cache/G1/G2/142_242', 'clean_cache: 142_242 after' ); ok( ! -f 'W/tmp/cache/G1/G2/177_277', 'clean_cache: 177_277 after' ); ok( ! -f 'W/tmp/cache/G1/G2/177_377', 'clean_cache: 177_377 after' ); ok( -f 'W/tmp/cache/G1/G2/177_777', 'clean_cache: 177_777 after' ); ok( ! -f 'W/tmp/cache/G1/G2/155_255', 'clean_cache: 155_255 after' ); note( 'Leaving tests_clean_cache()' ) ; return ; } sub tests_clean_cache_2 { note( 'Entering tests_clean_cache_2()' ) ; ok( ( not -d 'W/tmp/cache/G1/G2' or rmtree( 'W/tmp/cache/G1/G2' )), 'clean_cache_2: rmtree W/tmp/cache/G1/G2' ) ; ok( mkpath( 'W/tmp/cache/G1/G2' ), 'clean_cache_2: mkpath W/tmp/cache/G1/G2' ) ; my @test_files_cache = ( qw( W/tmp/cache/G1/G2/100_200 W/tmp/cache/G1/G2/101_201 W/tmp/cache/G1/G2/120_220 W/tmp/cache/G1/G2/142_242 W/tmp/cache/G1/G2/143_243 W/tmp/cache/G1/G2/177_277 W/tmp/cache/G1/G2/177_377 W/tmp/cache/G1/G2/177_777 W/tmp/cache/G1/G2/155_255 ) ) ; ok( touch(@test_files_cache), 'clean_cache_2: touch W/tmp/cache/G1/G2/...' ) ; ok( -f 'W/tmp/cache/G1/G2/100_200', 'clean_cache_2: 100_200 before' ); ok( -f 'W/tmp/cache/G1/G2/142_242', 'clean_cache_2: 142_242 before' ); ok( -f 'W/tmp/cache/G1/G2/177_277', 'clean_cache_2: 177_277 before' ); ok( -f 'W/tmp/cache/G1/G2/177_377', 'clean_cache_2: 177_377 before' ); ok( -f 'W/tmp/cache/G1/G2/177_777', 'clean_cache_2: 177_777 before' ); ok( -f 'W/tmp/cache/G1/G2/155_255', 'clean_cache_2: 155_255 before' ); my $cache = { 142 => 242, 177 => 777, } ; my $all_1 = { $NUMBER_100 => q{}, 142 => q{}, 177 => q{}, } ; my $all_2 = { 200 => q{}, 242 => q{}, 777 => q{}, } ; ok( clean_cache( \@test_files_cache, $cache, $all_1, $all_2 ), 'clean_cache_2: ' ) ; ok( -f 'W/tmp/cache/G1/G2/100_200', 'clean_cache_2: 100_200 after' ); ok( -f 'W/tmp/cache/G1/G2/142_242', 'clean_cache_2: 142_242 after' ); ok( ! -f 'W/tmp/cache/G1/G2/177_277', 'clean_cache_2: 177_277 after' ); ok( ! -f 'W/tmp/cache/G1/G2/177_377', 'clean_cache_2: 177_377 after' ); ok( -f 'W/tmp/cache/G1/G2/177_777', 'clean_cache_2: 177_777 after' ); ok( ! -f 'W/tmp/cache/G1/G2/155_255', 'clean_cache_2: 155_255 after' ); note( 'Leaving tests_clean_cache_2()' ) ; return ; } sub tests_mkpath { note( 'Entering tests_mkpath()' ) ; ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' )), 'mkpath: mkpath W/tmp/tests/' ) ; SKIP: { skip( 'Tests only for Unix', 10 ) if ( 'MSWin32' eq $OSNAME ) ; my $long_path_unix = '123456789/' x 30 ; ok( ( -d "W/tmp/tests/long/$long_path_unix" or mkpath( "W/tmp/tests/long/$long_path_unix" ) ), 'mkpath: mkpath 300 char' ) ; ok( -d "W/tmp/tests/long/$long_path_unix", 'mkpath: mkpath > 300 char verified' ) ; ok( ( -d "W/tmp/tests/long/$long_path_unix" and rmtree( 'W/tmp/tests/long/' ) ), 'mkpath: rmtree 300 char' ) ; ok( ! -d "W/tmp/tests/long/$long_path_unix", 'mkpath: rmtree 300 char verified' ) ; ok( ( -d 'W/tmp/tests/trailing_dots...' or mkpath( 'W/tmp/tests/trailing_dots...' ) ), 'mkpath: mkpath trailing_dots...' ) ; ok( -d 'W/tmp/tests/trailing_dots...', 'mkpath: mkpath trailing_dots... verified' ) ; ok( ( -d 'W/tmp/tests/trailing_dots...' and rmtree( 'W/tmp/tests/trailing_dots...' ) ), 'mkpath: rmtree trailing_dots...' ) ; ok( ! -d 'W/tmp/tests/trailing_dots...', 'mkpath: rmtree trailing_dots... verified' ) ; eval { ok( 1 / 0, 'mkpath: divide by 0' ) ; } or ok( 1, 'mkpath: can not divide by 0' ) ; ok( 1, 'mkpath: still alive' ) ; } ; SKIP: { skip( 'Tests only for MSWin32', 13 ) if ( 'MSWin32' ne $OSNAME ) ; my $long_path_2_prefix = ".\\imapsync_tests" || '\\\?\\E:\\TEMP\\imapsync_tests' ; myprint( "long_path_2_prefix: $long_path_2_prefix\n" ) ; my $long_path_100 = $long_path_2_prefix . '\\' . '123456789\\' x 10 . 'END' ; my $long_path_300 = $long_path_2_prefix . '\\' . '123456789\\' x 30 . 'END' ; #myprint( "$long_path_100\n" ) ; ok( ( -d $long_path_2_prefix or mkpath( $long_path_2_prefix ) ), 'mkpath: -d mkpath small path' ) ; ok( ( -d $long_path_2_prefix ), 'mkpath: -d mkpath small path done' ) ; ok( ( -d $long_path_100 or mkpath( $long_path_100 ) ), 'mkpath: mkpath > 100 char' ) ; ok( ( -d $long_path_100 ), 'mkpath: -d mkpath > 200 char done' ) ; ok( ( -d $long_path_2_prefix and rmtree( $long_path_2_prefix ) ), 'mkpath: rmtree > 100 char' ) ; ok( (! -d $long_path_2_prefix ), 'mkpath: ! -d rmtree done' ) ; # Without the eval the following mkpath 300 just kill the whole process without a whisper #myprint( "$long_path_300\n" ) ; eval { ok( ( -d $long_path_300 or mkpath( $long_path_300 ) ), 'mkpath: create a path with 300 characters' ) ; } or ok( 1, 'mkpath: can not create a path with 300 characters' ) ; ok( ( ( ! -d $long_path_300 ) or -d $long_path_300 and rmtree( $long_path_300 ) ), 'mkpath: rmtree the 300 character path' ) ; ok( 1, 'mkpath: still alive' ) ; ok( ( -d 'W/tmp/tests/trailing_dots...' or mkpath( 'W/tmp/tests/trailing_dots...' ) ), 'mkpath: mkpath trailing_dots...' ) ; ok( -d 'W/tmp/tests/trailing_dots...', 'mkpath: mkpath trailing_dots... verified' ) ; ok( ( -d 'W/tmp/tests/trailing_dots...' and rmtree( 'W/tmp/tests/trailing_dots...' ) ), 'mkpath: rmtree trailing_dots...' ) ; ok( ! -d 'W/tmp/tests/trailing_dots...', 'mkpath: rmtree trailing_dots... verified' ) ; } ; note( 'Leaving tests_mkpath()' ) ; # Keep this because of the eval used by the caller (failed badly?) return 1 ; } sub tests_touch { note( 'Entering tests_touch()' ) ; ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' )), 'touch: mkpath W/tmp/tests/' ) ; ok( 1 == touch( 'W/tmp/tests/lala'), 'touch: W/tmp/tests/lala') ; ok( 1 == touch( 'W/tmp/tests/\y'), 'touch: W/tmp/tests/\y') ; ok( 0 == touch( '/no/no/no/aaa'), 'touch: not /aaa') ; ok( 1 == touch( 'W/tmp/tests/lili', 'W/tmp/tests/lolo'), 'touch: 2 files') ; ok( 0 == touch( 'W/tmp/tests/\y', '/no/no/aaa'), 'touch: 2 files, 1 fails' ) ; note( 'Leaving tests_touch()' ) ; return ; } sub touch { my @files = @_ ; my $failures = 0 ; foreach my $file ( @files ) { my $fh = IO::File->new ; if ( $fh->open(">> $file" ) ) { $fh->close ; }else{ myprint( "Could not open file $file in write/append mode\n" ) ; $failures++ ; } } return( ! $failures ); } sub tests_tmpdir_has_colon_bug { note( 'Entering tests_tmpdir_has_colon_bug()' ) ; ok( 0 == tmpdir_has_colon_bug( q{} ), 'tmpdir_has_colon_bug: ' ) ; ok( 0 == tmpdir_has_colon_bug( '/tmp' ), 'tmpdir_has_colon_bug: /tmp' ) ; ok( 1 == tmpdir_has_colon_bug( 'C:' ), 'tmpdir_has_colon_bug: C:' ) ; ok( 1 == tmpdir_has_colon_bug( 'C:\temp' ), 'tmpdir_has_colon_bug: C:\temp' ) ; note( 'Leaving tests_tmpdir_has_colon_bug()' ) ; return ; } sub tmpdir_has_colon_bug { my $path = shift ; my $path_filtered = filter_forbidden_characters( $path ) ; if ( $path_filtered ne $path ) { ( -d $path_filtered ) and myprint( "Path $path was previously mistakely changed to $path_filtered\n" ) ; return( 1 ) ; } return( 0 ) ; } sub tmpdir_fix_colon_bug { my $mysync = shift ; my $err = 0 ; if ( not (-d $mysync->{ tmpdir } and -r _ and -w _) ) { myprint( "tmpdir $mysync->{ tmpdir } is not valid\n" ) ; return( 0 ) ; } my $cachedir_new = "$mysync->{ tmpdir }/imapsync_cache" ; if ( not tmpdir_has_colon_bug( $cachedir_new ) ) { return( 0 ) } ; # check if old cache directory already exists my $cachedir_old = filter_forbidden_characters( $cachedir_new ) ; if ( not ( -d $cachedir_old ) ) { myprint( "Old cache directory $cachedir_new no exists, nothing to do\n" ) ; return( 1 ) ; } # check if new cache directory already exists if ( -d $cachedir_new ) { myprint( "New fixed cache directory $cachedir_new already exists, not moving the old one $cachedir_old. Fix this manually.\n" ) ; return( 0 ) ; }else{ # move the old one to the new place myprint( "Moving $cachedir_old to $cachedir_new Do not interrupt this task.\n" ) ; File::Copy::Recursive::rmove( $cachedir_old, $cachedir_new ) or do { myprint( "Could not move $cachedir_old to $cachedir_new\n" ) ; $err++ ; } ; # check it succeeded if ( -d $cachedir_new and -r _ and -w _ ) { myprint( "New fixed cache directory $cachedir_new ok\n" ) ; }else{ myprint( "New fixed cache directory $cachedir_new does not exist\n" ) ; $err++ ; } if ( -d $cachedir_old ) { myprint( "Old cache directory $cachedir_old still exists\n" ) ; $err++ ; }else{ myprint( "Old cache directory $cachedir_old successfully moved\n" ) ; } } return( not $err ) ; } sub tests_cache_folder { note( 'Entering tests_cache_folder()' ) ; ok( '/path/fold1/fold2' eq cache_folder( q{}, '/path', 'fold1', 'fold2'), 'cache_folder: /path, fold1, fold2 -> /path/fold1/fold2' ) ; ok( '/pa_th/fold1/fold2' eq cache_folder( q{}, '/pa*th', 'fold1', 'fold2'), 'cache_folder: /pa*th, fold1, fold2 -> /path/fold1/fold2' ) ; ok( '/_p_a__th/fol_d1/fold2' eq cache_folder( q{}, '/>pp /path/fol_d1/fold2' ) ; ok( 'D:/path/fold1/fold2' eq cache_folder( 'D:', '/path', 'fold1', 'fold2'), 'cache_folder: /path, fold1, fold2 -> /path/fold1/fold2' ) ; ok( 'D:/pa_th/fold1/fold2' eq cache_folder( 'D:', '/pa*th', 'fold1', 'fold2'), 'cache_folder: /pa*th, fold1, fold2 -> /path/fold1/fold2' ) ; ok( 'D:/_p_a__th/fol_d1/fold2' eq cache_folder( 'D:', '/>pp /path/fol_d1/fold2' ) ; ok( '//' eq cache_folder( q{}, q{}, q{}, q{}), 'cache_folder: -> //' ) ; ok( '//_______' eq cache_folder( q{}, q{}, q{}, '*|?:"<>'), 'cache_folder: *|?:"<> -> //_______' ) ; note( 'Leaving tests_cache_folder()' ) ; return ; } sub cache_folder { my( $cache_base, $cache_dir, $h1_fold, $h2_fold ) = @_ ; my $sep_1 = $sync->{ h1_sep } || '/'; my $sep_2 = $sync->{ h2_sep } || '/'; #myprint( "$cache_dir h1_fold $h1_fold sep1 $sep_1 h2_fold $h2_fold sep2 $sep_2\n" ) ; $h1_fold = convert_sep_to_slash( $h1_fold, $sep_1 ) ; $h2_fold = convert_sep_to_slash( $h2_fold, $sep_2 ) ; my $cache_folder = "$cache_base" . filter_forbidden_characters( "$cache_dir/$h1_fold/$h2_fold" ) ; #myprint( "cache_folder [$cache_folder]\n" ) ; return( $cache_folder ) ; } sub tests_filter_forbidden_characters { note( 'Entering tests_filter_forbidden_characters()' ) ; is( undef , filter_forbidden_characters( ), 'filter_forbidden_characters: no args -> undef' ) ; is( 'a_b' , filter_forbidden_characters( 'a_b' ), 'filter_forbidden_characters: a_b -> a_b' ) ; is( 'a_b' , filter_forbidden_characters( 'a*b' ), 'filter_forbidden_characters: a*b -> a_b' ) ; is( 'a_b' , filter_forbidden_characters( 'a|b' ), 'filter_forbidden_characters: a|b -> a_b' ) ; is( 'a_b' , filter_forbidden_characters( 'a?b' ), 'filter_forbidden_characters: a?b -> a_b' ) ; is( 'a________b', filter_forbidden_characters( q{a*|?:"<>'b} ), q{filter_forbidden_characters: a*|?:"<>'b -> a________b} ) ; is( 'a_b_' , filter_forbidden_characters( 'a b ' ), 'filter_forbidden_characters: "a b " -> "a_b_"' ) ; is( 'a_b' , filter_forbidden_characters( "a\tb" ), 'filter_forbidden_characters: a\tb -> a_b' ) ; is( "a_b" , filter_forbidden_characters( "a\rb" ), 'filter_forbidden_characters: a\rb -> a_b' ) ; is( "a_b" , filter_forbidden_characters( "a\nb" ), 'filter_forbidden_characters: a\nb -> a_b' ) ; is( "a_b" , filter_forbidden_characters( "a\\b" ), 'filter_forbidden_characters: a\b -> a_b' ) ; is( 'a-b' , filter_forbidden_characters( 'a-b' ), 'filter_forbidden_characters: a-b -> a-b' ) ; is( 'a__-__-__-__-__b' , filter_forbidden_characters( 'aé-è-à -ç-Öb' ), 'filter_forbidden_characters: aé-è-à -ç-Öb -> a__-__-__-__-__b' ) ; is( 'abcdABCDwxyzWXYZ012789' , filter_forbidden_characters( 'abcdABCDwxyzWXYZ012789' ), 'filter_forbidden_characters: abcdABCDwxyzWXYZ012789 -> abcdABCDwxyzWXYZ012789' ) ; note( 'Leaving tests_filter_forbidden_characters()' ) ; return ; } sub filter_forbidden_characters { my $string = shift ; if ( ! defined $string ) { return ; } $string =~ s{[\Q*|?:"<>' \E\t\r\n\\]}{_}xg ; # replace all non-ascii and control characters by _ $string =~ s/[[:^ascii:][:cntrl:]]/_/xg ; #myprint( "[$string]\n" ) ; return( $string ) ; } sub tests_convert_sep_to_slash { note( 'Entering tests_convert_sep_to_slash()' ) ; ok(q{} eq convert_sep_to_slash(q{}, '/'), 'convert_sep_to_slash: no folder'); ok('INBOX' eq convert_sep_to_slash('INBOX', '/'), 'convert_sep_to_slash: INBOX'); ok('INBOX/foo' eq convert_sep_to_slash('INBOX/foo', '/'), 'convert_sep_to_slash: INBOX/foo'); ok('INBOX/foo' eq convert_sep_to_slash('INBOX_foo', '_'), 'convert_sep_to_slash: INBOX_foo'); ok('INBOX/foo/zob' eq convert_sep_to_slash('INBOX_foo_zob', '_'), 'convert_sep_to_slash: INBOX_foo_zob'); ok('INBOX/foo' eq convert_sep_to_slash('INBOX.foo', '.'), 'convert_sep_to_slash: INBOX.foo'); ok('INBOX/foo/hi' eq convert_sep_to_slash('INBOX.foo.hi', '.'), 'convert_sep_to_slash: INBOX.foo.hi'); note( 'Leaving tests_convert_sep_to_slash()' ) ; return ; } sub convert_sep_to_slash { my ( $folder, $sep ) = @_ ; $folder =~ s{\Q$sep\E}{/}xg ; return( $folder ) ; } sub tests_regexmess { note( 'Entering tests_regexmess()' ) ; ok( 'blabla' eq regexmess( 'blabla' ), 'regexmess: no regexmess, nothing to do' ) ; @regexmess = ( 'lalala' ) ; ok( not( defined regexmess( 'popopo' ) ), 'regexmess: bad regex lalala' ) ; @regexmess = ( 's/p/Z/g' ) ; ok( 'ZoZoZo' eq regexmess( 'popopo' ), 'regexmess: s/p/Z/g' ) ; @regexmess = ( 's{c}{C}gxms' ) ; ok("H1: abC\nH2: Cde\n\nBody abC" eq regexmess( "H1: abc\nH2: cde\n\nBody abc"), 'regexmess: c->C'); @regexmess = ( 's{\AFrom\ }{From:}gxms' ) ; ok( q{} eq regexmess(q{}), 'regexmess: From mbox 1 add colon blank'); ok( 'From:' eq regexmess('From '), 'regexmess: From mbox 2 add colo'); ok( "\n" . 'From ' eq regexmess("\n" . 'From '), 'regexmess: From mbox 3 add colo') ; ok( "From: zzz\n" . 'From ' eq regexmess("From zzz\n" . 'From '), 'regexmess: From mbox 4 add colo') ; @regexmess = ( 's{\AFrom\ [^\n]*(\n)?}{}gxms' ) ; ok( q{} eq regexmess(q{}), 'regexmess: From mbox 1 remove, blank'); ok( q{} eq regexmess('From '), 'regexmess: From mbox 2 remove'); ok( "\n" . 'From ' eq regexmess("\n" . 'From '), 'regexmess: From mbox 3 remove'); #myprint( "[", regexmess("From zzz\n" . 'From '), "]" ) ; ok( q{} . 'From ' eq regexmess("From zzz\n" . 'From '), 'regexmess: From mbox 4 remove'); is( <<'EOM' Date: Sat, 10 Jul 2010 05:34:45 -0700 From: Hello, Bye. EOM , regexmess( <<'EOM' From zzz Date: Sat, 10 Jul 2010 05:34:45 -0700 From: Hello, Bye. EOM ), 'regexmess: From mbox 5 remove'); @regexmess = ( 's{\A((?:[^\n]+\n)+|)^Disposition-Notification-To:[^\n]*\n(\r?\n|.*\n\r?\n)}{$1$2}xms' ) ; # SUPER SUPER BEST! ok( <<'EOM' Date: Sat, 10 Jul 2010 05:34:45 -0700 From: Hello, Bye. EOM eq regexmess( <<'EOM' Date: Sat, 10 Jul 2010 05:34:45 -0700 Disposition-Notification-To: Gilles LAMIRAL From: Hello, Bye. EOM ), 'regexmess: 1 Delete header Disposition-Notification-To:'); ok( <<'EOM' Date: Sat, 10 Jul 2010 05:34:45 -0700 From: Hello, Bye. EOM eq regexmess( <<'EOM' Date: Sat, 10 Jul 2010 05:34:45 -0700 From: Disposition-Notification-To: Gilles LAMIRAL Hello, Bye. EOM ), 'regexmess: 2 Delete header Disposition-Notification-To:'); ok( <<'EOM' Date: Sat, 10 Jul 2010 05:34:45 -0700 From: Hello, Bye. EOM eq regexmess( <<'EOM' Disposition-Notification-To: Gilles LAMIRAL Date: Sat, 10 Jul 2010 05:34:45 -0700 From: Hello, Bye. EOM ), 'regexmess: 3 Delete header Disposition-Notification-To:'); ok( <<'EOM' Date: Sat, 10 Jul 2010 05:34:45 -0700 From: Disposition-Notification-To: Gilles LAMIRAL Bye. EOM eq regexmess( <<'EOM' Disposition-Notification-To: Gilles LAMIRAL Date: Sat, 10 Jul 2010 05:34:45 -0700 From: Disposition-Notification-To: Gilles LAMIRAL Bye. EOM ), 'regexmess: 4 Delete header Disposition-Notification-To:'); ok( <<'EOM' Date: Sat, 10 Jul 2010 05:34:45 -0700 From: Disposition-Notification-To: Gilles LAMIRAL Bye. EOM eq regexmess( <<'EOM' Date: Sat, 10 Jul 2010 05:34:45 -0700 From: Disposition-Notification-To: Gilles LAMIRAL Bye. EOM ), 'regexmess: 5 Delete header Disposition-Notification-To:'); ok( <<'EOM' Date: Sat, 10 Jul 2010 05:34:45 -0700 From: Hello, Disposition-Notification-To: Gilles LAMIRAL Bye. EOM eq regexmess( <<'EOM' Date: Sat, 10 Jul 2010 05:34:45 -0700 From: Hello, Disposition-Notification-To: Gilles LAMIRAL Bye. EOM ), 'regexmess: 6 Delete header Disposition-Notification-To:'); ok( <<'EOM' Date: Sat, 10 Jul 2010 05:34:45 -0700 From: Hello, Disposition-Notification-To: Gilles LAMIRAL Bye. EOM eq regexmess( <<'EOM' Date: Sat, 10 Jul 2010 05:34:45 -0700 From: Hello, Disposition-Notification-To: Gilles LAMIRAL Bye. EOM ), 'regexmess: 7 Delete header Disposition-Notification-To:'); ok( <<'EOM' Date: Sat, 10 Jul 2010 05:34:45 -0700 From: Hello, Bye. EOM eq regexmess( <<'EOM' Date: Sat, 10 Jul 2010 05:34:45 -0700 From: Hello, Bye. EOM ), 'regexmess: 8 Delete header Disposition-Notification-To:'); ok( <<'EOM' Date: Sat, 10 Jul 2010 05:34:45 -0700 From: Hello, Disposition-Notification-To: Gilles LAMIRAL Bye. EOM eq regexmess( <<'EOM' Date: Sat, 10 Jul 2010 05:34:45 -0700 From: Hello, Disposition-Notification-To: Gilles LAMIRAL Bye. EOM ), 'regexmess: 9 Delete header Disposition-Notification-To:'); ok( <<'EOM' Date: Sat, 10 Jul 2010 05:34:45 -0700 From: Hello, Disposition-Notification-To: Gilles LAMIRAL Bye. EOM eq regexmess( <<'EOM' Date: Sat, 10 Jul 2010 05:34:45 -0700 From: Hello, Disposition-Notification-To: Gilles LAMIRAL Bye. EOM ), 'regexmess: 10 Delete header Disposition-Notification-To:'); ok( <<'EOM' Date: Sat, 10 Jul 2010 05:34:45 -0700 From: Hello, Disposition-Notification-To: Gilles LAMIRAL Bye. EOM eq regexmess( <<'EOM' Date: Sat, 10 Jul 2010 05:34:45 -0700 From: Hello, Disposition-Notification-To: Gilles LAMIRAL Bye. EOM ), 'regexmess: 11 Delete header Disposition-Notification-To:'); ok( <<'EOM' Date: Sat, 10 Jul 2010 05:34:45 -0700 From: Hello, Disposition-Notification-To: Gilles LAMIRAL Disposition-Notification-To: Gilles LAMIRAL Bye. EOM eq regexmess( <<'EOM' Date: Sat, 10 Jul 2010 05:34:45 -0700 From: Hello, Disposition-Notification-To: Gilles LAMIRAL Disposition-Notification-To: Gilles LAMIRAL Bye. EOM ), 'regexmess: 12 Delete header Disposition-Notification-To:'); @regexmess = ( 's{\A(.*?(?! ^$))^Disposition-Notification-To:(.*?)$}{$1X-Disposition-Notification-To:$2}igxms' ) ; # BAD! @regexmess = ( 's{\A((?:[^\n]+\n)+|)(^Disposition-Notification-To:[^\n]*\n)(\r?\n|.*\n\r?\n)}{$1X-$2$3}ims' ) ; ok( <<'EOM' Date: Sat, 10 Jul 2010 05:34:45 -0700 From: Hello, Disposition-Notification-To: Gilles LAMIRAL Disposition-Notification-To: Gilles LAMIRAL Bye. EOM eq regexmess( <<'EOM' Date: Sat, 10 Jul 2010 05:34:45 -0700 From: Hello, Disposition-Notification-To: Gilles LAMIRAL Disposition-Notification-To: Gilles LAMIRAL Bye. EOM ), 'regexmess: 13 Delete header Disposition-Notification-To:'); ok( <<'EOM' Date: Sat, 10 Jul 2010 05:34:45 -0700 X-Disposition-Notification-To: Gilles LAMIRAL From: Hello, Disposition-Notification-To: Gilles LAMIRAL Disposition-Notification-To: Gilles LAMIRAL Bye. EOM eq regexmess( <<'EOM' Date: Sat, 10 Jul 2010 05:34:45 -0700 Disposition-Notification-To: Gilles LAMIRAL From: Hello, Disposition-Notification-To: Gilles LAMIRAL Disposition-Notification-To: Gilles LAMIRAL Bye. EOM ), 'regexmess: 14 Delete header Disposition-Notification-To:'); ok( <<'EOM' Date: Sat, 10 Jul 2010 05:34:45 -0700 X-Disposition-Notification-To: Gilles LAMIRAL From: Hello, Bye. EOM eq regexmess( <<'EOM' Date: Sat, 10 Jul 2010 05:34:45 -0700 Disposition-Notification-To: Gilles LAMIRAL From: Hello, Bye. EOM ), 'regexmess: 15 Delete header Disposition-Notification-To:'); ok( <<'EOM' Date: Sat, 10 Jul 2010 05:34:45 -0700 From: X-Disposition-Notification-To: Gilles LAMIRAL Hello, Bye. EOM eq regexmess( <<'EOM' Date: Sat, 10 Jul 2010 05:34:45 -0700 From: Disposition-Notification-To: Gilles LAMIRAL Hello, Bye. EOM ), 'regexmess: 16 Delete header Disposition-Notification-To:'); ok( <<'EOM' X-Disposition-Notification-To: Gilles LAMIRAL Date: Sat, 10 Jul 2010 05:34:45 -0700 From: Hello, Bye. EOM eq regexmess( <<'EOM' Disposition-Notification-To: Gilles LAMIRAL Date: Sat, 10 Jul 2010 05:34:45 -0700 From: Hello, Bye. EOM ), 'regexmess: 17 Delete header Disposition-Notification-To:'); @regexmess = ( 's/.{11}\K.*//gs' ) ; is( "0123456789\n", regexmess( "0123456789\n" x 100 ), 'regexmess: truncate whole message after 11 characters' ) ; is( "0123456789\n", regexmess( "0123456789\n" x 100_000 ), 'regexmess: truncate whole message after 11 characters ~ 1MB' ) ; @regexmess = ( 's/.{10000}\K.*//gs' ) ; is( "123456789\n" x 1000, regexmess( "123456789\n" x 100_000 ), 'regexmess: truncate whole message after 10000 characters ~ 1MB' ) ; @regexmess = ( 's/^(X-Ham-Report.*?\n)^X-/X-/sm' ) ; is( <<'EOM' X-Spam-Score: -1 X-Spam-Bar: / X-Spam-Flag: NO Date: Sat, 10 Jul 2010 05:34:45 -0700 From: Hello, Bye. EOM , regexmess( <<'EOM' X-Spam-Score: -1 X-Spam-Bar: / X-Ham-Report: =?utf-8?Q?Spam_detection_software=2C_running?= =?utf-8?Q?_on_the_system_=22ohp-ag006.int200?= _has_NOT_identified_thi?= =?utf-8?Q?s_incoming_email_as_spam.__The_o?= _message_has_been_attac?= =?utf-8?Q?hed_to_this_so_you_can_view_it_o?= ___________________________?= =?utf-8?Q?__author's_domain X-Spam-Flag: NO Date: Sat, 10 Jul 2010 05:34:45 -0700 From: Hello, Bye. EOM ), 'regexmess: Delete header X-Ham-Report:'); # regex to play with Date: from the FAQ #@regexmess = 's{\A(.*?(?! ^$))^Date:(.*?)$}{$1Date:$2\nX-Date:$2}gxms' # Change 8bit characters in whole email to X characters @regexmess = ( 's{[\x80-\xff]}{X}gxms' ) ; is( 'X-8bit: kaka 1 XX kiki', regexmess('X-8bit: kaka 1 ¤ kiki'), 'regexmess: 1 Change 8bit characters in whole email to X characters'); # Same change but using tr @regexmess = ( 'tr [\x80-\xff] [X]' ) ; is( 'X-8bit: kaka 1 XXXX kiki', regexmess('X-8bit: kaka 1 ¤£ kiki'), 'regexmess: 2 Change 8bit characters in whole email to X characters, using tr'); # Add a final \r\n if missing @regexmess = ( 's{(? LaSuite: super Hello, Bye. EOM , regexmess( <<'EOM' Date: Sat, 10 Jul 2010 05:34:45 -0700 From: X-Spam-Report: caca caca caca caca LaSuite: super Hello, Bye. EOM ), 'regexmess: 1 remove buggy X-Spam-Report: across several lines, not the final header'); is( <<'EOM' Date: Sat, 10 Jul 2010 05:34:45 -0700 From: LaSuite: super LaSuite2: super 2 Hello, Bye. EOM , regexmess( <<'EOM' Date: Sat, 10 Jul 2010 05:34:45 -0700 From: X-Spam-Report: caca caca caca caca LaSuite: super LaSuite2: super 2 Hello, Bye. EOM ), 'regexmess: 2 remove buggy X-Spam-Report: across several lines, not the final header'); is( <<'EOM' Date: Sat, 10 Jul 2010 05:34:45 -0700 From: LaSuite: super LaSuite2: super 2 Hello, Bye. EOM , regexmess( <<'EOM' X-Spam-Report: caca caca caca caca Date: Sat, 10 Jul 2010 05:34:45 -0700 From: LaSuite: super LaSuite2: super 2 Hello, Bye. EOM ), 'regexmess: 3 remove buggy X-Spam-Report: across several lines, first header'); is( <<'EOM' Date: Sat, 10 Jul 2010 05:34:45 -0700 From: Hello, Bye. EOM , regexmess( <<'EOM' Date: Sat, 10 Jul 2010 05:34:45 -0700 From: X-Spam-Report: caca caca caca caca Hello, Bye. EOM ), 'regexmess: 4 remove buggy X-Spam-Report: across several lines, final header'); is( <<'EOM' Date: Sat, 10 Jul 2010 05:34:45 -0700 From: Hello, Bye. EOM , regexmess( <<'EOM' Date: Sat, 10 Jul 2010 05:34:45 -0700 From: Hello, Bye. EOM ), 'regexmess: 5 remove buggy X-Spam-Report: not there at all'); is( <<"EOM" Date: Sat, 10 Jul 2010 05:34:45 -0700\r From:\r LaSuite: super\r LaSuite2: super 2\r \r Hello,\r Bye.\r EOM , regexmess( <<"EOM" X-Spam-Report: caca\r caca\r caca\r caca\r Date: Sat, 10 Jul 2010 05:34:45 -0700\r From:\r LaSuite: super\r LaSuite2: super 2\r \r Hello,\r Bye.\r EOM ), 'regexmess: 6 remove buggy X-Spam-Report: across several lines, first header, with \r'); is( <<"EOM" Date: Sat, 10 Jul 2010 05:34:45 -0700\r From:\r LaSuite: super\r LaSuite2: super 2\r \r Hello,\r Bye.\r EOM , regexmess( <<"EOM" Date: Sat, 10 Jul 2010 05:34:45 -0700\r From:\r X-Spam-Report: caca\r caca\r caca\r caca\r LaSuite: super\r LaSuite2: super 2\r \r Hello,\r Bye.\r EOM ), 'regexmess: 7 remove buggy X-Spam-Report: across several lines, middle header, with \r'); is( <<"EOM" Date: Sat, 10 Jul 2010 05:34:45 -0700\r From:\r \r Hello,\r Bye.\r EOM , regexmess( <<"EOM" Date: Sat, 10 Jul 2010 05:34:45 -0700\r From:\r X-Spam-Report: caca\r caca\r caca\r caca\r \r Hello,\r Bye.\r EOM ), 'regexmess: 8 remove buggy X-Spam-Report: across several lines, final header, with \r'); undef @regexmess ; note( 'Leaving tests_regexmess()' ) ; return ; } sub regexmess { my ( $string ) = @_ ; foreach my $regexmess ( @regexmess ) { $sync->{ debug } and myprint( "eval \$string =~ $regexmess\n" ) ; my $ret = eval "\$string =~ $regexmess ; 1" ; #myprint( "eval [$ret]\n" ) ; if ( ( not $ret ) or $EVAL_ERROR ) { myprint( "Error: eval regexmess '$regexmess': $EVAL_ERROR" ) ; return( undef ) ; } } $sync->{ debug } and myprint( "$string\n" ) ; return( $string ) ; } sub tests_skipmess { note( 'Entering tests_skipmess()' ) ; ok( not( defined skipmess( 'blabla' ) ), 'skipmess, no skipmess, no skip' ) ; @skipmess = ('[') ; ok( not( defined skipmess( 'popopo' ) ), 'skipmess, bad regex [' ) ; @skipmess = ('lalala') ; ok( not( defined skipmess( 'popopo' ) ), 'skipmess, bad regex lalala' ) ; @skipmess = ('/popopo/') ; ok( 1 == skipmess( 'popopo' ), 'skipmess, popopo match regex /popopo/' ) ; @skipmess = ('/popopo/') ; ok( 0 == skipmess( 'rrrrrr' ), 'skipmess, rrrrrr does not match regex /popopo/' ) ; @skipmess = ('m{^$}') ; ok( 1 == skipmess( q{} ), 'skipmess: empty string yes' ) ; ok( 0 == skipmess( 'Hi!' ), 'skipmess: empty string no' ) ; @skipmess = ('m{i}') ; ok( 1 == skipmess( 'Hi!' ), 'skipmess: i string yes' ) ; ok( 0 == skipmess( 'Bye!' ), 'skipmess: i string no' ) ; @skipmess = ('m{[\x80-\xff]}') ; ok( 0 == skipmess( 'Hi!' ), 'skipmess: i 8bit no' ) ; ok( 1 == skipmess( "\xff" ), 'skipmess: \xff 8bit yes' ) ; @skipmess = ('m{A}', 'm{B}') ; ok( 0 == skipmess( 'Hi!' ), 'skipmess: A or B no' ) ; ok( 0 == skipmess( 'lala' ), 'skipmess: A or B no' ) ; ok( 0 == skipmess( "\xff" ), 'skipmess: A or B no' ) ; ok( 1 == skipmess( 'AB' ), 'skipmess: A or B yes' ) ; ok( 1 == skipmess( 'BA' ), 'skipmess: A or B yes' ) ; ok( 1 == skipmess( 'AA' ), 'skipmess: A or B yes' ) ; ok( 1 == skipmess( 'Ok Bye' ), 'skipmess: A or B yes' ) ; @skipmess = ( 'm#\A((?:[^\n]+\n)+|)^Content-Type: Message/Partial;[^\n]*\n(?:\n|.*\n\n)#ism' ) ; # SUPER BEST! ok( 1 == skipmess( <<'EOM' Date: Sat, 10 Jul 2010 05:34:45 -0700 Content-Type: Message/Partial; blabla From: Hello! Bye. EOM ), 'skipmess: 1 match Content-Type: Message/Partial' ) ; ok( 0 == skipmess( <<'EOM' Date: Sat, 10 Jul 2010 05:34:45 -0700 From: Hello! Bye. EOM ), 'skipmess: 2 not match Content-Type: Message/Partial' ) ; ok( 1 == skipmess( <<'EOM' Date: Sat, 10 Jul 2010 05:34:45 -0700 From: Content-Type: Message/Partial; blabla Hello! Bye. EOM ), 'skipmess: 3 match Content-Type: Message/Partial' ) ; ok( 0 == skipmess( <<'EOM' Date: Sat, 10 Jul 2010 05:34:45 -0700 From: Hello! Content-Type: Message/Partial; blabla Bye. EOM ), 'skipmess: 4 not match Content-Type: Message/Partial' ) ; ok( 0 == skipmess( <<'EOM' Date: Sat, 10 Jul 2010 05:34:45 -0700 From: Hello! Content-Type: Message/Partial; blabla Bye. EOM ), 'skipmess: 5 not match Content-Type: Message/Partial' ) ; ok( 1 == skipmess( <<'EOM' Date: Sat, 10 Jul 2010 05:34:45 -0700 Content-Type: Message/Partial; blabla From: Hello! Content-Type: Message/Partial; blabla Bye. EOM ), 'skipmess: 6 match Content-Type: Message/Partial' ) ; ok( 1 == skipmess( <<'EOM' Date: Sat, 10 Jul 2010 05:34:45 -0700 Content-Type: Message/Partial; From: Hello! Bye. EOM ), 'skipmess: 7 match Content-Type: Message/Partial' ) ; ok( 1 == skipmess( <<'EOM' Date: Wed, 2 Jul 2014 02:26:40 +0000 MIME-Version: 1.0 Content-Type: message/partial; id="TAN_U_P<1404267997.00007489ed17>"; number=3; total=3 6HQ6Hh3CdXj77qEGixerQ6zHx0OnQ/Cf5On4W0Y6vtU2crABZQtD46Hx1EOh8dDz4+OnTr1G Hello! Bye. EOM ), 'skipmess: 8 match Content-Type: Message/Partial' ) ; ok( 1 == skipmess( <<'EOM' Return-Path: Received: by lamiral.info (Postfix, from userid 1000) id 21EB12443BF; Mon, 2 Mar 2015 15:38:35 +0100 (CET) Subject: test: aethaecohngiexao To: X-Mailer: mail (GNU Mailutils 2.2) Message-Id: <20150302143835.21EB12443BF@lamiral.info> Content-Type: message/partial; id="TAN_U_P<1404267997.00007489ed17>"; number=3; total=3 Date: Mon, 2 Mar 2015 15:38:34 +0100 (CET) From: gilles@lamiral.info (Gilles LAMIRAL) test: aethaecohngiexao EOM ), 'skipmess: 9 match Content-Type: Message/Partial' ) ; ok( 1 == skipmess( <<'EOM' Date: Mon, 2 Mar 2015 15:38:34 +0100 (CET) From: gilles@lamiral.info (Gilles LAMIRAL) Content-Type: message/partial; id="TAN_U_P<1404267997.00007489ed17>"; number=3; total=3 test: aethaecohngiexao EOM . "lalala\n" x 3_000_000 ), 'skipmess: 10 match Content-Type: Message/Partial' ) ; ok( 0 == skipmess( <<'EOM' Date: Mon, 2 Mar 2015 15:38:34 +0100 (CET) From: gilles@lamiral.info (Gilles LAMIRAL) test: aethaecohngiexao EOM . "lalala\n" x 3_000_000 ), 'skipmess: 11 match Content-Type: Message/Partial' ) ; ok( 0 == skipmess( <<"EOM" From: fff\r To: fff\r Subject: Testing imapsync --skipmess\r Date: Mon, 22 Aug 2011 08:40:20 +0800\r Mime-Version: 1.0\r Content-Type: text/plain; charset=iso-8859-1\r Content-Transfer-Encoding: 7bit\r \r EOM . qq{!#"d%&'()*+,-./0123456789:;<=>?\@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefg\r\n } x 32_730 ), 'skipmess: 12 not match Content-Type: Message/Partial' ) ; # Complex regular subexpression recursion limit (32766) exceeded with more lines # exit; undef @skipmess ; note( 'Leaving tests_skipmess()' ) ; return ; } sub tests_skipmess_neg { note( 'Entering tests_skipmess_neg()' ) ; @skipmess = ('m{i}') ; ok( 1 == skipmess( 'Hi!' ), 'skipmess: i string yes' ) ; ok( 0 == skipmess( 'Ho!' ), 'skipmess: i string no' ) ; @skipmess = ('m{\A(?!.*i)}') ; ok( 0 == skipmess( 'Hi!' ), 'skipmess: not i string no' ) ; ok( 1 == skipmess( 'Ho!' ), 'skipmess: not i string yes' ) ; @skipmess = ('m{\A(?!.*^From:[^\n]*tartanpion\@machin\.truc)}xms') ; ok( 0 == skipmess( <<'EOM' Date: Sat, 10 Jul 2010 05:34:45 -0700 From: Bye. EOM ), 'skipmess: 1 not From tartanpion@machin.truc' ) ; ok( 1 == skipmess( <<'EOM' Date: Sat, 10 Jul 2010 05:34:45 -0700 From: Bye. EOM ), 'skipmess: 2 not From tartanpion@machin.truc' ) ; ok( 0 == skipmess( <<'EOM' Date: Sat, 10 Jul 2010 05:34:45 -0700 From: From: Bye. EOM ), 'skipmess: 3 not From tartanpion@machin.truc' ) ; ok( 1 == skipmess( <<'EOM' Date: Sat, 10 Jul 2010 05:34:45 -0700 From: From: Bye. EOM ), 'skipmess: 4 not From tartanpion@machin.truc' ) ; undef @skipmess ; note( 'Leaving tests_skipmess_neg()' ) ; return ; } sub skipmess { my ( $string ) = @_ ; my $match ; #myprint( "$string\n" ) ; foreach my $skipmess ( @skipmess ) { $sync->{ debug } and myprint( "eval \$match = \$string =~ $skipmess\n" ) ; my $ret = eval "\$match = \$string =~ $skipmess ; 1" ; #myprint( "eval [$ret]\n" ) ; $sync->{ debug } and myprint( "match [$match]\n" ) ; if ( ( not $ret ) or $EVAL_ERROR ) { myprint( "Error: eval skipmess '$skipmess': $EVAL_ERROR" ) ; return( undef ) ; } return( $match ) if ( $match ) ; } return( $match ) ; } sub tests_bytes_display_string_bin { note( 'Entering tests_bytes_display_string_bin()' ) ; is( 'NA', bytes_display_string_bin( ), 'bytes_display_string_bin: no args => NA' ) ; is( 'NA', bytes_display_string_bin( undef ), 'bytes_display_string_bin: undef => NA' ) ; is( 'NA', bytes_display_string_bin( 'blabla' ), 'bytes_display_string_bin: blabla => NA' ) ; is( '0.000 KiB', bytes_display_string_bin( 0 ), 'bytes_display_string_bin: 0 => 0.000 KiB' ) ; is( '0.001 KiB', bytes_display_string_bin( 1 ), 'bytes_display_string_bin: 1 => 0.001 KiB' ) ; is( '0.010 KiB', bytes_display_string_bin( 10 ), 'bytes_display_string_bin: 10 => 0.010 KiB' ) ; is( '0.976 KiB', bytes_display_string_bin( 999 ), 'bytes_display_string_bin: 999 => 0.976 KiB' ) ; note( bytes_display_string_bin( 999 ) ) ; is( '0.999 KiB', bytes_display_string_bin( 1023 ), 'bytes_display_string_bin: 1023 => 0.999 KiB' ) ; note( bytes_display_string_bin( 1023 ) ) ; is( '1.000 KiB', bytes_display_string_bin( 1024 ), 'bytes_display_string_bin: 1024 => 1.000 KiB' ) ; note( bytes_display_string_bin( 1024 ) ) ; is( '1.001 KiB', bytes_display_string_bin( 1025 ), 'bytes_display_string_bin: 1025 => 1.001 KiB' ) ; is( '9.999 KiB', bytes_display_string_bin( 10_239 ), 'bytes_display_string_bin: 10_239 => 9.999 KiB' ) ; note( bytes_display_string_bin( 10_239 ) ) ; is( '10.000 KiB', bytes_display_string_bin( 10_240 ), 'bytes_display_string_bin: 10_240 => 10.000 KiB' ) ; note( bytes_display_string_bin( 10_240 ) ) ; is( '999.999 KiB', bytes_display_string_bin( 1_023_999 ), 'bytes_display_string_bin: 1_023_999 => 999.999 KiB' ) ; note( bytes_display_string_bin( 1_023_999 ) ) ; is( '0.977 MiB', bytes_display_string_bin( 1_024_000 ), 'bytes_display_string_bin: 1_024_000 => 0.977 MiB' ) ; note( bytes_display_string_bin( 1_024_000 ) ) ; is( '0.999 MiB', bytes_display_string_bin( 1_047_527 ), 'bytes_display_string_bin: 1_047_527 => 0.999 MiB' ) ; note( bytes_display_string_bin( 1_047_527 ) ) ; is( '0.999 MiB', bytes_display_string_bin( 1_048_051 ), 'bytes_display_string_bin: 1_048_051 => 0.999 MiB' ) ; note( bytes_display_string_bin( 1_048_051 ) ) ; is( '1.000 MiB', bytes_display_string_bin( 1_048_052 ), 'bytes_display_string_bin: 1_048_052 => 1.000 MiB' ) ; note( bytes_display_string_bin( 1_048_052 ) ) ; is( '1.000 MiB', bytes_display_string_bin( 1_048_575 ), 'bytes_display_string_bin: 1_048_575 => 1.000 MiB' ) ; is( '1.000 MiB', bytes_display_string_bin( 1_048_576 ), 'bytes_display_string_bin: 1_048_576 => 1.000 MiB' ) ; is( '1.000 GiB', bytes_display_string_bin( 1_073_741_823 ), 'bytes_display_string_bin: 1_073_741_823 => 1.000 GiB' ) ; is( '1.000 GiB', bytes_display_string_bin( 1_073_741_824 ), 'bytes_display_string_bin: 1_073_741_824 => 1.000 GiB' ) ; is( '1.000 TiB', bytes_display_string_bin( 1_099_511_627_775 ), 'bytes_display_string_bin: 1_099_511_627_775 => 1.000 TiB' ) ; is( '1.000 TiB', bytes_display_string_bin( 1_099_511_627_776 ), 'bytes_display_string_bin: 1_099_511_627_776 => 1.000 TiB' ) ; is( '1.000 PiB', bytes_display_string_bin( 1_125_899_906_842_623 ), 'bytes_display_string_bin: 1_125_899_906_842_623 => 1.000 PiB' ) ; is( '1.000 PiB', bytes_display_string_bin( 1_125_899_906_842_624 ), 'bytes_display_string_bin: 1_125_899_906_842_624 => 1.000 PiB' ) ; is( '1024.000 PiB', bytes_display_string_bin( 1_152_921_504_606_846_975 ), 'bytes_display_string_bin: 1_152_921_504_606_846_975 => 1024.000 PiB' ) ; is( '1024.000 PiB', bytes_display_string_bin( 1_152_921_504_606_846_976 ), 'bytes_display_string_bin: 1_152_921_504_606_846_976 => 1024.000 PiB' ) ; is( '1048576.000 PiB', bytes_display_string_bin( 1_180_591_620_717_411_303_424 ), 'bytes_display_string_bin: 1_180_591_620_717_411_303_424 => 1048576.000 PiB' ) ; note( bytes_display_string_bin( 1_180_591_620_717_411_303_424 ) ) ; note( bytes_display_string_bin( 3_000_000_000 ) ) ; note( 'Leaving tests_bytes_display_string_bin()' ) ; return ; } sub bytes_display_string_bin { my ( $bytes ) = @_ ; my $readable_value = q{} ; if ( ! defined( $bytes ) ) { return( 'NA' ) ; } if ( not match_number( $bytes ) ) { return( 'NA' ) ; } SWITCH: { if ( abs( $bytes ) < ( 1000 * $KIBI ) ) { $readable_value = mysprintf( '%.3f KiB', $bytes / $KIBI) ; last SWITCH ; } if ( abs( $bytes ) < ( 1000 * $KIBI * $KIBI ) ) { $readable_value = mysprintf( '%.3f MiB', $bytes / ($KIBI * $KIBI) ) ; last SWITCH ; } if ( abs( $bytes ) < ( 1000 * $KIBI * $KIBI * $KIBI) ) { $readable_value = mysprintf( '%.3f GiB', $bytes / ($KIBI * $KIBI * $KIBI) ) ; last SWITCH ; } if ( abs( $bytes ) < ( 1000 * $KIBI * $KIBI * $KIBI * $KIBI) ) { $readable_value = mysprintf( '%.3f TiB', $bytes / ($KIBI * $KIBI * $KIBI * $KIBI) ) ; last SWITCH ; } else { $readable_value = mysprintf( '%.3f PiB', $bytes / ($KIBI * $KIBI * $KIBI * $KIBI * $KIBI) ) ; } # if you have exabytes (EiB) of email to transfer, you have too much email! } #myprint( "$bytes = $readable_value\n" ) ; return( $readable_value ) ; } sub tests_bytes_display_string_dec { note( 'Entering tests_bytes_display_string_dec()' ) ; is( 'NA', bytes_display_string_dec( ), 'bytes_display_string_dec: no args => NA' ) ; is( 'NA', bytes_display_string_dec( undef ), 'bytes_display_string_dec: undef => NA' ) ; is( 'NA', bytes_display_string_dec( 'blabla' ), 'bytes_display_string_dec: blabla => NA' ) ; is( '0 bytes', bytes_display_string_dec( 0 ), 'bytes_display_string_dec: 0 => 0 bytes' ) ; is( '1 bytes', bytes_display_string_dec( 1 ), 'bytes_display_string_dec: 1 => 1 bytes' ) ; is( '10 bytes', bytes_display_string_dec( 10 ), 'bytes_display_string_dec: 10 => 10 bytes' ) ; is( '999 bytes', bytes_display_string_dec( 999 ), 'bytes_display_string_dec: 999 => 999 bytes' ) ; is( '1.000 KB', bytes_display_string_dec( 1000 ), 'bytes_display_string_dec: 1000 => 1.000 KB' ) ; is( '1.001 KB', bytes_display_string_dec( 1001 ), 'bytes_display_string_dec: 1000 => 1.1001 KB' ) ; is( '999.999 KB', bytes_display_string_dec( 999_999 ), 'bytes_display_string_dec: 999_999 => 999.999 KB' ) ; is( '1.000 MB', bytes_display_string_dec( 1_000_000 ), 'bytes_display_string_dec: 1_000_000 => 1.000 MB' ) ; is( '1.000 MB', bytes_display_string_dec( 1_000_500 ), 'bytes_display_string_dec: 1_000_500 => 1.000 MB' ) ; is( '1.001 MB', bytes_display_string_dec( 1_000_501 ), 'bytes_display_string_dec: 1_000_501 => 1.001 MB' ) ; is( '999.999 MB', bytes_display_string_dec( 999_999_000 ), 'bytes_display_string_dec: 999_999_000 => 999.999 MB' ) ; is( '999.999 MB', bytes_display_string_dec( 999_999_499 ), 'bytes_display_string_dec: 999_999_499 => 999.999 MB' ) ; is( '1.000 GB', bytes_display_string_dec( 999_999_500 ), 'bytes_display_string_dec: 999_999_500 => 1.000 GB' ) ; is( '1.000 GB', bytes_display_string_dec( 1_000_000_000 ), 'bytes_display_string_dec: 1_000_000_000 => 1.000 GB' ) ; is( '1.000 GB', bytes_display_string_dec( 1_000_500_000 ), 'bytes_display_string_dec: 1_000_500_000 => 1.000 GB' ) ; is( '1.001 GB', bytes_display_string_dec( 1_000_500_001 ), 'bytes_display_string_dec: 1_000_501_000 => 1.001 GB' ) ; is( '999.999 GB', bytes_display_string_dec( 999_999_000_000 ), 'bytes_display_string_dec: 999_999_000_000 => 999.999 GB' ) ; is( '999.999 GB', bytes_display_string_dec( 999_999_499_999 ), 'bytes_display_string_dec: 999_999_499_999 => 999.999 GB' ) ; is( '1.000 TB', bytes_display_string_dec( 999_999_500_000 ), 'bytes_display_string_dec: 999_999_500_000 => 1.000 TB' ) ; is( '1.000 TB', bytes_display_string_dec( 1_000_000_000_000 ), 'bytes_display_string_dec: 1_000_000_000_000 => 1.000 TB' ) ; is( '1.000 TB', bytes_display_string_dec( 1_000_500_000_000 ), 'bytes_display_string_dec: 1_000_500_000_000 => 1.000 TB' ) ; is( '1.001 TB', bytes_display_string_dec( 1_000_500_000_001 ), 'bytes_display_string_dec: 1_000_500_000_000 => 1.000 TB' ) ; is( '999.999 TB', bytes_display_string_dec( 999_999_000_000_000 ), 'bytes_display_string_dec: 999_999_000_000_000 => 999.999 TB' ) ; is( '999.999 TB', bytes_display_string_dec( 999_999_499_999_999 ), 'bytes_display_string_dec: 999_999_499_999_999 => 999.999 TB' ) ; is( '1.000 PB', bytes_display_string_dec( 999_999_500_000_000 ), 'bytes_display_string_dec: 999_999_500_000_000 => 1.000 PB' ) ; is( '3.000 GB', bytes_display_string_dec( 3_000_000_000 ), 'bytes_display_string_dec: 3_000_000_000 => 3.000 GB' ) ; note( 'Leaving tests_bytes_display_string_dec()' ) ; return ; } sub bytes_display_string_dec { my ( $bytes ) = @_ ; my $readable_value = q{} ; if ( ! defined( $bytes ) ) { return( 'NA' ) ; } if ( not match_number( $bytes ) ) { return( 'NA' ) ; } SWITCH: { if ( abs( $bytes ) < ( 1000 ) ) { $readable_value = mysprintf( '%.0f bytes', $bytes ) ; last SWITCH ; } if ( abs( $bytes ) < ( 1000**2 ) ) { $readable_value = mysprintf( '%.3f KB', $bytes / 1000 ) ; last SWITCH ; } if ( abs( $bytes ) < ( 999_999_500 ) ) { $readable_value = mysprintf( '%.3f MB', $bytes / ( 1000**2 ) ) ; last SWITCH ; } if ( abs( $bytes ) < ( 999_999_500_000 ) ) { $readable_value = mysprintf( '%.3f GB', $bytes / ( 1000**3 ) ) ; last SWITCH ; } if ( abs( $bytes ) < ( 999_999_500_000_000 ) ) { $readable_value = mysprintf( '%.3f TB', $bytes / ( 1000**4 ) ) ; last SWITCH ; } else { $readable_value = mysprintf( '%.3f PB', $bytes / ( 1000**5 ) ) ; } # if you have exabytes (EiB) of email to transfer, you have too much email! } #myprint( "$bytes = $readable_value\n" ) ; return( $readable_value ) ; } sub tests_useheader_suggestion { note( 'Entering tests_useheader_suggestion()' ) ; is( undef, useheader_suggestion( ), 'useheader_suggestion: no args => undef' ) ; my $mysync = {} ; $mysync->{ h1_nb_msg_noheader } = 0 ; is( q{}, useheader_suggestion( $mysync ), 'useheader_suggestion: h1_nb_msg_noheader count null => no suggestion' ) ; $mysync->{ h1_nb_msg_noheader } = 2 ; is( q{in order to sync those 2 unidentified messages, add option --addheader}, useheader_suggestion( $mysync ), 'useheader_suggestion: h1_nb_msg_noheader count 2 => suggestion of --addheader' ) ; note( 'Leaving tests_useheader_suggestion()' ) ; return ; } sub useheader_suggestion { my $mysync = shift ; if ( ! defined $mysync->{ h1_nb_msg_noheader } ) { return ; } elsif ( 1 <= $mysync->{ h1_nb_msg_noheader } ) { return qq{in order to sync those $mysync->{ h1_nb_msg_noheader } unidentified messages, add option --addheader} ; } else { return q{} ; } return ; } sub do_and_print_stats { my $mysync = shift ; if ( ! $mysync->{can_do_stats} ) { return ; } my $timeend = time ; my $timediff = $timeend - $mysync->{timestart} ; my $timeend_str = localtimez( $timeend ) ; my $cpu_time = cpu_time( $mysync ) ; my $cpu_percent = cpu_percent( $mysync, $cpu_time, $timediff ) ; my $cpu_percent_global = cpu_percent_global( $mysync, $cpu_percent ) ; my $memory_consumption_at_end = memory_consumption( ) || 0 ; my $memory_consumption_at_start = $mysync->{ memory_consumption_at_start } || 0 ; my $memory_ratio = ( $mysync->{ biggest_message_transferred } ) ? mysprintf( '%.1f', $memory_consumption_at_end / $mysync->{ biggest_message_transferred } ) : 'NA' ; # my $useheader_suggestion = useheader_suggestion( $mysync ) ; myprint( "++++ Statistics\n" ) ; myprint( "Transfer started on : $mysync->{ timestart_str }\n" ) ; myprint( "Transfer ended on : $timeend_str\n" ) ; myprintf( "Transfer time : %.1f sec\n", $timediff ) ; myprint( "Folders synced : $h1_folders_wanted_ct/$h1_folders_wanted_nb synced\n" ) ; myprint( "Messages transferred : $mysync->{ nb_msg_transferred } " ) ; myprint( "(could be $nb_msg_skipped_dry_mode without dry mode)" ) if ( $mysync->{dry} ) ; myprint( "\n" ) ; myprint( "Messages skipped : $mysync->{ nb_msg_skipped }\n" ) ; myprint( "Messages found duplicate on host1 : $mysync->{ acc1 }->{ nb_msg_duplicate }\n" ) ; myprint( "Messages found duplicate on host2 : $mysync->{ acc2 }->{ nb_msg_duplicate }\n" ) ; myprint( "Messages found crossduplicate on host2 : $mysync->{ h2_nb_msg_crossdup }\n" ) ; myprint( "Messages void (noheader) on host1 : $mysync->{ h1_nb_msg_noheader } ", useheader_suggestion( $mysync ), "\n" ) ; myprint( "Messages void (noheader) on host2 : $h2_nb_msg_noheader\n" ) ; nb_messages_in_1_not_in_2( $mysync ) ; nb_messages_in_2_not_in_1( $mysync ) ; myprintf( "Messages found in host1 not in host2 : %s messages\n", $mysync->{ nb_messages_in_1_not_in_2 } ) ; myprintf( "Messages found in host2 not in host1 : %s messages\n", $mysync->{ nb_messages_in_2_not_in_1 } ) ; myprint( "Messages deleted on host1 : $mysync->{ acc1 }->{ nb_msg_deleted }\n" ) ; myprint( "Messages deleted on host2 : $mysync->{ acc2 }->{ nb_msg_deleted }\n" ) ; myprintf( "Total bytes transferred : %s (%s)\n", $mysync->{total_bytes_transferred}, bytes_display_string_bin( $mysync->{total_bytes_transferred} ) ) ; myprintf( "Total bytes skipped : %s (%s)\n", $mysync->{ total_bytes_skipped }, bytes_display_string_bin( $mysync->{ total_bytes_skipped } ) ) ; $timediff ||= 1 ; # No division per 0 myprintf("Message rate : %.1f messages/s\n", $mysync->{nb_msg_transferred} / $timediff ) ; myprintf("Average bandwidth rate : %.1f KiB/s\n", $mysync->{total_bytes_transferred} / $KIBI / $timediff ) ; myprint( "Reconnections to host1 : $mysync->{imap1}->{IMAPSYNC_RECONNECT_COUNT}\n" ) ; myprint( "Reconnections to host2 : $mysync->{imap2}->{IMAPSYNC_RECONNECT_COUNT}\n" ) ; myprintf("Memory consumption at the end : %.1f MiB (started with %.1f MiB)\n", $memory_consumption_at_end / $KIBI / $KIBI, $memory_consumption_at_start / $KIBI / $KIBI ) ; myprint( "Load end is : " . ( join( q{ }, loadavg( ) ) || 'unknown' ), " on $mysync->{cpu_number} cores\n" ) ; myprint( "CPU time and %cpu : $cpu_time sec $cpu_percent %cpu $cpu_percent_global %allcpus\n" ) ; myprintf("Biggest message transferred : %s bytes (%s)\n", $mysync->{ biggest_message_transferred }, bytes_display_string_bin( $mysync->{ biggest_message_transferred } ) ) ; myprint( "Memory/biggest message ratio : $memory_ratio\n" ) ; if ( $mysync->{ foldersizesatend } and $mysync->{ foldersizes } ) { my $nb_msg_start_diff = diff_or_NA( $mysync->{ h2_nb_msg_start }, $mysync->{ h1_nb_msg_start } ) ; my $bytes_start_diff = diff_or_NA( $mysync->{ h2_bytes_start }, $mysync->{ h1_bytes_start } ) ; myprintf("Start difference host2 - host1 : %s messages, %s bytes (%s)\n", $nb_msg_start_diff, $bytes_start_diff, bytes_display_string_bin( $bytes_start_diff ) ) ; my $nb_msg_end_diff = diff_or_NA( $h2_nb_msg_end, $h1_nb_msg_end ) ; my $bytes_end_diff = diff_or_NA( $h2_bytes_end, $h1_bytes_end ) ; myprintf("Final difference host2 - host1 : %s messages, %s bytes (%s)\n", $nb_msg_end_diff, $bytes_end_diff, bytes_display_string_bin( $bytes_end_diff ) ) ; } comment_on_final_diff_in_1_not_in_2( $mysync ) ; comment_on_final_diff_in_2_not_in_1( $mysync ) ; myprint( "Detected $mysync->{nb_errors} errors\n\n" ) ; myprint( $mysync->{ warn_release }, "\n" ) ; myprint( homepage( ), "\n" ) ; return ; } sub diff_or_NA { my( $n1, $n2 ) = @ARG ; if ( not defined $n1 or not defined $n2 ) { return 'NA' ; } if ( not match_number( $n1 ) or not match_number( $n2 ) ) { return 'NA' ; } return( $n1 - $n2 ) ; } sub match_number { my $n = shift @ARG ; if ( not defined $n ) { return 0 ; } if ( $n =~ /[0-9]+\.?[0-9]?/x ) { return 1 ; } else { return 0 ; } } sub tests_match_number { note( 'Entering tests_match_number()' ) ; is( 0, match_number( ), 'match_number: no parameters => 0' ) ; is( 0, match_number( undef ), 'match_number: undef => 0' ) ; is( 0, match_number( 'blabla' ), 'match_number: blabla => 0' ) ; is( 1, match_number( 0 ), 'match_number: 0 => 1' ) ; is( 1, match_number( 1 ), 'match_number: 1 => 1' ) ; is( 1, match_number( 1.0 ), 'match_number: 1.0 => 1' ) ; is( 1, match_number( 0.0 ), 'match_number: 0.0 => 1' ) ; note( 'Leaving tests_match_number()' ) ; return ; } sub tests_diff_or_NA { note( 'Entering tests_diff_or_NA()' ) ; is( 'NA', diff_or_NA( ), 'diff_or_NA: no parameters => NA' ) ; is( 'NA', diff_or_NA( undef ), 'diff_or_NA: undef => NA' ) ; is( 'NA', diff_or_NA( undef, undef ), 'diff_or_NA: undef undef => NA' ) ; is( 'NA', diff_or_NA( undef, 1 ), 'diff_or_NA: undef 1 => NA' ) ; is( 'NA', diff_or_NA( 1, undef ), 'diff_or_NA: 1 undef => NA' ) ; is( 'NA', diff_or_NA( 'blabla', 1 ), 'diff_or_NA: blabla 1 => NA' ) ; is( 'NA', diff_or_NA( 1, 'blabla' ), 'diff_or_NA: 1 blabla => NA' ) ; is( 0, diff_or_NA( 1, 1 ), 'diff_or_NA: 1 1 => 0' ) ; is( 1, diff_or_NA( 1, 0 ), 'diff_or_NA: 1 0 => 1' ) ; is( -1, diff_or_NA( 0, 1 ), 'diff_or_NA: 0 1 => -1' ) ; is( 0, diff_or_NA( 1.0, 1 ), 'diff_or_NA: 1.0 1 => 0' ) ; is( 1, diff_or_NA( 1.0, 0 ), 'diff_or_NA: 1.0 0 => 1' ) ; is( -1, diff_or_NA( 0, 1.0 ), 'diff_or_NA: 0 1.0 => -1' ) ; note( 'Leaving tests_diff_or_NA()' ) ; return ; } sub homepage { return( 'Homepage: https://imapsync.lamiral.info/' ) ; } sub load_modules { if ( $sync->{ssl1} or $sync->{ssl2} or $sync->{tls1} or $sync->{tls2}) { if ( $sync->{inet4} ) { IO::Socket::SSL->import( 'inet4' ) ; } if ( $sync->{inet6} ) { IO::Socket::SSL->import( 'inet6' ) ; } } return ; } # Globals: $skipsize $wholeheaderifneeded sub parse_header_msg { my ( $mysync, $imap, $m_uid, $s_heads, $s_fir, $side, $s_hash ) = @_ ; my $head = $s_heads->{$m_uid} ; my $headnum = scalar keys %{ $head } ; $mysync->{ debug } and myprint( "$side: uid $m_uid number of headers, pass one: ", $headnum, "\n" ) ; if ( ( ! $headnum ) and ( $wholeheaderifneeded ) ){ $mysync->{ debug } and myprint( "$side: uid $m_uid no header by parse_headers so taking whole header with BODY.PEEK[HEADER]\n" ) ; $imap->fetch($m_uid, 'BODY.PEEK[HEADER]' ) ; my $whole_header = $imap->_transaction_literals ; #myprint( $whole_header ) ; $head = decompose_header( $whole_header ) ; $headnum = scalar keys %{ $head } ; $mysync->{ debug } and myprint( "$side: uid $m_uid number of headers, pass two: ", $headnum, "\n" ) ; } #myprint( Data::Dumper->Dump( [ $head, \%useheader ] ) ) ; my $headstr = header_construct( $mysync, $head, $side, $m_uid ) ; if ( ( ! $headstr ) and ( $mysync->{addheader} ) and ( $side eq 'Host1' ) ) { my $header = add_header( $m_uid ) ; $mysync->{ debug } and myprint( "$side: uid $m_uid no header found so adding our own [$header]\n" ) ; $headstr .= uc $header ; $s_fir->{$m_uid}->{NO_HEADER} = 1; } return if ( ! $headstr ) ; my $size = $s_fir->{$m_uid}->{'RFC822.SIZE'} ; my $flags = $s_fir->{$m_uid}->{'FLAGS'} ; my $idate = $s_fir->{$m_uid}->{'INTERNALDATE'} ; $size = length $headstr unless ( $size ) ; my $m_md5 = md5_base64( $headstr ) ; my $key ; if ( $skipsize ) { $key = "$m_md5"; } else { $key = "$m_md5:$size"; } if ( exists $s_hash->{"$key"} ) { # 0 return code is used to identify duplicate message hash my $dup_ref = $s_hash->{"$key"}->{'U'} ; my $num = scalar( @{ $dup_ref } ) ; push( @{ $dup_ref }, $m_uid ) ; my $keydup = "$key#$num" ; $mysync->{ debug } and myprint( "$side: uid $m_uid sig $keydup size $size idate $idate dup @{ $dup_ref }\n" ) ; if ( $mysync->{ syncduplicates } ) { $s_hash->{"$keydup"}{'5'} = $m_md5 ; $s_hash->{"$keydup"}{'s'} = $size ; $s_hash->{"$keydup"}{'D'} = $idate ; $s_hash->{"$keydup"}{'F'} = $flags ; $s_hash->{"$keydup"}{'m'} = $m_uid ; } return 0 ; } else { $s_hash->{"$key"}{'5'} = $m_md5 ; $s_hash->{"$key"}{'s'} = $size ; $s_hash->{"$key"}{'D'} = $idate ; $s_hash->{"$key"}{'F'} = $flags ; $s_hash->{"$key"}{'m'} = $m_uid ; $s_hash->{"$key"}{'U'} = [ $m_uid ] ; # ? or [ ] ? $mysync->{ debug } and myprint( "$side: uid $m_uid sig $key size $size idate $idate\n" ) ; return( 1 ) ; } # we should not be here return ; } sub tests_header_construct { note( 'Entering tests_header_construct()' ) ; is( undef, header_construct( ), 'header_construct: no args => undef' ) ; my $mysync = {} ; my $head = { 'key1' => [ 'val1_key1' ] } ; is( undef, header_construct( $mysync, $head, 'Host1', '1' ), 'header_construct: key1 val1_key1 no useheader => undef' ) ; $mysync->{useheader}->{ 'KEY1' } = 1 ; is( 'KEY1: VAL1_KEY1', header_construct( $mysync, $head, 'Host1', '1' ), 'header_construct: key1 val1_key1 => KEY1: VAL1_KEY1' ) ; $head = { 'key1' => [ 'val1_key1', 'val3_key1', 'val2_key1' ] } ; is( 'KEY1: VAL1_KEY1KEY1: VAL2_KEY1KEY1: VAL3_KEY1', header_construct( $mysync, $head, 'Host1', '1' ), 'header_construct: key1 val1_key1 val3_key1 val2_key1 => KEY1: VAL1_KEY1KEY1: VAL2_KEY1KEY1: VAL3_KEY1' ) ; $head = { 'key1' => [ 'val1_key1', 'val3_key1', ' val2_key1' ] } ; is( 'KEY1: VAL1_KEY1KEY1: VAL2_KEY1KEY1: VAL3_KEY1', header_construct( $mysync, $head, 'Host1', '1' ), 'header_construct: key1 val1_key1 val3_key1 val2_key1 => KEY1: VAL1_KEY1KEY1: VAL2_KEY1KEY1: VAL3_KEY1' ) ; $mysync->{useheader}->{ 'ALL' } = 1 ; is( 'KEY1: VAL1_KEY1KEY1: VAL2_KEY1KEY1: VAL3_KEY1', header_construct( $mysync, $head, 'Host1', '1' ), 'header_construct: key1 val1_key1 val3_key1 val2_key1 useheader ALL => KEY1: VAL1_KEY1KEY1: VAL2_KEY1KEY1: VAL3_KEY1' ) ; $mysync->{skipheader} = 'key1' ; is( undef, header_construct( $mysync, $head, 'Host1', '1' ), 'header_construct: key1 val1_key1 val3_key1 val2_key1 useheader ALL => undef' ) ; $head = { 'key1' => [ 'val1_key1', 'val3_key1', ' val2_key1' ], 'key2' => [ 'val1_key2', 'val3_key2', ' val2_key2' ] } ; is( 'KEY2: VAL1_KEY2KEY2: VAL2_KEY2KEY2: VAL3_KEY2', header_construct( $mysync, $head, 'Host1', '1' ), 'header_construct: ... useheader ALL skipheader key1 => KEY2: VAL1_KEY2KEY2: VAL2_KEY2KEY2: VAL3_KEY2' ) ; note( 'Leaving tests_header_construct()' ) ; return ; } # No global in header_construct sub header_construct { my( $mysync, $head, $side, $m_uid ) = @_ ; my @headstr ; foreach my $h ( sort keys %{ $head } ) { next if ( not ( exists $mysync->{useheader}->{ uc $h } ) and ( not exists $mysync->{useheader}->{ 'ALL' } ) ) ; foreach my $val ( @{$head->{$h}} ) { my $H = header_line_normalize( $h, $val ) ; # show stuff in debug mode $mysync->{ debug } and myprint( "$side uid $m_uid header [$H]", "\n" ) ; if ( $mysync->{skipheader} and $H =~ m/$mysync->{skipheader}/xi) { $mysync->{ debug } and myprint( "$side uid $m_uid skipping header [$H]\n" ) ; next ; } push @headstr, $H ; } } my $headstr = join( '', sort @headstr ) || undef ; return( $headstr ) ; } sub header_line_normalize { my( $header_key, $header_val ) = @_ ; # no 8-bit data in headers ! $header_val =~ s/[\x80-\xff]/X/xog; # change tabulations to space (Gmail bug on with "Received:" on multilines) $header_val =~ s/\t/\ /xgo ; # remove the first blanks ( dbmail bug? ) $header_val =~ s/^\s*//xo; # remove the last blanks ( Gmail bug ) $header_val =~ s/\s*$//xo; # remove successive blanks ( Mailenable does it ) $header_val =~ s/\s+/ /xgo; # remove Message-Id value domain part ( Mailenable changes it ) if ( ( $messageidnodomain ) and ( 'MESSAGE-ID' eq uc $header_key ) ) { $header_val =~ s/^([^@]+).*$/$1/xo ; } # and uppercase header line # (dbmail and dovecot) my $header_line = uc "$header_key: $header_val" ; return( $header_line ) ; } sub tests_header_line_normalize { note( 'Entering tests_header_line_normalize()' ) ; ok( ': ' eq header_line_normalize( q{}, q{} ), 'header_line_normalize: empty args' ) ; ok( 'HHH: VVV' eq header_line_normalize( 'hhh', 'vvv' ), 'header_line_normalize: hhh vvv ' ) ; ok( 'HHH: VVV' eq header_line_normalize( 'hhh', ' vvv' ), 'header_line_normalize: remove first blancs' ) ; ok( 'HHH: AA BB CCC D' eq header_line_normalize( 'hhh', 'aa bb ccc d' ), 'header_line_normalize: remove succesive blanks' ) ; ok( 'HHH: AA BB CCC' eq header_line_normalize( 'hhh', 'aa bb ccc ' ), 'header_line_normalize: remove last blanks' ) ; ok( 'HHH: VVV XX YY' eq header_line_normalize( 'hhh', "vvv\t\txx\tyy" ), 'header_line_normalize: tabs' ) ; ok( 'HHH: XABX' eq header_line_normalize( 'hhh', "\x80AB\xff" ), 'header_line_normalize: 8bit' ) ; note( 'Leaving tests_header_line_normalize()' ) ; return ; } sub tests_firstline { note( 'Entering tests_firstline()' ) ; is( q{}, firstline( 'W/tmp/tests/noexist.txt' ), 'firstline: getting empty string from inexisting W/tmp/tests/noexist.txt' ) ; ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'firstline: mkpath W/tmp/tests/' ) ; is( "blabla\n" , string_to_file( "blabla\n", 'W/tmp/tests/firstline.txt' ), 'firstline: put blabla in W/tmp/tests/firstline.txt' ) ; is( 'blabla' , firstline( 'W/tmp/tests/firstline.txt' ), 'firstline: get blabla from W/tmp/tests/firstline.txt' ) ; is( q{} , string_to_file( q{}, 'W/tmp/tests/firstline2.txt' ), 'firstline: put empty string in W/tmp/tests/firstline2.txt' ) ; is( q{} , firstline( 'W/tmp/tests/firstline2.txt' ), 'firstline: get empty string from W/tmp/tests/firstline2.txt' ) ; is( "\n" , string_to_file( "\n", 'W/tmp/tests/firstline3.txt' ), 'firstline: put CR in W/tmp/tests/firstline3.txt' ) ; is( q{} , firstline( 'W/tmp/tests/firstline3.txt' ), 'firstline: get empty string from W/tmp/tests/firstline3.txt' ) ; is( "blabla\nTiti\n" , string_to_file( "blabla\nTiti\n", 'W/tmp/tests/firstline4.txt' ), 'firstline: put blabla\nTiti\n in W/tmp/tests/firstline4.txt' ) ; is( 'blabla' , firstline( 'W/tmp/tests/firstline4.txt' ), 'firstline: get blabla from W/tmp/tests/firstline4.txt' ) ; note( 'Leaving tests_firstline()' ) ; return ; } sub firstline { # extract the first line of a file (without \n) # return empty string if error or empty string my $file = shift ; my $line ; $line = nthline( $file, 1 ) ; return $line ; } sub tests_secondline { note( 'Entering tests_secondline()' ) ; is( q{}, secondline( 'W/tmp/tests/noexist.txt' ), 'secondline: getting empty string from inexisting W/tmp/tests/noexist.txt' ) ; is( q{}, secondline( 'W/tmp/tests/noexist.txt', 2 ), 'secondline: 2nd getting empty string from inexisting W/tmp/tests/noexist.txt' ) ; ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'secondline: mkpath W/tmp/tests/' ) ; is( "L1\nL2\nL3\nL4\n" , string_to_file( "L1\nL2\nL3\nL4\n", 'W/tmp/tests/secondline.txt' ), 'secondline: put L1\nL2\nL3\nL4\n in W/tmp/tests/secondline.txt' ) ; is( 'L2' , secondline( 'W/tmp/tests/secondline.txt' ), 'secondline: get L2 from W/tmp/tests/secondline.txt' ) ; note( 'Leaving tests_secondline()' ) ; return ; } sub secondline { # extract the second line of a file (without \n) # return empty string if error or empty string my $file = shift ; my $line ; $line = nthline( $file, 2 ) ; return $line ; } sub tests_nthline { note( 'Entering tests_nthline()' ) ; is( q{}, nthline( 'W/tmp/tests/noexist.txt' ), 'nthline: getting empty string from inexisting W/tmp/tests/noexist.txt' ) ; is( q{}, nthline( 'W/tmp/tests/noexist.txt', 2 ), 'nthline: 2nd getting empty string from inexisting W/tmp/tests/noexist.txt' ) ; ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'nthline: mkpath W/tmp/tests/' ) ; is( "L1\nL2\nL3\nL4\n" , string_to_file( "L1\nL2\nL3\nL4\n", 'W/tmp/tests/nthline.txt' ), 'nthline: put L1\nL2\nL3\nL4\n in W/tmp/tests/nthline.txt' ) ; is( 'L3' , nthline( 'W/tmp/tests/nthline.txt', 3 ), 'nthline: get L3 from W/tmp/tests/nthline.txt' ) ; note( 'Leaving tests_nthline()' ) ; return ; } sub nthline { # extract the nth line of a file (without \n) # return empty string if error or empty string my $file = shift ; my $num = shift ; if ( ! all_defined( $file, $num ) ) { return q{} ; } my $line ; $line = ( file_to_array( $file ) )[$num - 1] ; if ( ! defined $line ) { return q{} ; } else { chomp $line ; return $line ; } } sub tests_file_to_array { note( 'Entering tests_file_to_array()' ) ; is( undef, file_to_array( ), 'file_to_array: no args => undef' ) ; is( undef, file_to_array( '/noexist' ), 'file_to_array: /noexist => undef' ) ; is( undef, file_to_array( '/' ), 'file_to_array: reading a directory => undef' ) ; ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'file_to_array: mkpath W/tmp/tests/' ) ; is( "L1\nL2\nL3\nL4\n" , string_to_file( "L1\nL2\nL3\nL4\n", 'W/tmp/tests/file_to_array.txt' ), 'file_to_array: put L1\nL2\nL3\nL4\n in W/tmp/tests/file_to_array.txt' ) ; is_deeply( [ "L1\n", "L2\n", "L3\n", "L4\n" ] , [ file_to_array( 'W/tmp/tests/file_to_array.txt' ) ], 'file_to_array: get back L1\n L2\n L3\n L4\n from W/tmp/tests/file_to_array.txt' ) ; note( 'Leaving tests_file_to_array()' ) ; return ; } sub file_to_array { my( $file ) = shift ; if ( ! $file ) { return ; } if ( ! -e $file ) { return ; } if ( ! -f $file ) { return ; } if ( ! -r $file ) { return ; } my @string ; if ( open my $FILE, '<', $file ) { @string = <$FILE> ; close $FILE ; return( @string ) ; } else { myprint( "Error reading file $file : $OS_ERROR\n" ) ; return ; } } sub tests_file_to_string { note( 'Entering tests_file_to_string()' ) ; is( undef, file_to_string( ), 'file_to_string: no args => undef' ) ; is( undef, file_to_string( '/noexist' ), 'file_to_string: /noexist => undef' ) ; is( undef, file_to_string( '/' ), 'file_to_string: reading a directory => undef' ) ; ok( file_to_string( $PROGRAM_NAME ), 'file_to_string: reading myself' ) ; ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'file_to_string: mkpath W/tmp/tests/' ) ; is( 'lilili', string_to_file( 'lilili', 'W/tmp/tests/canbewritten' ), 'file_to_string: string_to_file filling W/tmp/tests/canbewritten with lilili' ) ; is( 'lilili', file_to_string( 'W/tmp/tests/canbewritten' ), 'file_to_string: reading W/tmp/tests/canbewritten is lilili' ) ; is( q{}, string_to_file( q{}, 'W/tmp/tests/empty' ), 'file_to_string: string_to_file filling W/tmp/tests/empty with empty string' ) ; is( q{}, file_to_string( 'W/tmp/tests/empty' ), 'file_to_string: reading W/tmp/tests/empty is empty' ) ; note( 'Leaving tests_file_to_string()' ) ; return ; } sub file_to_string { my $file = shift ; if ( ! $file ) { return ; } if ( ! -e $file ) { return ; } if ( ! -f $file ) { return ; } if ( ! -r $file ) { return ; } return( join q{}, file_to_array( $file ) ) ; } sub tests_string_to_file { note( 'Entering tests_string_to_file()' ) ; is( undef, string_to_file( ), 'string_to_file: no args => undef' ) ; is( undef, string_to_file( 'lalala' ), 'string_to_file: one arg => undef' ) ; is( undef, string_to_file( 'lalala', '.' ), 'string_to_file: writing a directory => undef' ) ; ok( (-d 'W/tmp/tests/' or mkpath( 'W/tmp/tests/' ) ), 'string_to_file: mkpath W/tmp/tests/' ) ; is( 'lalala', string_to_file( 'lalala', 'W/tmp/tests/canbewritten' ), 'string_to_file: W/tmp/tests/canbewritten with lalala' ) ; is( q{}, string_to_file( q{}, 'W/tmp/tests/empty' ), 'string_to_file: W/tmp/tests/empty with empty string' ) ; SKIP: { Readonly my $NB_UNX_tests_string_to_file => 1 ; skip( 'Not on Unix non-root', $NB_UNX_tests_string_to_file ) if ('MSWin32' eq $OSNAME or '0' eq $EFFECTIVE_USER_ID ) ; is( undef, string_to_file( 'lalala', '/cantouch' ), 'string_to_file: /cantouch denied => undef' ) ; } note( 'Leaving tests_string_to_file()' ) ; return ; } sub string_to_file { my( $string, $file ) = @_ ; if( ! defined $string ) { return ; } if( ! defined $file ) { return ; } if ( ! -e $file && ! -w dirname( $file ) ) { myprint( "string_to_file: directory of $file is not writable\n" ) ; return ; } if ( ! sysopen( FILE, $file, O_WRONLY|O_TRUNC|O_CREAT, 0600) ) { myprint( "string_to_file: failure writing to $file with error: $OS_ERROR\n" ) ; return ; } print FILE $string ; close FILE ; return $string ; } 0 and <<'MULTILINE_COMMENT' ; This is a multiline comment. Based on David Carter discussion, to do: * Call parameters stay the same. * Now always "return( $string, $error )". Descriptions below. OK * Still capture STDOUT via "1> $output_tmpfile" to finish in $string and "return( $string, $error )" OK * Now also capture STDERR via "2> $error_tmpfile" to finish in $error and "return( $string, $error )" OK * in case of CHILD_ERROR, return( undef, $error ) and print $error, with folder/UID/maybeSubject context, on console and at the end with the final error listing. Count this as a sync error. * in case of good command, take final $string as is, unless void. In case $error with value then print it. * in case of good command and final $string empty, consider it like CHILD_ERROR => return( undef, $error ) and print $error, with folder/UID/maybeSubject context, on console and at the end with the final error listing. Count this as a sync error. MULTILINE_COMMENT # End of multiline comment. sub pipemess { my ( $string, @commands ) = @_ ; my $error = q{} ; foreach my $command ( @commands ) { my $input_tmpfile = "$sync->{ tmpdir }/imapsync_tmp_file.$PROCESS_ID.inp.txt" ; my $output_tmpfile = "$sync->{ tmpdir }/imapsync_tmp_file.$PROCESS_ID.out.txt" ; my $error_tmpfile = "$sync->{ tmpdir }/imapsync_tmp_file.$PROCESS_ID.err.txt" ; string_to_file( $string, $input_tmpfile ) ; ` $command < $input_tmpfile 1> $output_tmpfile 2> $error_tmpfile ` ; my $is_command_ko = $CHILD_ERROR ; my $error_cmd = file_to_string( $error_tmpfile ) ; chomp( $error_cmd ) ; $string = file_to_string( $output_tmpfile ) ; my $string_len = length( $string ) ; unlink $input_tmpfile, $output_tmpfile, $error_tmpfile ; if ( $is_command_ko or ( ! $string_len ) ) { my $cmd_exit_value = $CHILD_ERROR >> 8 ; my $cmd_end_signal = $CHILD_ERROR & 127 ; my $signal_log = ( $cmd_end_signal ) ? " signal $cmd_end_signal and" : q{} ; my $error_log = qq{Failure: --pipemess command "$command" ended with$signal_log "$string_len" characters exit value "$cmd_exit_value" and STDERR "$error_cmd"\n} ; myprint( $error_log ) ; if ( wantarray ) { return @{ [ undef, $error_log ] } }else{ return ; } } if ( $error_cmd ) { $error .= qq{STDERR of --pipemess "$command": $error_cmd\n} ; myprint( qq{STDERR of --pipemess "$command": $error_cmd\n} ) ; } } #myprint( "[$string]\n" ) ; if ( wantarray ) { return ( $string, $error ) ; }else{ return $string ; } } sub tests_pipemess { note( 'Entering tests_pipemess()' ) ; SKIP: { Readonly my $NB_WIN_tests_pipemess => 3 ; skip( 'Not on MSWin32', $NB_WIN_tests_pipemess ) if ('MSWin32' ne $OSNAME) ; # Windows # "type" command does not accept redirection of STDIN with < # "sort" does ok( "nochange\n" eq pipemess( 'nochange', 'sort' ), 'pipemess: nearly no change by sort' ) ; ok( "nochange2\n" eq pipemess( 'nochange2', qw( sort sort ) ), 'pipemess: nearly no change by sort,sort' ) ; # command not found #diag( 'Warning and failure about cacaprout are on purpose' ) ; ok( ! defined( pipemess( q{}, 'cacaprout' ) ), 'pipemess: command not found' ) ; } ; my ( $stringT, $errorT ) ; SKIP: { Readonly my $NB_UNX_tests_pipemess => 25 ; skip( 'Not on Unix', $NB_UNX_tests_pipemess ) if ('MSWin32' eq $OSNAME) ; # Unix ok( 'nochange' eq pipemess( 'nochange', 'cat' ), 'pipemess: no change by cat' ) ; ok( 'nochange2' eq pipemess( 'nochange2', 'cat', 'cat' ), 'pipemess: no change by cat,cat' ) ; ok( " 1\tnumberize\n" eq pipemess( "numberize\n", 'cat -n' ), 'pipemess: numberize by cat -n' ) ; ok( " 1\tnumberize\n 2\tnumberize\n" eq pipemess( "numberize\nnumberize\n", 'cat -n' ), 'pipemess: numberize by cat -n' ) ; ok( "A\nB\nC\n" eq pipemess( "A\nC\nB\n", 'sort' ), 'pipemess: sort' ) ; # command not found #diag( 'Warning and failure about cacaprout are on purpose' ) ; is( undef, pipemess( q{}, 'cacaprout' ), 'pipemess: command not found' ) ; # success with true but no output at all is( undef, pipemess( q{blabla}, 'true' ), 'pipemess: true but no output' ) ; # failure with false and no output at all is( undef, pipemess( q{blabla}, 'false' ), 'pipemess: false and no output' ) ; # Failure since pipemess is not a real pipe, so first cat wait for standard input is( q{blabla}, pipemess( q{blabla}, '( cat|cat ) ' ), 'pipemess: ok by ( cat|cat )' ) ; ( $stringT, $errorT ) = pipemess( 'nochange', 'cat' ) ; is( $stringT, 'nochange', 'pipemess: list context, no change by cat, string' ) ; is( $errorT, q{}, 'pipemess: list context, no change by cat, no error' ) ; ( $stringT, $errorT ) = pipemess( 'dontcare', 'true' ) ; is( $stringT, undef, 'pipemess: list context, true but no output, string' ) ; like( $errorT, qr{\QFailure: --pipemess command "true" ended with "0" characters exit value "0" and STDERR ""\E}xm, 'pipemess: list context, true but no output, error' ) ; ( $stringT, $errorT ) = pipemess( 'dontcare', 'false' ) ; is( $stringT, undef, 'pipemess: list context, false and no output, string' ) ; like( $errorT, qr{\QFailure: --pipemess command "false" ended with "0" characters exit value "1" and STDERR ""\E}xm, 'pipemess: list context, false and no output, error' ) ; ( $stringT, $errorT ) = pipemess( 'dontcare', '/bin/echo -n blablabla' ) ; is( $stringT, q{blablabla}, 'pipemess: list context, "echo -n blablabla", string' ) ; is( $errorT, q{}, 'pipemess: list context, "echo blablabla", error' ) ; ( $stringT, $errorT ) = pipemess( 'dontcare', '( echo -n blablabla 3>&1 1>&2 2>&3 )' ) ; is( $stringT, undef, 'pipemess: list context, "no output STDERR blablabla", string' ) ; like( $errorT, qr{blablabla"}xm, 'pipemess: list context, "no output STDERR blablabla", error' ) ; ( $stringT, $errorT ) = pipemess( 'dontcare', '( echo -n blablabla 3>&1 1>&2 2>&3 )', 'false' ) ; is( $stringT, undef, 'pipemess: list context, "no output STDERR blablabla then false", string' ) ; like( $errorT, qr{blablabla"}xm, 'pipemess: list context, "no output STDERR blablabla then false", error' ) ; ( $stringT, $errorT ) = pipemess( 'dontcare', 'false', '( echo -n blablabla 3>&1 1>&2 2>&3 )' ) ; is( $stringT, undef, 'pipemess: list context, "false then STDERR blablabla", string' ) ; like( $errorT, qr{\QFailure: --pipemess command "false" ended with "0" characters exit value "1" and STDERR ""\E}xm, 'pipemess: list context, "false then STDERR blablabla", error' ) ; ( $stringT, $errorT ) = pipemess( 'dontcare', '( echo rrrrr ; echo -n error_blablabla 3>&1 1>&2 2>&3 )' ) ; like( $stringT, qr{rrrrr}xm, 'pipemess: list context, "STDOUT rrrrr STDERR error_blablabla", string' ) ; like( $errorT, qr{STDERR.*error_blablabla}xm, 'pipemess: list context, "STDOUT rrrrr STDERR error_blablabla", error' ) ; } ( $stringT, $errorT ) = pipemess( 'dontcare', 'cacaprout' ) ; is( $stringT, undef, 'pipemess: list context, cacaprout not found, string' ) ; like( $errorT, qr{\QFailure: --pipemess command "cacaprout" ended with "0" characters exit value\E}xm, 'pipemess: list context, cacaprout not found, error' ) ; note( 'Leaving tests_pipemess()' ) ; return ; } sub tests_is_a_release_number { note( 'Entering tests_is_a_release_number()' ) ; is( undef, is_a_release_number( ), 'is_a_release_number: no args => undef' ) ; ok( is_a_release_number( $RELEASE_NUMBER_EXAMPLE_1 ), 'is_a_release_number 1.351' ) ; ok( is_a_release_number( $RELEASE_NUMBER_EXAMPLE_2 ), 'is_a_release_number 42.4242' ) ; ok( is_a_release_number( imapsync_version( $sync ) ), 'is_a_release_number imapsync_version( )' ) ; ok( ! is_a_release_number( 'blabla' ), '! is_a_release_number blabla' ) ; note( 'Leaving tests_is_a_release_number()' ) ; return ; } sub is_a_release_number { my $number = shift ; if ( ! defined $number ) { return ; } return( $number =~ m{^\d+\.\d+$}xo ) ; } sub imapsync_version_public { my $local_version = imapsync_version( $sync ) ; my $imapsync_basename = imapsync_basename( ) ; my $context = imapsync_context( ) ; my $agent_info = "$OSNAME system, perl " . mysprintf( '%vd', $PERL_VERSION) . ", Mail::IMAPClient $Mail::IMAPClient::VERSION" . " $imapsync_basename" . " $context" ; my $sock = IO::Socket::INET->new( PeerAddr => 'imapsync.lamiral.info', PeerPort => 80, Proto => 'tcp', ) ; return( 'unknown' ) if not $sock ; print $sock "GET /prj/imapsync/VERSION HTTP/1.0\r\n", "User-Agent: imapsync/$local_version ($agent_info)\r\n", "Host: ks.lamiral.info\r\n\r\n" ; my @line = <$sock> ; close $sock ; my $last_release = $line[$LAST] ; chomp $last_release ; return( $last_release ) ; } sub not_long_imapsync_version_public { #myprint( "Entering not_long_imapsync_version_public\n" ) ; my $fake = shift ; if ( $fake ) { return $fake } my $val ; # Doesn't work with gethostbyname (see perlipc) #local $SIG{ALRM} = sub { die "alarm\n" } ; if ('MSWin32' eq $OSNAME) { local $SIG{ALRM} = sub { die "alarm\n" } ; }else{ POSIX::sigaction(SIGALRM, POSIX::SigAction->new(sub { croak 'alarm' } ) ) or myprint( "Error setting SIGALRM handler: $OS_ERROR\n" ) ; } my $ret = eval { alarm 3 ; { $val = imapsync_version_public( ) ; #sleep 4 ; #myprint( "End of imapsync_version_public\n" ) ; } alarm 0 ; 1 ; } ; #myprint( "eval [$ret]\n" ) ; if ( ( not $ret ) or $EVAL_ERROR ) { #myprint( "$EVAL_ERROR" ) ; if ($EVAL_ERROR =~ /alarm/) { # timed out return('timeout') ; }else{ alarm 0 ; return( 'unknown' ) ; # propagate unexpected errors } }else { # Good! return( $val ) ; } } sub tests_not_long_imapsync_version_public { note( 'Entering tests_not_long_imapsync_version_public()' ) ; is( 1, is_a_release_number( not_long_imapsync_version_public( ) ), 'not_long_imapsync_version_public: public release is a number' ) ; note( 'Leaving tests_not_long_imapsync_version_public()' ) ; return ; } sub check_last_release { my $fake = shift ; my $public_release = not_long_imapsync_version_public( $fake ) ; $sync->{ debug } and myprint( "check_last_release: [$public_release]\n" ) ; my $inline_help_when_on = '( Use --noreleasecheck to avoid this release check. )' ; if ( $public_release eq 'unknown' ) { return( 'Imapsync public release is unknown.' . $inline_help_when_on ) ; } if ( $public_release eq 'timeout' ) { return( 'Imapsync public release is unknown (timeout).' . $inline_help_when_on ) ; } if ( ! is_a_release_number( $public_release ) ) { return( "Imapsync public release is unknown ($public_release)." . $inline_help_when_on ) ; } my $imapsync_here = imapsync_version( $sync ) ; if ( $public_release > $imapsync_here ) { return( 'This imapsync is not up to date. ' . "( local $imapsync_here < official $public_release )" . $inline_help_when_on ) ; }else{ return( 'This imapsync is up to date. ' . "( local $imapsync_here >= official $public_release )" . $inline_help_when_on ) ; } return( 'really unknown' ) ; # Should never arrive here } sub tests_check_last_release { note( 'Entering tests_check_last_release()' ) ; diag( check_last_release( 1.1 ) ) ; # \Q \E here to avoid putting \ before each space like( check_last_release( 1.1 ), qr/\Qis up to date\E/mxs, 'check_last_release: up to date' ) ; like( check_last_release( 1.1 ), qr/1\.1/mxs, 'check_last_release: up to date, include number' ) ; diag( check_last_release( 999.999 ) ) ; like( check_last_release( 999.999 ), qr/\Qnot up to date\E/mxs, 'check_last_release: not up to date' ) ; like( check_last_release( 999.999 ), qr/999\.999/mxs, 'check_last_release: not up to date, include number' ) ; like( check_last_release( 'unknown' ), qr/\QImapsync public release is unknown\E/mxs, 'check_last_release: unknown' ) ; like( check_last_release( 'timeout' ), qr/\QImapsync public release is unknown (timeout)\E/mxs, 'check_last_release: timeout' ) ; like( check_last_release( 'lalala' ), qr/\QImapsync public release is unknown (lalala)\E/mxs, 'check_last_release: lalala' ) ; diag( check_last_release( ) ) ; note( 'Leaving tests_check_last_release()' ) ; return ; } sub tests_imapsync_context { note( 'Entering tests_imapsync_context()' ) ; like( imapsync_context( ), qr/^CGI|^Docker|^DockerCGI|^Standard/, 'imapsync_context: CGI or Docker or DockerCGI or Standard' ) ; note( 'Leaving tests_imapsync_context()' ) ; return ; } sub imapsync_context { my $mysync = shift ; my $context = q{} ; if ( under_docker_context( $mysync ) && under_cgi_context( $mysync ) ) { $context = 'DockerCGI' ; } elsif ( under_docker_context( $mysync ) ) { $context = 'Docker' ; } elsif ( under_cgi_context( $mysync ) ) { $context = 'CGI' ; } else { $context = 'Standard' ; } return $context ; } sub imapsync_version { my $mysync = shift ; my $rcs = $mysync->{rcs} ; my $version ; $version = version_from_rcs( $rcs ) ; return( $version ) ; } sub tests_version_from_rcs { note( 'Entering tests_version_from_rcs()' ) ; is( undef, version_from_rcs( ), 'version_from_rcs: no args => undef' ) ; is( 1.831, version_from_rcs( q{imapsync,v 1.831 2017/08/27} ), 'version_from_rcs: imapsync,v 1.831 2017/08/27 => 1.831' ) ; is( 'UNKNOWN', version_from_rcs( 1.831 ), 'version_from_rcs: 1.831 => UNKNOWN' ) ; note( 'Leaving tests_version_from_rcs()' ) ; return ; } sub version_from_rcs { my $rcs = shift ; if ( ! $rcs ) { return ; } my $version = 'UNKNOWN' ; if ( $rcs =~ m{,v\s+(\d+\.\d+)}mxso ) { $version = $1 } return( $version ) ; } sub tests_imapsync_basename { note( 'Entering tests_imapsync_basename()' ) ; ok( imapsync_basename() =~ m/imapsync/, 'imapsync_basename: match imapsync'); ok( 'blabla' ne imapsync_basename(), 'imapsync_basename: do not equal blabla'); note( 'Leaving tests_imapsync_basename()' ) ; return ; } sub imapsync_basename { return basename( $PROGRAM_NAME ) ; } sub localhost_info { my $mysync = shift ; my( $infos ) = join( q{}, "Here is imapsync ", imapsync_version( $mysync ), " on host " . hostname(), ", a $OSNAME system with ", ram_memory_info( ), "\n", 'with Perl ', mysprintf( '%vd ', $PERL_VERSION), "and Mail::IMAPClient $Mail::IMAPClient::VERSION", ) ; return( $infos ) ; } sub tests_cpu_number { note( 'Entering tests_cpu_number()' ) ; is( 1, is_integer( cpu_number( ) ), "cpu_number: is_integer" ) ; ok( 1 <= cpu_number( ), "cpu_number: 1 or more" ) ; is( 1, cpu_number( 1 ), "cpu_number: 1 => 1" ) ; is( 1, cpu_number( $MINUS_ONE ), "cpu_number: -1 => 1" ) ; is( 1, cpu_number( 'lalala' ), "cpu_number: lalala => 1" ) ; is( $NUMBER_42, cpu_number( $NUMBER_42 ), "cpu_number: $NUMBER_42 => $NUMBER_42" ) ; note( "cpu_number = " . cpu_number( ) . "\n" ) ; note( "hostname = " . hostname( ) . "\n" ) ; SKIP: { if ( ! ( 'i005' eq hostname() ) ) { skip( 'cpu_number on host != i005 (FreeBSD)', 1 ) ; } is( 4, cpu_number( ), "cpu_number: on i005 (FreeBSD) => 4" ) ; } ; SKIP: { if ( ! ( 'petite' eq hostname() ) ) { skip( 'cpu_number on host != petite (Linux)', 1 ) ; } is( 2, cpu_number( ), "cpu_number: on petite (Linux) => 2" ) ; } ; SKIP: { if ( ! ( skip_macosx( ) ) ) { skip( 'cpu_number on host != polarhome macosx (Darwin MacOS X 10.7.5 Lion)', 1 ) ; } is( 2, cpu_number( ), "cpu_number: on polarhome macosx (Darwin MacOS X 10.7.5 Lion) => 2" ) ; } ; SKIP: { if ( ! ( 'pcHPDV7-HP' eq hostname() ) ) { skip( 'cpu_number on host != pcHPDV7-HP (Windows 7, 64bits)', 1 ) ; } is( 2, cpu_number( ), "cpu_number: on pcHPDV7-HP (Windows 7, 64bits) => 2" ) ; } ; SKIP: { if ( ! ( 'CUILLERE' eq hostname() ) ) { skip( 'cpu_number on host != CUILLERE (Windows XP, 32bits)', 1 ) ; } is( 1, cpu_number( ), "cpu_number: on CUILLERE (Windows XP, 32bits) => 1" ) ; } ; note( 'Leaving tests_cpu_number()' ) ; return ; } sub cpu_number { my $cpu_number_forced = shift ; # Well, here 1 is better than 0 or undef my $cpu_number = 1 ; # Default value, erased if better found my @cpuinfo ; if ( $ENV{"NUMBER_OF_PROCESSORS"} ) { # might be under a Windows system $cpu_number = $ENV{"NUMBER_OF_PROCESSORS"} ; #myprint( "Number of processors found by env var NUMBER_OF_PROCESSORS: $cpu_number\n" ) ; } if ( 'darwin' eq $OSNAME ) { $cpu_number = backtick( "sysctl -n hw.ncpu" ) ; chomp( $cpu_number ) ; #myprint( "Number of processors found by cmd 'sysctl -n hw.ncpu': $cpu_number\n" ) ; } if ( 'freebsd' eq $OSNAME ) { $cpu_number = backtick( "sysctl -n kern.smp.cpus" ) ; chomp( $cpu_number ) ; #myprint( "Number of processors found by cmd 'sysctl -n kern.smp.cpus': $cpu_number\n" ) ; } if ( 'linux' eq $OSNAME && -e '/proc/cpuinfo' ) { @cpuinfo = file_to_array( '/proc/cpuinfo' ) ; $cpu_number = grep { /^processor/mxs } @cpuinfo ; #myprint( "Number of processors found via /proc/cpuinfo: $cpu_number\n" ) ; } if ( defined $cpu_number_forced ) { $cpu_number = $cpu_number_forced ; } return( integer_or_1( $cpu_number ) ) ; } sub tests_integer_or_1 { note( 'Entering tests_integer_or_1()' ) ; is( 1, integer_or_1( ), 'integer_or_1: no args => 1' ) ; is( 1, integer_or_1( undef ), 'integer_or_1: undef => 1' ) ; is( $NUMBER_10, integer_or_1( $NUMBER_10 ), 'integer_or_1: 10 => 10' ) ; is( 1, integer_or_1( q{} ), 'integer_or_1: empty string => 1' ) ; is( 1, integer_or_1( 'lalala' ), 'integer_or_1: lalala => 1' ) ; note( 'Leaving tests_integer_or_1()' ) ; return ; } sub integer_or_1 { my $number = shift ; if ( is_integer( $number ) ) { return $number ; } # else return 1 ; } sub tests_is_integer { note( 'Entering tests_is_integer()' ) ; is( undef, is_integer( ), 'is_integer: no args => undef ' ) ; ok( is_integer( 1 ), 'is_integer: 1 => yes ') ; ok( is_integer( $NUMBER_42 ), 'is_integer: 42 => yes ') ; ok( is_integer( "$NUMBER_42" ), 'is_integer: "$NUMBER_42" => yes ') ; ok( is_integer( '42' ), 'is_integer: "42" => yes ') ; ok( is_integer( $NUMBER_104_857_600 ), 'is_integer: 104_857_600 => yes') ; ok( is_integer( "$NUMBER_104_857_600" ), 'is_integer: "$NUMBER_104_857_600" => yes') ; ok( is_integer( '104857600' ), 'is_integer: 104857600 => yes') ; ok( ! is_integer( 'blabla' ), 'is_integer: blabla => no' ) ; ok( ! is_integer( q{} ), 'is_integer: empty string => no' ) ; note( 'Leaving tests_is_integer()' ) ; return ; } sub is_integer { my $number = shift ; if ( ! defined $number ) { return ; } return( $number =~ m{^\d+$}xo ) ; } sub tests_loadavg { note( 'Entering tests_loadavg()' ) ; SKIP: { skip( 'Tests for darwin', 3 ) if ('darwin' ne $OSNAME) ; is( undef, loadavg( '/noexist' ), 'loadavg: /noexist => undef' ) ; is_deeply( [ '0.11', '0.22', '0.33' ], [ loadavg( 'vm.loadavg: { 0.11 0.22 0.33 }' ) ], 'loadavg: "vm.loadavg: { 0.11 0.22 0.33 }" => 0.11 0.22 0.33' ) ; note( join( " ", "loadavg:", loadavg( ) ) ) ; is( 3, scalar( my @loadavg = loadavg( ) ), 'loadavg: 3 values' ) ; } ; SKIP: { skip( 'Tests for linux', 3 ) if ('linux' ne $OSNAME) ; is( undef, loadavg( '/noexist' ), 'loadavg: /noexist => undef' ) ; ok( loadavg( ), 'loadavg: no args' ) ; is_deeply( [ '0.39', '0.30', '0.37', '1/602' ], [ loadavg( '0.39 0.30 0.37 1/602 6073' ) ], 'loadavg 0.39 0.30 0.37 1/602 6073 => [0.39, 0.30, 0.37, 1/602]' ) ; } ; SKIP: { skip( 'Tests for Windows', 1 ) if ('MSWin32' ne $OSNAME) ; is_deeply( [ 0 ], [ loadavg( ) ], 'loadavg on MSWin32 => 0' ) ; } ; note( 'Leaving tests_loadavg()' ) ; return ; } sub loadavg { if ( 'linux' eq $OSNAME ) { return ( loadavg_linux( @ARG ) ) ; } if ( 'freebsd' eq $OSNAME ) { return ( loadavg_freebsd( @ARG ) ) ; } if ( 'darwin' eq $OSNAME ) { return ( loadavg_darwin( @ARG ) ) ; } if ( 'MSWin32' eq $OSNAME ) { return ( loadavg_windows( @ARG ) ) ; } return( 'unknown' ) ; } sub loadavg_linux { my $line = shift ; if ( ! $line ) { $line = firstline( '/proc/loadavg' ) or return ; } my ( $avg_1_min, $avg_5_min, $avg_15_min, $current_runs ) = split /\s/mxs, $line ; if ( all_defined( $avg_1_min, $avg_5_min, $avg_15_min ) ) { $sync->{ debug } and myprint( "System load: $avg_1_min $avg_5_min $avg_15_min $current_runs\n" ) ; return ( $avg_1_min, $avg_5_min, $avg_15_min, $current_runs ) ; } return ; } sub loadavg_freebsd { my $file = shift ; # Example of output of command "sysctl vm.loadavg": # vm.loadavg: { 0.15 0.08 0.08 } my $loadavg ; if ( ! defined $file ) { eval { $loadavg = `/sbin/sysctl vm.loadavg` ; #myprint( "LOADAVG FREEBSD: $loadavg\n" ) ; } ; if ( $EVAL_ERROR ) { myprint( "[$EVAL_ERROR]\n" ) ; return ; } }else{ $loadavg = firstline( $file ) or return ; } my ( $avg_1_min, $avg_5_min, $avg_15_min ) = $loadavg =~ /vm\.loadavg\s*[:=]\s*\{?\s*(\d+\.?\d*)\s+(\d+\.?\d*)\s+(\d+\.?\d*)/mxs ; $sync->{ debug } and myprint( "System load: $avg_1_min $avg_5_min $avg_15_min\n" ) ; return ( $avg_1_min, $avg_5_min, $avg_15_min ) ; } sub loadavg_darwin { my $line = shift ; # Example of output of command "sysctl vm.loadavg": # vm.loadavg: { 0.15 0.08 0.08 } my $loadavg ; if ( ! defined $line ) { eval { # $loadavg = `/usr/sbin/sysctl vm.loadavg` ; $loadavg = `LANG= /usr/sbin/sysctl vm.loadavg` ; #myprint( "LOADAVG DARWIN: $loadavg\n" ) ; } ; if ( $EVAL_ERROR ) { myprint( "[$EVAL_ERROR]\n" ) ; return ; } }else{ $loadavg = $line ; } my ( $avg_1_min, $avg_5_min, $avg_15_min ) = $loadavg =~ /vm\.loadavg\s*[:=]\s*\{?\s*(\d+\.?\d*)\s+(\d+\.?\d*)\s+(\d+\.?\d*)/mxs ; #$sync->{ debug } and myprint( "System load: $avg_1_min $avg_5_min $avg_15_min\n" ) ; return ( $avg_1_min, $avg_5_min, $avg_15_min ) ; } sub loadavg_windows { my $file = shift ; # Example of output of command "wmic cpu get loadpercentage": # LoadPercentage # 12 my $loadavg ; if ( ! defined $file ) { eval { #$loadavg = `CMD wmic cpu get loadpercentage` ; $loadavg = "LoadPercentage\n0\n" ; #myprint( "LOADAVG WIN: $loadavg\n" ) ; } ; if ( $EVAL_ERROR ) { myprint( "[$EVAL_ERROR]\n" ) ; return ; } }else{ $loadavg = file_to_string( $file ) or return ; #myprint( "$loadavg" ) ; } $loadavg =~ /LoadPercentage\n(\d+)/xms ; my $num = $1 ; $num /= 100 ; $sync->{ debug } and myprint( "System load: $num\n" ) ; return ( $num ) ; } sub tests_load_and_delay { note( 'Entering tests_load_and_delay()' ) ; is( undef, load_and_delay( ), 'load_and_delay: no args => undef ' ) ; is( undef, load_and_delay( 1 ), 'load_and_delay: not 4 args => undef ' ) ; is( undef, load_and_delay( 0, 1, 1, 1 ), 'load_and_delay: division per 0 => undef ' ) ; # ( $cpu_num, $avg_1_min, $avg_5_min, $avg_15_min ) is( 0, load_and_delay( 1, 1, 1, 1 ), 'load_and_delay: one core, loads are all 1 => ok ' ) ; is( 0, load_and_delay( 1, 1, 1, 1, 'lalala' ), 'load_and_delay: five arguments is ok' ) ; is( 0, load_and_delay( 2, 2, 2, 2 ), 'load_and_delay: two core, loads are all 2 => ok ' ) ; is( 0, load_and_delay( 2, 2, 4, 5 ), 'load_and_delay: two core, load1m is 2 => ok ' ) ; is( 0, load_and_delay( 1, 0, 0, 0 ), 'load_and_delay: one core, load1m=0 load5m=0 load15m=0 => 0 ' ) ; is( 0, load_and_delay( 1, 0, 0, 2 ), 'load_and_delay: one core, load1m=0 load5m=0 load15m=2 => 0 ' ) ; is( 0, load_and_delay( 1, 0, 2, 0 ), 'load_and_delay: one core, load1m=0 load5m=2 load15m=0 => 0 ' ) ; is( 0, load_and_delay( 1, 0, 2, 2 ), 'load_and_delay: one core, load1m=0 load5m=2 load15m=2 => 0 ' ) ; is( 0, load_and_delay( 1, 0, 3, 3 ), 'load_and_delay: one core, load1m=0 load5m=3 load15m=3 => 0 ' ) ; is( 0, load_and_delay( 1, 0, 4, 4 ), 'load_and_delay: one core, load1m=0 load5m=3 load15m=3 => 0 ' ) ; is( 0, load_and_delay( 1, 2, 0, 0 ), 'load_and_delay: one core, load1m=2 load5m=0 load15m=0 => 0 ' ) ; is( 0, load_and_delay( 1, 2, 0, 2 ), 'load_and_delay: one core, load1m=2 load5m=0 load15m=2 => 0 ' ) ; is( 0, load_and_delay( 1, 2, 2, 0 ), 'load_and_delay: one core, load1m=2 load5m=2 load15m=0 => 0 ' ) ; is( 0, load_and_delay( 1, 2, 2, 2 ), 'load_and_delay: one core, load1m=2 load5m=2 load15m=2 => 0 ' ) ; is( 0, load_and_delay( 1, 2.9, 2.9, 2.9 ), 'load_and_delay: one core, load1m=2.9 load5m=2.9 load15m=2.9 => 0 ' ) ; is( 0, load_and_delay( 1, 3, 0, 0 ), 'load_and_delay: one core, load1m=3 load5m=0 load15m=0 => 0 ' ) ; is( 0, load_and_delay( 1, 3, 2.9, 2.9 ), 'load_and_delay: one core, load1m=3 load5m=2.9 load15m=2.9 => 0 ' ) ; is( 0, load_and_delay( 1, 3, 3, 2.9 ), 'load_and_delay: one core, load1m=3 load5m=3 load15m=2.9 => 0 ' ) ; is( 0, load_and_delay( 1, 3, 3, 3 ), 'load_and_delay: one core, load1m=3 load5m=3 load15m=3 => 0 ' ) ; is( 1, load_and_delay( 1, 6, 0, 0 ), 'load_and_delay: one core, load1m=3 load5m=0 load15m=0 => 1 ' ) ; is( 1, load_and_delay( 1, 6, 5.9, 5.9 ), 'load_and_delay: one core, load1m=3 load5m=2.9 load15m=2.9 => 1 ' ) ; is( 5, load_and_delay( 1, 6, 6, 5.9 ), 'load_and_delay: one core, load1m=3 load5m=3 load15m=2.9 => 5 ' ) ; is( 15, load_and_delay( 1, 6, 6, 6 ), 'load_and_delay: one core, load1m=3 load5m=3 load15m=3 => 15 ' ) ; note( 'Leaving tests_load_and_delay()' ) ; return ; } sub load_and_delay { # Basically return 0 if load is not heavy, ie <= 1 per processor # Not enough arguments if ( 4 > scalar @ARG ) { return ; } my ( $cpu_num, $avg_1_min, $avg_5_min, $avg_15_min ) = @ARG ; if ( 0 == $cpu_num ) { return ; } # Let divide by number of cores ( $avg_1_min, $avg_5_min, $avg_15_min ) = map { $_ / $cpu_num } ( $avg_1_min, $avg_5_min, $avg_15_min ) ; # One of avg ok => ok, for now it is a OR if ( $avg_1_min < 6 ) { return 0 ; } if ( $avg_5_min < 6 ) { return 1 ; } # Retry in 1 minute if ( $avg_15_min < 6 ) { return 5 ; } # Retry in 5 minutes return 15 ; # Retry in 15 minutes } sub tests_cpu_time { note( 'Entering tests_cpu_time()' ) ; ok( is_number( cpu_time( ) ), 'cpu_time: no args => a number' ) ; my $mysync = { } ; $mysync->{ debug } = 1 ; ok( is_number( cpu_time( $mysync ) ), 'cpu_time: {} => a number' ) ; note( 'Leaving tests_cpu_time()' ) ; return ; } sub cpu_time { my $mysync = shift ; my @cpu_times = times ; if ( ! @cpu_times ) { return ; } my $cpu_time = 0 ; # last element is the sum of all elements $cpu_time = ( map { $cpu_time += $_ } @cpu_times )[ -1 ] ; my $cpu_time_round = mysprintf( '%.2f', $cpu_time ) ; $mysync->{ debug } and myprint( join(' + ', @cpu_times), " = $cpu_time ~ $cpu_time_round\n" ) ; return $cpu_time ; } sub tests_cpu_percent { note( 'Entering tests_cpu_percent()' ) ; is( '0.0', cpu_percent( ), 'cpu_percent: no args => 0.0' ) ; my $mysync = { } ; $mysync->{ debug } = 1 ; is( '0.0', cpu_percent( $mysync ), 'cpu_percent: {} => 0.0' ) ; is( '0.0', cpu_percent( $mysync, 0 ), 'cpu_percent: {} 0 => 0.0' ) ; is( '300.0', cpu_percent( $mysync, 3 ), 'cpu_percent: {} 3 => 300.0' ) ; is( '30.0', cpu_percent( $mysync, 3, 10 ), 'cpu_percent: {} 3 10 => 30.0' ) ; is( '0.0', cpu_percent( $mysync, 0, 10 ), 'cpu_percent: {} 0 10 => 0.0' ) ; note( 'Leaving tests_cpu_percent()' ) ; return ; } sub cpu_percent { my $mysync = shift ; my $cpu_time = shift || 0 ; my $timediff = shift || 1 ; # no division by 0 if ( $cpu_time > $timediff ) { myprint( "Strange: cpu_time $cpu_time > timediff $timediff\n" ) ; } my $cpu_percent = 0 ; $cpu_percent = mysprintf( '%.1f', 100 * $cpu_time / $timediff ) ; $mysync->{ debug } and myprint( "cpu_percent: $cpu_percent \n" ) ; return $cpu_percent ; } sub tests_cpu_percent_global { note( 'Entering tests_cpu_percent_global()' ) ; is( '0.0', cpu_percent_global( ), 'cpu_percent_global: no args => 0' ) ; my $mysync = { } ; $mysync->{ debug } = 1 ; is( '0.0', cpu_percent_global( $mysync ), 'cpu_percent_global: {} => 0' ) ; is( '0.0', cpu_percent_global( $mysync, 0 ), 'cpu_percent_global: {} 0 => 0' ) ; SKIP: { if ( ! ( 'i005' eq hostname() ) ) { skip( 'cpu_percent_global on host != i005', 1 ) ; } is( '25.0', cpu_percent_global( $mysync, 100 ), 'cpu_percent_global: {} 100 => 25 on host i005' ) ; } ; SKIP: { if ( ! ( 'petite' eq hostname() ) ) { skip( 'cpu_percent_global on host != petite', 1 ) ; } is( '50.0', cpu_percent_global( $mysync, 100 ), 'cpu_percent_global: {} 100 => 50 on host petite' ) ; } ; note( 'Leaving tests_cpu_percent_global()' ) ; return ; } sub cpu_percent_global { my $mysync = shift ; my $cpu_percent = shift || 0 ; my $cpu_number = cpu_number( ) ; my $cpu_percent_global ; $cpu_percent_global = mysprintf( '%.1f', $cpu_percent / $cpu_number ) ; $mysync->{ debug } and myprint( "cpu_percent_global: $cpu_percent_global \n" ) ; return( $cpu_percent_global ) ; } sub ram_memory_info { # In GigaBytes so division by 1024 * 1024 * 1024 # return( sprintf( "%.1f/%.1f free GiB of RAM", Sys::MemInfo::get("freemem") / ( $KIBI ** 3 ), Sys::MemInfo::get("totalmem") / ( $KIBI ** 3 ), ) ) ; } sub tests_memory_stress { note( 'Entering tests_memory_stress()' ) ; is( undef, memory_stress( ), 'memory_stress: => undef' ) ; note( 'Leaving tests_memory_stress()' ) ; return ; } sub memory_stress { my $total_ram_in_MB = Sys::MemInfo::get("totalmem") / ( $KIBI * $KIBI ) ; my $i = 1 ; myprintf("Stress memory consumption before: %.1f MiB\n", memory_consumption( ) / $KIBI / $KIBI ) ; while ( $i < $total_ram_in_MB / 1.7 ) { $a .= "A" x 1000_000; $i++ } ; myprintf("Stress memory consumption after: %.1f MiB\n", memory_consumption( ) / $KIBI / $KIBI ) ; return ; } sub tests_memory_consumption { note( 'Entering tests_memory_consumption()' ) ; note( "memory_consumption: " . memory_consumption() . " bytes aka " . bytes_display_string_dec( memory_consumption() ) ) ; like( memory_consumption( ), qr{\d+}xms,'memory_consumption no args') ; like( memory_consumption( 1 ), qr{\d+}xms,'memory_consumption 1') ; like( memory_consumption( $PROCESS_ID ), qr{\d+}xms,"memory_consumption_of_pids $PROCESS_ID") ; like( memory_consumption_ratio(), qr{\d+}xms, 'memory_consumption_ratio' ) ; like( memory_consumption_ratio(1), qr{\d+}xms, 'memory_consumption_ratio 1' ) ; like( memory_consumption_ratio(10), qr{\d+}xms, 'memory_consumption_ratio 10' ) ; note( 'Leaving tests_memory_consumption()' ) ; return ; } sub memory_consumption { # memory consumed by imapsync until now in bytes return( ( memory_consumption_of_pids( ) )[0] ); } sub debugmemory { my $mysync = shift ; if ( ! $mysync->{debugmemory} ) { return q{} ; } my $precision = shift ; return( mysprintf( "Memory consumption$precision: %.1f MiB\n", memory_consumption( ) / $KIBI / $KIBI ) ) ; } sub memory_consumption_of_pids { my @pid = @_; @pid = ( @pid ) ? @pid : ( $PROCESS_ID ) ; $sync->{ debug } and myprint( "memory_consumption_of_pids PIDs: @pid\n" ) ; my @val ; if ( ( 'MSWin32' eq $OSNAME ) or ( 'cygwin' eq $OSNAME ) ) { @val = memory_consumption_of_pids_win32( @pid ) ; } elsif ( 'darwin' eq $OSNAME ) { @val = memory_consumption_of_pids_mac( @pid ) ; } else { # Unix my @ps = qx{ ps -o vsz -p @pid } ; shift @ps ; # First line is column name "VSZ" chomp @ps ; # convert to octets @val = map { $_ * $KIBI } @ps ; } return( @val ) ; } sub memory_consumption_of_pids_mac { my @pid = @_ ; # Use IPC::Open3 from perlcrit -3 # But it stalls on Darwin, I don't understand why! #my @ps = backtick( "ps -o rss -p @pid" ) ; #myprint( "ps: @ps" ) ; my @ps = qx{ ps -o rss -p @pid } ; shift @ps ; # First line is column name "RSS" chomp @ps ; my @val = map { $_ * $KIBI } @ps ; return( @val ) ; } sub memory_consumption_of_pids_win32 { # Windows my @PID = @_; my %PID; # hash of pids as key values map { $PID{$_}++ } @PID; # Does not work but should work reading the tasklist documentation #@ps = qx{ tasklist /FI "PID eq @PID" }; my @ps = qx{ tasklist /NH /FO CSV } ; #my @ps = backtick( 'tasklist /NH /FO CSV' ) ; #myprint( "-" x $STD_CHAR_PER_LINE, "\n", @ps, "-" x $STD_CHAR_PER_LINE, "\n" ) ; my @val; foreach my $line (@ps) { my($name, $pid, $mem) = (split ',', $line )[0,1,4]; next if (! $pid); #myprint( "[$name][$pid][$mem]" ) ; if ($PID{remove_qq($pid)}) { #myprint( "MATCH !\n" ) ; chomp $mem ; $mem = remove_qq($mem); $mem = remove_Ko($mem); $mem = remove_not_num($mem); #myprint( "[$mem]\n" ) ; push @val, $mem * $KIBI; } } return(@val); } sub tests_backtick { note( 'Entering tests_backtick()' ) ; is( undef, backtick( ), 'backtick: no args' ) ; is( undef, backtick( q{} ), 'backtick: empty command' ) ; SKIP: { skip( 'test for MSWin32', 5 ) if ('MSWin32' ne $OSNAME) ; my @output ; @output = backtick( 'echo Hello World!' ) ; # Add \r on Windows. ok( "Hello World!\r\n" eq $output[0], 'backtick: echo Hello World!' ) ; $sync->{ debug } and myprint( "[@output]" ) ; @output = backtick( 'echo Hello & echo World!' ) ; ok( "Hello \r\n" eq $output[0], 'backtick: echo Hello & echo World! line 1' ) ; ok( "World!\r\n" eq $output[1], 'backtick: echo Hello & echo World! line 2' ) ; $sync->{ debug } and myprint( "[@output][$output[0]][$output[1]]" ) ; # Scalar context ok( "Hello World!\r\n" eq backtick( 'echo Hello World!' ), 'backtick: echo Hello World! scalar' ) ; ok( "Hello \r\nWorld!\r\n" eq backtick( 'echo Hello & echo World!' ), 'backtick: echo Hello & echo World! scalar 2 lines' ) ; } ; SKIP: { skip( 'test for Unix', 7 ) if ('MSWin32' eq $OSNAME) ; is( undef, backtick( 'aaaarrrg' ), 'backtick: aaaarrrg command not found' ) ; # Array context my @output ; @output = backtick( 'echo Hello World!' ) ; ok( "Hello World!\n" eq $output[0], 'backtick: echo Hello World!' ) ; $sync->{ debug } and myprint( "[@output]" ) ; @output = backtick( "echo Hello\necho World!" ) ; ok( "Hello\n" eq $output[0], 'backtick: echo Hello; echo World! line 1' ) ; ok( "World!\n" eq $output[1], 'backtick: echo Hello; echo World! line 2' ) ; $sync->{ debug } and myprint( "[@output]" ) ; # Scalar context ok( "Hello World!\n" eq backtick( 'echo Hello World!' ), 'backtick: echo Hello World! scalar' ) ; ok( "Hello\nWorld!\n" eq backtick( "echo Hello\necho World!" ), 'backtick: echo Hello; echo World! scalar 2 lines' ) ; # Return error positive value, that's ok is( undef, backtick( 'false' ), 'backtick: false returns no output' ) ; my $mem = backtick( "ps -o vsz -p $PROCESS_ID" ) ; $sync->{ debug } and myprint( "MEM=$mem\n" ) ; } note( 'Leaving tests_backtick()' ) ; return ; } sub backtick { my $command = shift ; if ( ! $command ) { return ; } my ( $writer, $reader, $err ) ; my @output ; my $pid ; my $eval = eval { $pid = IPC::Open3::open3( $writer, $reader, $err, $command ) ; } ; if ( $EVAL_ERROR ) { myprint( $EVAL_ERROR ) ; return ; } if ( ! $eval ) { return ; } if ( ! $pid ) { return ; } waitpid( $pid, 0 ) ; @output = <$reader>; # Output here # #my @errors = <$err>; #Errors here, instead of the console if ( not @output ) { return ; } #myprint( @output ) ; if ( $output[0] =~ /\Qopen3: exec of $command failed\E/mxs ) { return ; } if ( wantarray ) { return( @output ) ; } else { return( join( q{}, @output) ) ; } } sub tests_check_binary_embed_all_dyn_libs { note( 'Entering tests_check_binary_embed_all_dyn_libs()' ) ; is( 1, check_binary_embed_all_dyn_libs( ), 'check_binary_embed_all_dyn_libs: no args => 1' ) ; note( 'Leaving tests_check_binary_embed_all_dyn_libs()' ) ; return ; } sub check_binary_embed_all_dyn_libs { my @search_dyn_lib_locale = search_dyn_lib_locale( ) ; if ( @search_dyn_lib_locale ) { myprint( "Found myself $PROGRAM_NAME pid $PROCESS_ID using locale dynamic libraries that seems out of myself:\n" ) ; myprint( @search_dyn_lib_locale ) ; if ( $PROGRAM_NAME =~ m{imapsync_bin_Darwin} ) { return 0 ; } elsif ( $PROGRAM_NAME =~ m{imapsync.*\.exe} ) { return 0 ; } else { # is always ok for non binary return 1 ; } } else { # Found only embedded dynamic lib myprint( "Found only embedded dynamic lib. Good!\n" ) ; return 1 ; } } sub search_dyn_lib_locale { if ( 'darwin' eq $OSNAME ) { return search_dyn_lib_locale_darwin( ) ; } if ( 'linux' eq $OSNAME ) { return search_dyn_lib_locale_linux( ) ; } if ( 'MSWin32' eq $OSNAME ) { return search_dyn_lib_locale_MSWin32( ) ; } } sub search_dyn_lib_locale_darwin { my $command = qq{ lsof -p $PROCESS_ID | grep ' REG ' | grep .dylib | grep -v '/par-' } ; myprint( "Search non embeded dynamic libs with the command: $command\n" ) ; return backtick( $command ) ; } sub search_dyn_lib_locale_linux { my $command = qq{ lsof -p $PROCESS_ID | grep ' REG ' | grep -v '/tmp/par-' | grep '\.so' } ; myprint( "Search non embeded dynamic libs with the command: $command\n" ) ; return backtick( $command ) ; } sub search_dyn_lib_locale_MSWin32 { my $command = qq{ Listdlls.exe $PROCESS_ID|findstr Strawberry } ; # $command = qq{ Listdlls.exe $PROCESS_ID|findstr Strawberry } ; myprint( "Search non embeded dynamic libs with the command: $command\n" ) ; return qx( $command ) ; } sub remove_not_num { my $string = shift ; $string =~ tr/0-9//cd ; #myprint( "tr [$string]\n" ) ; return( $string ) ; } sub tests_remove_not_num { note( 'Entering tests_remove_not_num()' ) ; ok( '123' eq remove_not_num( 123 ), 'remove_not_num( 123 )' ) ; ok( '123' eq remove_not_num( '123' ), q{remove_not_num( '123' )} ) ; ok( '123' eq remove_not_num( '12 3' ), q{remove_not_num( '12 3' )} ) ; ok( '123' eq remove_not_num( 'a 12 3 Ko' ), q{remove_not_num( 'a 12 3 Ko' )} ) ; note( 'Leaving tests_remove_not_num()' ) ; return ; } sub remove_Ko { my $string = shift; if ($string =~ /^(.*)\sKo$/xo) { return($1); }else{ return($string); } } sub remove_qq { my $string = shift; if ($string =~ /^"(.*)"$/xo) { return($1); }else{ return($string); } } sub memory_consumption_ratio { my ($base) = @_; $base ||= 1; my $consu = memory_consumption(); return($consu / $base); } sub date_from_rcs { my $d = shift ; my %num2mon = qw( 01 Jan 02 Feb 03 Mar 04 Apr 05 May 06 Jun 07 Jul 08 Aug 09 Sep 10 Oct 11 Nov 12 Dec ) ; if ($d =~ m{(\d{4})/(\d{2})/(\d{2})\s(\d{2}):(\d{2}):(\d{2})}xo ) { # Handles the following format # 2015/07/10 11:05:59 -- Generated by RCS Date tag. #myprint( "$d\n" ) ; #myprint( "header: [$1][$2][$3][$4][$5][$6]\n" ) ; my ($year, $month, $day, $hour, $min, $sec) = ($1,$2,$3,$4,$5,$6) ; $month = $num2mon{$month} ; $d = "$day-$month-$year $hour:$min:$sec +0000" ; #myprint( "$d\n" ) ; } return( $d ) ; } sub tests_date_from_rcs { note( 'Entering tests_date_from_rcs()' ) ; ok('19-Sep-2015 16:11:07 +0000' eq date_from_rcs('Date: 2015/09/19 16:11:07 '), 'date_from_rcs from RCS date' ) ; note( 'Leaving tests_date_from_rcs()' ) ; return ; } sub good_date { # two incoming formats: # header Tue, 24 Aug 2010 16:00:00 +0200 # internal 24-Aug-2010 16:00:00 +0200 # outgoing format: internal date format # 24-Aug-2010 16:00:00 +0200 my $d = shift ; return(q{}) if not defined $d; SWITCH: { if ( $d =~ m{(\d?)(\d-...-\d{4})(\s\d{2}:\d{2}:\d{2})(\s(?:\+|-)\d{4})?}xo ) { #myprint( "internal: [$1][$2][$3][$4]\n" ) ; my ($day_1, $date_rest, $hour, $zone) = ($1,$2,$3,$4) ; $day_1 = '0' if ($day_1 eq q{}) ; $zone = ' +0000' if not defined $zone ; $d = $day_1 . $date_rest . $hour . $zone ; last SWITCH ; } if ($d =~ m{(?:\w{3,},\s)?(\d{1,2}),?\s+(\w{3,})\s+(\d{2,4})\s+(\d{1,2})(?::|\.)(\d{1,2})(?:(?::|\.)(\d{1,2}))?\s*((?:\+|-)\d{4})?}xo ) { # Handles any combination of following formats # Tue, 24 Aug 2010 16:00:00 +0200 -- Standard # 24 Aug 2010 16:00:00 +0200 -- Missing Day of Week # Tue, 24 Aug 97 16:00:00 +0200 -- Two digit year # Tue, 24 Aug 1997 16.00.00 +0200 -- Periods instead of colons # Tue, 24 Aug 1997 16:00:00 +0200 -- Extra whitespace between year and hour # Tue, 24 Aug 1997 6:5:2 +0200 -- Single digit hour, min, or second # Tue, 24, Aug 1997 16:00:00 +0200 -- Extra comma #myprint( "header: [$1][$2][$3][$4][$5][$6][$7][$8]\n" ) ; my ($day, $month, $year, $hour, $min, $sec, $zone) = ($1,$2,$3,$4,$5,$6,$7,$8); $year = '19' . $year if length($year) == 2 && $year =~ m/^[789]/xo; $year = '20' . $year if length($year) == 2; $month = substr $month, 0, 3 if length($month) > 4; $day = mysprintf( '%02d', $day); $hour = mysprintf( '%02d', $hour); $min = mysprintf( '%02d', $min); $sec = '00' if not defined $sec ; $sec = mysprintf( '%02d', $sec ) ; $zone = '+0000' if not defined $zone ; $d = "$day-$month-$year $hour:$min:$sec $zone" ; last SWITCH ; } if ($d =~ m{(?:.{3})\s(...)\s+(\d{1,2})\s(\d{1,2}):(\d{1,2}):(\d{1,2})\s(?:\w{3})?\s?(\d{4})}xo ) { # Handles any combination of following formats # Sun Aug 20 11:55:09 2006 # Wed Jan 24 11:58:38 MST 2007 # Wed Jan 2 08:40:57 2008 #myprint( "header: [$1][$2][$3][$4][$5][$6]\n" ) ; my ($month, $day, $hour, $min, $sec, $year) = ($1,$2,$3,$4,$5,$6); $day = mysprintf( '%02d', $day ) ; $hour = mysprintf( '%02d', $hour ) ; $min = mysprintf( '%02d', $min ) ; $sec = mysprintf( '%02d', $sec ) ; $d = "$day-$month-$year $hour:$min:$sec +0000" ; last SWITCH ; } my %num2mon = qw( 01 Jan 02 Feb 03 Mar 04 Apr 05 May 06 Jun 07 Jul 08 Aug 09 Sep 10 Oct 11 Nov 12 Dec ) ; if ($d =~ m{(\d{4})/(\d{2})/(\d{2})\s(\d{2}):(\d{2}):(\d{2})}xo ) { # Handles the following format # 2015/07/10 11:05:59 -- Generated by RCS Date tag. #myprint( "$d\n" ) ; #myprint( "header: [$1][$2][$3][$4][$5][$6]\n" ) ; my ($year, $month, $day, $hour, $min, $sec) = ($1,$2,$3,$4,$5,$6) ; $month = $num2mon{$month} ; $d = "$day-$month-$year $hour:$min:$sec +0000" ; #myprint( "$d\n" ) ; last SWITCH ; } if ($d =~ m{(\d{2})/(\d{2})/(\d{2})\s(\d{2}):(\d{2}):(\d{2})}xo ) { # Handles the following format # 02/06/09 22:18:08 -- Generated by AVTECH TemPageR devices #myprint( "header: [$1][$2][$3][$4][$5][$6]\n" ) ; my ($month, $day, $year, $hour, $min, $sec) = ($1,$2,$3,$4,$5,$6); $year = '20' . $year; $month = $num2mon{$month}; $d = "$day-$month-$year $hour:$min:$sec +0000"; last SWITCH ; } if ($d =~ m{\w{6,},\s(\w{3})\w+\s+(\d{1,2}),\s(\d{4})\s(\d{2}):(\d{2})\s(AM|PM)}xo ) { # Handles the following format # Saturday, December 14, 2002 05:00 PM - KBtoys.com order confirmations my ($month, $day, $year, $hour, $min, $apm) = ($1,$2,$3,$4,$5,$6); $hour += 12 if $apm eq 'PM' ; $day = mysprintf( '%02d', $day ) ; $d = "$day-$month-$year $hour:$min:00 +0000" ; last SWITCH ; } if ($d =~ m{(\w{3})\s(\d{1,2})\s(\d{4})\s(\d{2}):(\d{2}):(\d{2})\s((?:\+|-)\d{4})}xo ) { # Handles the following format # Saturday, December 14, 2002 05:00 PM - jr.com order confirmations my ($month, $day, $year, $hour, $min, $sec, $zone) = ($1,$2,$3,$4,$5,$6,$7); $day = mysprintf( '%02d', $day ) ; $d = "$day-$month-$year $hour:$min:$sec $zone"; last SWITCH ; } if ($d =~ m{(\d{1,2})-(\w{3})-(\d{4})}xo ) { # Handles the following format # 21-Jun-2001 - register.com domain transfer email circa 2001 my ($day, $month, $year) = ($1,$2,$3); $day = mysprintf( '%02d', $day); $d = "$day-$month-$year 11:11:11 +0000"; last SWITCH ; } # unknown or unmatch => return same string return($d); } $d = qq("$d") ; return( $d ) ; } sub tests_good_date { note( 'Entering tests_good_date()' ) ; ok(q{} eq good_date(), 'good_date no arg'); ok('"24-Aug-2010 16:00:00 +0200"' eq good_date('24-Aug-2010 16:00:00 +0200'), 'good_date internal 2digit zone'); ok('"24-Aug-2010 16:00:00 +0000"' eq good_date('24-Aug-2010 16:00:00'), 'good_date internal 2digit no zone'); ok('"01-Sep-2010 16:00:00 +0200"' eq good_date( '1-Sep-2010 16:00:00 +0200'), 'good_date internal SP 1digit'); ok('"24-Aug-2010 16:00:00 +0200"' eq good_date('Tue, 24 Aug 2010 16:00:00 +0200'), 'good_date header 2digit zone'); ok('"01-Sep-2010 16:00:00 +0000"' eq good_date('Wed, 1 Sep 2010 16:00:00'), 'good_date header SP 1digit zone'); ok('"01-Sep-2010 16:00:00 +0200"' eq good_date('Wed, 1 Sep 2010 16:00:00 +0200'), 'good_date header SP 1digit zone'); ok('"01-Sep-2010 16:00:00 +0200"' eq good_date('Wed, 1 Sep 2010 16:00:00 +0200 (CEST)'), 'good_date header SP 1digit zone'); ok('"06-Feb-2009 22:18:08 +0000"' eq good_date('02/06/09 22:18:08'), 'good_date header TemPageR'); ok('"02-Jan-2008 08:40:57 +0000"' eq good_date('Wed Jan 2 08:40:57 2008'), 'good_date header dice.com support 1digit day'); ok('"20-Aug-2006 11:55:09 +0000"' eq good_date('Sun Aug 20 11:55:09 2006'), 'good_date header dice.com support 2digit day'); ok('"24-Jan-2007 11:58:38 +0000"' eq good_date('Wed Jan 24 11:58:38 MST 2007'), 'good_date header status-now.com'); ok('"24-Aug-2010 16:00:00 +0200"' eq good_date('24 Aug 2010 16:00:00 +0200'), 'good_date header missing date of week'); ok('"24-Aug-2067 16:00:00 +0200"' eq good_date('Tue, 24 Aug 67 16:00:00 +0200'), 'good_date header 2digit year'); ok('"24-Aug-1977 16:00:00 +0200"' eq good_date('Tue, 24 Aug 77 16:00:00 +0200'), 'good_date header 2digit year'); ok('"24-Aug-1987 16:00:00 +0200"' eq good_date('Tue, 24 Aug 87 16:00:00 +0200'), 'good_date header 2digit year'); ok('"24-Aug-1997 16:00:00 +0200"' eq good_date('Tue, 24 Aug 97 16:00:00 +0200'), 'good_date header 2digit year'); ok('"24-Aug-2004 16:00:00 +0200"' eq good_date('Tue, 24 Aug 04 16:00:00 +0200'), 'good_date header 2digit year'); ok('"24-Aug-1997 16:00:00 +0200"' eq good_date('Tue, 24 Aug 1997 16.00.00 +0200'), 'good_date header period time sep'); ok('"24-Aug-1997 16:00:00 +0200"' eq good_date('Tue, 24 Aug 1997 16:00:00 +0200'), 'good_date header extra white space type1'); ok('"24-Aug-1997 05:06:02 +0200"' eq good_date('Tue, 24 Aug 1997 5:6:2 +0200'), 'good_date header 1digit time vals'); ok('"24-Aug-1997 05:06:02 +0200"' eq good_date('Tue, 24, Aug 1997 05:06:02 +0200'), 'good_date header extra commas'); ok('"01-Oct-2003 12:45:24 +0000"' eq good_date('Wednesday, 01 October 2003 12:45:24 CDT'), 'good_date header no abbrev'); ok('"11-Jan-2005 17:58:27 -0500"' eq good_date('Tue, 11 Jan 2005 17:58:27 -0500'), 'good_date extra white space'); ok('"18-Dec-2002 15:07:00 +0000"' eq good_date('Wednesday, December 18, 2002 03:07 PM'), 'good_date kbtoys.com orders'); ok('"16-Dec-2004 02:01:49 -0500"' eq good_date('Dec 16 2004 02:01:49 -0500'), 'good_date jr.com orders'); ok('"21-Jun-2001 11:11:11 +0000"' eq good_date('21-Jun-2001'), 'good_date register.com domain transfer'); ok('"18-Nov-2012 18:34:38 +0100"' eq good_date('Sun, 18 Nov 2012 18:34:38 +0100'), 'good_date pop2imap bug (Westeuropäische Normalzeit)'); ok('"19-Sep-2015 16:11:07 +0000"' eq good_date('Date: 2015/09/19 16:11:07 '), 'good_date from RCS date' ) ; note( 'Leaving tests_good_date()' ) ; return ; } sub tests_list_keys_in_2_not_in_1 { note( 'Entering tests_list_keys_in_2_not_in_1()' ) ; my @list; ok( ! list_keys_in_2_not_in_1( {}, {}), 'list_keys_in_2_not_in_1: {} {}'); ok( 0 == compare_lists( [], [ list_keys_in_2_not_in_1( {}, {} ) ] ), 'list_keys_in_2_not_in_1: {} {}'); ok( 0 == compare_lists( ['a','b'], [ list_keys_in_2_not_in_1( {}, {'a' => 1, 'b' => 1}) ]), 'list_keys_in_2_not_in_1: {} {a, b}'); ok( 0 == compare_lists( ['b'], [ list_keys_in_2_not_in_1( {'a' => 1}, {'a' => 1, 'b' => 1}) ]), 'list_keys_in_2_not_in_1: {a} {a, b}'); ok( 0 == compare_lists( [], [ list_keys_in_2_not_in_1( {'a' => 1, 'b' => 1}, {'a' => 1, 'b' => 1}) ]), 'list_keys_in_2_not_in_1: {a, b} {a, b}'); ok( 0 == compare_lists( [], [ list_keys_in_2_not_in_1( {'a' => 1, 'b' => 1, 'c' => 1}, {'a' => 1, 'b' => 1}) ]), 'list_keys_in_2_not_in_1: {a, b, c} {a, b}'); ok( 0 == compare_lists( ['b'], [ list_keys_in_2_not_in_1( {'a' => 1, 'c' => 1}, {'a' => 1, 'b' => 1}) ]), 'list_keys_in_2_not_in_1: {a, b, c} {a, b}'); note( 'Leaving tests_list_keys_in_2_not_in_1()' ) ; return ; } sub list_keys_in_2_not_in_1 { my $hash_1_ref = shift; my $hash_2_ref = shift; my @list; foreach my $key ( sort keys %{ $hash_2_ref } ) { #$sync->{ debug } and print "$key\n" ; if ( exists $hash_1_ref->{$key} ) { next ; } #$sync->{ debug } and print "list_keys_in_2_not_in_1: $key\n" ; push @list, $key ; } #$sync->{ debug } and print "@list\n" ; return( @list ) ; } sub list_folders_in_2_not_in_1 { my ( @h2_folders_not_in_h1, %h2_folders_not_in_h1 ) ; @h2_folders_not_in_h1 = list_keys_in_2_not_in_1( \%h1_folders_all, \%h2_folders_all ) ; map { $h2_folders_not_in_h1{$_} = 1} @h2_folders_not_in_h1 ; @h2_folders_not_in_h1 = list_keys_in_2_not_in_1( \%h2_folders_from_1_all, \%h2_folders_not_in_h1 ) ; #$sync->{ debug } and print "h2_folders_not_in_h1: @h2_folders_not_in_h1\n" ; return( reverse @h2_folders_not_in_h1 ) ; } sub tests_nb_messages_in_2_not_in_1 { note( 'Entering tests_stats_across_folders()' ) ; is( undef, nb_messages_in_2_not_in_1( ), 'nb_messages_in_2_not_in_1: no args => undef' ) ; my $mysync->{ h1_folders_of_md5 }->{ 'some_id_01' }->{ 'some_folder_01' } = 1 ; is( 0, nb_messages_in_2_not_in_1( $mysync ), 'nb_messages_in_2_not_in_1: no messages in 2 => 0' ) ; $mysync->{ h1_folders_of_md5 }->{ 'some_id_in_1_and_2' }->{ 'some_folder_01' } = 2 ; $mysync->{ h2_folders_of_md5 }->{ 'some_id_in_1_and_2' }->{ 'some_folder_02' } = 4 ; is( 0, nb_messages_in_2_not_in_1( $mysync ), 'nb_messages_in_2_not_in_1: a common message => 0' ) ; $mysync->{ h2_folders_of_md5 }->{ 'some_id_in_2_not_in_1' }->{ 'some_folder_02' } = 1 ; is( 1, nb_messages_in_2_not_in_1( $mysync ), 'nb_messages_in_2_not_in_1: one message in_2_not_in_1 => 1' ) ; $mysync->{ h2_folders_of_md5 }->{ 'some_other_id_in_2_not_in_1' }->{ 'some_folder_02' } = 3 ; is( 2, nb_messages_in_2_not_in_1( $mysync ), 'nb_messages_in_2_not_in_1: two messages in_2_not_in_1 => 2' ) ; note( 'Leaving tests_stats_across_folders()' ) ; return ; } sub nb_messages_in_2_not_in_1 { my $mysync = shift ; if ( not defined $mysync ) { return ; } $mysync->{ nb_messages_in_2_not_in_1 } = scalar( list_keys_in_2_not_in_1( $mysync->{ h1_folders_of_md5 }, $mysync->{ h2_folders_of_md5 } ) ) ; return $mysync->{ nb_messages_in_2_not_in_1 } ; } sub nb_messages_in_1_not_in_2 { my $mysync = shift ; if ( not defined $mysync ) { return ; } $mysync->{ nb_messages_in_1_not_in_2 } = scalar( list_keys_in_2_not_in_1( $mysync->{ h2_folders_of_md5 }, $mysync->{ h1_folders_of_md5 } ) ) ; return $mysync->{ nb_messages_in_1_not_in_2 } ; } sub comment_on_final_diff_in_1_not_in_2 { my $mysync = shift ; if ( not defined $mysync or $mysync->{ justfolders } or $mysync->{ useuid } ) { return ; } my $nb_identified_h1_messages = scalar( keys %{ $mysync->{ h1_folders_of_md5 } } ) ; my $nb_identified_h2_messages = scalar( keys %{ $mysync->{ h2_folders_of_md5 } } ) ; $mysync->{ debug } and myprint( "nb_keys h1_folders_of_md5 $nb_identified_h1_messages\n" ) ; $mysync->{ debug } and myprint( "nb_keys h2_folders_of_md5 $nb_identified_h2_messages\n" ) ; if ( 0 == $nb_identified_h1_messages ) { return ; } # Calculate if not yet done if ( not defined $mysync->{ nb_messages_in_1_not_in_2 } ) { nb_messages_in_1_not_in_2( $mysync ) ; } if ( 0 == $mysync->{ nb_messages_in_1_not_in_2 } ) { myprint( "The sync looks good, all ", $nb_identified_h1_messages, " identified messages in host1 are on host2.\n" ) ; } else { myprint( "The sync is not finished, there are ", $mysync->{ nb_messages_in_1_not_in_2 }, " among ", $nb_identified_h1_messages, " identified messages in host1 that are not on host2.\n" ) ; } if ( 1 <= $mysync->{ h1_nb_msg_noheader } ) { myprint( "There are ", $mysync->{ h1_nb_msg_noheader }, " unidentified messages (usually Sent or Draft messages).", " To sync them add option --addheader\n" ) ; } else { myprint( "There is no unidentified message on host1.\n" ) ; } return ; } sub comment_on_final_diff_in_2_not_in_1 { my $mysync = shift ; if ( not defined $mysync or $mysync->{ justfolders } or $mysync->{ useuid } ) { return ; } my $nb_identified_h2_messages = scalar( keys %{ $mysync->{ h2_folders_of_md5 } } ) ; # Calculate if not done yet if ( not defined $mysync->{ nb_messages_in_2_not_in_1 } ) { nb_messages_in_2_not_in_1( $mysync ) ; } if ( 0 == $mysync->{ nb_messages_in_2_not_in_1 } ) { myprint( "The sync is strict, all ", $nb_identified_h2_messages, " identified messages in host2 are on host1.\n" ) ; } else { myprint( "The sync is not strict, there are ", $mysync->{ nb_messages_in_2_not_in_1 }, " among ", $nb_identified_h2_messages, " identified messages in host2 that are not on host1.", " Use --delete2 and sync again to delete them and have a strict sync.\n" ) ; } return ; } sub tests_match { note( 'Entering tests_match()' ) ; # undef serie is( undef, match( ), 'match: no args => undef' ) ; is( undef, match( 'lalala' ), 'match: one args => undef' ) ; # This one gives 0 under a binary made by pp # but 1 under "normal" Perl interpreter. So a PAR bug? #is( 1, match( q{}, q{} ), 'match: q{} =~ q{} => 1' ) ; is( 'lalala', match( 'lalala', 'lalala' ), 'match: lalala =~ lalala => lalala' ) ; is( 'lalala', match( 'lalala', '^lalala' ), 'match: lalala =~ ^lalala => lalala' ) ; is( 'lalala', match( 'lalala', 'lalala$' ), 'match: lalala =~ lalala$ => lalala' ) ; is( 'lalala', match( 'lalala', '^lalala$' ), 'match: lalala =~ ^lalala$ => lalala' ) ; is( '_lalala_', match( '_lalala_', 'lalala' ), 'match: _lalala_ =~ lalala => _lalala_' ) ; is( 'lalala', match( 'lalala', '.*' ), 'match: lalala =~ .* => lalala' ) ; is( 'lalala', match( 'lalala', '.' ), 'match: lalala =~ . => lalala' ) ; is( '/lalala/', match( '/lalala/', '/lalala/' ), 'match: /lalala/ =~ /lalala/ => /lalala/' ) ; is( 0, match( 'foo', 's/foo/bar/g' ), 'match: foo =~ s/foo/bar/g => 0' ) ; is( 's/foo/bar/g', match( 's/foo/bar/g', 's/foo/bar/g' ), 'match: s/foo/bar/g =~ s/foo/bar/g => s/foo/bar/g' ) ; is( 0, match( 'lalala', 'ooo' ), 'match: lalala =~ ooo => 0' ) ; is( 0, match( 'lalala', 'lal_ala' ), 'match: lalala =~ lal_ala => 0' ) ; is( 0, match( 'lalala', '\.' ), 'match: lalala =~ \. => 0' ) ; is( 0, match( 'lalalaX', '^lalala$' ), 'match: lalalaX =~ ^lalala$ => 0' ) ; is( 0, match( 'lalala', '/lalala/' ), 'match: lalala =~ /lalala/ => 0' ) ; is( 'LALALA', match( 'LALALA', '(?i:lalala)' ), 'match: LALALA =~ (?i:lalala) => 1' ) ; is( undef, match( 'LALALA', '(?{`ls /`})' ), 'match: LALALA =~ (?{`ls /`}) => undef' ) ; is( undef, match( 'LALALA', '(?{print "CACA"})' ), 'match: LALALA =~ (?{print "CACA"}) => undef' ) ; is( undef, match( 'CACA', '(??{print "CACA"})' ), 'match: CACA =~ (??{print "CACA"}) => undef' ) ; note( 'Leaving tests_match()' ) ; return ; } sub match { my( $var, $regex ) = @ARG ; # undef cases if ( ( ! defined $var ) or ( ! defined $regex ) ) { return ; } # normal cases if ( eval { $var =~ qr{$regex} } ) { return $var ; }elsif ( $EVAL_ERROR ) { myprint( "Fatal regex $regex\n" ) ; return ; } else { return 0 ; } return ; } sub tests_notmatch { note( 'Entering tests_notmatch()' ) ; # undef serie is( undef, notmatch( ), 'notmatch: no args => undef' ) ; is( undef, notmatch( 'lalala' ), 'notmatch: one args => undef' ) ; is( 1, notmatch( 'lalala', '/lalala/' ), 'notmatch: lalala !~ /lalala/ => 1' ) ; is( 0, notmatch( '/lalala/', '/lalala/' ), 'notmatch: /lalala/ !~ /lalala/ => 0' ) ; is( 1, notmatch( 'lalala', '/ooo/' ), 'notmatch: lalala !~ /ooo/ => 1' ) ; # This one gives 1 under a binary made by pp # but 0 under "normal" Perl interpreter. So a PAR bug, same in tests_match . #is( 0, notmatch( q{}, q{} ), 'notmatch: q{} !~ q{} => 0' ) ; is( 0, notmatch( 'lalala', 'lalala' ), 'notmatch: lalala !~ lalala => 0' ) ; is( 0, notmatch( 'lalala', '^lalala' ), 'notmatch: lalala !~ ^lalala => 0' ) ; is( 0, notmatch( 'lalala', 'lalala$' ), 'notmatch: lalala !~ lalala$ => 0' ) ; is( 0, notmatch( 'lalala', '^lalala$' ), 'notmatch: lalala !~ ^lalala$ => 0' ) ; is( 0, notmatch( '_lalala_', 'lalala' ), 'notmatch: _lalala_ !~ lalala => 0' ) ; is( 0, notmatch( 'lalala', '.*' ), 'notmatch: lalala !~ .* => 0' ) ; is( 0, notmatch( 'lalala', '.' ), 'notmatch: lalala !~ . => 0' ) ; is( 1, notmatch( 'lalala', 'ooo' ), 'notmatch: does not match regex => 1' ) ; is( 1, notmatch( 'lalala', 'lal_ala' ), 'notmatch: does not match regex => 1' ) ; is( 1, notmatch( 'lalala', '\.' ), 'notmatch: matches regex => 0' ) ; is( 1, notmatch( 'lalalaX', '^lalala$' ), 'notmatch: does not match regex => 1' ) ; note( 'Leaving tests_notmatch()' ) ; return ; } sub notmatch { my( $var, $regex ) = @ARG ; # undef cases if ( ( ! defined $var ) or ( ! defined $regex ) ) { return ; } # normal cases if ( eval { $var !~ $regex } ) { return 1 ; }elsif ( $EVAL_ERROR ) { myprint( "Fatal regex $regex\n" ) ; return ; }else{ return 0 ; } return ; } sub delete_folders_in_2_not_in_1 { foreach my $folder ( @h2_folders_not_in_1 ) { if ( defined $delete2foldersonly and eval "\$folder !~ $delete2foldersonly" ) { myprint( "Not deleting $folder because of --delete2foldersonly $delete2foldersonly\n" ) ; next ; } if ( defined $delete2foldersbutnot and eval "\$folder =~ $delete2foldersbutnot" ) { myprint( "Not deleting $folder because of --delete2foldersbutnot $delete2foldersbutnot\n" ) ; next ; } my $res = $sync->{dry} ; # always success in dry mode! $sync->{imap2}->unsubscribe( $folder ) if ( ! $sync->{dry} ) ; $res = $sync->{imap2}->delete( $folder ) if ( ! $sync->{dry} ) ; if ( $res ) { myprint( "Deleted $folder", "$sync->{dry_message}", "\n" ) ; }else{ myprint( "Deleting $folder failed", "\n" ) ; } } return ; } sub delete_folder { my ( $mysync, $imap, $folder, $Side ) = @_ ; if ( ! $mysync ) { return ; } if ( ! $imap ) { return ; } if ( ! $folder ) { return ; } $Side ||= 'HostX' ; my $res = $mysync->{dry} ; # always success in dry mode! if ( ! $mysync->{dry} ) { $imap->unsubscribe( $folder ) ; $res = $imap->delete( $folder ) ; } if ( $res ) { myprint( "$Side deleted $folder", $mysync->{dry_message}, "\n" ) ; return 1 ; }else{ myprint( "$Side deleting $folder failed", "\n" ) ; return ; } } sub delete1emptyfolders { my $mysync = shift ; if ( ! $mysync ) { return ; } # abort if no parameter if ( ! $mysync->{delete1emptyfolders} ) { return ; } # abort if --delete1emptyfolders off my $imap = $mysync->{imap1} ; if ( ! $imap ) { return ; } # abort if no imap if ( $imap->IsUnconnected( ) ) { return ; } # abort if disconnected my %folders_kept ; myprint( qq{Host1 deleting empty folders\n} ) ; foreach my $folder ( reverse sort @{ $mysync->{h1_folders_wanted} } ) { my $parenthood = $imap->is_parent( $folder ) ; if ( defined $parenthood and $parenthood ) { myprint( "Host1: folder $folder has subfolders\n" ) ; $folders_kept{ $folder }++ ; next ; } my $nb_messages_select = examine_folder_and_count( $mysync, $imap, $folder, 'Host1' ) ; if ( ! defined $nb_messages_select ) { next ; } # Select failed => Neither continue nor keep this folder } my $nb_messages_search = scalar( @{ $imap->messages( ) } ) ; if ( 0 != $nb_messages_select and 0 != $nb_messages_search ) { myprint( "Host1: folder $folder has messages: $nb_messages_search (search) $nb_messages_select (select)\n" ) ; $folders_kept{ $folder }++ ; next ; } if ( 0 != $nb_messages_select + $nb_messages_search ) { myprint( "Host1: folder $folder odd messages count: $nb_messages_search (search) $nb_messages_select (select)\n" ) ; $folders_kept{ $folder }++ ; next ; } # Here we must have 0 messages by messages() aka "SEARCH ALL" and also "EXAMINE" if ( uc $folder eq 'INBOX' ) { myprint( "Host1: Not deleting $folder\n" ) ; $folders_kept{ $folder }++ ; next ; } myprint( "Host1: deleting empty folder $folder\n" ) ; # can not delete a SELECTed or EXAMINEd folder so closing it # could changed be SELECT INBOX $imap->close( ) ; # close after examine does not expunge; anyway expunging an empty folder... if ( delete_folder( $mysync, $imap, $folder, 'Host1' ) ) { next ; # Deleted, good! }else{ $folders_kept{ $folder }++ ; next ; # Not deleted, bad! } } remove_deleted_folders_from_wanted_list( $mysync, %folders_kept ) ; myprint( qq{Host1 ended deleting empty folders\n} ) ; return ; } sub remove_deleted_folders_from_wanted_list { my ( $mysync, %folders_kept ) = @ARG ; my @h1_folders_wanted_init = @{ $mysync->{h1_folders_wanted} } ; my @h1_folders_wanted_last ; foreach my $folder ( @h1_folders_wanted_init ) { if ( $folders_kept{ $folder } ) { push @h1_folders_wanted_last, $folder ; } } @{ $mysync->{h1_folders_wanted} } = @h1_folders_wanted_last ; return ; } sub examine_folder_and_count { my ( $mysync, $imap, $folder, $Side ) = @_ ; $Side ||= 'HostX' ; if ( ! examine_folder( $mysync, $imap, $folder, $Side ) ) { return ; } my $nb_messages_select = count_from_select( $imap->History ) ; return $nb_messages_select ; } sub tests_delete1emptyfolders { note( 'Entering tests_delete1emptyfolders()' ) ; is( undef, delete1emptyfolders( ), q{delete1emptyfolders: undef} ) ; my $syncT ; is( undef, delete1emptyfolders( $syncT ), q{delete1emptyfolders: undef 2} ) ; my $imapT ; $syncT->{imap1} = $imapT ; is( undef, delete1emptyfolders( $syncT ), q{delete1emptyfolders: undef imap} ) ; require_ok( "Test::MockObject" ) ; $imapT = Test::MockObject->new( ) ; $syncT->{imap1} = $imapT ; $imapT->set_true( 'IsUnconnected' ) ; is( undef, delete1emptyfolders( $syncT ), q{delete1emptyfolders: Unconnected imap} ) ; # Now connected tests $imapT->set_false( 'IsUnconnected' ) ; $imapT->mock( 'LastError', sub { q{LastError mocked} } ) ; $syncT->{delete1emptyfolders} = 0 ; tests_delete1emptyfolders_unit( $syncT, [ qw{ INBOX DELME1 DELME2 } ], [ qw{ INBOX DELME1 DELME2 } ], q{tests_delete1emptyfolders: --delete1emptyfolders OFF} ) ; # All are parents => no deletion at all $imapT->set_true( 'is_parent' ) ; $syncT->{delete1emptyfolders} = 1 ; tests_delete1emptyfolders_unit( $syncT, [ qw{ INBOX DELME1 DELME2 } ], [ qw{ INBOX DELME1 DELME2 } ], q{tests_delete1emptyfolders: --delete1emptyfolders ON} ) ; # No parents but examine false for all => skip all $imapT->set_false( 'is_parent', 'examine' ) ; tests_delete1emptyfolders_unit( $syncT, [ qw{ INBOX DELME1 DELME2 } ], [ ], q{tests_delete1emptyfolders: EXAMINE fails} ) ; # examine ok for all but History bad => skip all $imapT->set_true( 'examine' ) ; $imapT->mock( 'History', sub { ( q{History badly mocked} ) } ) ; tests_delete1emptyfolders_unit( $syncT, [ qw{ INBOX DELME1 DELME2 } ], [ ], q{tests_delete1emptyfolders: examine ok but History badly mocked so count messages fails} ) ; # History good but some messages EXISTS == messages() => no deletion $imapT->mock( 'History', sub { ( q{* 2 EXISTS} ) } ) ; $imapT->mock( 'messages', sub { [ qw{ UID_1 UID_2 } ] } ) ; tests_delete1emptyfolders_unit( $syncT, [ qw{ INBOX DELME1 DELME2 } ], [ qw{ INBOX DELME1 DELME2 } ], q{tests_delete1emptyfolders: History EXAMINE ok, several messages} ) ; # 0 EXISTS but != messages() => no deletion $imapT->mock( 'History', sub { ( q{* 0 EXISTS} ) } ) ; $imapT->mock( 'messages', sub { [ qw{ UID_1 UID_2 } ] } ) ; tests_delete1emptyfolders_unit( $syncT, [ qw{ INBOX DELME1 DELME2 } ], [ qw{ INBOX DELME1 DELME2 } ], q{tests_delete1emptyfolders: 0 EXISTS but 2 by messages()} ) ; # 1 EXISTS but != 0 == messages() => no deletion $imapT->mock( 'History', sub { ( q{* 1 EXISTS} ) } ) ; $imapT->mock( 'messages', sub { [ ] } ) ; tests_delete1emptyfolders_unit( $syncT, [ qw{ INBOX DELME1 DELME2 } ], [ qw{ INBOX DELME1 DELME2 } ], q{tests_delete1emptyfolders: 1 EXISTS but 0 by messages()} ) ; # 0 EXISTS and 0 == messages() => deletion except INBOX $imapT->mock( 'History', sub { ( q{* 0 EXISTS} ) } ) ; $imapT->mock( 'messages', sub { [ ] } ) ; $imapT->set_true( qw{ delete close unsubscribe } ) ; $syncT->{dry_message} = q{ (not really since in a mocked test)} ; tests_delete1emptyfolders_unit( $syncT, [ qw{ INBOX DELME1 DELME2 } ], [ qw{ INBOX } ], q{tests_delete1emptyfolders: 0 EXISTS 0 by messages() delete folders, keep INBOX} ) ; note( 'Leaving tests_delete1emptyfolders()' ) ; return ; } sub tests_delete1emptyfolders_unit { note( 'Entering tests_delete1emptyfolders_unit()' ) ; my $syncT = shift ; my $folders1wanted_init_ref = shift ; my $folders1wanted_after_ref = shift ; my $comment = shift || q{delete1emptyfolders:} ; my @folders1wanted_init = @{ $folders1wanted_init_ref } ; my @folders1wanted_after = @{ $folders1wanted_after_ref } ; @{ $syncT->{h1_folders_wanted} } = @folders1wanted_init ; is_deeply( $syncT->{h1_folders_wanted}, \@folders1wanted_init, qq{$comment, init check} ) ; delete1emptyfolders( $syncT ) ; is_deeply( $syncT->{h1_folders_wanted}, \@folders1wanted_after, qq{$comment, after check} ) ; note( 'Leaving tests_delete1emptyfolders_unit()' ) ; return ; } sub extract_header { my $string = shift ; my ( $header ) = split /\n\n/x, $string ; if ( ! $header ) { return( q{} ) ; } #myprint( "[$header]\n" ) ; return( $header ) ; } sub tests_extract_header { note( 'Entering tests_extract_header()' ) ; my $h = <<'EOM'; Message-Id: <20100428101817.A66CB162474E@plume.est.belle> Date: Wed, 28 Apr 2010 12:18:17 +0200 (CEST) From: gilles@louloutte.dyndns.org (Gilles LAMIRAL) EOM chomp $h ; ok( $h eq extract_header( <<'EOM' Message-Id: <20100428101817.A66CB162474E@plume.est.belle> Date: Wed, 28 Apr 2010 12:18:17 +0200 (CEST) From: gilles@louloutte.dyndns.org (Gilles LAMIRAL) body lalala EOM ), 'extract_header: 1') ; note( 'Leaving tests_extract_header()' ) ; return ; } sub decompose_header{ my $string = shift ; # a hash, for a keyword header KEY value are list of strings [VAL1, VAL1_other, etc] # Think of multiple "Received:" header lines. my $header = { } ; my ($key, $val ) ; my @line = split /\n|\r\n/x, $string ; foreach my $line ( @line ) { #myprint( "DDD $line\n" ) ; # End of header last if ( $line =~ m{^$}xo ) ; # Key: value if ( $line =~ m/(^[^:]+):\s(.*)/xo ) { $key = $1 ; $val = $2 ; $debugdev and myprint( "DDD KV [$key] [$val]\n" ) ; push @{ $header->{ $key } }, $val ; # blanc and value => value from previous line continues }elsif( $line =~ m/^(\s+)(.*)/xo ) { $val = $2 ; $debugdev and myprint( "DDD V [$val]\n" ) ; @{ $header->{ $key } }[ $LAST ] .= " $val" if $key ; # dirty line? }else{ next ; } } #myprint( Data::Dumper->Dump( [ $header ] ) ) ; return( $header ) ; } sub tests_decompose_header{ note( 'Entering tests_decompose_header()' ) ; my $header_dec ; $header_dec = decompose_header( <<'EOH' KEY_1: VAL_1 KEY_2: VAL_2 VAL_2_+ VAL_2_++ KEY_3: VAL_3 KEY_1: VAL_1_other KEY_4: VAL_4 VAL_4_+ KEY_5 BLANC: VAL_5 KEY_6_BAD_BODY: VAL_6 EOH ) ; ok( 'VAL_3' eq $header_dec->{ 'KEY_3' }[0], 'decompose_header: VAL_3' ) ; ok( 'VAL_1' eq $header_dec->{ 'KEY_1' }[0], 'decompose_header: VAL_1' ) ; ok( 'VAL_1_other' eq $header_dec->{ 'KEY_1' }[1], 'decompose_header: VAL_1_other' ) ; ok( 'VAL_2 VAL_2_+ VAL_2_++' eq $header_dec->{ 'KEY_2' }[0], 'decompose_header: VAL_2 VAL_2_+ VAL_2_++' ) ; ok( 'VAL_4 VAL_4_+' eq $header_dec->{ 'KEY_4' }[0], 'decompose_header: VAL_4 VAL_4_+' ) ; ok( ' VAL_5' eq $header_dec->{ 'KEY_5 BLANC' }[0], 'decompose_header: KEY_5 BLANC' ) ; ok( not( defined $header_dec->{ 'KEY_6_BAD_BODY' }[0] ), 'decompose_header: KEY_6_BAD_BODY' ) ; $header_dec = decompose_header( <<'EOH' Message-Id: <20100428101817.A66CB162474E@plume.est.belle> Date: Wed, 28 Apr 2010 12:18:17 +0200 (CEST) From: gilles@louloutte.dyndns.org (Gilles LAMIRAL) EOH ) ; ok( '<20100428101817.A66CB162474E@plume.est.belle>' eq $header_dec->{ 'Message-Id' }[0], 'decompose_header: 1' ) ; $header_dec = decompose_header( <<'EOH' Return-Path: Received: by plume.est.belle (Postfix, from userid 1000) id 120A71624742; Wed, 28 Apr 2010 01:46:40 +0200 (CEST) Subject: test:eekahceishukohpe EOH ) ; ok( 'by plume.est.belle (Postfix, from userid 1000) id 120A71624742; Wed, 28 Apr 2010 01:46:40 +0200 (CEST)' eq $header_dec->{ 'Received' }[0], 'decompose_header: 2' ) ; $header_dec = decompose_header( <<'EOH' Received: from plume (localhost [127.0.0.1]) by plume.est.belle (Postfix) with ESMTP id C6EB73F6C9 for ; Mon, 26 Nov 2007 10:39:06 +0100 (CET) Received: from plume [192.168.68.7] by plume with POP3 (fetchmail-6.3.6) for (single-drop); Mon, 26 Nov 2007 10:39:06 +0100 (CET) EOH ) ; ok( 'from plume (localhost [127.0.0.1]) by plume.est.belle (Postfix) with ESMTP id C6EB73F6C9 for ; Mon, 26 Nov 2007 10:39:06 +0100 (CET)' eq $header_dec->{ 'Received' }[0], 'decompose_header: 3' ) ; ok( 'from plume [192.168.68.7] by plume with POP3 (fetchmail-6.3.6) for (single-drop); Mon, 26 Nov 2007 10:39:06 +0100 (CET)' eq $header_dec->{ 'Received' }[1], 'decompose_header: 3' ) ; # Bad header beginning with a blank character $header_dec = decompose_header( <<'EOH' KEY_1: VAL_1 KEY_2: VAL_2 VAL_2_+ VAL_2_++ KEY_3: VAL_3 KEY_1: VAL_1_other EOH ) ; ok( 'VAL_3' eq $header_dec->{ 'KEY_3' }[0], 'decompose_header: Bad header VAL_3' ) ; ok( 'VAL_1_other' eq $header_dec->{ 'KEY_1' }[0], 'decompose_header: Bad header VAL_1_other' ) ; ok( 'VAL_2 VAL_2_+ VAL_2_++' eq $header_dec->{ 'KEY_2' }[0], 'decompose_header: Bad header VAL_2 VAL_2_+ VAL_2_++' ) ; note( 'Leaving tests_decompose_header()' ) ; return ; } sub tests_epoch { note( 'Entering tests_epoch()' ) ; ok( '1282658400' eq epoch( '24-Aug-2010 16:00:00 +0200' ), 'epoch 24-Aug-2010 16:00:00 +0200 -> 1282658400' ) ; ok( '1282658400' eq epoch( '24-Aug-2010 14:00:00 +0000' ), 'epoch 24-Aug-2010 14:00:00 +0000 -> 1282658400' ) ; ok( '1282658400' eq epoch( '24-Aug-2010 12:00:00 -0200' ), 'epoch 24-Aug-2010 12:00:00 -0200 -> 1282658400' ) ; ok( '1282658400' eq epoch( '24-Aug-2010 16:01:00 +0201' ), 'epoch 24-Aug-2010 16:01:00 +0201 -> 1282658400' ) ; ok( '1282658400' eq epoch( '24-Aug-2010 14:01:00 +0001' ), 'epoch 24-Aug-2010 14:01:00 +0001 -> 1282658400' ) ; ok( '1280671200' eq epoch( '1-Aug-2010 16:00:00 +0200' ), 'epoch 1-Aug-2010 16:00:00 +0200 -> 1280671200' ) ; ok( '1280671200' eq epoch( '1-Aug-2010 14:00:00 +0000' ), 'epoch 1-Aug-2010 14:00:00 +0000 -> 1280671200' ) ; ok( '1280671200' eq epoch( '1-Aug-2010 12:00:00 -0200' ), 'epoch 1-Aug-2010 12:00:00 -0200 -> 1280671200' ) ; ok( '1280671200' eq epoch( '1-Aug-2010 16:01:00 +0201' ), 'epoch 1-Aug-2010 16:01:00 +0201 -> 1280671200' ) ; ok( '1280671200' eq epoch( '1-Aug-2010 14:01:00 +0001' ), 'epoch 1-Aug-2010 14:01:00 +0001 -> 1280671200' ) ; is( '1280671200', epoch( '1-Aug-2010 14:01:00 +0001' ), 'epoch 1-Aug-2010 14:01:00 +0001 -> 1280671200' ) ; is( '946684800', epoch( '00-Jan-0000 00:00:00 +0000' ), 'epoch 1-Aug-2010 14:01:00 +0001 -> 1280671200' ) ; note( 'Leaving tests_epoch()' ) ; return ; } sub epoch { # incoming format: # internal date 24-Aug-2010 16:00:00 +0200 # outgoing format: epoch my $d = shift ; return(q{}) if not defined $d; my ( $mday, $month, $year, $hour, $min, $sec, $sign, $zone_h, $zone_m ) ; my $time ; if ( $d =~ m{(\d{1,2})-([A-Z][a-z]{2})-(\d{4})\s(\d{2}):(\d{2}):(\d{2})\s((?:\+|-))(\d{2})(\d{2})}xo ) { #myprint( "internal: [$1][$2][$3][$4][$5][$6][$7][$8][$9]\n" ) ; ( $mday, $month, $year, $hour, $min, $sec, $sign, $zone_h, $zone_m ) = ( $1, $2, $3, $4, $5, $6, $7, $8, $9 ) ; #myprint( "( $mday, $month, $year, $hour, $min, $sec, $sign, $zone_h, $zone_m )\n" ) ; $sign = +1 if ( '+' eq $sign ) ; $sign = $MINUS_ONE if ( '-' eq $sign ) ; if ( 0 == $mday ) { myprint( "buggy day in $d. Fixed to 01\n" ) ; $mday = '01' ; } $time = timegm( $sec, $min, $hour, $mday, $month_abrev{$month}, $year ) - $sign * ( 3600 * $zone_h + 60 * $zone_m ) ; #myprint( "$time ", scalar localtime($time), "\n"); } return( $time ) ; } sub tests_add_header { note( 'Entering tests_add_header()' ) ; ok( 'Message-Id: ' eq add_header(), 'add_header no arg' ) ; ok( 'Message-Id: <123456789@imapsync>' eq add_header( '123456789' ), 'add_header 123456789' ) ; note( 'Leaving tests_add_header()' ) ; return ; } sub add_header { my $header_uid = shift || 'mistake' ; my $header_Message_Id = 'Message-Id: <' . $header_uid . '@imapsync>' ; return( $header_Message_Id ) ; } sub tests_max_line_length { note( 'Entering tests_max_line_length()' ) ; ok( 0 == max_line_length( q{} ), 'max_line_length: 0 == null string' ) ; ok( 1 == max_line_length( "\n" ), 'max_line_length: 1 == \n' ) ; ok( 1 == max_line_length( "\n\n" ), 'max_line_length: 1 == \n\n' ) ; ok( 1 == max_line_length( "\n" x 500 ), 'max_line_length: 1 == 500 \n' ) ; ok( 1 == max_line_length( 'a' ), 'max_line_length: 1 == a' ) ; ok( 2 == max_line_length( "a\na" ), 'max_line_length: 2 == a\na' ) ; ok( 2 == max_line_length( "a\na\n" ), 'max_line_length: 2 == a\na\n' ) ; ok( 3 == max_line_length( "a\nab\n" ), 'max_line_length: 3 == a\nab\n' ) ; ok( 3 == max_line_length( "a\nab\n" x 1_000 ), 'max_line_length: 3 == 1_000 a\nab\n' ) ; ok( 3 == max_line_length( "a\nab\nabc" ), 'max_line_length: 3 == a\nab\nabc' ) ; ok( 4 == max_line_length( "a\nab\nabc\n" ), 'max_line_length: 4 == a\nab\nabc\n' ) ; ok( 5 == max_line_length( "a\nabcd\nabc\n" ), 'max_line_length: 5 == a\nabcd\nabc\n' ) ; ok( 5 == max_line_length( "a\nabcd\nabc\n\nabcd\nabcd\nabcd\nabcd\nabcd\nabcd\nabcd\nabcd" ), 'max_line_length: 5 == a\nabcd\nabc\n\nabcd\nabcd\nabcd\nabcd\nabcd\nabcd\nabcd\nabcd' ) ; note( 'Leaving tests_max_line_length()' ) ; return ; } sub max_line_length { my $string = shift ; my $max = 0 ; while ( $string =~ m/([^\n]*\n?)/msxg ) { $max = max( $max, length $1 ) ; } return( $max ) ; } sub set_checknoabletosearch { my $mysync = shift @ARG ; if ( defined $mysync->{ checknoabletosearch } ) { return ; } elsif ( $mysync->{ justfolders } ) { $mysync->{ checknoabletosearch } = 0 ; } else { $mysync->{ checknoabletosearch } = 1 ; } return ; } sub tests_setlogfile { note( 'Entering tests_setlogfile()' ) ; my $mysync = {} ; $mysync->{logdir} = 'vallogdir' ; $mysync->{logfile} = 'vallogfile.txt' ; is( 'vallogdir/vallogfile.txt', setlogfile( $mysync ), 'setlogfile: logdir vallogdir, logfile vallogfile.txt, vallogdir/vallogfile.txt' ) ; SKIP: { skip( 'Too hard to have a well known timezone on Windows', 9 ) if ( 'MSWin32' eq $OSNAME ) ; local $ENV{TZ} = 'GMT' ; $mysync = { timestart => 2, } ; is( "$DEFAULT_LOGDIR/1970_01_01_00_00_02_000__.txt", setlogfile( $mysync ), "setlogfile: default is like $DEFAULT_LOGDIR/1970_01_01_00_00_02_000__.txt" ) ; $mysync = { timestart => 2, user1 => 'user1', user2 => 'user2', abort => 1, } ; is( "$DEFAULT_LOGDIR/1970_01_01_00_00_02_000_user1_user2_abort.txt", setlogfile( $mysync ), "setlogfile: default is like $DEFAULT_LOGDIR/1970_01_01_00_00_02_000_user1_user2_abort.txt" ) ; $mysync = { timestart => 2, user1 => 'user1', user2 => 'user2', remote => 'zzz', } ; is( "$DEFAULT_LOGDIR/1970_01_01_00_00_02_000_user1_user2_remote.txt", setlogfile( $mysync ), "setlogfile: default is like $DEFAULT_LOGDIR/1970_01_01_00_00_02_000_user1_user2_remote.txt" ) ; $mysync = { timestart => 2, user1 => 'user1', user2 => 'user2', remote => 'zzz', abort => 1, } ; is( "$DEFAULT_LOGDIR/1970_01_01_00_00_02_000_user1_user2_remote_abort.txt", setlogfile( $mysync ), "setlogfile: default is like $DEFAULT_LOGDIR/1970_01_01_00_00_02_000_user1_user2_remote_abort.txt" ) ; $mysync = { timestart => 2, user1 => 'user1', user2 => 'user2', } ; is( "$DEFAULT_LOGDIR/1970_01_01_00_00_02_000_user1_user2.txt", setlogfile( $mysync ), "setlogfile: default is like $DEFAULT_LOGDIR/1970_01_01_00_00_02_000_user1_user2.txt" ) ; $mysync->{logdir} = undef ; $mysync->{logfile} = undef ; is( "$DEFAULT_LOGDIR/1970_01_01_00_00_02_000_user1_user2.txt", setlogfile( $mysync ), "setlogfile: logdir undef, $DEFAULT_LOGDIR/1970_01_01_00_00_02_000_user1_user2.txt" ) ; $mysync->{logdir} = q{} ; $mysync->{logfile} = undef ; is( '1970_01_01_00_00_02_000_user1_user2.txt', setlogfile( $mysync ), 'setlogfile: logdir empty, 1970_01_01_00_00_02_000_user1_user2.txt' ) ; $mysync->{logdir} = 'vallogdir' ; $mysync->{logfile} = undef ; is( 'vallogdir/1970_01_01_00_00_02_000_user1_user2.txt', setlogfile( $mysync ), 'setlogfile: logdir vallogdir, vallogdir/1970_01_01_00_00_02_000_user1_user2.txt' ) ; $mysync = { user1 => 'us/er1a*|?:"<>b', user2 => 'u/ser2a*|?:"<>b', } ; is( "$DEFAULT_LOGDIR/1970_01_01_00_00_00_000_us_er1a_______b_u_ser2a_______b.txt", setlogfile( $mysync ), "setlogfile: logdir undef, $DEFAULT_LOGDIR/1970_01_01_00_00_00_000_us_er1a_______b_u_ser2a_______b.txt" ) ; } ; note( 'Leaving tests_setlogfile()' ) ; return ; } sub setlogfile { my( $mysync ) = shift ; # When aborting another process the log file name finishes with "_abort.txt" my $abort_suffix = ( $mysync->{ abort } ) ? '_abort' : q{} ; # When acting as a proxy the log file name finishes with "_remote.txt" # proxy mode is not done in imapsync, it is done by proximapsync my $remote_suffix = ( $mysync->{ remote } ) ? '_remote' : q{} ; my $suffix = ( filter_forbidden_characters( slash_to_underscore( $mysync->{ user1 } ) ) || q{} ) . '_' . ( filter_forbidden_characters( slash_to_underscore( $mysync->{ user2 } ) ) || q{} ) . $remote_suffix . $abort_suffix ; $mysync->{ logdir } = defined $mysync->{ logdir } ? $mysync->{ logdir } : $DEFAULT_LOGDIR ; $mysync->{ logfile } = defined $mysync->{ logfile } ? "$mysync->{ logdir }/$mysync->{ logfile }" : logfile( $mysync->{ timestart }, $suffix, $mysync->{ logdir } ) ; return( $mysync->{ logfile } ) ; } sub tests_logfile { note( 'Entering tests_logfile()' ) ; SKIP: { # Too hard to have a well known timezone on Windows skip( 'Too hard to have a well known timezone on Windows', 10 ) if ( 'MSWin32' eq $OSNAME ) ; local $ENV{TZ} = 'GMT' ; { POSIX::tzset unless ('MSWin32' eq $OSNAME) ; is( '1970_01_01_00_00_00_000.txt', logfile( ), 'logfile: no args => 1970_01_01_00_00_00.txt' ) ; is( '1970_01_01_00_00_00_000.txt', logfile( 0 ), 'logfile: 0 => 1970_01_01_00_00_00.txt' ) ; is( '1970_01_01_00_01_01_000.txt', logfile( 61 ), 'logfile: 0 => 1970_01_01_00_01_01.txt' ) ; is( '1970_01_01_00_01_01_234.txt', logfile( 61.234 ), 'logfile: 0 => 1970_01_01_00_01_01.txt' ) ; is( '2010_08_24_14_00_00_000.txt', logfile( 1_282_658_400 ), 'logfile: 1_282_658_400 => 2010_08_24_14_00_00.txt' ) ; is( '2010_08_24_14_01_01_000.txt', logfile( 1_282_658_461 ), 'logfile: 1_282_658_461 => 2010_08_24_14_01_01.txt' ) ; is( '2010_08_24_14_01_01_000_poupinette.txt', logfile( 1_282_658_461, 'poupinette' ), 'logfile: 1_282_658_461 poupinette => 2010_08_24_14_01_01_poupinette.txt' ) ; is( '2010_08_24_14_01_01_000_removeblanks.txt', logfile( 1_282_658_461, ' remove blanks ' ), 'logfile: 1_282_658_461 remove blanks => 2010_08_24_14_01_01_000_removeblanks' ) ; is( '2010_08_24_14_01_01_234_poup.txt', logfile( 1_282_658_461.2347, 'poup' ), 'logfile: 1_282_658_461.2347 poup => 2010_08_24_14_01_01_234_poup.txt' ) ; is( 'dirdir/2010_08_24_14_01_01_234_poup.txt', logfile( 1_282_658_461.2347, 'poup', 'dirdir' ), 'logfile: 1_282_658_461.2347 poup dirdir => dirdir/2010_08_24_14_01_01_234_poup.txt' ) ; } POSIX::tzset unless ('MSWin32' eq $OSNAME) ; } ; note( 'Leaving tests_logfile()' ) ; return ; } sub logfile { my ( $time, $suffix, $dir ) = @_ ; $time ||= 0 ; $suffix ||= q{} ; $suffix =~ tr/ //ds ; my $sep_suffix = ( $suffix ) ? '_' : q{} ; $dir ||= q{} ; my $sep_dir = ( $dir ) ? '/' : q{} ; my $date_str = POSIX::strftime( '%Y_%m_%d_%H_%M_%S', localtime $time ) ; # Because of ab tests or web accesses, more than one sync withing one second is possible # so we add also milliseconds $date_str .= sprintf "_%03d", ($time - int( $time ) ) * 1000 ; # without rounding my $logfile = "${dir}${sep_dir}${date_str}${sep_suffix}${suffix}.txt" ; return( $logfile ) ; } sub tests_localtimez { note( 'Entering tests_localtimez()' ) ; SKIP: { # Too hard to have a well known timezone on Windows skip( 'Too hard to have a well known timezone on Windows', 1 ) if ( 'MSWin32' eq $OSNAME ) ; local $ENV{TZ} = 'GMT' ; like( localtimez( 0 ), qr'1970-01-01 00:00:00 \+0000 (GMT|UTC)', 'localtimez: 0 => match 1970-01-01 00:00:00 +0000 GMT' ) ; } is( localtimez( ), localtimez( time ), 'localtimez: undef => equals currrent' ) ; note( 'Leaving tests_localtimez()' ) ; return ; } sub localtimez { my $time = shift ; $time = defined( $time ) ? $time : time ; my $datetimestr = POSIX::strftime( '%A %e %B %Y-%m-%d %H:%M:%S %z %Z', localtime( $time ) ) ; #myprint( "$datetimestr\n" ) ; return $datetimestr ; } sub tests_slash_to_underscore { note( 'Entering tests_slash_to_underscore()' ) ; is( undef, slash_to_underscore( ), 'slash_to_underscore: no parameters => undef' ) ; is( '_', slash_to_underscore( '/' ), 'slash_to_underscore: / => _' ) ; is( '_abc_def_', slash_to_underscore( '/abc/def/' ), 'slash_to_underscore: /abc/def/ => _abc_def_' ) ; note( 'Leaving tests_slash_to_underscore()' ) ; return ; } sub slash_to_underscore { my $string = shift ; if ( ! defined $string ) { return ; } $string =~ tr{/}{_} ; return( $string ) ; } sub tests_million_folders_baby_2 { note( 'Entering tests_million_folders_baby_2()' ) ; my %long ; @long{ 1 .. 900_000 } = (1) x 900_000 ; #myprint( %long, "\n" ) ; my $pasglop = 0 ; foreach my $elem ( 1 .. 900_000 ) { #$debug and myprint( "$elem " ) ; if ( not exists $long{ $elem } ) { $pasglop++ ; } } ok( 0 == $pasglop, 'tests_million_folders_baby_2: search among 900_000' ) ; # myprint( "$pasglop\n" ) ; note( 'Leaving tests_million_folders_baby_2()' ) ; return ; } sub tests_always_fail { note( 'Entering tests_always_fail()' ) ; is( 0, 1, 'always_fail: 0 is 1' ) ; note( 'Leaving tests_always_fail()' ) ; return ; } sub tests_logfileprepa { note( 'Entering tests_logfileprepa()' ) ; is( undef, logfileprepa( ), 'logfileprepa: no args => undef' ) ; my $logfile = 'W/tmp/tests/tests_logfileprepa.txt' ; is( 1, logfileprepa( $logfile ), 'logfileprepa: W/tmp/tests/tests_logfileprepa.txt => 1' ) ; note( 'Leaving tests_logfileprepa()' ) ; return ; } sub logfileprepa { my $logfile = shift ; if ( ! defined( $logfile ) ) { return ; }else { #myprint( "[$logfile]\n" ) ; my $dirname = dirname( $logfile ) ; do_valid_directory( $dirname ) || return( 0 ) ; return( 1 ) ; } } sub tests_teelaunch { note( 'Entering tests_teelaunch()' ) ; is( undef, teelaunch( ), 'teelaunch: no args => undef' ) ; my $mysync = {} ; is( undef, teelaunch( $mysync ), 'teelaunch: arg empty {} => undef' ) ; $mysync->{logfile} = q{} ; is( undef, teelaunch( $mysync ), 'teelaunch: logfile empty string => undef' ) ; # First time, learning IO::Tee intrasics $mysync->{logfile} = 'W/tmp/tests/tests_teelaunch.txt' ; isa_ok( my $tee = teelaunch( $mysync ), 'IO::Tee' , 'teelaunch: logfile W/tmp/tests/tests_teelaunch.txt' ) ; is( 1, print( $tee "Hi!\n" ), 'teelaunch: write Hi!') ; is( "Hi!\n", file_to_string( 'W/tmp/tests/tests_teelaunch.txt' ), 'teelaunch: reading W/tmp/tests/tests_teelaunch.txt is Hi!\n' ) ; is( 1, print( $tee "Hoo\n" ), 'teelaunch: write Hoo') ; is( "Hi!\nHoo\n", file_to_string( 'W/tmp/tests/tests_teelaunch.txt' ), 'teelaunch: reading W/tmp/tests/tests_teelaunch.txt is Hi!\nHoo\n' ) ; # closing so tee won't be happy close $mysync->{logfile_handle} ; is( undef, print( $tee "Argh1\n" ), 'teelaunch: write Argh1') ; is( undef, print( $tee "Argh2\n" ), 'teelaunch: write Argh2') ; # write not done is( "Hi!\nHoo\n", file_to_string( 'W/tmp/tests/tests_teelaunch.txt' ), 'teelaunch: reading W/tmp/tests/tests_teelaunch.txt is still Hi!\nHoo\n' ) ; print join( ' ', $tee->handles ), "\n"; is( 2, scalar $tee->handles, 'teelaunch: 2 handles') ; shift @{*{$tee}}; print join(' ', $tee->handles), "\n" ; is( 1, scalar $tee->handles, 'teelaunch: 1 handle') ; is( 1, print( $tee "Argh3\n" ), 'teelaunch: write Argh3 yeah') ; shift @{*{$tee}}; # will not print anything now is( 0, scalar $tee->handles, 'teelaunch: 0 handle') ; is( 1, print( $tee "Argh 4\n" ), 'teelaunch: write Argh4 no') ; # Second time, lesson learnt IO::Tee $mysync->{logfile} = 'W/tmp/tests/tests_teelaunch2.txt' ; isa_ok( $tee = teelaunch( $mysync ), 'IO::Tee' , 'teelaunch: logfile W/tmp/tests/tests_teelaunch2.txt' ) ; is( 1, print( $tee "Hi!\n" ), 'teelaunch: write Hi!') ; is( "Hi!\n", file_to_string( 'W/tmp/tests/tests_teelaunch2.txt' ), 'teelaunch: reading W/tmp/tests/tests_teelaunch2.txt is Hi!\n' ) ; is( 1, print( $tee "Hoo\n" ), 'teelaunch: write Hoo') ; is( "Hi!\nHoo\n", file_to_string( 'W/tmp/tests/tests_teelaunch2.txt' ), 'teelaunch: reading W/tmp/tests/tests_teelaunch2.txt is Hi!\nHoo\n' ) ; is( 1, teefinish( $mysync ), 'teefinish: return 1') ; is( 1, print( $tee "Argh1\n" ), 'teelaunch: write Argh1') ; is( 1, print( $tee "Argh2\n" ), 'teelaunch: write Argh2') ; is( "Hi!\nHoo\n", file_to_string( 'W/tmp/tests/tests_teelaunch2.txt' ), 'teelaunch: reading W/tmp/tests/tests_teelaunch2.txt is still Hi!\nHoo\n' ) ; is( 1, teefinish( $mysync ), 'teefinish: still return 1') ; note( 'Leaving tests_teelaunch()' ) ; return ; } sub teelaunch { my $mysync = shift ; if ( ! defined( $mysync ) ) { return ; } my $logfile = $mysync->{logfile} ; if ( ! $logfile ) { return ; } logfileprepa( $logfile ) || croak "Error no valid directory to write log file $logfile : $OS_ERROR" ; # This is a log file opened during the whole sync ## no critic (InputOutput::RequireBriefOpen) open my $logfile_handle, '>', $logfile or croak( "Can not open $logfile for write: $OS_ERROR" ) ; binmode $logfile_handle, ":encoding(UTF-8)" ; my $tee = IO::Tee->new( $logfile_handle, \*STDOUT ) ; $tee->autoflush( 1 ) ; $mysync->{logfile_handle} = $logfile_handle ; $mysync->{tee} = $tee ; return $tee ; } sub teefinish { my $mysync = shift ; if ( ! defined( $mysync ) ) { return ; } my $tee = $mysync->{tee} ; if ( ! defined( $tee ) ) { return ; } if ( 2 == scalar $tee->handles ) { shift @{*{$tee}}; } else { # nothing } return scalar $tee->handles ; } sub getpwuid_any_os { my $uid = shift ; return( scalar getlogin ) if ( 'MSWin32' eq $OSNAME ) ; # Windows system return( scalar getpwuid $uid ) ; # Unix system } sub abortifneeded { my $mysync = shift ; if ( -e $mysync->{ abortfile } ) { myprint( "Asked to terminate by file $mysync->{ abortfile }\n" ) ; do_and_print_stats( $mysync ) ; myprint( "You should resynchronize those accounts by running a sync again,\n", "since some messages and entire folders might still be missing on host2.\n" ) ; exit_clean( $mysync, $EXIT_BY_FILE ) ; return ; } else { return ; } } sub simulong { my $mysync = shift ; my $max_seconds = $mysync->{ simulong } ; if ( ! $max_seconds ) { return ; } my $division = 5 ; my $last_count = int( $division * $max_seconds ) ; $mysync->{ debug } and myprint "last_count $last_count = int( division $division * max_seconds $max_seconds)\n" ; foreach my $i ( 1 .. ( $last_count ) ) { myprint( "Are you still here ETA: " . ( $last_count - $i ) . "/$last_count msgs left\n" ) ; #this one is for testing huge page behavior #myprint( "Are you still here ETA: " . ($last_count - $i) . "/$last_count msgs left\n" . ( "Ah" x 40 . "\n") x 4000 ) ; sleep( 1 / $division ) ; abortifneeded( $mysync ) ; } return ; } sub printenv { myprint( "Environment variables listing:\n", ( map { "$_ => $ENV{$_}\n" } sort keys %ENV), "Environment variables listing end\n" ) ; return ; } sub unittestssuite { my $mysync = shift ; if ( ! ( $mysync->{ tests } or $mysync->{ testsdebug } or $mysync->{ testsunit } ) ) { return ; } my $test_builder = Test::More->builder ; tests( $mysync ) ; testsdebug( $mysync ) ; testunitsession( $mysync ) ; my @summary = $test_builder->summary() ; my @details = $test_builder->details() ; my $nb_tests_run = scalar( @summary ) ; my $nb_tests_expected = $test_builder->expected_tests() ; my $nb_tests_failed = count_0s( @summary ) ; my $tests_failed = report_failures( @details ) ; if ( $nb_tests_failed or ( $nb_tests_run != $nb_tests_expected ) ) { #$test_builder->reset( ) ; myprint( "Summary of tests: failed $nb_tests_failed tests, run $nb_tests_run tests, expected to run $nb_tests_expected tests.\n", "List of failed tests:\n", $tests_failed ) ; return $EXIT_TESTS_FAILED ; } cleanup_mess_from_tests( ) ; return 0 ; } sub cleanup_mess_from_tests { undef @pipemess ; return ; } sub after_get_options { my $mysync = shift ; my $numopt = shift ; # exit with --help option or no option at all $mysync->{ debug } and myprint( "numopt:$numopt\n" ) ; if ( $help or not $numopt ) { myprint( usage( $mysync ) ) ; exit ; } return ; } sub tests_remove_edging_blanks { note( 'Entering tests_remove_edging_blanks()' ) ; is( undef, remove_edging_blanks( ), 'remove_edging_blanks: no args => undef' ) ; is( 'abcd', remove_edging_blanks( 'abcd' ), 'remove_edging_blanks: abcd => abcd' ) ; is( 'ab cd', remove_edging_blanks( ' ab cd ' ), 'remove_edging_blanks: " ab cd " => "ab cd"' ) ; note( 'Leaving tests_remove_edging_blanks()' ) ; return ; } sub remove_edging_blanks { my $string = shift ; if ( ! defined $string ) { return ; } $string =~ s,^ +| +$,,g ; return $string ; } sub tests_sanitize { note( 'Entering tests_remove_edging_blanks()' ) ; is( undef, sanitize( ), 'sanitize: no args => undef' ) ; my $mysync = {} ; $mysync->{ host1 } = ' example.com ' ; $mysync->{ user1 } = ' to to ' ; $mysync->{ password1 } = ' sex is good! ' ; is( undef, sanitize( $mysync ), 'sanitize: => undef' ) ; is( 'example.com', $mysync->{ host1 }, 'sanitize: host1 " example.com " => "example.com"' ) ; is( 'to to', $mysync->{ user1 }, 'sanitize: user1 " to to " => "to to"' ) ; is( 'sex is good!', $mysync->{ password1 }, 'sanitize: password1 " sex is good! " => "sex is good!"' ) ; note( 'Leaving tests_remove_edging_blanks()' ) ; return ; } sub sanitize { my $mysync = shift ; if ( ! defined $mysync ) { return ; } foreach my $parameter ( qw( host1 host2 user1 user2 password1 password2 ) ) { $mysync->{ $parameter } = remove_edging_blanks( $mysync->{ $parameter } ) ; } return ; } sub easyany { my $mysync = shift ; # Gmail if ( $mysync->{gmail1} and $mysync->{gmail2} ) { $mysync->{ debug } and myprint( "gmail1 gmail2\n") ; gmail12( $mysync ) ; return ; } if ( $mysync->{gmail1} ) { $mysync->{ debug } and myprint( "gmail1\n" ) ; gmail1( $mysync ) ; } if ( $mysync->{gmail2} ) { $mysync->{ debug } and myprint( "gmail2\n" ) ; gmail2( $mysync ) ; } # Office 365 if ( $mysync->{office1} ) { office1( $mysync ) ; } if ( $mysync->{office2} ) { office2( $mysync ) ; } # Exchange if ( $mysync->{exchange1} ) { exchange1( $mysync ) ; } if ( $mysync->{exchange2} ) { exchange2( $mysync ) ; } # Domino if ( $mysync->{domino1} ) { domino1( $mysync ) ; } if ( $mysync->{domino2} ) { domino2( $mysync ) ; } return ; } # From and for https://imapsync.lamiral.info/FAQ.d/FAQ.Gmail.txt sub gmail12 { my $mysync = shift ; # Gmail at host1 and host2 $mysync->{host1} ||= 'imap.gmail.com' ; $mysync->{ssl1} = ( defined $mysync->{ssl1} ) ? $mysync->{ssl1} : 1 ; $mysync->{host2} ||= 'imap.gmail.com' ; $mysync->{ssl2} = ( defined $mysync->{ssl2} ) ? $mysync->{ssl2} : 1 ; $mysync->{maxbytespersecond} ||= 20_000 ; # should be less than 10_000 when computed from Gmail documentation $mysync->{maxbytesafter} ||= 1_000_000_000 ; # In fact it is documented as half: 500_000_000 $mysync->{automap} = ( defined $mysync->{automap} ) ? $mysync->{automap} : 1 ; $mysync->{maxsleep} = ( defined $mysync->{maxsleep} ) ? $mysync->{maxsleep} : $MAX_SLEEP ; ; $skipcrossduplicates = ( defined $skipcrossduplicates ) ? $skipcrossduplicates : 0 ; $mysync->{ synclabels } = ( defined $mysync->{ synclabels } ) ? $mysync->{ synclabels } : 1 ; $mysync->{ resynclabels } = ( defined $mysync->{ resynclabels } ) ? $mysync->{ resynclabels } : 1 ; push @useheader, 'X-Gmail-Received', 'Message-Id' ; push @exclude, '\[Gmail\]$' ; push @folderlast, '[Gmail]/All Mail' ; return ; } sub gmail1 { my $mysync = shift ; # Gmail at host2 $mysync->{host1} ||= 'imap.gmail.com' ; $mysync->{ssl1} = ( defined $mysync->{ssl1} ) ? $mysync->{ssl1} : 1 ; $mysync->{maxbytespersecond} ||= 40_000 ; # should be 30_000 computed from by Gmail documentation $mysync->{maxbytesafter} ||= 3_000_000_000 ; # $mysync->{automap} = ( defined $mysync->{automap} ) ? $mysync->{automap} : 1 ; $mysync->{maxsleep} = ( defined $mysync->{maxsleep} ) ? $mysync->{maxsleep} : $MAX_SLEEP ; ; $skipcrossduplicates = ( defined $skipcrossduplicates ) ? $skipcrossduplicates : 1 ; push @useheader, 'X-Gmail-Received', 'Message-Id' ; push @{ $mysync->{ regextrans2 } }, 's,\[Gmail\].,,' ; push @folderlast, '[Gmail]/All Mail' ; return ; } sub gmail2 { my $mysync = shift ; # Gmail at host2 $mysync->{ host2 } ||= 'imap.gmail.com' ; $mysync->{ ssl2 } = ( defined $mysync->{ ssl2 } ) ? $mysync->{ ssl2 } : 1 ; $mysync->{ maxbytespersecond } ||= 20_000 ; # should be less than 10_000 computed from by Gmail documentation $mysync->{ maxbytesafter } ||= 1_000_000_000 ; # In fact it is documented as half: 500_000_000 $mysync->{ automap } = ( defined $mysync->{ automap } ) ? $mysync->{ automap } : 1 ; #$skipcrossduplicates = ( defined $skipcrossduplicates ) ? $skipcrossduplicates : 1 ; $mysync->{ expunge1 } = ( defined $mysync->{ expunge1 } ) ? $mysync->{ expunge1 } : 1 ; $mysync->{ addheader } = ( defined $mysync->{ addheader } ) ? $mysync->{ addheader } : 1 ; $mysync->{ maxsleep } = ( defined $mysync->{ maxsleep } ) ? $mysync->{ maxsleep } : $MAX_SLEEP ; ; #$mysync->{ maxsize } = ( defined $mysync->{ maxsize } ) ? $mysync->{ maxsize } : $GMAIL_MAXSIZE ; if ( ! $mysync->{ noexclude } ) { push @exclude, '\[Gmail\]$' ; } push @useheader, 'Message-Id' ; push @{ $mysync->{ regextrans2 } }, 's,\[Gmail\].,,' ; # push @{ $mysync->{ regextrans2 } }, 's/[ ]+/_/g' ; # is now replaced # by the two more specific following regexes, # they remove just the beginning and trailing blanks, not all. push @{ $mysync->{ regextrans2 } }, 's,^ +| +$,,g' ; push @{ $mysync->{ regextrans2 } }, 's,/ +| +/,/,g' ; # push @{ $mysync->{ regextrans2 } }, q{s/['\\^"]/_/g} ; # Verified this push @folderlast, '[Gmail]/All Mail' ; return ; } # From https://imapsync.lamiral.info/FAQ.d/FAQ.Exchange.txt sub office1 { # Office 365 at host1 my $mysync = shift ; output( $mysync, q{Option --office1 is like: --host1 outlook.office365.com --ssl1 --exclude "^Files$"} . "\n" ) ; output( $mysync, "Option --office1 (cont) : unless overrided with --host1 otherhost --nossl1 --noexclude\n" ) ; $mysync->{host1} ||= 'outlook.office365.com' ; $mysync->{ssl1} = ( defined $mysync->{ssl1} ) ? $mysync->{ssl1} : 1 ; if ( ! $mysync->{noexclude} ) { push @exclude, '^Files$' ; } return ; } sub office2 { # Office 365 at host2 my $mysync = shift ; output( $mysync, qq{Option --office2 is like: --host2 outlook.office365.com --ssl2 --maxsize 45_000_000 --maxmessagespersecond 4\n} ) ; output( $mysync, qq{Option --office2 (cont) : --disarmreadreceipts --regexmess "wrap 10500" --f1f2 "Files=Files_renamed_by_imapsync"\n} ) ; output( $mysync, qq{Option --office2 (cont) : unless overrided with --host2 otherhost --nossl2 ... --nodisarmreadreceipts --noregexmess\n} ) ; output( $mysync, qq{Option --office2 (cont) : and --nof1f2 to avoid Files folder renamed to Files_renamed_by_imapsync\n} ) ; $mysync->{host2} ||= 'outlook.office365.com' ; $mysync->{ssl2} = ( defined $mysync->{ssl2} ) ? $mysync->{ssl2} : 1 ; $mysync->{ maxsize } ||= 45_000_000 ; $mysync->{maxmessagespersecond} ||= 4 ; #push @{ $mysync->{ regexflag } }, 's/\\\\Flagged//g' ; # No problem without! tested 2018_09_10 $disarmreadreceipts = ( defined $disarmreadreceipts ) ? $disarmreadreceipts : 1 ; # I dislike double negation but here is one if ( ! $mysync->{noregexmess} ) { push @regexmess, 's,(.{10239}),$1\r\n,g' ; } # and another... if ( ! $mysync->{nof1f2} ) { push @{ $mysync->{f1f2} }, 'Files=Files_renamed_by_imapsync' ; } return ; } sub exchange1 { # Exchange 2010/2013 at host1 my $mysync = shift ; output( $mysync, "Option --exchange1 does nothing (except printing this line...)\n" ) ; # Well nothing to do so far return ; } sub exchange2 { # Exchange 2010/2013 at host2 my $mysync = shift ; output( $mysync, "Option --exchange2 is like: --maxsize 10_000_000 --maxmessagespersecond 4 --disarmreadreceipts\n" ) ; output( $mysync, "Option --exchange2 (cont) : --regexflag del Flagged --regexmess wrap 10500\n" ) ; output( $mysync, "Option --exchange2 (cont) : unless overrided with --maxsize xxx --nodisarmreadreceipts --noregexflag --noregexmess\n" ) ; $mysync->{ maxsize } ||= 10_000_000 ; $mysync->{maxmessagespersecond} ||= 4 ; $disarmreadreceipts = ( defined $disarmreadreceipts ) ? $disarmreadreceipts : 1 ; # I dislike double negation but here are two if ( ! $mysync->{noregexflag} ) { push @{ $mysync->{ regexflag } }, 's/\\\\Flagged//g' ; } if ( ! $mysync->{noregexmess} ) { push @regexmess, 's,(.{10239}),$1\r\n,g' ; } return ; } sub domino1 { # Domino at host1 my $mysync = shift ; $mysync->{ sep1 } = q{\\} ; $prefix1 = q{} ; $messageidnodomain = ( defined $messageidnodomain ) ? $messageidnodomain : 1 ; return ; } sub domino2 { # Domino at host1 my $mysync = shift ; $mysync->{ sep2 } = q{\\} ; $prefix2 = q{} ; $messageidnodomain = ( defined $messageidnodomain ) ? $messageidnodomain : 1 ; push @{ $mysync->{ regextrans2 } }, 's,^Inbox\\\\(.*),$1,i' ; return ; } sub tests_resolv { note( 'Entering tests_resolv()' ) ; # is( , resolv( ), 'resolv: => ' ) ; is( undef, resolv( ), 'resolv: no args => undef' ) ; is( undef, resolv( q{} ), 'resolv: empty string => undef' ) ; is( undef, resolv( 'hostnotexist' ), 'resolv: hostnotexist => undef' ) ; is( '127.0.0.1', resolv( '127.0.0.1' ), 'resolv: 127.0.0.1 => 127.0.0.1' ) ; is( '127.0.0.1', resolv( 'localhost' ), 'resolv: localhost => 127.0.0.1' ) ; is( '2001:41d0:2:84e0::1', resolv( 'imapsync.lamiral.info' ), 'resolv: imapsync.lamiral.info => 2001:41d0:2:84e0::1' ) ; # ip6-localhost ( in /etc/hosts ) is( '::1', resolv( 'ip6-localhost' ), 'resolv: ip6-localhost => ::1' ) ; is( '::1', resolv( '::1' ), 'resolv: ::1 => ::1' ) ; # ks2ipv6 now has CNAME ks6ipv6 is( '2001:41d0:8:d8b6::1', resolv( '2001:41d0:8:d8b6::1' ), 'resolv: 2001:41d0:8:d8b6::1 => 2001:41d0:8:d8b6::1' ) ; is( '2001:41d0:8:9951::1', resolv( 'ks6ipv6.lamiral.info' ), 'resolv: ks6ipv6.lamiral.info => 2001:41d0:8:9951::1' ) ; # ks6 is( '2001:41d0:8:9951::1', resolv( '2001:41d0:8:9951::1' ), 'resolv: 2001:41d0:8:9951::1 => 2001:41d0:8:9951::1' ) ; is( '2001:41d0:8:9951::1', resolv( 'ks6ipv6.lamiral.info' ), 'resolv: ks6ipv6.lamiral.info => 2001:41d0:8:9951::1' ) ; # ks3 is( '2001:41d0:8:bebd::1', resolv( '2001:41d0:8:bebd::1' ), 'resolv: 2001:41d0:8:bebd::1 => 2001:41d0:8:bebd::1' ) ; is( '2001:41d0:8:bebd::1', resolv( 'ks3ipv6.lamiral.info' ), 'resolv: ks3ipv6.lamiral.info => 2001:41d0:8:bebd::1' ) ; note( 'Leaving tests_resolv()' ) ; return ; } sub resolv { my $host = shift @ARG ; if ( ! $host ) { return ; } my $addr ; if ( defined &Socket::getaddrinfo ) { $addr = resolv_with_getaddrinfo( $host ) ; return( $addr ) ; } my $iaddr = inet_aton( $host ) ; if ( ! $iaddr ) { return ; } $addr = inet_ntoa( $iaddr ) ; return $addr ; } sub resolv_with_getaddrinfo { my $host = shift @ARG ; $sync->{ debug } and myprint( "Entering resolv_with_getaddrinfo( $host )\n" ) ; if ( ! $host ) { return ; } my ( $err_getaddrinfo, @res ) = Socket::getaddrinfo( $host, "", { socktype => Socket::SOCK_RAW } ) ; if ( $err_getaddrinfo ) { myprint( "Cannot getaddrinfo of $host: $err_getaddrinfo\n" ) ; return ; } my @addr ; while( my $ai = shift @res ) { my ( $err_getnameinfo, $ipaddr ) = Socket::getnameinfo( $ai->{addr}, Socket::NI_NUMERICHOST(), Socket::NIx_NOSERV() ) ; if ( $err_getnameinfo ) { myprint( "Cannot getnameinfo of $host: $err_getnameinfo\n" ) ; return ; }else{ $sync->{ debug } and myprint( "$host => $ipaddr\n" ) ; push @addr, $ipaddr ; my $reverse ; ( $err_getnameinfo, $reverse ) = Socket::getnameinfo( $ai->{addr}, 0, Socket::NIx_NOSERV() ) ; $sync->{ debug } and myprint( "$host => $ipaddr => $reverse\n" ) ; } $sync->{ debug } and myprint( "$host => $ipaddr\n" ) ; } $sync->{ debug } and myprint( "Leaving resolv_with_getaddrinfo( $host => $addr[0])\n" ) ; return $addr[0] ; } sub tests_resolvrev { note( 'Entering tests_resolvrev()' ) ; # is( , resolvrev( ), 'resolvrev: => ' ) ; is( undef, resolvrev( ), 'resolvrev: no args => undef' ) ; is( undef, resolvrev( q{} ), 'resolvrev: empty string => undef' ) ; is( undef, resolvrev( 'hostnotexist' ), 'resolvrev: hostnotexist => undef' ) ; is( 'localhost', resolvrev( '127.0.0.1' ), 'resolvrev: 127.0.0.1 => localhost' ) ; is( 'localhost', resolvrev( 'localhost' ), 'resolvrev: localhost => localhost' ) ; is( 'ks.lamiral.info', resolvrev( 'imapsync.lamiral.info' ), 'resolvrev: imapsync.lamiral.info => ks.lamiral.info' ) ; # ip6-localhost ( in /etc/hosts ) is( 'ip6-localhost', resolvrev( 'ip6-localhost' ), 'resolvrev: ip6-localhost => ip6-localhost' ) ; is( 'ip6-localhost', resolvrev( '::1' ), 'resolvrev: ::1 => ip6-localhost' ) ; # ks2 is( 'ks6ipv6.lamiral.info', resolvrev( '2001:41d0:8:d8b6::1' ), 'resolvrev: 2001:41d0:8:d8b6::1 => ks6ipv6.lamiral.info' ) ; is( 'ks6ipv6.lamiral.info', resolvrev( 'ks6ipv6.lamiral.info' ), 'resolvrev: ks6ipv6.lamiral.info => ks6ipv6.lamiral.info' ) ; # ks3 is( 'ks3ipv6.lamiral.info', resolvrev( '2001:41d0:8:bebd::1' ), 'resolvrev: 2001:41d0:8:bebd::1 => ks3ipv6.lamiral.info' ) ; is( 'ks3ipv6.lamiral.info', resolvrev( 'ks3ipv6.lamiral.info' ), 'resolvrev: ks3ipv6.lamiral.info => ks3ipv6.lamiral.info' ) ; note( 'Leaving tests_resolvrev()' ) ; return ; } sub resolvrev { my $host = shift @ARG ; if ( ! $host ) { return ; } if ( defined &Socket::getaddrinfo ) { my $name = resolvrev_with_getaddrinfo( $host ) ; return( $name ) ; } return ; } sub resolvrev_with_getaddrinfo { my $host = shift @ARG ; if ( ! $host ) { return ; } my ( $err, @res ) = Socket::getaddrinfo( $host, "", { socktype => Socket::SOCK_RAW } ) ; if ( $err ) { myprint( "Cannot getaddrinfo of $host: $err\n" ) ; return ; } my @name ; while( my $ai = shift @res ) { my ( $err, $reverse ) = Socket::getnameinfo( $ai->{addr}, 0, Socket::NIx_NOSERV() ) ; if ( $err ) { myprint( "Cannot getnameinfo of $host: $err\n" ) ; return ; } $sync->{ debug } and myprint( "$host => $reverse\n" ) ; push @name, $reverse ; } return $name[0] ; } sub tests_imapsping { note( 'Entering tests_imapsping()' ) ; is( undef, imapsping( ), 'imapsping: no args => undef' ) ; is( undef, imapsping( 'hostnotexist' ), 'imapsping: hostnotexist => undef' ) ; is( 1, imapsping( 'imapsync.lamiral.info' ), 'imapsping: imapsync.lamiral.info => 1' ) ; is( 1, imapsping( 'ks6ipv6.lamiral.info' ), 'imapsping: ks6ipv6.lamiral.info => 1' ) ; note( 'Leaving tests_imapsping()' ) ; return ; } sub imapsping { my $host = shift ; return tcpping( $host, $IMAP_SSL_PORT ) ; } sub tests_tcpping { note( 'Entering tests_tcpping()' ) ; is( undef, tcpping( ), 'tcpping: no args => undef' ) ; is( undef, tcpping( 'hostnotexist' ), 'tcpping: one arg => undef' ) ; is( undef, tcpping( undef, 888 ), 'tcpping: arg undef, port => undef' ) ; is( undef, tcpping( 'hostnotexist', 993 ), 'tcpping: hostnotexist 993 => undef' ) ; is( undef, tcpping( 'hostnotexist', 888 ), 'tcpping: hostnotexist 888 => undef' ) ; is( 1, tcpping( 'imapsync.lamiral.info', 993 ), 'tcpping: imapsync.lamiral.info 993 => 1' ) ; is( 0, tcpping( 'imapsync.lamiral.info', 888 ), 'tcpping: imapsync.lamiral.info 888 => 0' ) ; is( 1, tcpping( '5.135.158.182', 993 ), 'tcpping: 5.135.158.182 993 => 1' ) ; is( 0, tcpping( '5.135.158.182', 888 ), 'tcpping: 5.135.158.182 888 => 0' ) ; # Net::Ping supports ipv6 only after release 1.50 # http://cpansearch.perl.org/src/RURBAN/Net-Ping-2.59/Changes # Anyway I plan to avoid Net-Ping for that too long standing feature # Net-Ping is integrated in Perl itself, who knows ipv6 for a long time is( 1, tcpping( '2001:41d0:8:d8b6::1', 993 ), 'tcpping: 2001:41d0:8:d8b6::1 993 => 1' ) ; is( 0, tcpping( '2001:41d0:8:d8b6::1', 888 ), 'tcpping: 2001:41d0:8:d8b6::1 888 => 0' ) ; note( 'Leaving tests_tcpping()' ) ; return ; } sub tcpping { if ( 2 != scalar( @ARG ) ) { return ; } my ( $host, $port ) = @ARG ; if ( ! $host ) { return ; } if ( ! $port ) { return ; } my $mytimeout = $TCP_PING_TIMEOUT ; require Net::Ping ; #my $p = Net::Ping->new( 'tcp' ) ; my $p = Net::Ping->new( ) ; $p->{port_num} = $port ; $p->service_check( 1 ) ; $p->hires( 1 ) ; my ($ping_ok, $rtt, $ip ) = $p->ping( $host, $mytimeout ) ; if ( ! defined $ping_ok ) { return ; } my $rtt_approx = sprintf( "%.3f", $rtt ) ; $sync->{ debug } and myprint( "Host $host timeout $mytimeout port $port ok $ping_ok ip $ip acked in $rtt_approx s\n" ) ; $p->close( ) ; if( $ping_ok ) { return 1 ; }else{ return 0 ; } } sub tests_sslcheck { note( 'Entering tests_sslcheck()' ) ; my $mysync ; is( undef, sslcheck( $mysync ), 'sslcheck: no sslcheck => undef' ) ; $mysync = { sslcheck => 1, } ; is( 0, sslcheck( $mysync ), 'sslcheck: no host => 0' ) ; $mysync = { sslcheck => 1, host1 => 'test1.lamiral.info', tls1 => 1, } ; is( 0, sslcheck( $mysync ), 'sslcheck: tls1 => 0' ) ; $mysync = { sslcheck => 1, host1 => 'test1.lamiral.info', } ; is( 1, sslcheck( $mysync ), 'sslcheck: test1.lamiral.info => 1' ) ; is( 1, $mysync->{ssl1}, 'sslcheck: test1.lamiral.info => ssl1 1' ) ; $mysync->{sslcheck} = 0 ; is( undef, sslcheck( $mysync ), 'sslcheck: sslcheck off => undef' ) ; $mysync = { sslcheck => 1, host1 => 'test1.lamiral.info', host2 => 'test2.lamiral.info', } ; is( 2, sslcheck( $mysync ), 'sslcheck: test1.lamiral.info + test2.lamiral.info => 2' ) ; $mysync = { sslcheck => 1, host1 => 'test1.lamiral.info', host2 => 'test2.lamiral.info', tls1 => 1, } ; is( 1, sslcheck( $mysync ), 'sslcheck: test1.lamiral.info + test2.lamiral.info + tls1 => 1' ) ; note( 'Leaving tests_sslcheck()' ) ; return ; } sub sslcheck { my $mysync = shift ; if ( ! $mysync->{sslcheck} ) { return ; } my $nb_on = 0 ; $mysync->{ debug } and myprint( "sslcheck\n" ) ; if ( ( ! defined $mysync->{port1} ) and ( ! defined $mysync->{tls1} ) and ( ! defined $mysync->{ssl1} ) and ( defined $mysync->{host1} ) ) { myprint( "Host1: probing ssl on port $IMAP_SSL_PORT ( use --nosslcheck to avoid this ssl probe ) \n" ) ; if ( probe_imapssl( $mysync->{host1} ) ) { $mysync->{ssl1} = 1 ; myprint( "Host1: sslcheck detected open ssl port $IMAP_SSL_PORT so turning ssl on (use --nossl1 --notls1 to turn off SSL and TLS wizardry)\n" ) ; $nb_on++ ; }else{ myprint( "Host1: sslcheck did not detected open ssl port $IMAP_SSL_PORT. Will use standard $IMAP_PORT port.\n" ) ; } } if ( ( ! defined $mysync->{port2} ) and ( ! defined $mysync->{tls2} ) and ( ! defined $mysync->{ssl2} ) and ( defined $mysync->{host2} ) ) { myprint( "Host2: probing ssl on port $IMAP_SSL_PORT ( use --nosslcheck to avoid this ssl probe ) \n" ) ; if ( probe_imapssl( $mysync->{host2} ) ) { $mysync->{ssl2} = 1 ; myprint( "Host2: sslcheck detected open ssl port $IMAP_SSL_PORT so turning ssl on (use --nossl2 --notls2 to turn off SSL and TLS wizardry)\n" ) ; $nb_on++ ; }else{ myprint( "Host2: sslcheck did not detected open ssl port $IMAP_SSL_PORT. Will use standard $IMAP_PORT port.\n" ) ; } } return $nb_on ; } sub testslive_init { my $mysync = shift ; $mysync->{host1} ||= 'test1.lamiral.info' ; $mysync->{user1} ||= 'test1' ; $mysync->{password1} ||= 'secret1' ; $mysync->{host2} ||= 'test2.lamiral.info' ; $mysync->{user2} ||= 'test2' ; $mysync->{password2} ||= 'secret2' ; return ; } sub testslive6_init { my $mysync = shift ; $mysync->{host1} ||= 'ks6ipv6.lamiral.info' ; $mysync->{user1} ||= 'test1' ; $mysync->{password1} ||= 'secret1' ; $mysync->{host2} ||= 'ks6ipv6.lamiral.info' ; $mysync->{user2} ||= 'test2' ; $mysync->{password2} ||= 'secret2' ; return ; } sub tests_backslash_caret { note( 'Entering tests_backslash_caret()' ) ; is( "lalala", backslash_caret( "lalala" ), 'backslash_caret: lalala => lalala' ) ; is( "lalala\n", backslash_caret( "lalala\n" ), 'backslash_caret: lalala => lalala 2nd' ) ; is( '^', backslash_caret( '\\' ), 'backslash_caret: \\ => ^' ) ; is( "^\n", backslash_caret( "\\\n" ), 'backslash_caret: \\ => ^' ) ; is( "\\lalala", backslash_caret( "\\lalala" ), 'backslash_caret: \\lalala => \\lalala' ) ; is( "\\lal\\ala", backslash_caret( "\\lal\\ala" ), 'backslash_caret: \\lal\\ala => \\lal\\ala' ) ; is( "\\lalala\n", backslash_caret( "\\lalala\n" ), 'backslash_caret: \\lalala => \\lalala 2nd' ) ; is( "lalala^\n", backslash_caret( "lalala\\\n" ), 'backslash_caret: lalala\\\n => lalala^\n' ) ; is( "lalala^\nlalala^\n", backslash_caret( "lalala\\\nlalala\\\n" ), 'backslash_caret: lalala\\\nlalala\\\n => lalala^\nlalala^\n' ) ; is( "lal\\ala^\nlalala^\n", backslash_caret( "lal\\ala\\\nlalala\\\n" ), 'backslash_caret: lal\\ala\\\nlalala\\\n => lal\\ala^\nlalala^\n' ) ; note( 'Leaving tests_backslash_caret()' ) ; return ; } sub backslash_caret { my $string = shift ; $string =~ s{\\ $ }{^}gxms ; return $string ; } sub tests_split_around_equal { note( 'Entering tests_split_around_equal()' ) ; is( undef, split_around_equal( ), 'split_around_equal: no args => undef' ) ; is_deeply( { toto => 'titi' }, { split_around_equal( 'toto=titi' ) }, 'split_around_equal: toto=titi => toto => titi' ) ; is_deeply( { A => 'B', C => 'D' }, { split_around_equal( 'A=B=C=D' ) }, 'split_around_equal: toto=titi => toto => titi' ) ; is_deeply( { A => 'B', C => 'D' }, { split_around_equal( 'A=B', 'C=D' ) }, 'split_around_equal: A=B C=D => A => B, C=>D' ) ; note( 'Leaving tests_split_around_equal()' ) ; return ; } sub split_around_equal { if ( ! @ARG ) { return ; } ; return map { split /=/mxs, $_ } @ARG ; } sub tests_sig_install { note( 'Entering tests_sig_install()' ) ; my $mysync ; is( undef, sig_install( ), 'sig_install: no args => undef' ) ; is( undef, sig_install( $mysync ), 'sig_install: arg undef => undef' ) ; $mysync = { } ; is( undef, sig_install( $mysync ), 'sig_install: empty hash => undef' ) ; SKIP: { Readonly my $SKIP_15 => 15 ; if ( 'MSWin32' eq $OSNAME ) { skip( 'Tests only for Unix', $SKIP_15 ) ; } # Default to ignore USR1 USR2 in case future install fails local $SIG{ USR1 } = local $SIG{ USR2 } = sub { } ; kill( 'USR1', $PROCESS_ID ) ; $mysync->{ debugsig } = 1 ; # Assign USR1 to call sub tototo # Surely a better value than undef should be returned when doing real signal stuff is( undef, sig_install( $mysync, 'tototo', 'USR1' ), 'sig_install: USR1 tototo' ) ; is( 1, kill( 'USR1', $PROCESS_ID ), 'sig_install: kill USR1 myself 1' ) ; is( 1, $mysync->{ tototo_calls }, 'sig_install: tototo call nb 1' ) ; #return ; # Assign USR2 to call sub tototo is( undef, sig_install( $mysync, 'tototo', 'USR2' ), 'sig_install: USR2 tototo' ) ; is( 1, kill( 'USR2', $PROCESS_ID ), 'sig_install: kill USR2 myself 1' ) ; is( 2, $mysync->{ tototo_calls }, 'sig_install: tototo call nb 2' ) ; is( 1, kill( 'USR1', $PROCESS_ID ), 'sig_install: kill USR1 myself 2' ) ; is( 3, $mysync->{ tototo_calls }, 'sig_install: tototo call nb 3' ) ; local $SIG{ USR1 } = local $SIG{ USR2 } = sub { } ; is( 1, kill( 'USR1', $PROCESS_ID ), 'sig_install: kill USR1 myself 3' ) ; is( 3, $mysync->{ tototo_calls }, 'sig_install: tototo call still nb 3' ) ; # Assign USR1 + USR2 to call sub tototo is( undef, sig_install( $mysync, 'tototo', 'USR1', 'USR2' ), 'sig_install: USR1 USR2 tototo' ) ; is( 1, kill( 'USR1', $PROCESS_ID ), 'sig_install: kill USR1 myself 4' ) ; is( 4, $mysync->{ tototo_calls }, 'sig_install: tototo call now nb 4' ) ; is( 1, kill( 'USR2', $PROCESS_ID ), 'sig_install: kill USR1 myself 2' ) ; is( 5, $mysync->{ tototo_calls }, 'sig_install: tototo call now nb 5' ) ; } note( 'Leaving tests_sig_install()' ) ; return ; } # sub sig_install { my $mysync = shift ; if ( ! $mysync ) { return ; } my $mysubname = shift ; if ( ! $mysubname ) { return ; } if ( ! @ARG ) { return ; } my @signals = @ARG ; my $mysub = \&$mysubname ; #$mysync->{ debugsig } = 1 ; $mysync->{ debugsig } and myprint( "In sig_install with sub $mysubname and signal @ARG\n" ) ; my $subsignal = sub { my $signame = shift ; $mysync->{ debugsig } and myprint( "In subsignal with $signame and $mysubname\n" ) ; &$mysub( $mysync, $signame ) ; } ; foreach my $signal ( @signals ) { $mysync->{ debugsig } and myprint( "Installing signal $signal to call sub $mysubname\n") ; output( $mysync, "kill -$signal $PROCESS_ID # special behavior: call to sub $mysubname\n" ) ; ## no critic (RequireLocalizedPunctuationVars) $SIG{ $signal } = $subsignal ; } return ; } sub tototo { my $mysync = shift ; myprint("In tototo with @ARG\n" ) ; $mysync->{ tototo_calls } += 1 ; return ; } sub mygetppid { if ( 'MSWin32' eq $OSNAME ) { return( 'unknown under MSWin32 (too complicated)' ) ; } else { # Unix return( getppid( ) ) ; } } sub tests_toggle_sleep { note( 'Entering tests_toggle_sleep()' ) ; is( undef, toggle_sleep( ), 'toggle_sleep: no args => undef' ) ; my $mysync ; is( undef, toggle_sleep( $mysync ), 'toggle_sleep: undef => undef' ) ; $mysync = { } ; is( undef, toggle_sleep( $mysync ), 'toggle_sleep: no maxsleep => undef' ) ; $mysync->{maxsleep} = 3 ; is( 0, toggle_sleep( $mysync ), 'toggle_sleep: 3 => 0' ) ; is( $MAX_SLEEP, toggle_sleep( $mysync ), "toggle_sleep: 0 => $MAX_SLEEP" ) ; is( 0, toggle_sleep( $mysync ), "toggle_sleep: $MAX_SLEEP => 0" ) ; is( $MAX_SLEEP, toggle_sleep( $mysync ), "toggle_sleep: 0 => $MAX_SLEEP" ) ; is( 0, toggle_sleep( $mysync ), "toggle_sleep: $MAX_SLEEP => 0" ) ; SKIP: { Readonly my $SKIP_9 => 9 ; if ( 'MSWin32' eq $OSNAME ) { skip( 'Tests only for Unix', $SKIP_9 ) ; } # Default to ignore USR1 USR2 in case future install fails local $SIG{ USR1 } = sub { } ; kill( 'USR1', $PROCESS_ID ) ; $mysync->{ debugsig } = 1 ; # Assign USR1 to call sub toggle_sleep is( undef, sig_install( $mysync, \&toggle_sleep, 'USR1' ), 'toggle_sleep: install USR1 toggle_sleep' ) ; $mysync->{maxsleep} = 4 ; is( 1, kill( 'USR1', $PROCESS_ID ), 'toggle_sleep: kill USR1 myself' ) ; is( 0, $mysync->{ maxsleep }, 'toggle_sleep: toggle_sleep called => sleeps are 0s' ) ; is( 1, kill( 'USR1', $PROCESS_ID ), 'toggle_sleep: kill USR1 myself again' ) ; is( $MAX_SLEEP, $mysync->{ maxsleep }, "toggle_sleep: toggle_sleep called => sleeps are ${MAX_SLEEP}s" ) ; is( 1, kill( 'USR1', $PROCESS_ID ), 'toggle_sleep: kill USR1 myself' ) ; is( 0, $mysync->{ maxsleep }, 'toggle_sleep: toggle_sleep called => sleeps are 0s' ) ; is( 1, kill( 'USR1', $PROCESS_ID ), 'toggle_sleep: kill USR1 myself again' ) ; is( $MAX_SLEEP, $mysync->{ maxsleep }, "toggle_sleep: toggle_sleep called => sleeps are ${MAX_SLEEP}s" ) ; } note( 'Leaving tests_toggle_sleep()' ) ; return ; } sub toggle_sleep { my $mysync = shift ; myprint("In toggle_sleep with @ARG\n" ) ; if ( !defined( $mysync ) ) { return ; } if ( !defined( $mysync->{maxsleep} ) ) { return ; } $mysync->{ maxsleep } = max( 0, $MAX_SLEEP - $mysync->{maxsleep} ) ; myprint("Resetting maxsleep to ", $mysync->{maxsleep}, "s\n" ) ; return $mysync->{maxsleep} ; } sub mypod2usage { my $fh_pod2usage = shift ; pod2usage( -exitval => 'NOEXIT', -noperldoc => 1, -verbose => 99, -sections => [ qw(NAME VERSION USAGE OPTIONS) ], -indent => 1, -loose => 1, -output => $fh_pod2usage, ) ; return ; } sub usage { my $mysync = shift ; if ( ! defined $mysync ) { return ; } my $usage = q{} ; my $usage_from_pod ; my $usage_footer = usage_footer( $mysync ) ; # pod2usage writes on a filehandle only and I want a variable open my $fh_pod2usage, ">", \$usage_from_pod or do { warn $OS_ERROR ; return ; } ; mypod2usage( $fh_pod2usage ) ; close $fh_pod2usage ; if ( 'MSWin32' eq $OSNAME ) { $usage_from_pod = backslash_caret( $usage_from_pod ) ; } $usage = join( q{}, $usage_from_pod, $usage_footer ) ; return( $usage ) ; } sub tests_usage { note( 'Entering tests_usage()' ) ; my $usage ; like( $usage = usage( $sync ), qr/Name:/, 'usage: contains Name:' ) ; myprint( $usage ) ; like( $usage, qr/Version:/, 'usage: contains Version:' ) ; like( $usage, qr/Usage:/, 'usage: contains Usage:' ) ; like( $usage, qr/imapsync/, 'usage: contains imapsync' ) ; is( undef, usage( ), 'usage: no args => undef' ) ; note( 'Leaving tests_usage()' ) ; return ; } sub usage_footer { my $mysync = shift ; my $footer = q{} ; my $localhost_info = localhost_info( $mysync ) ; my $rcs = $mysync->{rcs} ; my $homepage = homepage( ) ; my $imapsync_release = $STR_use_releasecheck ; if ( $mysync->{releasecheck} ) { $imapsync_release = check_last_release( ) ; } $footer = qq{$localhost_info $rcs $imapsync_release $homepage } ; return( $footer ) ; } sub usage_complete { # Unused, I guess this function could be deleted my $usage = <<'EOF' ; --skipheader reg : Don't take into account header keyword matching reg ex: --skipheader 'X.*' --skipsize : Don't take message size into account to compare messages on both sides. On by default. Use --no-skipsize for using size comparaison. --allowsizemismatch : allow RFC822.SIZE != fetched msg size consider also --skipsize to avoid duplicate messages when running syncs more than one time per mailbox --reconnectretry1 int : reconnect to host1 if connection is lost up to int times per imap command (default is 3) --reconnectretry2 int : same as --reconnectretry1 but for host2 --split1 int : split the requests in several parts on host1. int is the number of messages handled per request. default is like --split1 100. --split2 int : same thing on host2. --nofixInboxINBOX : Don't fix Inbox INBOX mapping. EOF return( $usage ) ; } sub setvalfromcgikey { my ( $mysync, $mycgi, $key, $val ) = @ARG ; my $badthings = 0 ; my ( $name, $type, $struct ) ; if ( $key !~ m/^([\w\d\|]+)([=:][isf])?([\+!\@\%])?$/mxs ) { $badthings++ ; next ; # Unknown item } else { $name = [ split '|', $1, 1 ]->[0] ; # option name ab|cd|ef => keep only ab $type = $2 ; # = or : followed by i or s or f $struct = $3 ; # + or ! or @ or % } if ( ( $struct || q{} ) eq '+' ) { ${$val} = $mycgi->param( $name ) ; # "Incremental" integer } elsif ( $type ) { my @values = $mycgi->multi_param( $name ) ; #myprint( "type[$type]values[@values]\$struct[", $struct || q{}, "]val[$val]ref(val)[", ref($val), "]\n" ) ; if ( ( $struct || q{} ) eq '%' or ref( $val ) eq 'HASH' ) { setvalfromhash( $val, $type, @values ) ; } else { setvalfromlist( $mysync, $val, $name, $type, $struct, @values ) ; } } else { setvalfromcheckbox( $mysync, $mycgi, $key, $name, $val ) ; } return $badthings ; } sub setvalfromlist { my ( $mysync, $val, $name, $type, $struct, @values ) = @ARG ; if ( $type =~ m/i$/mxs ) { @values = map { q{} ne $_ ? int $_ : undef } @values ; } elsif ( $type =~ m/f$/mxs ) { @values = map { 0 + $_ } @values ; } if ( ( $struct || q{} ) eq '@' ) { @{ ${$val} } = @values ; my @option = map { +( "--$name", "$_" ) } @values ; push @{ $mysync->{ cmdcgi } }, @option ; } elsif ( ref( $val ) eq 'ARRAY' ) { @{$val} = @values ; } elsif ( my $value = $values[0] ) { ${$val} = $value ; push @{ $mysync->{ cmdcgi } }, "--$name", $value ; } else { } return ; } sub setvalfromhash { my ( $val, $type, @values ) = @ARG ; my %values = map { split /=/mxs, $_ } @values ; if ( $type =~ m/i$/mxs ) { foreach my $k ( keys %values ) { $values{$k} = int $values{$k} ; } } elsif ( $type =~ m/f$/mxs ) { foreach my $k ( keys %values ) { $values{$k} = 0 + $values{$k}; } } if ( 'REF' eq ref $val ) { %{ ${$val} } = %values ; } else { %{$val} = %values ; } return ; } sub setvalfromcheckbox { my ( $mysync, $mycgi, $key, $name, $val ) = @ARG ; # Checkbox # --noname is set by name=0 or name= my $value = $mycgi->param( $name ) ; if ( defined $value ) { ${$val} = $value ; if ( $value ) { push @{ $mysync->{ cmdcgi } }, "--$name" ; } else { push @{ $mysync->{ cmdcgi } }, "--no$name" ; } } else { ${$val} = undef ; } return ; } sub myGetOptions { # Started as a copy of Luke Ross Getopt::Long::CGI # https://metacpan.org/release/Getopt-Long-CGI # So this sub function is under the same license as Getopt-Long-CGI Luke Ross wants it, # which was Perl 5.6 or later licenses at the date of the copy. # It also applies for the sub functions called from this one. my $mysync = shift @ARG ; my $arguments_ref = shift @ARG ; my %options = @ARG ; my $mycgi = $mysync->{cgi} ; if ( not under_cgi_context() ) { # Not CGI - pass upstream for normal command line handling return Getopt::Long::GetOptionsFromArray( $arguments_ref, %options ) ; } # We must be in CGI context now if ( ! defined( $mycgi ) ) { return ; } my $badthings = 0 ; foreach my $key ( sort keys %options ) { my $val = $options{$key} ; $badthings += setvalfromcgikey( $mysync, $mycgi, $key, $val ) ; } if ( $badthings ) { return ; # undef or () } else { return ( 1 ) ; } } sub tests_get_options_extra { note( 'Entering tests_get_options_extra()' ) ; is( undef, get_options_extra( ), 'get_options_extra: no args => undef' ) ; my $mysync = { } ; is( undef, get_options_extra( $mysync ), 'get_options_extra: undef => undef' ) ; my $cwd_save = getcwd( ) ; ok( (-d 'W/tmp/tests/options_extra/' or mkpath( 'W/tmp/tests/options_extra/' )), 'get_options_extra: mkpath W/tmp/tests/options_extra/' ) ; chdir 'W/tmp/tests/options_extra/' ; is( '--debugimap1', string_to_file( '--debugimap1', 'options_extra.txt' ), 'get_options_extra: string_to_file filling options_extra.txt with --debugimap1' ) ; is( '--debugimap1', file_to_string( 'options_extra.txt' ), 'get_options_extra: reading options_extra.txt is --debugimap1' ) ; is( '', get_options_extra( $mysync ), 'get_options_extra: --debugimap1 in options_extra.txt => nothing left, empty string return' ) ; is( 1, $mysync->{ acc1 }->{ debugimap }, 'get_options_extra: --debugimap1 in options_extra.txt => ok, acc1->debugimap = 1' ) ; is( '--tls1 proutcaca', string_to_file( '--tls1 proutcaca', 'options_extra.txt' ), 'get_options_extra: string_to_file filling options_extra.txt with --tls1 proutcaca' ) ; is( 'proutcaca', get_options_extra( $mysync ), 'get_options_extra: --tls1 proutcaca in options_extra.txt => proutcaca left, proutcaca return' ) ; chdir $cwd_save ; note( 'Leaving tests_get_options_extra()' ) ; return ; } sub get_options_extra { my $mysync = shift @ARG ; if ( ! defined $mysync ) { return ; } if ( -f -r 'options_extra.txt' ) { my $cwd = getcwd( ) ; my $string = firstline( 'options_extra.txt' ) ; my $rest = get_options_from_string( $mysync, $string ) ; output( $mysync, "Reading extra options from file options_extra.txt (cwd: $cwd) : $string\n" ) ; return $rest ; } else { return ; } } sub tests_get_options_from_string { note( 'Entering tests_get_options_from_string()' ) ; is( undef, get_options_from_string( ), 'get_options_from_string: no args => undef' ) ; my $mysync = { } ; is( undef, get_options_from_string( $mysync ), 'get_options_from_string: undef => undef' ) ; is( '', get_options_from_string( $mysync, '--debugimap1' ), 'get_options_from_string: --debugimap1 => ok, nothing left, empty string return' ) ; is( 1, $mysync->{ acc1 }->{ debugimap }, 'get_options_from_string: --debugimap1 => ok, acc1->debugimap = 1' ) ; $mysync = { } ; # reset is( 'caca', get_options_from_string( $mysync, '--debugimap1 caca' ), 'get_options_from_string: --debugimap1 caca => ok, caca left, caca return' ) ; is( 1, $mysync->{ acc1 }->{ debugimap }, 'get_options_from_string: --debugimap1 => ok, acc1->debugimap = 1' ) ; is( 'popo roro', get_options_from_string( $mysync, '--debugimap2 popo roro' ), 'get_options_from_string: --debugimap1 popo roro => ok, popo roro left, popo roro return' ) ; is( 1, $mysync->{ acc2 }->{ debugimap }, 'get_options_from_string: --debugimap2 popo roro => ok, acc2->debugimap = 1' ) ; is( 1, $mysync->{ acc1 }->{ debugimap }, 'get_options_from_string: acc1->debugimap = 1 still' ) ; is( '', get_options_from_string( $mysync, '--nodebugimap1 --debugflags --errorsmax 2' ), 'get_options_from_string: --nodebugimap1 --debugflags --errorsmax 2 => ok, empty string return' ) ; is( 0, $mysync->{ acc1 }->{ debugimap }, 'get_options_from_string: acc1->debugimap = 0 now' ) ; is( 1, $debugflags, 'get_options_from_string: debugflags = 1 now' ) ; is( 2, $mysync->{ errorsmax }, 'get_options_from_string: mysync->errorsmax = 2 now' ) ; is( '', get_options_from_string( $mysync, '--folder "IN BOX" --folder JOE' ), 'get_options_from_string: --folder "IN BOX" --folder JOE => ok, empty string return' ) ; is_deeply( [ 'IN BOX', 'JOE' ], [@{$mysync->{ folder }}], 'get_options_from_string: "IN BOX" "JOE"' ) ; is( '', get_options_from_string( $mysync, '--debugflags --koko' ), 'get_options_from_string: --debugflags --koko => ok, empty string return, with "Unknown option: koko" on STDERR' ) ; note( 'Leaving tests_get_options_from_string()' ) ; return ; } sub get_options_from_string { my $mysync = shift @ARG ; my $mystring = shift @ARG ; if ( ! defined $mystring ) { return ; } my ( $ret, $args ) = Getopt::Long::GetOptionsFromString( $mystring, 'debugimap!' => \$mysync->{ debugimap }, 'debugimap1!' => \$mysync->{ acc1 }->{ debugimap }, 'debugimap2!' => \$mysync->{ acc2 }->{ debugimap }, 'debugflags!' => \$debugflags, 'debugsleep=f' => \$mysync->{ debugsleep }, 'errorsmax=i' => \$mysync->{ errorsmax }, 'folder=s@' => \$mysync->{ folder }, 'timeout=f' => \$mysync->{ timeout }, 'timeout1=f' => \$mysync->{ acc1 }->{ timeout }, 'timeout2=f' => \$mysync->{ acc2 }->{ timeout }, 'keepalive1!' => \$mysync->{ acc1 }->{ keepalive }, 'keepalive2!' => \$mysync->{ acc2 }->{ keepalive }, 'reconnectretry1=i' => \$mysync->{ acc1 }->{ reconnectretry }, 'reconnectretry2=i' => \$mysync->{ acc2 }->{ reconnectretry }, 'ssl1!' => \$mysync->{ ssl1 }, 'ssl2!' => \$mysync->{ ssl2 }, 'tls1!' => \$mysync->{ tls1 }, 'tls2!' => \$mysync->{ tls2 }, 'compress1!' => \$mysync->{ acc1 }->{ compress }, 'compress2!' => \$mysync->{ acc2 }->{ compress }, ) ; my $left = join( ' ', @$args ) ; return $left ; } sub tests_get_options_cgi_context { note( 'Entering tests_get_options_cgi_context()' ) ; # Temporary, have to think harder about testing CGI context in command line --tests # API: # * input arguments: two ways, command line or CGI # * the program arguments # * QUERY_STRING env variable # * return # * QUERY_STRING length # CGI context local $ENV{SERVER_SOFTWARE} = 'Votre serviteur' ; # Real full test # = 'host1=test1.lamiral.info&user1=test1&password1=secret1&host2=test2.lamiral.info&user2=test2&password2=secret2&debugenv=on' my $mysync ; is( undef, get_options( $mysync ), 'get_options cgi context: no CGI module => undef' ) ; # skip all next tests if the CGI module is not available SKIP: { if ( ! eval { require CGI ; } ) { skip( "CGI Perl module is not installed", 19 ) ; } CGI->import( qw( -no_debug -utf8 ) ) ; is( undef, get_options( $mysync ), 'get_options cgi context: no CGI param => undef' ) ; # Testing boolean $mysync->{cgi} = CGI->new( 'version=on&debugenv=on' ) ; local $ENV{'QUERY_STRING'} = 'version=on&debugenv=on' ; is( 22, get_options( $mysync ), 'get_options cgi context: QUERY_STRING => 22' ) ; is( 'on', $mysync->{ version }, 'get_options cgi context: --version => on' ) ; # debugenv is not allowed in cgi context is( undef, $mysync->{debugenv}, 'get_options cgi context: $mysync->{debugenv} => undef' ) ; # QUERY_STRING in this test is only for return value of get_options # Have to think harder, GET/POST context, is this return value a good thing? local $ENV{'QUERY_STRING'} = 'host1=test1.lamiral.info&user1=test1' ; $mysync->{cgi} = CGI->new( 'host1=test1.lamiral.info&user1=test1' ) ; is( 36, get_options( $mysync, ), 'get_options cgi context: QUERY_STRING => 36' ) ; is( 'test1', $mysync->{user1}, 'get_options cgi context: $mysync->{user1} => test1' ) ; #local $ENV{'QUERY_STRING'} = undef ; # Testing s@ as ref $mysync->{cgi} = CGI->new( 'folder=fd1' ) ; get_options( $mysync ) ; is_deeply( [ 'fd1' ], $mysync->{ folder }, 'get_options cgi context: $mysync->{ folder } => fd1' ) ; $mysync->{cgi} = CGI->new( 'folder=fd1&folder=fd2' ) ; get_options( $mysync ) ; is_deeply( [ 'fd1', 'fd2' ], $mysync->{ folder }, 'get_options cgi context: $mysync->{ folder } => fd1, fd2' ) ; # Testing % $mysync->{cgi} = CGI->new( 'f1f2h=s1=d1&f1f2h=s2=d2&f1f2h=s3=d3' ) ; get_options( $mysync ) ; is_deeply( { 's1' => 'd1', 's2' => 'd2', 's3' => 'd3' }, $mysync->{f1f2h}, 'get_options cgi context: f1f2h => s1=d1 s2=d2 s3=d3' ) ; # Testing boolean ! with --noxxx, doesnot work $mysync->{cgi} = CGI->new( 'nodry=on' ) ; get_options( $mysync ) ; is( undef, $mysync->{dry}, 'get_options cgi context: --nodry => $mysync->{dry} => undef' ) ; $mysync->{cgi} = CGI->new( 'host1=example.com' ) ; get_options( $mysync ) ; is( 'example.com', $mysync->{host1}, 'get_options cgi context: --host1=example.com => $mysync->{host1} => example.com' ) ; #myprint( Data::Dumper->Dump( [ $mysync ] ) ) ; $mysync->{cgi} = CGI->new( 'simulong=' ) ; get_options( $mysync ) ; is( undef, $mysync->{simulong}, 'get_options cgi context: --simulong= => $mysync->{simulong} => undef' ) ; $mysync->{cgi} = CGI->new( 'simulong' ) ; get_options( $mysync ) ; is( undef, $mysync->{simulong}, 'get_options cgi context: --simulong => $mysync->{simulong} => undef' ) ; $mysync->{cgi} = CGI->new( 'simulong=4' ) ; get_options( $mysync ) ; is( 4, $mysync->{simulong}, 'get_options cgi context: --simulong=4 => $mysync->{simulong} => 4' ) ; is( undef, $mysync->{ folder }, 'get_options cgi context: --simulong=4 => $mysync->{ folder } => undef' ) ; #myprint( Data::Dumper->Dump( [ $mysync ] ) ) ; $mysync ={} ; $mysync->{cgi} = CGI->new( 'testslive=on' ) ; get_options( $mysync ) ; is( 'on', $mysync->{ testslive }, 'get_options cgi context: --testslive=on => testslive => on' ) ; #myprint( Data::Dumper->Dump( [ $mysync ] ) ) ; $mysync ={} ; $mysync->{cgi} = CGI->new( 'log=0' ) ; get_options( $mysync ) ; is( 0, $mysync->{ log }, 'get_options cgi context: --log=0 => log => 0' ) ; #myprint( Data::Dumper->Dump( [ $mysync ] ) ) ; # What is this fucked up indentation? } note( 'Leaving tests_get_options_cgi_context()' ) ; return ; } sub get_options_cgi { # In CGI context arguments are not in @ARGV but in QUERY_STRING variable (with GET). my $mysync = shift @ARG ; $mysync->{cgi} || return ; my @arguments = @ARG ; # final 0 is used to print usage when no option is given my $numopt = length $ENV{'QUERY_STRING'} || 1 ; $mysync->{f1f2h} = {} ; my $opt_ret = myGetOptions( $mysync, \@arguments, 'abort' => \$mysync->{ abort }, 'abortbyfile' => \$mysync->{ abortbyfile }, 'host1=s' => \$mysync->{ host1 }, 'host2=s' => \$mysync->{ host2 }, 'user1=s' => \$mysync->{ user1 }, 'user2=s' => \$mysync->{ user2 }, 'password1=s' => \$mysync->{ password1 }, 'password2=s' => \$mysync->{ password2 }, 'dry!' => \$mysync->{ dry }, 'dry1!' => \$mysync->{ dry1 }, 'version' => \$mysync->{ version }, 'ssl1!' => \$mysync->{ ssl1 }, 'ssl2!' => \$mysync->{ ssl2 }, 'tls1!' => \$mysync->{ tls1 }, 'tls2!' => \$mysync->{ tls2 }, 'compress1!' => \$mysync->{ acc1 }->{ compress }, 'compress2!' => \$mysync->{ acc2 }->{ compress }, 'justbanner!' => \$mysync->{ justbanner }, 'justlogin!' => \$mysync->{ justlogin }, 'justconnect!' => \$mysync->{ justconnect }, 'addheader!' => \$mysync->{ addheader }, 'automap!' => \$mysync->{ automap }, 'justautomap!' => \$mysync->{ justautomap }, 'gmail1' => \$mysync->{ gmail1 }, 'gmail2' => \$mysync->{ gmail2 }, 'office1' => \$mysync->{ office1 }, 'office2' => \$mysync->{ office2 }, 'exchange1' => \$mysync->{ exchange1 }, 'exchange2' => \$mysync->{ exchange2 }, 'domino1' => \$mysync->{ domino1 }, 'domino2' => \$mysync->{ domino2 }, 'f1f2=s@' => \$mysync->{ f1f2 }, 'f1f2h=s%' => \$mysync->{ f1f2h }, 'folder=s@' => \$mysync->{ folder }, 'testslive!' => \$mysync->{ testslive }, 'testslive6!' => \$mysync->{ testslive6 }, 'releasecheck!' => \$mysync->{ releasecheck }, 'simulong=f' => \$mysync->{ simulong }, 'debugsleep=f' => \$mysync->{ debugsleep }, 'subfolder1=s' => \$mysync->{ subfolder1 }, 'subfolder2=s' => \$mysync->{ subfolder2 }, 'justfolders!' => \$mysync->{ justfolders }, 'justfoldersizes!' => \$mysync->{ justfoldersizes }, 'delete1!' => \$mysync->{ delete1 }, 'delete2!' => \$mysync->{ delete2 }, 'delete2duplicates!' => \$mysync->{ delete2duplicates }, 'tail!' => \$mysync->{ tail }, 'tmphash=s' => \$mysync->{ tmphash }, 'exitwhenover=i' => \$mysync->{ exitwhenover }, 'syncduplicates!' => \$mysync->{ syncduplicates }, 'log!' => \$mysync->{ log }, 'loglogfile!' => \$mysync->{ loglogfile }, # f1f2h=s% could be removed but # tests_get_options_cgi() should be split before # with a sub tests_myGetOptions() ) ; $mysync->{ debug } and output( $mysync, "get options: [$opt_ret][$numopt]\n" ) ; if ( ! $opt_ret ) { return ; } return $numopt ; } sub get_options_cmd { my $mysync = shift @ARG ; my @arguments = @ARG ; my $mycgi = $mysync->{cgi} ; # final 0 is used to print usage when no option is given on command line my $numopt = scalar @arguments || 0 ; my $argv = join "\x00", @arguments ; if ( $argv =~ m/-delete\x002/x ) { output( $mysync, "May be you mean --delete2 instead of --delete 2\n" ) ; return ; } $mysync->{f1f2h} = {} ; my $opt_ret = myGetOptions( $mysync, \@arguments, 'debug!' => \$mysync->{ debug }, 'debuglist!' => \$debuglist, 'debugcontent!' => \$mysync->{ debugcontent }, 'debugsleep=f' => \$mysync->{ debugsleep }, 'debugflags!' => \$debugflags, 'debugimap!' => \$mysync->{ debugimap }, 'debugimap1!' => \$mysync->{ acc1 }->{ debugimap }, 'debugimap2!' => \$mysync->{ acc2 }->{ debugimap }, 'debugdev!' => \$debugdev, 'debugmemory!' => \$mysync->{debugmemory}, 'debugfolders!' => \$mysync->{debugfolders}, 'debugssl=i' => \$mysync->{debugssl}, 'debugcgi!' => \$debugcgi, 'debugenv!' => \$mysync->{debugenv}, 'debugsig!' => \$mysync->{debugsig}, 'debuglabels!' => \$mysync->{debuglabels}, 'simulong=f' => \$mysync->{simulong}, 'abort' => \$mysync->{abort}, 'abortbyfile' => \$mysync->{abortbyfile}, 'host1=s' => \$mysync->{ host1 }, 'host2=s' => \$mysync->{ host2 }, 'port1=i' => \$mysync->{ port1 }, 'port2=i' => \$mysync->{ port2 }, 'inet4|ipv4' => \$mysync->{ inet4 }, 'inet6|ipv6' => \$mysync->{ inet6 }, 'user1=s' => \$mysync->{ user1 }, 'user2=s' => \$mysync->{ user2 }, 'gmail1' => \$mysync->{gmail1}, 'gmail2' => \$mysync->{gmail2}, 'office1' => \$mysync->{office1}, 'office2' => \$mysync->{office2}, 'exchange1' => \$mysync->{exchange1}, 'exchange2' => \$mysync->{exchange2}, 'domino1' => \$mysync->{domino1}, 'domino2' => \$mysync->{domino2}, 'domain1=s' => \$mysync->{ acc1 }->{ domain }, 'domain2=s' => \$mysync->{ acc2 }->{ domain }, 'password1=s' => \$mysync->{password1}, 'password2=s' => \$mysync->{password2}, 'passfile1=s' => \$mysync->{ passfile1 }, 'passfile2=s' => \$mysync->{ passfile2 }, 'authmd5!' => \$authmd5, 'authmd51!' => \$authmd51, 'authmd52!' => \$authmd52, 'trylogin!' => \$mysync->{ trylogin }, 'oauthdirect1=s' => \$mysync->{ acc1 }->{ oauthdirect }, 'oauthdirect2=s' => \$mysync->{ acc2 }->{ oauthdirect }, 'oauthaccesstoken1=s' => \$mysync->{ acc1 }->{ oauthaccesstoken }, 'oauthaccesstoken2=s' => \$mysync->{ acc2 }->{ oauthaccesstoken }, 'sep1=s' => \$mysync->{ sep1 }, 'sep2=s' => \$mysync->{ sep2 }, 'sanitize!' => \$mysync->{ sanitize }, 'folder=s@' => \$mysync->{ folder }, 'folderrec=s' => \@folderrec, 'include=s' => \@include, 'exclude=s' => \@exclude, 'noexclude' => \$mysync->{noexclude}, 'folderfirst=s' => \@folderfirst, 'folderlast=s' => \@folderlast, 'prefix1=s' => \$prefix1, 'prefix2=s' => \$prefix2, 'subfolder1=s' => \$mysync->{ subfolder1 }, 'subfolder2=s' => \$mysync->{ subfolder2 }, 'fixslash2!' => \$mysync->{ fixslash2 }, 'fixInboxINBOX!' => \$fixInboxINBOX, 'regextrans2=s@' => \$mysync->{ regextrans2 }, 'mixfolders!' => \$mixfolders, 'skipemptyfolders!' => \$mysync->{ skipemptyfolders }, 'regexmess=s' => \@regexmess, 'noregexmess' => \$mysync->{noregexmess}, 'skipmess=s' => \@skipmess, 'pipemess=s' => \@pipemess, 'pipemesscheck!' => \$pipemesscheck, 'disarmreadreceipts!' => \$disarmreadreceipts, 'regexflag=s@' => \$mysync->{ regexflag }, 'noregexflag' => \$mysync->{ noregexflag }, 'filterflags!' => \$mysync->{ filterflags }, 'filterbuggyflags!' => \$mysync->{ filterbuggyflags }, 'flagscase!' => \$mysync->{ flagscase }, 'syncflagsaftercopy!' => \$syncflagsaftercopy, 'resyncflags!' => \$mysync->{ resyncflags }, 'synclabels!' => \$mysync->{ synclabels }, 'resynclabels!' => \$mysync->{ resynclabels }, 'delete|delete1!' => \$mysync->{ delete1 }, 'delete2!' => \$mysync->{ delete2 }, 'delete2duplicates!' => \$mysync->{ delete2duplicates }, 'delete2folders!' => \$delete2folders, 'delete2foldersonly=s' => \$delete2foldersonly, 'delete2foldersbutnot=s' => \$delete2foldersbutnot, 'syncinternaldates!' => \$syncinternaldates, 'idatefromheader!' => \$idatefromheader, 'syncacls!' => \$mysync->{ syncacls }, 'maxsize=i' => \$mysync->{ maxsize }, 'appendlimit=i' => \$mysync->{ appendlimit }, 'truncmess=i' => \$mysync->{ truncmess }, 'minsize=i' => \$minsize, 'maxage=f' => \$maxage, 'minage=f' => \$minage, 'search=s' => \$search, 'search1=s' => \$mysync->{ search1 }, 'search2=s' => \$mysync->{ search2 }, 'foldersizes!' => \$mysync->{ foldersizes }, 'foldersizesatend!' => \$mysync->{ foldersizesatend }, 'dry!' => \$mysync->{dry}, 'dry1!' => \$mysync->{dry1}, 'expunge1|expunge!' => \$mysync->{ expunge1 }, 'expunge2!' => \$mysync->{ expunge2 }, 'uidexpunge2!' => \$mysync->{ uidexpunge2 }, 'subscribed' => \$subscribed, 'subscribe!' => \$subscribe, 'subscribeall|subscribe_all!' => \$subscribeall, 'justbanner!' => \$mysync->{ justbanner }, 'justfolders!'=> \$mysync->{ justfolders }, 'justfoldersizes!' => \$mysync->{ justfoldersizes }, 'version' => \$mysync->{version}, 'help' => \$help, 'timeout=f' => \$mysync->{timeout}, 'timeout1=f' => \$mysync->{ acc1 }->{timeout}, 'timeout2=f' => \$mysync->{ acc2 }->{timeout}, 'skipheader=s' => \$mysync->{ skipheader }, 'useheader=s' => \@useheader, 'wholeheaderifneeded!' => \$wholeheaderifneeded, 'messageidnodomain!' => \$messageidnodomain, 'skipsize!' => \$skipsize, 'allowsizemismatch!' => \$allowsizemismatch, 'fastio1!' => \$mysync->{ acc1 }->{ fastio }, 'fastio2!' => \$mysync->{ acc2 }->{ fastio }, 'sslcheck!' => \$mysync->{sslcheck}, 'ssl1!' => \$mysync->{ssl1}, 'ssl2!' => \$mysync->{ssl2}, 'ssl1_ssl_version=s' => \$mysync->{ acc1 }->{sslargs}->{SSL_version}, 'ssl2_ssl_version=s' => \$mysync->{ acc2 }->{sslargs}->{SSL_version}, 'sslargs1=s%' => \$mysync->{ acc1 }->{sslargs}, 'sslargs2=s%' => \$mysync->{ acc2 }->{sslargs}, 'tls1!' => \$mysync->{tls1}, 'tls2!' => \$mysync->{tls2}, 'uid1!' => \$uid1, 'uid2!' => \$uid2, 'authmech1=s' => \$mysync->{ acc1 }->{ authmech }, 'authmech2=s' => \$mysync->{ acc2 }->{ authmech }, 'authuser1=s' => \$mysync->{ acc1 }->{ authuser }, 'authuser2=s' => \$mysync->{ acc2 }->{ authuser }, 'proxyauth1' => \$mysync->{ acc1 }->{ proxyauth }, 'proxyauth2' => \$mysync->{ acc2 }->{ proxyauth }, 'compress1!' => \$mysync->{ acc1 }->{ compress }, 'compress2!' => \$mysync->{ acc2 }->{ compress }, 'keepalive1!' => \$mysync->{ acc1 }->{ keepalive }, 'keepalive2!' => \$mysync->{ acc2 }->{ keepalive }, 'split1=i' => \$split1, 'split2=i' => \$split2, 'buffersize=i' => \$buffersize, 'reconnectretry1=i' => \$mysync->{ acc1 }->{ reconnectretry }, 'reconnectretry2=i' => \$mysync->{ acc2 }->{ reconnectretry }, 'tests!' => \$mysync->{ tests }, 'testsdebug|tests_debug!' => \$mysync->{ testsdebug }, 'testsunit=s@' => \$mysync->{testsunit}, 'testslive!' => \$mysync->{testslive}, 'testslive6!' => \$mysync->{testslive6}, 'justlogin!' => \$mysync->{justlogin}, 'justconnect!' => \$mysync->{justconnect}, 'tmpdir=s' => \$mysync->{ tmpdir }, 'pidfile=s' => \$mysync->{pidfile}, 'pidfilelocking!' => \$mysync->{pidfilelocking}, 'sigexit=s@' => \$mysync->{ sigexit }, 'sigreconnect=s@' => \$mysync->{ sigreconnect }, 'sigignore=s@' => \$mysync->{ sigignore }, 'releasecheck!' => \$mysync->{releasecheck}, 'modulesversion|modules_version!' => \$modulesversion, 'usecache!' => \$usecache, 'cacheaftercopy!' => \$cacheaftercopy, 'debugcache!' => \$debugcache, 'useuid!' => \$useuid, 'addheader!' => \$mysync->{addheader}, 'exitwhenover=i' => \$mysync->{ exitwhenover }, 'checkselectable!' => \$mysync->{ checkselectable }, 'checkfoldersexist!' => \$mysync->{ checkfoldersexist }, 'checkmessageexists!' => \$checkmessageexists, 'expungeaftereach!' => \$mysync->{ expungeaftereach }, 'abletosearch!' => \$mysync->{abletosearch}, 'abletosearch1!' => \$mysync->{abletosearch1}, 'abletosearch2!' => \$mysync->{abletosearch2}, 'showpasswords!' => \$mysync->{showpasswords}, 'maxlinelength=i' => \$maxlinelength, 'maxlinelengthcmd=s' => \$maxlinelengthcmd, 'minmaxlinelength=i' => \$minmaxlinelength, 'debugmaxlinelength!' => \$debugmaxlinelength, 'fixcolonbug!' => \$fixcolonbug, 'create_folder_old!' => \$create_folder_old, 'maxmessagespersecond=f' => \$mysync->{maxmessagespersecond}, 'maxbytespersecond=i' => \$mysync->{maxbytespersecond}, 'maxbytesafter=i' => \$mysync->{maxbytesafter}, 'maxsleep=f' => \$mysync->{maxsleep}, 'skipcrossduplicates!' => \$skipcrossduplicates, 'debugcrossduplicates!' => \$debugcrossduplicates, 'log!' => \$mysync->{log}, 'tail!' => \$mysync->{tail}, 'logfile=s' => \$mysync->{logfile}, 'logdir=s' => \$mysync->{logdir}, 'errorsmax=i' => \$mysync->{errorsmax}, 'errorsdump!' => \$mysync->{ errorsdump }, 'fetch_hash_set=s' => \$fetch_hash_set, 'automap!' => \$mysync->{automap}, 'justautomap!' => \$mysync->{justautomap}, 'id!' => \$mysync->{id}, 'f1f2=s@' => \$mysync->{f1f2}, 'nof1f2' => \$mysync->{nof1f2}, 'f1f2h=s%' => \$mysync->{f1f2h}, 'justfolderlists!' => \$mysync->{justfolderlists}, 'delete1emptyfolders' => \$mysync->{delete1emptyfolders}, 'checknoabletosearch!' => \$mysync->{checknoabletosearch}, 'syncduplicates!' => \$mysync->{ syncduplicates }, 'dockercontext!' => \$mysync->{ dockercontext }, ) ; #myprint( Data::Dumper->Dump( [ $mysync ] ) ) ; $mysync->{ debug } and output( $mysync, "get options: [$opt_ret][$numopt]\n" ) ; my $numopt_after = scalar @arguments ; #myprint( "get options: [$opt_ret][$numopt][$numopt_after]\n" ) ; # The $arguments[0] test is just because parallel adds "" when it is # used with {=7=} in sync_parallel_unix.sh if ( $numopt_after and $arguments[0] ) { myprint( "Found ", scalar( @arguments ), " extra arguments : [@arguments]\n", "It usually means a quoting issue in the command line ", "or some misspelling options.\n", ) ; return ; } if ( ! $opt_ret ) { return ; } return $numopt ; } sub tests_get_options { note( 'Entering tests_get_options()' ) ; # CAVEAT: still setting global variables, be careful # with tests, the context increases! $debug stays on for example. # API: # * input arguments: two ways, command line or CGI # * the program arguments # * QUERY_STRING env variable # * return # * undef if bad things happened like # * options not known # * --delete 2 input # * number of arguments or QUERY_STRING length my $mysync = { } ; is( undef, get_options( $mysync, qw( --noexist ) ), 'get_options: --noexist => undef' ) ; is( undef, $mysync->{ noexist }, 'get_options: --noexist => undef' ) ; $mysync = { } ; is( undef, get_options( $mysync, qw( --lalala --noexist --version ) ), 'get_options: --lalala --noexist --version => undef' ) ; is( 1, $mysync->{ version }, 'get_options: --version => 1' ) ; is( undef, $mysync->{ noexist }, 'get_options: --noexist => undef' ) ; $mysync = { } ; is( 1, get_options( $mysync, qw( --delete2 ) ), 'get_options: --delete2 => 1' ) ; is( 1, $mysync->{ delete2 }, 'get_options: --delete2 => var delete2 = 1' ) ; $mysync = { } ; is( undef, get_options( $mysync, qw( --delete 2 ) ), 'get_options: --delete 2 => var undef' ) ; is( undef, $mysync->{ delete1 }, 'get_options: --delete 2 => var still undef ; good!' ) ; $mysync = { } ; is( undef, get_options( $mysync, "--delete 2" ), 'get_options: --delete 2 => undef' ) ; is( 1, get_options( $mysync, "--version" ), 'get_options: --version => 1' ) ; is( 1, get_options( $mysync, "--help" ), 'get_options: --help => 1' ) ; is( undef, get_options( $mysync, qw( --noexist --version ) ), 'get_options: --debug --noexist --version => undef' ) ; is( 1, get_options( $mysync, qw( --version ) ), 'get_options: --version => 1' ) ; is( undef, get_options( $mysync, qw( extra ) ), 'get_options: extra => undef' ) ; is( undef, get_options( $mysync, qw( extra1 --version extra2 ) ), 'get_options: extra1 --version extra2 => undef' ) ; $mysync = { } ; is( 2, get_options( $mysync, qw( --host1 HOST_01) ), 'get_options: --host1 HOST_01 => 1' ) ; is( 'HOST_01', $mysync->{ host1 }, 'get_options: --host1 HOST_01 => HOST_01' ) ; #myprint( Data::Dumper->Dump( [ $mysync ] ) ) ; note( 'Leaving tests_get_options()' ) ; return ; } sub get_options { my $mysync = shift @ARG ; my @arguments = @ARG ; #myprint( "1 mysync: ", Data::Dumper->Dump( [ $mysync ] ) ) ; my $ret ; if ( under_cgi_context( ) ) { # CGI context $ret = get_options_cgi( $mysync, @arguments ) ; }else{ # Command line context ; $ret = get_options_cmd( $mysync, @arguments ) ; } ; #myprint( "2 mysync: ", Data::Dumper->Dump( [ $mysync ] ) ) ; foreach my $key ( sort keys %{ $mysync } ) { if ( ! defined $mysync->{$key} ) { delete $mysync->{$key} ; next ; } if ( 'ARRAY' eq ref( $mysync->{$key} ) and 0 == scalar( @{ $mysync->{$key} } ) ) { delete $mysync->{$key} ; } } return $ret ; } sub tests_infos { note( 'Entering tests_infos()' ) ; note( "OSNAME=$OSNAME" ) ; note( "hostname=". hostname() ) ; note( "cwd=" . getcwd( ) ) ; note( "PROGRAM_NAME=$PROGRAM_NAME" ) ; my $stat = stat("$PROGRAM_NAME") ; my $perms = sprintf( "%04o\n", $stat->mode & oct($PERMISSION_FILTER) ) ; note( "permissions=$perms" ) ; note( "PROCESS_ID=$PROCESS_ID" ) ; note( "REAL_USER_ID=$REAL_USER_ID" ) ; note( "EFFECTIVE_USER_ID=$EFFECTIVE_USER_ID" ) ; note( "context: " . imapsync_context( $sync ) ) ; note( "memory_consumption: " . memory_consumption() . " bytes aka " . bytes_display_string_dec( memory_consumption() ) ) ; cpu_number note( "cpu_number: " . cpu_number() ) ; note( $sync->{rcs} ) ; note( 'Leaving tests_infos()' ) ; return ; } sub condition_to_leave_after_tests { my $mysync = shift ; if ( $mysync->{ testslive } or $mysync->{ testslive6 } ) { return 0 ; } if ( $mysync->{ tests } or $mysync->{ testsdebug } or $mysync->{ testsunit } ) { return 1 ; } } sub testunitsession { my $mysync = shift ; if ( ! $mysync ) { return ; } if ( ! $mysync->{ testsunit } ) { return ; } my @functions = @{ $mysync->{ testsunit } } ; if ( ! @functions ) { return ; } SKIP: { if ( ! @functions ) { skip 'No test in normal run' ; } testsunit( @functions ) ; done_testing( ) ; } return ; } sub tests_count_0s { note( 'Entering tests_count_zeros()' ) ; is( 0, count_0s( ), 'count_0s: no parameters => 0' ) ; is( 1, count_0s( 0 ), 'count_0s: 0 => 1' ) ; is( 0, count_0s( 1 ), 'count_0s: 1 => 0' ) ; is( 1, count_0s( 1, 0, 1 ), 'count_0s: 1, 0, 1 => 1' ) ; is( 2, count_0s( 1, 0, 1, 0 ), 'count_0s: 1, 0, 1, 0 => 2' ) ; note( 'Leaving tests_count_zeros()' ) ; return ; } sub count_0s { my @array = @ARG ; if ( ! @array ) { return 0 ; } my $nb_zeros = 0 ; map { $_ == 0 and $nb_zeros += 1 } @array ; return $nb_zeros ; } sub tests_report_failures { note( 'Entering tests_report_failures()' ) ; is( undef, report_failures( ), 'report_failures: no parameters => undef' ) ; is( "nb 1 - first\n", report_failures( ({'ok' => 0, name => 'first'}) ), 'report_failures: "first" failed => nb 1 - first' ) ; is( q{}, report_failures( ( {'ok' => 1, name => 'first'} ) ), 'report_failures: "first" success =>' ) ; is( "nb 2 - second\n", report_failures( ( {'ok' => 1, name => 'second'}, {'ok' => 0, name => 'second'} ) ), 'report_failures: "second" failed => nb 2 - second' ) ; is( "nb 1 - first\nnb 2 - second\n", report_failures( ( {'ok' => 0, name => 'first'}, {'ok' => 0, name => 'second'} ) ), 'report_failures: both failed => nb 1 - first nb 2 - second' ) ; note( 'Leaving tests_report_failures()' ) ; return ; } sub report_failures { my @details = @ARG ; if ( ! @details ) { return ; } my $counter = 1 ; my $report = q{} ; foreach my $details ( @details ) { if ( ! $details->{ 'ok' } ) { my $name = $details->{ 'name' } || 'NONAME' ; $report .= "nb $counter - $name\n" ; } $counter += 1 ; } return $report ; } sub tests_true { note( 'Entering tests_true()' ) ; is( 1, 1, 'true: 1 is 1' ) ; note( 'Leaving tests_true()' ) ; return ; } sub tests_testsunit { note( 'Entering tests_testunit()' ) ; is( undef, testsunit( ), 'testsunit: no parameters => undef' ) ; is( undef, testsunit( undef ), 'testsunit: an undef parameter => undef' ) ; is( undef, testsunit( q{} ), 'testsunit: an empty parameter => undef' ) ; is( undef, testsunit( 'idonotexist' ), 'testsunit: a do not exist function as parameter => undef' ) ; is( undef, testsunit( 'tests_true' ), 'testsunit: tests_true => undef' ) ; note( 'Leaving tests_testunit()' ) ; return ; } sub testsunit { my @functions = @ARG ; if ( ! @functions ) { # myprint( "testsunit warning: no argument given\n" ) ; return ; } foreach my $function ( @functions ) { if ( ! $function ) { myprint( "testsunit warning: argument is empty\n" ) ; next ; } if ( ! exists &$function ) { myprint( "testsunit warning: function $function does not exist\n" ) ; next ; } if ( ! defined &$function ) { myprint( "testsunit warning: function $function is not defined\n" ) ; next ; } my $function_ref = \&{ $function } ; &$function_ref() ; } return ; } sub testsdebug { # Now a little obsolete since there is # imapsync ... --testsunit "anyfunction" my $mysync = shift ; if ( ! $mysync->{ testsdebug } ) { return ; } SKIP: { if ( ! $mysync->{ testsdebug } ) { skip 'No test in normal run' ; } note( 'Entering testsdebug()' ) ; #ok( ( ( not -d 'W/tmp/tests' ) or rmtree( 'W/tmp/tests/' ) ), 'testsdebug: rmtree W/tmp/tests' ) ; #tests_check_binary_embed_all_dyn_libs( ) ; #tests_killpid_by_parent( ) ; #tests_killpid_by_brother( ) ; #tests_kill_zero( ) ; #tests_connect_socket( ) ; #tests_probe_imapssl( ) ; #tests_cpu_number( ) ; #tests_mailimapclient_connect( ) ; tests_loadavg( ) ; #tests_always_fail( ) ; note( 'Leaving testsdebug()' ) ; done_testing( ) ; } return ; } sub tests { my $mysync = shift ; if ( ! $mysync->{ tests } ) { return ; } SKIP: { skip 'No test in normal run' if ( ! $mysync->{ tests } ) ; note( 'Entering tests()' ) ; tests_folder_routines( ) ; tests_compare_lists( ) ; tests_regexmess( ) ; tests_skipmess( ) ; tests_regexflags( ); tests_ucsecond( ) ; tests_permanentflags(); tests_flags_filter( ) ; tests_separator_invert( ) ; tests_imap2_folder_name( ) ; tests_command_line_nopassword( ) ; tests_good_date( ) ; tests_max( ) ; tests_remove_not_num(); tests_memory_consumption( ) ; tests_is_a_release_number(); tests_imapsync_basename(); tests_list_keys_in_2_not_in_1(); tests_convert_sep_to_slash( ) ; tests_match_a_cache_file( ) ; tests_cache_map( ) ; tests_get_cache( ) ; tests_clean_cache( ) ; tests_clean_cache_2( ) ; tests_touch( ) ; tests_flagscase( ) ; tests_mkpath( ) ; tests_extract_header( ) ; tests_decompose_header( ) ; tests_epoch( ) ; tests_add_header( ) ; tests_cache_dir_fix( ) ; tests_cache_dir_fix_win( ) ; tests_filter_forbidden_characters( ) ; tests_cache_folder( ) ; tests_time_remaining( ) ; tests_decompose_regex( ) ; tests_backtick( ) ; tests_bytes_display_string_bin( ) ; tests_bytes_display_string_dec( ) ; tests_header_line_normalize( ) ; tests_fix_Inbox_INBOX_mapping( ) ; tests_max_line_length( ) ; tests_subject( ) ; tests_msgs_from_maxmin( ) ; tests_tmpdir_has_colon_bug( ) ; tests_sleep_max_messages( ) ; tests_sleep_max_bytes( ) ; tests_logfile( ) ; tests_setlogfile( ) ; tests_jux_utf8_old( ) ; tests_jux_utf8( ) ; tests_pipemess( ) ; tests_jux_utf8_list( ) ; tests_guess_prefix( ) ; tests_guess_separator( ) ; tests_format_for_imap_arg( ) ; tests_imapsync_id( ) ; tests_date_from_rcs( ) ; tests_quota_extract_storage_limit_in_bytes( ) ; tests_quota_extract_storage_current_in_bytes( ) ; tests_guess_special( ) ; tests_do_valid_directory( ) ; tests_delete1emptyfolders( ) ; tests_message_for_host2( ) ; tests_length_ref( ) ; tests_firstline( ) ; tests_diff_or_NA( ) ; tests_match_number( ) ; tests_all_defined( ) ; tests_special_from_folders_hash( ) ; tests_notmatch( ) ; tests_match( ) ; tests_get_options( ) ; tests_get_options_cgi_context( ) ; tests_rand32( ) ; tests_hashsynclocal( ) ; tests_hashsync( ) ; tests_output( ) ; tests_output_reset_with( ) ; tests_output_start( ) ; tests_check_last_release( ) ; tests_loadavg( ) ; tests_cpu_number( ) ; tests_load_and_delay( ) ; #tests_imapsping( ) ; #tests_tcpping( ) ; tests_sslcheck( ) ; tests_not_long_imapsync_version_public( ) ; tests_reconnect_if_needed( ) ; tests_reconnect_12_if_needed( ) ; tests_sleep_if_needed( ) ; tests_string_to_file( ) ; tests_file_to_string( ) ; tests_under_cgi_context( ) ; tests_umask( ) ; tests_umask_str( ) ; tests_set_umask( ) ; tests_createhashfileifneeded( ) ; tests_slash_to_underscore( ) ; tests_testsunit( ) ; tests_count_0s( ) ; tests_report_failures( ) ; tests_min( ) ; #tests_connect_socket( ) ; #tests_resolvrev( ) ; tests_usage( ) ; tests_version_from_rcs( ) ; tests_backslash_caret( ) ; #tests_mailimapclient_connect_bug( ) ; # it fails with Mail-IMAPClient <= 3.39 tests_write_pidfile( ) ; tests_remove_pidfile_not_running( ) ; tests_match_a_pid_number( ) ; tests_prefix_seperator_invertion( ) ; tests_is_integer( ) ; tests_integer_or_1( ) ; tests_is_number( ) ; tests_sig_install( ) ; tests_template( ) ; tests_split_around_equal( ) ; tests_toggle_sleep( ) ; tests_labels( ) ; tests_synclabels( ) ; tests_uidexpunge_or_expunge( ) ; tests_appendlimit_from_capability( ) ; tests_maxsize_setting( ) ; tests_mock_capability( ) ; tests_appendlimit( ) ; tests_capability_of( ) ; tests_search_in_array( ) ; tests_operators_and_exclam_precedence( ) ; tests_teelaunch( ) ; tests_logfileprepa( ) ; tests_useheader_suggestion( ) ; tests_nb_messages_in_2_not_in_1( ) ; tests_labels_add_subfolder2( ) ; tests_labels_remove_subfolder1( ) ; tests_resynclabels( ) ; tests_labels_remove_special( ) ; tests_uniq( ) ; tests_remove_from_requested_folders( ) ; tests_errors_log( ) ; tests_add_subfolder1_to_folderrec( ) ; tests_sanitize_subfolder( ) ; tests_remove_edging_blanks( ) ; tests_sanitize( ) ; tests_remove_last_char_if_is( ) ; tests_check_binary_embed_all_dyn_libs( ) ; tests_nthline( ) ; tests_secondline( ) ; tests_tail( ) ; tests_truncmess( ) ; tests_eta( ) ; tests_timesince( ) ; tests_timenext( ) ; tests_foldersize( ) ; tests_imapsync_context( ) ; tests_abort( ) ; tests_probe_imapssl( ) ; tests_mailimapclient_connect( ) ; tests_checknoabletosearch( ) ; tests_errorsdump( ) ; tests_errorsanalyse( ) ; tests_most_common_error( ) ; tests_errorclassify( ) ; tests_error_type( ) ; tests_sanitize_host( ) ; tests_hmac_sha1_hex( ) ; tests_total_bytes_max_reached( ) ; tests_header_construct( ) ; tests_remove_doublequotes_if_any( ) ; tests_login_imap( ) ; tests_login_imap_oauth( ) ; tests_skipmess_neg( ) ; tests_localtimez( ) ; tests_file_to_array( ) ; tests_cpu_time( ) ; tests_cpu_percent( ) ; tests_cpu_percent_global( ) ; tests_flags_for_host2( ) ; tests_under_docker_context( ) ; tests_exit_value( ) ; tests_comment_of_error_type( ) ; tests_debugcontent( ) ; tests_compress_ssl( ) ; tests_compress( ) ; tests_get_options_extra( ) ; tests_get_options_from_string( ) ; tests_infos( ) ; #tests_resolv( ) ; # Those three are for later use, when webserver will be inside imapsync # or will be deleted them if I abandon the project. #tests_killpid_by_parent( ) ; #tests_killpid_by_brother( ) ; #tests_kill_zero( ) ; #tests_always_fail( ) ; done_testing( 1860 ) ; note( 'Leaving tests()' ) ; } return ; } sub tests_template { note( 'Entering tests_template()' ) ; is( undef, template( ), 'template: no args => undef' ) ; my $mysync = { } ; is( undef, template( $mysync ), 'template: undef => undef' ) ; is_deeply( {}, {}, 'template: a hash is a hash' ) ; is_deeply( [], [], 'template: an array is an array' ) ; note( 'Leaving tests_template()' ) ; return ; } sub template { my $mysync = shift @ARG ; return ; } ================================================ FILE: data/Dockerfiles/dovecot/imapsync_runner.pl ================================================ #!/usr/bin/perl use DBI; use LockFile::Simple qw(lock trylock unlock); use Proc::ProcessTable; use Data::Dumper qw(Dumper); use IPC::Run 'run'; use File::Temp; use Try::Tiny; use sigtrap 'handler' => \&sig_handler, qw(INT TERM KILL QUIT); sub trim { my $s = shift; $s =~ s/^\s+|\s+$//g; return $s }; my $t = Proc::ProcessTable->new; my $imapsync_running = grep { $_->{cmndline} =~ /imapsync\s/i } @{$t->table}; if ($imapsync_running ge 1) { print "imapsync is active, exiting..."; exit; } sub qqw($) { my @params = (); my @values = split(/(?=--)/, $_[0]); foreach my $val (@values) { my @tmpparam = split(/ /, $val, 2); foreach my $tmpval (@tmpparam) { if ($tmpval ne '') { push @params, $tmpval; } } } foreach my $val (@params) { $val=trim($val); } return @params; } $run_dir="/tmp"; $dsn = 'DBI:mysql:database=' . $ENV{'DBNAME'} . ';mysql_socket=/var/run/mysqld/mysqld.sock'; $lock_file = $run_dir . "/imapsync_busy"; $lockmgr = LockFile::Simple->make(-autoclean => 1, -max => 1); $lockmgr->lock($lock_file) || die "can't lock ${lock_file}"; $dbh = DBI->connect($dsn, $ENV{'DBUSER'}, $ENV{'DBPASS'}, { mysql_auto_reconnect => 1, mysql_enable_utf8mb4 => 1 }); $dbh->do("UPDATE imapsync SET is_running = 0"); sub sig_handler { # Send die to force exception in "run" die "sig_handler received signal, preparing to exit...\n"; }; open my $file, '<', "/etc/sogo/sieve.creds"; my $creds = <$file>; close $file; my ($master_user, $master_pass) = split /:/, $creds; my $sth = $dbh->prepare("SELECT id, user1, user2, host1, authmech1, password1, exclude, port1, enc1, delete2duplicates, maxage, subfolder2, delete1, delete2, automap, skipcrossduplicates, maxbytespersecond, custom_params, subscribeall, timeout1, timeout2, dry FROM imapsync WHERE active = 1 AND is_running = 0 AND ( UNIX_TIMESTAMP(NOW()) - UNIX_TIMESTAMP(last_run) > mins_interval * 60 OR last_run IS NULL) ORDER BY last_run"); $sth->execute(); my $row; while ($row = $sth->fetchrow_arrayref()) { $id = @$row[0]; $user1 = @$row[1]; $user2 = @$row[2]; $host1 = @$row[3]; $authmech1 = @$row[4]; $password1 = @$row[5]; $exclude = @$row[6]; $port1 = @$row[7]; $enc1 = @$row[8]; $delete2duplicates = @$row[9]; $maxage = @$row[10]; $subfolder2 = @$row[11]; $delete1 = @$row[12]; $delete2 = @$row[13]; $automap = @$row[14]; $skipcrossduplicates = @$row[15]; $maxbytespersecond = @$row[16]; $custom_params = @$row[17]; $subscribeall = @$row[18]; $timeout1 = @$row[19]; $timeout2 = @$row[20]; $dry = @$row[21]; if ($enc1 eq "TLS") { $enc1 = "--tls1"; } elsif ($enc1 eq "SSL") { $enc1 = "--ssl1"; } else { undef $enc1; } my $template = $run_dir . '/imapsync.XXXXXXX'; my $passfile1 = File::Temp->new(TEMPLATE => $template); my $passfile2 = File::Temp->new(TEMPLATE => $template); binmode( $passfile1, ":utf8" ); print $passfile1 "$password1\n"; print $passfile2 trim($master_pass) . "\n"; my @custom_params_a = qqw($custom_params); my $custom_params_ref = \@custom_params_a; my $generated_cmds = [ "/usr/local/bin/imapsync", "--tmpdir", "/tmp", "--nofoldersizes", "--addheader", ($timeout1 le "0" ? () : ('--timeout1', $timeout1)), ($timeout2 le "0" ? () : ('--timeout2', $timeout2)), ($exclude eq "" ? () : ("--exclude", $exclude)), ($subfolder2 eq "" ? () : ('--subfolder2', $subfolder2)), ($maxage eq "0" ? () : ('--maxage', $maxage)), ($maxbytespersecond eq "0" ? () : ('--maxbytespersecond', $maxbytespersecond)), ($delete2duplicates ne "1" ? () : ('--delete2duplicates')), ($subscribeall ne "1" ? () : ('--subscribeall')), ($delete1 ne "1" ? () : ('--delete')), ($delete2 ne "1" ? () : ('--delete2')), ($automap ne "1" ? () : ('--automap')), ($skipcrossduplicates ne "1" ? () : ('--skipcrossduplicates')), (!defined($enc1) ? () : ($enc1)), "--host1", $host1, "--user1", $user1, "--passfile1", $passfile1->filename, "--port1", $port1, "--host2", "localhost", "--user2", $user2 . '*' . trim($master_user), "--passfile2", $passfile2->filename, ($dry eq "1" ? ('--dry') : ()), '--no-modulesversion', '--noreleasecheck']; try { $is_running = $dbh->prepare("UPDATE imapsync SET is_running = 1, success = NULL, exit_status = NULL WHERE id = ?"); $is_running->bind_param( 1, ${id} ); $is_running->execute(); run [@$generated_cmds, @$custom_params_ref], '&>', \my $stdout; # check exit code and status ($exit_code, $exit_status) = ($stdout =~ m/Exiting\swith\sreturn\svalue\s(\d+)\s\(([^:)]+)/); $success = 0; if (defined $exit_code && $exit_code == 0) { $success = 1; } $update = $dbh->prepare("UPDATE imapsync SET returned_text = ?, success = ?, exit_status = ? WHERE id = ?"); $update->bind_param( 1, ${stdout} ); $update->bind_param( 2, ${success} ); $update->bind_param( 3, ${exit_status} ); $update->bind_param( 4, ${id} ); $update->execute(); } catch { $update = $dbh->prepare("UPDATE imapsync SET returned_text = 'Could not start or finish imapsync', success = 0 WHERE id = ?"); $update->bind_param( 1, ${id} ); $update->execute(); } finally { $update = $dbh->prepare("UPDATE imapsync SET last_run = NOW(), is_running = 0 WHERE id = ?"); $update->bind_param( 1, ${id} ); $update->execute(); }; } $sth->finish(); $dbh->disconnect(); $lockmgr->unlock($lock_file); ================================================ FILE: data/Dockerfiles/dovecot/maildir_gc.sh ================================================ #!/bin/bash [ -d /var/vmail/_garbage/ ] && /usr/bin/find /var/vmail/_garbage/ -mindepth 1 -maxdepth 1 -type d -cmin +${MAILDIR_GC_TIME} -exec rm -r {} \; ================================================ FILE: data/Dockerfiles/dovecot/optimize-fts.sh ================================================ #!/bin/bash if [[ "${SKIP_FTS}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then exit 0 else doveadm fts optimize -A fi ================================================ FILE: data/Dockerfiles/dovecot/quarantine_notify.py ================================================ #!/usr/bin/python3 import smtplib import os import sys import MySQLdb from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from email.utils import COMMASPACE, formatdate import jinja2 from jinja2 import TemplateError from jinja2.sandbox import SandboxedEnvironment import json import redis import time import html2text import socket pid = str(os.getpid()) pidfile = "/tmp/quarantine_notify.pid" if os.path.isfile(pidfile): print("%s already exists, exiting" % (pidfile)) sys.exit() pid = str(os.getpid()) f = open(pidfile, 'w') f.write(pid) f.close() try: while True: try: r = redis.StrictRedis(host='redis', decode_responses=True, port=6379, db=0, password=os.environ['REDISPASS']) r.ping() except Exception as ex: print('%s - trying again...' % (ex)) time.sleep(3) else: break time_now = int(time.time()) mailcow_hostname = os.environ.get('MAILCOW_HOSTNAME') max_score = float(r.get('Q_MAX_SCORE') or "9999.0") if max_score == "": max_score = 9999.0 def query_mysql(query, headers = True, update = False): while True: try: cnx = MySQLdb.connect(user=os.environ.get('DBUSER'), password=os.environ.get('DBPASS'), database=os.environ.get('DBNAME'), charset="utf8mb4", collation="utf8mb4_general_ci") except Exception as ex: print('%s - trying again...' % (ex)) time.sleep(3) else: break cur = cnx.cursor() cur.execute(query) if not update: result = [] columns = tuple( [d[0] for d in cur.description] ) for row in cur: if headers: result.append(dict(list(zip(columns, row)))) else: result.append(row) cur.close() cnx.close() return result else: cnx.commit() cur.close() cnx.close() def notify_rcpt(rcpt, msg_count, quarantine_acl, category): if category == "add_header": category = "add header" meta_query = query_mysql('SELECT `qhash`, id, subject, score, sender, created, action FROM quarantine WHERE notified = 0 AND rcpt = "%s" AND score < %f AND (action = "%s" OR "all" = "%s")' % (rcpt, max_score, category, category)) print("%s: %d of %d messages qualify for notification" % (rcpt, len(meta_query), msg_count)) if len(meta_query) == 0: return msg_count = len(meta_query) env = SandboxedEnvironment() if r.get('Q_HTML'): try: template = env.from_string(r.get('Q_HTML')) except Exception: print("Error: Cannot parse quarantine template, falling back to default template.") with open('/templates/quarantine.tpl') as file_: template = env.from_string(file_.read()) else: with open('/templates/quarantine.tpl') as file_: template = env.from_string(file_.read()) try: html = template.render(meta=meta_query, username=rcpt, counter=msg_count, hostname=mailcow_hostname, quarantine_acl=quarantine_acl) except (jinja2.exceptions.SecurityError, TemplateError) as ex: print(f"SecurityError or TemplateError in template rendering: {ex}") return text = html2text.html2text(html) count = 0 while count < 15: count += 1 try: server = smtplib.SMTP('postfix', 590, 'quarantine') server.ehlo() msg = MIMEMultipart('alternative') msg_from = r.get('Q_SENDER') or "quarantine@localhost" # Remove non-ascii chars from field msg['From'] = ''.join([i if ord(i) < 128 else '' for i in msg_from]) msg['Subject'] = r.get('Q_SUBJ') or "Spam Quarantine Notification" msg['Date'] = formatdate(localtime = True) text_part = MIMEText(text, 'plain', 'utf-8') html_part = MIMEText(html, 'html', 'utf-8') msg.attach(text_part) msg.attach(html_part) msg['To'] = str(rcpt) bcc = r.get('Q_BCC') or "" redirect = r.get('Q_REDIRECT') or "" text = msg.as_string() if bcc == '': if redirect == '': server.sendmail(msg['From'], str(rcpt), text) else: server.sendmail(msg['From'], str(redirect), text) else: if redirect == '': server.sendmail(msg['From'], [str(rcpt)] + [str(bcc)], text) else: server.sendmail(msg['From'], [str(redirect)] + [str(bcc)], text) server.quit() for res in meta_query: query_mysql('UPDATE quarantine SET notified = 1 WHERE id = "%d"' % (res['id']), update = True) r.hset('Q_LAST_NOTIFIED', record['rcpt'], time_now) break except Exception as ex: server.quit() print('%s' % (ex)) time.sleep(3) records = query_mysql('SELECT IFNULL(user_acl.quarantine, 0) AS quarantine_acl, count(id) AS counter, rcpt FROM quarantine LEFT OUTER JOIN user_acl ON user_acl.username = rcpt WHERE notified = 0 AND score < %f AND rcpt in (SELECT username FROM mailbox) GROUP BY rcpt' % (max_score)) for record in records: attrs = '' attrs_json = '' time_trans = { "hourly": 3600, "daily": 86400, "weekly": 604800 } try: last_notification = int(r.hget('Q_LAST_NOTIFIED', record['rcpt'])) if last_notification > time_now: print('Last notification is > time now, assuming never') last_notification = 0 except Exception as ex: print('Could not determine last notification for %s, assuming never' % (record['rcpt'])) last_notification = 0 attrs_json = query_mysql('SELECT attributes FROM mailbox WHERE username = "%s"' % (record['rcpt'])) attrs = attrs_json[0]['attributes'] if isinstance(attrs, str): # if attr is str then just load it attrs = json.loads(attrs) else: # if it's bytes then decode and load it attrs = json.loads(attrs.decode('utf-8')) if attrs['quarantine_notification'] not in ('hourly', 'daily', 'weekly'): continue if last_notification == 0 or (last_notification + time_trans[attrs['quarantine_notification']]) <= time_now: print("Notifying %s: Considering %d new items in quarantine (policy: %s)" % (record['rcpt'], record['counter'], attrs['quarantine_notification'])) notify_rcpt(record['rcpt'], record['counter'], record['quarantine_acl'], attrs['quarantine_category']) finally: os.unlink(pidfile) ================================================ FILE: data/Dockerfiles/dovecot/quota_notify.py ================================================ #!/usr/bin/python3 import smtplib import os from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from email.utils import COMMASPACE, formatdate import jinja2 from jinja2.sandbox import SandboxedEnvironment import redis import time import json import sys import html2text from subprocess import Popen, PIPE, STDOUT if len(sys.argv) > 2: percent = int(sys.argv[1]) username = str(sys.argv[2]) else: print("Args missing") sys.exit(1) while True: try: r = redis.StrictRedis(host='redis', decode_responses=True, port=6379, db=0, username='quota_notify', password='') r.ping() except Exception as ex: print('%s - trying again...' % (ex)) time.sleep(3) else: break if r.get('QW_HTML'): try: env = SandboxedEnvironment() template = env.from_string(r.get('QW_HTML')) except Exception: print("Error: Cannot parse quota template, falling back to default template.") with open('/templates/quota.tpl') as file_: env = SandboxedEnvironment() template = env.from_string(file_.read()) else: with open('/templates/quota.tpl') as file_: env = SandboxedEnvironment() template = env.from_string(file_.read()) try: html = template.render(username=username, percent=percent) except (jinja2.exceptions.SecurityError, jinja2.TemplateError) as ex: print(f"SecurityError or TemplateError in template rendering: {ex}") sys.exit(1) text = html2text.html2text(html) try: msg = MIMEMultipart('alternative') msg['From'] = r.get('QW_SENDER') or "quota-warning@localhost" msg['Subject'] = r.get('QW_SUBJ') or "Quota warning" msg['Date'] = formatdate(localtime = True) text_part = MIMEText(text, 'plain', 'utf-8') html_part = MIMEText(html, 'html', 'utf-8') msg.attach(text_part) msg.attach(html_part) msg['To'] = username p = Popen(['/usr/libexec/dovecot/dovecot-lda', '-d', username, '-o', '"plugin/quota=maildir:User quota:noenforcing"'], stdout=PIPE, stdin=PIPE, stderr=STDOUT) p.communicate(input=bytes(msg.as_string(), 'utf-8')) domain = username.split("@")[-1] if domain and r.hget('QW_BCC', domain): bcc_data = json.loads(r.hget('QW_BCC', domain)) bcc_rcpts = bcc_data['bcc_rcpts'] if bcc_data['active'] == 1: for rcpt in bcc_rcpts: msg = MIMEMultipart('alternative') msg['From'] = username subject = r.get('QW_SUBJ') or "Quota warning" msg['Subject'] = subject + ' (' + username + ')' msg['Date'] = formatdate(localtime = True) text_part = MIMEText(text, 'plain', 'utf-8') html_part = MIMEText(html, 'html', 'utf-8') msg.attach(text_part) msg.attach(html_part) msg['To'] = rcpt server = smtplib.SMTP('postfix', 588, 'quotanotification') server.ehlo() server.sendmail(msg['From'], str(rcpt), msg.as_string()) server.quit() except Exception as ex: print('Failed to send quota notification: %s' % (ex)) sys.exit(1) try: sys.stdout.close() except: pass try: sys.stderr.close() except: pass ================================================ FILE: data/Dockerfiles/dovecot/repl_health.sh ================================================ #!/bin/bash source /source_env.sh # Do not attempt to write to slave if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then REDIS_CMDLINE="redis-cli -h ${REDIS_SLAVEOF_IP} -p ${REDIS_SLAVEOF_PORT} -a ${REDISPASS} --no-auth-warning" else REDIS_CMDLINE="redis-cli -h redis -p 6379 -a ${REDISPASS} --no-auth-warning" fi # Is replication active? # grep on file is less expensive than doveconf if [ -n ${MAILCOW_REPLICA_IP} ]; then ${REDIS_CMDLINE} SET DOVECOT_REPL_HEALTH 1 > /dev/null exit fi FAILED_SYNCS=$(doveadm replicator status | grep "Waiting 'failed' requests" | grep -oE '[0-9]+') # Set amount of failed jobs as DOVECOT_REPL_HEALTH # 1 failed job for mailcow.local is expected and healthy if [[ "${FAILED_SYNCS}" != 0 ]] && [[ "${FAILED_SYNCS}" != 1 ]]; then printf "Dovecot replicator has %d failed jobs\n" "${FAILED_SYNCS}" ${REDIS_CMDLINE} SET DOVECOT_REPL_HEALTH "${FAILED_SYNCS}" > /dev/null else ${REDIS_CMDLINE} SET DOVECOT_REPL_HEALTH 1 > /dev/null fi ================================================ FILE: data/Dockerfiles/dovecot/report-ham.sieve ================================================ require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "variables"]; if environment :matches "imap.mailbox" "*" { set "mailbox" "${1}"; } if string "${mailbox}" "Trash" { stop; } pipe :copy "rspamd-pipe-ham"; ================================================ FILE: data/Dockerfiles/dovecot/report-spam.sieve ================================================ require ["vnd.dovecot.pipe", "copy"]; pipe :copy "rspamd-pipe-spam"; ================================================ FILE: data/Dockerfiles/dovecot/rspamd-pipe-ham ================================================ #!/bin/bash FILE=/tmp/mail$$ cat > $FILE trap "/bin/rm -f $FILE" 0 1 2 3 13 15 cat ${FILE} | /usr/bin/curl -H "Flag: 11" -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd.${COMPOSE_PROJECT_NAME}_mailcow-network/fuzzydel cat ${FILE} | /usr/bin/curl -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd.${COMPOSE_PROJECT_NAME}_mailcow-network/learnham cat ${FILE} | /usr/bin/curl -H "Flag: 13" -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd.${COMPOSE_PROJECT_NAME}_mailcow-network/fuzzyadd exit 0 ================================================ FILE: data/Dockerfiles/dovecot/rspamd-pipe-spam ================================================ #!/bin/bash FILE=/tmp/mail$$ cat > $FILE trap "/bin/rm -f $FILE" 0 1 2 3 13 15 cat ${FILE} | /usr/bin/curl -H "Flag: 13" -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd.${COMPOSE_PROJECT_NAME}_mailcow-network/fuzzydel cat ${FILE} | /usr/bin/curl -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd.${COMPOSE_PROJECT_NAME}_mailcow-network/learnspam cat ${FILE} | /usr/bin/curl -H "Flag: 11" -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd.${COMPOSE_PROJECT_NAME}_mailcow-network/fuzzyadd exit 0 ================================================ FILE: data/Dockerfiles/dovecot/sa-rules.sh ================================================ #!/bin/bash # Create temp directories [[ ! -d /tmp/sa-rules-heinlein ]] && mkdir -p /tmp/sa-rules-heinlein # Hash current SA rules if [[ ! -f /etc/rspamd/custom/sa-rules ]]; then HASH_SA_RULES=0 else HASH_SA_RULES=$(cat /etc/rspamd/custom/sa-rules | md5sum | cut -d' ' -f1) fi # Deploy if curl --connect-timeout 15 --retry 5 --max-time 30 https://www.spamassassin.heinlein-support.de/$(dig txt 1.4.3.spamassassin.heinlein-support.de +short | tr -d '"' | tr -dc '0-9').tar.gz --output /tmp/sa-rules-heinlein.tar.gz; then if gzip -t /tmp/sa-rules-heinlein.tar.gz; then tar xfvz /tmp/sa-rules-heinlein.tar.gz -C /tmp/sa-rules-heinlein cat /tmp/sa-rules-heinlein/*cf > /etc/rspamd/custom/sa-rules fi else echo "Failed to download SA rules. Exiting." exit 0 # Must be 0 otherwise dovecot would not start at all fi sed -i -e 's/\([^\\]\)\$\([^\/]\)/\1\\$\2/g' /etc/rspamd/custom/sa-rules if [[ "$(cat /etc/rspamd/custom/sa-rules | md5sum | cut -d' ' -f1)" != "${HASH_SA_RULES}" ]]; then CONTAINER_NAME=rspamd-mailcow CONTAINER_ID=$(curl --silent --insecure https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/json | \ jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], project: .Config.Labels[\"com.docker.compose.project\"], id: .Id}" | \ jq -rc "select( .name | tostring | contains(\"${CONTAINER_NAME}\")) | select( .project | tostring | contains(\"${COMPOSE_PROJECT_NAME,,}\")) | .id") if [[ ! -z ${CONTAINER_ID} ]]; then curl --silent --insecure -XPOST --connect-timeout 15 --max-time 120 https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/${CONTAINER_ID}/restart fi fi # Cleanup rm -rf /tmp/sa-rules-heinlein /tmp/sa-rules-heinlein.tar.gz ================================================ FILE: data/Dockerfiles/dovecot/stop-supervisor.sh ================================================ #!/bin/bash printf "READY\n"; while read line; do echo "Processing Event: $line" >&2; kill -3 $(cat "/var/run/supervisord.pid") done < /dev/stdin ================================================ FILE: data/Dockerfiles/dovecot/supervisord.conf ================================================ [supervisord] nodaemon=true user=root pidfile=/var/run/supervisord.pid [program:syslog-ng] command=/usr/sbin/syslog-ng --foreground --no-caps stdout_logfile=/dev/stdout stdout_logfile_maxbytes=0 stderr_logfile=/dev/stderr stderr_logfile_maxbytes=0 autostart=true [program:dovecot] command=/usr/sbin/dovecot -F stdout_logfile=/dev/stdout stdout_logfile_maxbytes=0 stderr_logfile=/dev/stderr stderr_logfile_maxbytes=0 autorestart=true [eventlistener:processes] command=/usr/local/sbin/stop-supervisor.sh events=PROCESS_STATE_STOPPED, PROCESS_STATE_EXITED, PROCESS_STATE_FATAL ================================================ FILE: data/Dockerfiles/dovecot/syslog-ng-redis_slave.conf ================================================ @version: 4.5 @include "scl.conf" options { chain_hostnames(off); flush_lines(0); use_dns(no); use_fqdn(no); owner("root"); group("adm"); perm(0640); stats(freq(0)); keep_timestamp(no); bad_hostname("^gconfd$"); }; source s_dgram { unix-dgram("/dev/log"); internal(); }; destination d_stdout { pipe("/dev/stdout"); }; destination d_redis_ui_log { redis( host("`REDIS_SLAVEOF_IP`") persist-name("redis1") port(`REDIS_SLAVEOF_PORT`) auth("`REDISPASS`") command("LPUSH" "DOVECOT_MAILLOG" "$(format-json time=\"$S_UNIXTIME\" priority=\"$PRIORITY\" program=\"$PROGRAM\" message=\"$MESSAGE\")\n") ); }; destination d_redis_f2b_channel { redis( host("`REDIS_SLAVEOF_IP`") persist-name("redis2") port(`REDIS_SLAVEOF_PORT`) auth("`REDISPASS`") command("PUBLISH" "F2B_CHANNEL" "$(sanitize $MESSAGE)") ); }; filter f_mail { facility(mail); }; filter f_replica { not match("User has no mail_replica in userdb" value("MESSAGE")); not match("Error: sync: Unknown user in remote" value("MESSAGE")); }; filter f_dovecot_auth_try { not match("- trying the next passdb" value("MESSAGE")) and not match("- trying the next userdb" value("MESSAGE")); }; log { source(s_dgram); filter(f_dovecot_auth_try); filter(f_replica); destination(d_stdout); filter(f_mail); destination(d_redis_ui_log); destination(d_redis_f2b_channel); }; ================================================ FILE: data/Dockerfiles/dovecot/syslog-ng.conf ================================================ @version: 4.5 @include "scl.conf" options { chain_hostnames(off); flush_lines(0); use_dns(no); use_fqdn(no); owner("root"); group("adm"); perm(0640); stats(freq(0)); keep_timestamp(no); bad_hostname("^gconfd$"); }; source s_dgram { unix-dgram("/dev/log"); internal(); }; destination d_stdout { pipe("/dev/stdout"); }; destination d_redis_ui_log { redis( host("redis-mailcow") persist-name("redis1") port(6379) auth("`REDISPASS`") command("LPUSH" "DOVECOT_MAILLOG" "$(format-json time=\"$S_UNIXTIME\" priority=\"$PRIORITY\" program=\"$PROGRAM\" message=\"$MESSAGE\")\n") ); }; destination d_redis_f2b_channel { redis( host("redis-mailcow") persist-name("redis2") port(6379) auth("`REDISPASS`") command("PUBLISH" "F2B_CHANNEL" "$(sanitize $MESSAGE)") ); }; filter f_mail { facility(mail); }; filter f_replica { not match("User has no mail_replica in userdb" value("MESSAGE")); not match("Error: sync: Unknown user in remote" value("MESSAGE")); }; filter f_dovecot_auth_try { not match("- trying the next passdb" value("MESSAGE")) and not match("- trying the next userdb" value("MESSAGE")); }; log { source(s_dgram); filter(f_dovecot_auth_try); filter(f_replica); destination(d_stdout); filter(f_mail); destination(d_redis_ui_log); destination(d_redis_f2b_channel); }; ================================================ FILE: data/Dockerfiles/dovecot/trim_logs.sh ================================================ #!/bin/bash catch_non_zero() { CMD=${1} ${CMD} > /dev/null EC=$? if [ ${EC} -ne 0 ]; then echo "Command ${CMD} failed to execute, exit code was ${EC}" fi } source /source_env.sh # Do not attempt to write to slave if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then REDIS_CMDLINE="redis-cli -h ${REDIS_SLAVEOF_IP} -p ${REDIS_SLAVEOF_PORT} -a ${REDISPASS} --no-auth-warning" else REDIS_CMDLINE="redis-cli -h redis -p 6379 -a ${REDISPASS} --no-auth-warning" fi catch_non_zero "${REDIS_CMDLINE} LTRIM ACME_LOG 0 ${LOG_LINES}" catch_non_zero "${REDIS_CMDLINE} LTRIM POSTFIX_MAILLOG 0 ${LOG_LINES}" catch_non_zero "${REDIS_CMDLINE} LTRIM DOVECOT_MAILLOG 0 ${LOG_LINES}" catch_non_zero "${REDIS_CMDLINE} LTRIM SOGO_LOG 0 ${LOG_LINES}" catch_non_zero "${REDIS_CMDLINE} LTRIM NETFILTER_LOG 0 ${LOG_LINES}" catch_non_zero "${REDIS_CMDLINE} LTRIM AUTODISCOVER_LOG 0 ${LOG_LINES}" catch_non_zero "${REDIS_CMDLINE} LTRIM API_LOG 0 ${LOG_LINES}" catch_non_zero "${REDIS_CMDLINE} LTRIM RL_LOG 0 ${LOG_LINES}" catch_non_zero "${REDIS_CMDLINE} LTRIM WATCHDOG_LOG 0 ${LOG_LINES}" catch_non_zero "${REDIS_CMDLINE} LTRIM CRON_LOG 0 ${LOG_LINES}" ================================================ FILE: data/Dockerfiles/netfilter/Dockerfile ================================================ FROM alpine:3.23 LABEL maintainer = "The Infrastructure Company GmbH " WORKDIR /app ARG PIP_BREAK_SYSTEM_PACKAGES=1 ENV XTABLES_LIBDIR /usr/lib/xtables ENV PYTHON_IPTABLES_XTABLES_VERSION 12 ENV IPTABLES_LIBDIR /usr/lib RUN apk add --virtual .build-deps \ gcc \ python3-dev \ libffi-dev \ openssl-dev \ && apk add -U python3 \ iptables \ iptables-dev \ ip6tables \ xtables-addons \ nftables \ tzdata \ py3-pip \ py3-nftables \ musl-dev \ && pip3 install --ignore-installed --upgrade pip \ jsonschema \ python-iptables \ redis \ ipaddress \ dnspython \ && apk del .build-deps # && pip3 install --upgrade pip python-iptables==0.13.0 redis ipaddress dnspython \ COPY modules /app/modules COPY main.py /app/ COPY ./docker-entrypoint.sh /app/ RUN chmod +x /app/docker-entrypoint.sh CMD ["/bin/sh", "-c", "/app/docker-entrypoint.sh"] ================================================ FILE: data/Dockerfiles/netfilter/docker-entrypoint.sh ================================================ #!/bin/sh backend=nftables nft list table ip filter &>/dev/null nftables_found=$? iptables -L -n &>/dev/null iptables_found=$? if [ $nftables_found -lt $iptables_found ]; then backend=nftables fi if [ $nftables_found -gt $iptables_found ]; then backend=iptables fi if [ $nftables_found -eq 0 ] && [ $nftables_found -eq $iptables_found ]; then nftables_lines=$(nft list ruleset | wc -l) iptables_lines=$(iptables-save | wc -l) if [ $nftables_lines -gt $iptables_lines ]; then backend=nftables else backend=iptables fi fi exec python -u /app/main.py $backend ================================================ FILE: data/Dockerfiles/netfilter/main.py ================================================ #!/usr/bin/env python3 DEBUG = False import re import os import sys import time import atexit import signal import ipaddress from collections import Counter from random import randint from threading import Thread from threading import Lock import redis import json import dns.resolver import dns.exception import uuid from modules.Logger import Logger from modules.IPTables import IPTables from modules.NFTables import NFTables def logdebug(msg): if DEBUG: logger.logInfo("DEBUG: %s" % msg) # Globals WHITELIST = [] BLACKLIST = [] bans = {} quit_now = False exit_code = 0 lock = Lock() chain_name = "MAILCOW" r = None pubsub = None clear_before_quit = False def refreshF2boptions(): global f2boptions global quit_now global exit_code f2boptions = {} if not r.get('F2B_OPTIONS'): f2boptions['ban_time'] = r.get('F2B_BAN_TIME') f2boptions['max_ban_time'] = r.get('F2B_MAX_BAN_TIME') f2boptions['ban_time_increment'] = r.get('F2B_BAN_TIME_INCREMENT') f2boptions['max_attempts'] = r.get('F2B_MAX_ATTEMPTS') f2boptions['retry_window'] = r.get('F2B_RETRY_WINDOW') f2boptions['netban_ipv4'] = r.get('F2B_NETBAN_IPV4') f2boptions['netban_ipv6'] = r.get('F2B_NETBAN_IPV6') else: try: f2boptions = json.loads(r.get('F2B_OPTIONS')) except ValueError as e: logger.logCrit( 'Error loading F2B options: F2B_OPTIONS is not json. Exception: %s' % e) quit_now = True exit_code = 2 verifyF2boptions(f2boptions) r.set('F2B_OPTIONS', json.dumps(f2boptions, ensure_ascii=False)) def verifyF2boptions(f2boptions): verifyF2boption(f2boptions, 'ban_time', 1800) verifyF2boption(f2boptions, 'max_ban_time', 10000) verifyF2boption(f2boptions, 'ban_time_increment', True) verifyF2boption(f2boptions, 'max_attempts', 10) verifyF2boption(f2boptions, 'retry_window', 600) verifyF2boption(f2boptions, 'netban_ipv4', 32) verifyF2boption(f2boptions, 'netban_ipv6', 128) verifyF2boption(f2boptions, 'banlist_id', str(uuid.uuid4())) verifyF2boption(f2boptions, 'manage_external', 0) def verifyF2boption(f2boptions, f2boption, f2bdefault): f2boptions[f2boption] = f2boptions[f2boption] if f2boption in f2boptions and f2boptions[f2boption] is not None else f2bdefault def refreshF2bregex(): global f2bregex global quit_now global exit_code if not r.get('F2B_REGEX'): f2bregex = {} f2bregex[1] = r'mailcow UI: Invalid password for .+ by ([0-9a-f\.:]+)' f2bregex[2] = r'Rspamd UI: Invalid password by ([0-9a-f\.:]+)' f2bregex[3] = r'warning: .*\[([0-9a-f\.:]+)\]: SASL .+ authentication failed: (?!.*Connection lost to authentication server).+' f2bregex[4] = r'warning: non-SMTP command from .*\[([0-9a-f\.:]+)]:.+' f2bregex[5] = r'NOQUEUE: reject: RCPT from \[([0-9a-f\.:]+)].+Protocol error.+' f2bregex[6] = r'\w+\([^,]+,([0-9a-f\.:]+),<[^>]+>\): Password mismatch \(SHA1 of given password: [a-f0-9]+\)' f2bregex[7] = r'\w+\([^,]+,([0-9a-f\.:]+),<[^>]+>\): unknown user \(SHA1 of given password: [a-f0-9]+\)' f2bregex[8] = r'SOGo.+ Login from \'([0-9a-f\.:]+)\' for user .+ might not have worked' f2bregex[9] = r'([0-9a-f\.:]+) \"GET \/SOGo\/.* HTTP.+\" 403 .+' r.set('F2B_REGEX', json.dumps(f2bregex, ensure_ascii=False)) else: try: f2bregex = {} f2bregex = json.loads(r.get('F2B_REGEX')) except ValueError: logger.logCrit('Error loading F2B options: F2B_REGEX is not json') quit_now = True exit_code = 2 def get_ip(address): ip = ipaddress.ip_address(address) if type(ip) is ipaddress.IPv6Address and ip.ipv4_mapped: ip = ip.ipv4_mapped if ip.is_private or ip.is_loopback: return False return ip def ban(address): global f2boptions global lock logdebug("ban() called with address=%s" % address) refreshF2boptions() MAX_ATTEMPTS = int(f2boptions['max_attempts']) RETRY_WINDOW = int(f2boptions['retry_window']) NETBAN_IPV4 = '/' + str(f2boptions['netban_ipv4']) NETBAN_IPV6 = '/' + str(f2boptions['netban_ipv6']) ip = get_ip(address) if not ip: logdebug("No valid IP -- skipping ban()") return address = str(ip) self_network = ipaddress.ip_network(address) with lock: temp_whitelist = set(WHITELIST) logdebug("Checking if %s overlaps with any WHITELIST entries" % self_network) if temp_whitelist: for wl_key in temp_whitelist: wl_net = ipaddress.ip_network(wl_key, False) logdebug("Checking overlap between %s and %s" % (self_network, wl_net)) if wl_net.overlaps(self_network): logger.logInfo( 'Address %s is allowlisted by rule %s' % (self_network, wl_net)) return net = ipaddress.ip_network( (address + (NETBAN_IPV4 if type(ip) is ipaddress.IPv4Address else NETBAN_IPV6)), strict=False) net = str(net) logdebug("Ban net: %s" % net) if not net in bans: bans[net] = {'attempts': 0, 'last_attempt': 0, 'ban_counter': 0} logdebug("Initing new ban counter for %s" % net) current_attempt = time.time() logdebug("Current attempt ts=%s, previous: %s, retry_window: %s" % (current_attempt, bans[net]['last_attempt'], RETRY_WINDOW)) if current_attempt - bans[net]['last_attempt'] > RETRY_WINDOW: bans[net]['attempts'] = 0 logdebug("Ban counter for %s reset as window expired" % net) bans[net]['attempts'] += 1 bans[net]['last_attempt'] = current_attempt logdebug("%s attempts now %d" % (net, bans[net]['attempts'])) if bans[net]['attempts'] >= MAX_ATTEMPTS: cur_time = int(round(time.time())) NET_BAN_TIME = calcNetBanTime(bans[net]['ban_counter']) logger.logCrit('Banning %s for %d minutes' % (net, NET_BAN_TIME / 60 )) if type(ip) is ipaddress.IPv4Address and int(f2boptions['manage_external']) != 1: with lock: logdebug("Calling tables.banIPv4(%s)" % net) tables.banIPv4(net) elif int(f2boptions['manage_external']) != 1: with lock: logdebug("Calling tables.banIPv6(%s)" % net) tables.banIPv6(net) logdebug("Updating F2B_ACTIVE_BANS[%s]=%d" % (net, cur_time + NET_BAN_TIME)) r.hset('F2B_ACTIVE_BANS', '%s' % net, cur_time + NET_BAN_TIME) else: logger.logWarn('%d more attempts in the next %d seconds until %s is banned' % ( MAX_ATTEMPTS - bans[net]['attempts'], RETRY_WINDOW, net)) def unban(net): global lock logdebug("Calling unban() with net=%s" % net) if not net in bans: logger.logInfo( '%s is not banned, skipping unban and deleting from queue (if any)' % net) r.hdel('F2B_QUEUE_UNBAN', '%s' % net) return logger.logInfo('Unbanning %s' % net) if type(ipaddress.ip_network(net)) is ipaddress.IPv4Network: with lock: logdebug("Calling tables.unbanIPv4(%s)" % net) tables.unbanIPv4(net) else: with lock: logdebug("Calling tables.unbanIPv6(%s)" % net) tables.unbanIPv6(net) r.hdel('F2B_ACTIVE_BANS', '%s' % net) r.hdel('F2B_QUEUE_UNBAN', '%s' % net) if net in bans: logdebug("Unban for %s, setting attempts=0, ban_counter+=1" % net) bans[net]['attempts'] = 0 bans[net]['ban_counter'] += 1 def permBan(net, unban=False): global f2boptions global lock is_unbanned = False is_banned = False if type(ipaddress.ip_network(net, strict=False)) is ipaddress.IPv4Network: with lock: if unban: is_unbanned = tables.unbanIPv4(net) elif int(f2boptions['manage_external']) != 1: is_banned = tables.banIPv4(net) else: with lock: if unban: is_unbanned = tables.unbanIPv6(net) elif int(f2boptions['manage_external']) != 1: is_banned = tables.banIPv6(net) if is_unbanned: r.hdel('F2B_PERM_BANS', '%s' % net) logger.logCrit('Removed host/network %s from denylist' % net) elif is_banned: r.hset('F2B_PERM_BANS', '%s' % net, int(round(time.time()))) logger.logCrit('Added host/network %s to denylist' % net) def clear(): global lock logger.logInfo('Clearing all bans') for net in bans.copy(): logdebug("Unbanning net: %s" % net) unban(net) with lock: logdebug("Clearing IPv4/IPv6 table") tables.clearIPv4Table() tables.clearIPv6Table() try: if r is not None: r.delete('F2B_ACTIVE_BANS') r.delete('F2B_PERM_BANS') except Exception as ex: logger.logWarn('Error clearing redis keys F2B_ACTIVE_BANS and F2B_PERM_BANS: %s' % ex) def watch(): global pubsub global quit_now global exit_code logger.logInfo('Watching Redis channel F2B_CHANNEL') pubsub.subscribe('F2B_CHANNEL') while not quit_now: try: for item in pubsub.listen(): refreshF2bregex() for rule_id, rule_regex in f2bregex.items(): if item['data'] and item['type'] == 'message': try: result = re.search(rule_regex, item['data']) except re.error: result = False if result: addr = result.group(1) ip = ipaddress.ip_address(addr) if ip.is_private or ip.is_loopback: continue logger.logWarn('%s matched rule id %s (%s)' % (addr, rule_id, item['data'])) ban(addr) except Exception as ex: logger.logWarn('Error reading log line from pubsub: %s' % ex) pubsub = None quit_now = True exit_code = 2 def snat4(snat_target): global lock global quit_now while not quit_now: time.sleep(10) with lock: tables.snat4(snat_target, os.getenv('IPV4_NETWORK', '172.22.1') + '.0/24') def snat6(snat_target): global lock global quit_now while not quit_now: time.sleep(10) with lock: tables.snat6(snat_target, os.getenv('IPV6_NETWORK', 'fd4d:6169:6c63:6f77::/64')) def autopurge(): global f2boptions logdebug("autopurge thread started") while not quit_now: logdebug("autopurge tick") time.sleep(10) refreshF2boptions() MAX_ATTEMPTS = int(f2boptions['max_attempts']) QUEUE_UNBAN = r.hgetall('F2B_QUEUE_UNBAN') logdebug("QUEUE_UNBAN: %s" % QUEUE_UNBAN) if QUEUE_UNBAN: for net in QUEUE_UNBAN: logdebug("Autopurge: unbanning queued net: %s" % net) unban(str(net)) # Only check expiry for actively banned IPs: active_bans = r.hgetall('F2B_ACTIVE_BANS') now = time.time() for net_str, expire_str in active_bans.items(): logdebug("Checking ban expiry for (actively banned): %s" % net_str) # Defensive: always process if timer missing or expired try: expire = float(expire_str) except Exception: logdebug("Invalid expire time for %s; unbanning" % net_str) unban(net_str) continue time_left = expire - now logdebug("Time left for %s: %.1f seconds" % (net_str, time_left)) if time_left <= 0: logdebug("Ban expired for %s" % net_str) unban(net_str) def mailcowChainOrder(): global lock global quit_now global exit_code while not quit_now: time.sleep(10) with lock: quit_now, exit_code = tables.checkIPv4ChainOrder() if quit_now: return quit_now, exit_code = tables.checkIPv6ChainOrder() def calcNetBanTime(ban_counter): global f2boptions BAN_TIME = int(f2boptions['ban_time']) MAX_BAN_TIME = int(f2boptions['max_ban_time']) BAN_TIME_INCREMENT = bool(f2boptions['ban_time_increment']) NET_BAN_TIME = BAN_TIME if not BAN_TIME_INCREMENT else BAN_TIME * 2 ** ban_counter NET_BAN_TIME = max([BAN_TIME, min([NET_BAN_TIME, MAX_BAN_TIME])]) return NET_BAN_TIME def isIpNetwork(address): try: ipaddress.ip_network(address, False) except ValueError: return False return True def genNetworkList(list): resolver = dns.resolver.Resolver() hostnames = [] networks = [] for key in list: if isIpNetwork(key): networks.append(key) else: hostnames.append(key) for hostname in hostnames: hostname_ips = [] for rdtype in ['A', 'AAAA']: try: answer = resolver.resolve(qname=hostname, rdtype=rdtype, lifetime=3) except dns.exception.Timeout: logger.logInfo('Hostname %s timedout on resolve' % hostname) break except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer): continue except dns.exception.DNSException as dnsexception: logger.logInfo('%s' % dnsexception) continue for rdata in answer: hostname_ips.append(rdata.to_text()) networks.extend(hostname_ips) return set(networks) def whitelistUpdate(): global lock global quit_now global WHITELIST while not quit_now: start_time = time.time() list = r.hgetall('F2B_WHITELIST') new_whitelist = [] if list: new_whitelist = genNetworkList(list) with lock: if Counter(new_whitelist) != Counter(WHITELIST): WHITELIST = new_whitelist logger.logInfo('Allowlist was changed, it has %s entries' % len(WHITELIST)) time.sleep(60.0 - ((time.time() - start_time) % 60.0)) def blacklistUpdate(): global quit_now global BLACKLIST while not quit_now: start_time = time.time() list = r.hgetall('F2B_BLACKLIST') new_blacklist = [] if list: new_blacklist = genNetworkList(list) if Counter(new_blacklist) != Counter(BLACKLIST): addban = set(new_blacklist).difference(BLACKLIST) delban = set(BLACKLIST).difference(new_blacklist) BLACKLIST = new_blacklist logger.logInfo('Denylist was changed, it has %s entries' % len(BLACKLIST)) if addban: for net in addban: permBan(net=net) if delban: for net in delban: permBan(net=net, unban=True) time.sleep(60.0 - ((time.time() - start_time) % 60.0)) def sigterm_quit(signum, frame): global clear_before_quit logdebug("SIGTERM received, setting clear_before_quit to True and exiting") clear_before_quit = True sys.exit(exit_code) def before_quit(): logdebug("before_quit called, clear_before_quit=%s" % clear_before_quit) if clear_before_quit: clear() if pubsub is not None: pubsub.unsubscribe() if __name__ == '__main__': logger = Logger() logdebug("Sys.argv: %s" % sys.argv) atexit.register(before_quit) signal.signal(signal.SIGTERM, sigterm_quit) backend = sys.argv[1] logdebug("Backend: %s" % backend) if backend == "nftables": logger.logInfo('Using NFTables backend') tables = NFTables(chain_name, logger) else: logger.logInfo('Using IPTables backend') logger.logWarn( "DEPRECATION: iptables-legacy is deprecated and will be removed in future releases. " "Please switch to nftables on your host to ensure complete compatibility." ) time.sleep(5) tables = IPTables(chain_name, logger) clear() logger.logInfo("Initializing mailcow netfilter chain") tables.initChainIPv4() tables.initChainIPv6() if os.getenv("DISABLE_NETFILTER_ISOLATION_RULE", "").lower() in ("y", "yes"): logger.logInfo(f"Skipping {chain_name} isolation") else: logger.logInfo(f"Setting {chain_name} isolation") tables.create_mailcow_isolation_rule("br-mailcow", [3306, 6379, 8983, 12345], os.getenv("MAILCOW_REPLICA_IP")) # connect to redis while True: try: redis_slaveof_ip = os.getenv('REDIS_SLAVEOF_IP', '') redis_slaveof_port = os.getenv('REDIS_SLAVEOF_PORT', '') logdebug( "Connecting redis (SLAVEOF_IP:%s, PORT:%s)" % (redis_slaveof_ip, redis_slaveof_port)) if "".__eq__(redis_slaveof_ip): r = redis.StrictRedis( host=os.getenv('IPV4_NETWORK', '172.22.1') + '.249', decode_responses=True, port=6379, db=0, password=os.environ['REDISPASS']) else: r = redis.StrictRedis( host=redis_slaveof_ip, decode_responses=True, port=redis_slaveof_port, db=0, password=os.environ['REDISPASS']) r.ping() pubsub = r.pubsub() except Exception as ex: logdebug( 'Redis connection failed: %s - trying again in 3 seconds' % (ex)) time.sleep(3) else: break logger.set_redis(r) logdebug("Redis connection established, setting up F2B keys") if r.exists('F2B_LOG'): logdebug("Renaming F2B_LOG to NETFILTER_LOG") r.rename('F2B_LOG', 'NETFILTER_LOG') r.delete('F2B_ACTIVE_BANS') r.delete('F2B_PERM_BANS') refreshF2boptions() watch_thread = Thread(target=watch) watch_thread.daemon = True watch_thread.start() if os.getenv('SNAT_TO_SOURCE') and os.getenv('SNAT_TO_SOURCE') != 'n': try: snat_ip = os.getenv('SNAT_TO_SOURCE') snat_ipo = ipaddress.ip_address(snat_ip) if type(snat_ipo) is ipaddress.IPv4Address: snat4_thread = Thread(target=snat4, args=(snat_ip,)) snat4_thread.daemon = True snat4_thread.start() except ValueError: print(os.getenv('SNAT_TO_SOURCE') + ' is not a valid IPv4 address') if os.getenv('SNAT6_TO_SOURCE') and os.getenv('SNAT6_TO_SOURCE') != 'n': try: snat_ip = os.getenv('SNAT6_TO_SOURCE') snat_ipo = ipaddress.ip_address(snat_ip) if type(snat_ipo) is ipaddress.IPv6Address: snat6_thread = Thread(target=snat6,args=(snat_ip,)) snat6_thread.daemon = True snat6_thread.start() except ValueError: print(os.getenv('SNAT6_TO_SOURCE') + ' is not a valid IPv6 address') autopurge_thread = Thread(target=autopurge) autopurge_thread.daemon = True autopurge_thread.start() mailcowchainwatch_thread = Thread(target=mailcowChainOrder) mailcowchainwatch_thread.daemon = True mailcowchainwatch_thread.start() blacklistupdate_thread = Thread(target=blacklistUpdate) blacklistupdate_thread.daemon = True blacklistupdate_thread.start() whitelistupdate_thread = Thread(target=whitelistUpdate) whitelistupdate_thread.daemon = True whitelistupdate_thread.start() while not quit_now: time.sleep(0.5) logdebug("Exiting with code %s" % exit_code) sys.exit(exit_code) ================================================ FILE: data/Dockerfiles/netfilter/modules/IPTables.py ================================================ import iptc import time import os class IPTables: def __init__(self, chain_name, logger): self.chain_name = chain_name self.logger = logger def initChainIPv4(self): if not iptc.Chain(iptc.Table(iptc.Table.FILTER), self.chain_name) in iptc.Table(iptc.Table.FILTER).chains: iptc.Table(iptc.Table.FILTER).create_chain(self.chain_name) for c in ['FORWARD', 'INPUT']: chain = iptc.Chain(iptc.Table(iptc.Table.FILTER), c) rule = iptc.Rule() rule.src = '0.0.0.0/0' rule.dst = '0.0.0.0/0' target = iptc.Target(rule, self.chain_name) rule.target = target if rule not in chain.rules: chain.insert_rule(rule) def initChainIPv6(self): if not iptc.Chain(iptc.Table6(iptc.Table6.FILTER), self.chain_name) in iptc.Table6(iptc.Table6.FILTER).chains: iptc.Table6(iptc.Table6.FILTER).create_chain(self.chain_name) for c in ['FORWARD', 'INPUT']: chain = iptc.Chain(iptc.Table6(iptc.Table6.FILTER), c) rule = iptc.Rule6() rule.src = '::/0' rule.dst = '::/0' target = iptc.Target(rule, self.chain_name) rule.target = target if rule not in chain.rules: chain.insert_rule(rule) def checkIPv4ChainOrder(self): filter_table = iptc.Table(iptc.Table.FILTER) filter_table.refresh() return self.checkChainOrder(filter_table) def checkIPv6ChainOrder(self): filter_table = iptc.Table6(iptc.Table6.FILTER) filter_table.refresh() return self.checkChainOrder(filter_table) def checkChainOrder(self, filter_table): err = False exit_code = None forward_chain = iptc.Chain(filter_table, 'FORWARD') input_chain = iptc.Chain(filter_table, 'INPUT') for chain in [forward_chain, input_chain]: target_found = False for position, item in enumerate(chain.rules): if item.target.name == self.chain_name: target_found = True if position > 2: self.logger.logCrit('Error in %s chain: %s target not found, restarting container' % (chain.name, self.chain_name)) err = True exit_code = 2 if not target_found: self.logger.logCrit('Error in %s chain: %s target not found, restarting container' % (chain.name, self.chain_name)) err = True exit_code = 2 return err, exit_code def clearIPv4Table(self): self.clearTable(iptc.Table(iptc.Table.FILTER)) def clearIPv6Table(self): self.clearTable(iptc.Table6(iptc.Table6.FILTER)) def clearTable(self, filter_table): filter_table.autocommit = False forward_chain = iptc.Chain(filter_table, "FORWARD") input_chain = iptc.Chain(filter_table, "INPUT") mailcow_chain = iptc.Chain(filter_table, self.chain_name) if mailcow_chain in filter_table.chains: for rule in mailcow_chain.rules: mailcow_chain.delete_rule(rule) for rule in forward_chain.rules: if rule.target.name == self.chain_name: forward_chain.delete_rule(rule) for rule in input_chain.rules: if rule.target.name == self.chain_name: input_chain.delete_rule(rule) filter_table.delete_chain(self.chain_name) filter_table.commit() filter_table.refresh() filter_table.autocommit = True def banIPv4(self, source): chain = iptc.Chain(iptc.Table(iptc.Table.FILTER), self.chain_name) rule = iptc.Rule() rule.src = source target = iptc.Target(rule, "REJECT") rule.target = target if rule in chain.rules: return False chain.insert_rule(rule) return True def banIPv6(self, source): chain = iptc.Chain(iptc.Table6(iptc.Table6.FILTER), self.chain_name) rule = iptc.Rule6() rule.src = source target = iptc.Target(rule, "REJECT") rule.target = target if rule in chain.rules: return False chain.insert_rule(rule) return True def unbanIPv4(self, source): chain = iptc.Chain(iptc.Table(iptc.Table.FILTER), self.chain_name) rule = iptc.Rule() rule.src = source target = iptc.Target(rule, "REJECT") rule.target = target if rule not in chain.rules: return False chain.delete_rule(rule) return True def unbanIPv6(self, source): chain = iptc.Chain(iptc.Table6(iptc.Table6.FILTER), self.chain_name) rule = iptc.Rule6() rule.src = source target = iptc.Target(rule, "REJECT") rule.target = target if rule not in chain.rules: return False chain.delete_rule(rule) return True def snat4(self, snat_target, source): try: table = iptc.Table('nat') table.refresh() chain = iptc.Chain(table, 'POSTROUTING') table.autocommit = False new_rule = self.getSnat4Rule(snat_target, source) if not chain.rules: # if there are no rules in the chain, insert the new rule directly self.logger.logInfo(f'Added POSTROUTING rule for source network {new_rule.src} to SNAT target {snat_target}') chain.insert_rule(new_rule) else: for position, rule in enumerate(chain.rules): if not hasattr(rule.target, 'parameter'): continue match = all(( new_rule.get_src() == rule.get_src(), new_rule.get_dst() == rule.get_dst(), new_rule.target.parameters == rule.target.parameters, new_rule.target.name == rule.target.name )) if position == 0: if not match: self.logger.logInfo(f'Added POSTROUTING rule for source network {new_rule.src} to SNAT target {snat_target}') chain.insert_rule(new_rule) else: if match: self.logger.logInfo(f'Remove rule for source network {new_rule.src} to SNAT target {snat_target} from POSTROUTING chain at position {position}') chain.delete_rule(rule) table.commit() table.autocommit = True return True except: self.logger.logCrit('Error running SNAT4, retrying...') return False def snat6(self, snat_target, source): try: table = iptc.Table6('nat') table.refresh() chain = iptc.Chain(table, 'POSTROUTING') table.autocommit = False new_rule = self.getSnat6Rule(snat_target, source) if new_rule not in chain.rules: self.logger.logInfo('Added POSTROUTING rule for source network %s to SNAT target %s' % (new_rule.src, snat_target)) chain.insert_rule(new_rule) else: for position, item in enumerate(chain.rules): if item == new_rule: if position != 0: chain.delete_rule(new_rule) table.commit() table.autocommit = True except: self.logger.logCrit('Error running SNAT6, retrying...') def getSnat4Rule(self, snat_target, source): rule = iptc.Rule() rule.src = source rule.dst = '!' + rule.src target = rule.create_target("SNAT") target.to_source = snat_target match = rule.create_match("comment") match.comment = f'{int(round(time.time()))}' return rule def getSnat6Rule(self, snat_target, source): rule = iptc.Rule6() rule.src = source rule.dst = '!' + rule.src target = rule.create_target("SNAT") target.to_source = snat_target return rule def create_mailcow_isolation_rule(self, _interface:str, _dports:list, _allow:str = ""): try: chain = iptc.Chain(iptc.Table(iptc.Table.FILTER), self.chain_name) # insert mailcow isolation rule rule = iptc.Rule() rule.in_interface = f'!{_interface}' rule.out_interface = _interface rule.protocol = 'tcp' rule.create_target("DROP") match = rule.create_match("multiport") match.dports = ','.join(map(str, _dports)) if rule in chain.rules: chain.delete_rule(rule) chain.insert_rule(rule, position=0) # insert mailcow isolation exception rule if _allow != "": rule = iptc.Rule() rule.src = _allow rule.in_interface = f'!{_interface}' rule.out_interface = _interface rule.protocol = 'tcp' rule.create_target("ACCEPT") match = rule.create_match("multiport") match.dports = ','.join(map(str, _dports)) if rule in chain.rules: chain.delete_rule(rule) chain.insert_rule(rule, position=0) return True except Exception as e: self.logger.logCrit(f"Error adding {self.chain_name} isolation: {e}") return False ================================================ FILE: data/Dockerfiles/netfilter/modules/Logger.py ================================================ import time import json import datetime class Logger: def __init__(self): self.r = None def set_redis(self, redis): self.r = redis def _format_timestamp(self): # Local time with milliseconds return datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") def log(self, priority, message): # build redis-friendly dict tolog = { 'time': int(round(time.time())), # keep raw timestamp for Redis 'priority': priority, 'message': message } # print human-readable message with timestamp ts = self._format_timestamp() print(f"{ts} {priority.upper()}: {message}", flush=True) # also push JSON to Redis if connected if self.r is not None: try: self.r.lpush('NETFILTER_LOG', json.dumps(tolog, ensure_ascii=False)) except Exception as ex: print(f'{ts} WARN: Failed logging to redis: {ex}', flush=True) def logWarn(self, message): self.log('warn', message) def logCrit(self, message): self.log('crit', message) def logInfo(self, message): self.log('info', message) ================================================ FILE: data/Dockerfiles/netfilter/modules/NFTables.py ================================================ import nftables import ipaddress import os class NFTables: def __init__(self, chain_name, logger): self.chain_name = chain_name self.logger = logger self.nft = nftables.Nftables() self.nft.set_json_output(True) self.nft.set_handle_output(True) self.nft_chain_names = {'ip': {'filter': {'input': '', 'forward': ''}, 'nat': {'postrouting': ''} }, 'ip6': {'filter': {'input': '', 'forward': ''}, 'nat': {'postrouting': ''} } } self.search_current_chains() def initChainIPv4(self): self.insert_mailcow_chains("ip") def initChainIPv6(self): self.insert_mailcow_chains("ip6") def checkIPv4ChainOrder(self): return self.checkChainOrder("ip") def checkIPv6ChainOrder(self): return self.checkChainOrder("ip6") def checkChainOrder(self, filter_table): err = False exit_code = None for chain in ['input', 'forward']: chain_position = self.check_mailcow_chains(filter_table, chain) if chain_position is None: continue if chain_position is False: self.logger.logCrit(f'MAILCOW target not found in {filter_table} {chain} table, restarting container to fix it...') err = True exit_code = 2 if chain_position > 0: chain_position += 1 self.logger.logCrit(f'MAILCOW target is in position {chain_position} in the {filter_table} {chain} table, restarting container to fix it...') err = True exit_code = 2 return err, exit_code def clearIPv4Table(self): self.clearTable("ip") def clearIPv6Table(self): self.clearTable("ip6") def clearTable(self, _family): is_empty_dict = True json_command = self.get_base_dict() chain_handle = self.get_chain_handle(_family, "filter", self.chain_name) # if no handle, the chain doesn't exists if chain_handle is not None: is_empty_dict = False # flush chain mailcow_chain = {'family': _family, 'table': 'filter', 'name': self.chain_name} flush_chain = {'flush': {'chain': mailcow_chain}} json_command["nftables"].append(flush_chain) # remove rule in forward chain # remove rule in input chain chains_family = [self.nft_chain_names[_family]['filter']['input'], self.nft_chain_names[_family]['filter']['forward'] ] for chain_base in chains_family: if not chain_base: continue rules_handle = self.get_rules_handle(_family, "filter", chain_base) if rules_handle is not None: for r_handle in rules_handle: is_empty_dict = False mailcow_rule = {'family':_family, 'table': 'filter', 'chain': chain_base, 'handle': r_handle } delete_rules = {'delete': {'rule': mailcow_rule} } json_command["nftables"].append(delete_rules) # remove chain # after delete all rules referencing this chain if chain_handle is not None: mc_chain_handle = {'family':_family, 'table': 'filter', 'name': self.chain_name, 'handle': chain_handle } delete_chain = {'delete': {'chain': mc_chain_handle} } json_command["nftables"].append(delete_chain) if is_empty_dict == False: if self.nft_exec_dict(json_command): self.logger.logInfo(f"Clear completed: {_family}") def banIPv4(self, source): ban_dict = self.get_ban_ip_dict(source, "ip") return self.nft_exec_dict(ban_dict) def banIPv6(self, source): ban_dict = self.get_ban_ip_dict(source, "ip6") return self.nft_exec_dict(ban_dict) def unbanIPv4(self, source): unban_dict = self.get_unban_ip_dict(source, "ip") if not unban_dict: return False return self.nft_exec_dict(unban_dict) def unbanIPv6(self, source): unban_dict = self.get_unban_ip_dict(source, "ip6") if not unban_dict: return False return self.nft_exec_dict(unban_dict) def snat4(self, snat_target, source): self.snat_rule("ip", snat_target, source) def snat6(self, snat_target, source): self.snat_rule("ip6", snat_target, source) def nft_exec_dict(self, query: dict): if not query: return False rc, output, error = self.nft.json_cmd(query) if rc != 0: #self.logger.logCrit(f"Nftables Error: {error}") return False # Prevent returning False or empty string on commands that do not produce output if rc == 0 and len(output) == 0: return True return output def get_base_dict(self): return {'nftables': [{ 'metainfo': { 'json_schema_version': 1} } ] } def search_current_chains(self): nft_chain_priority = {'ip': {'filter': {'input': None, 'forward': None}, 'nat': {'postrouting': None} }, 'ip6': {'filter': {'input': None, 'forward': None}, 'nat': {'postrouting': None} } } # Command: 'nft list chains' _list = {'list' : {'chains': 'null'} } command = self.get_base_dict() command['nftables'].append(_list) kernel_ruleset = self.nft_exec_dict(command) if kernel_ruleset: for _object in kernel_ruleset['nftables']: chain = _object.get("chain") if not chain: continue _family = chain['family'] _table = chain['table'] _hook = chain.get("hook") _priority = chain.get("prio") _name = chain['name'] if _family not in self.nft_chain_names: continue if _table not in self.nft_chain_names[_family]: continue if _hook not in self.nft_chain_names[_family][_table]: continue if _priority is None: continue _saved_priority = nft_chain_priority[_family][_table][_hook] if _saved_priority is None or _priority < _saved_priority: # at this point, we know the chain has: # hook and priority set # and it has the lowest priority nft_chain_priority[_family][_table][_hook] = _priority self.nft_chain_names[_family][_table][_hook] = _name def search_for_chain(self, kernel_ruleset: dict, chain_name: str): found = False for _object in kernel_ruleset["nftables"]: chain = _object.get("chain") if not chain: continue ch_name = chain.get("name") if ch_name == chain_name: found = True break return found def get_chain_dict(self, _family: str, _name: str): # nft (add | create) chain [] _chain_opts = {'family': _family, 'table': 'filter', 'name': _name } _add = {'add': {'chain': _chain_opts} } final_chain = self.get_base_dict() final_chain["nftables"].append(_add) return final_chain def get_mailcow_jump_rule_dict(self, _family: str, _chain: str): _jump_rule = self.get_base_dict() _expr_opt=[] _expr_counter = {'family': _family, 'table': 'filter', 'packets': 0, 'bytes': 0} _counter_dict = {'counter': _expr_counter} _expr_opt.append(_counter_dict) _jump_opts = {'jump': {'target': self.chain_name} } _expr_opt.append(_jump_opts) _rule_params = {'family': _family, 'table': 'filter', 'chain': _chain, 'expr': _expr_opt, 'comment': "mailcow" } _add_rule = {'insert': {'rule': _rule_params} } _jump_rule["nftables"].append(_add_rule) return _jump_rule def insert_mailcow_chains(self, _family: str): nft_input_chain = self.nft_chain_names[_family]['filter']['input'] nft_forward_chain = self.nft_chain_names[_family]['filter']['forward'] # Command: 'nft list table filter' _table_opts = {'family': _family, 'name': 'filter'} _list = {'list': {'table': _table_opts} } command = self.get_base_dict() command['nftables'].append(_list) kernel_ruleset = self.nft_exec_dict(command) if kernel_ruleset: # chain if not self.search_for_chain(kernel_ruleset, self.chain_name): cadena = self.get_chain_dict(_family, self.chain_name) if self.nft_exec_dict(cadena): self.logger.logInfo(f"MAILCOW {_family} chain created successfully.") input_jump_found, forward_jump_found = False, False for _object in kernel_ruleset["nftables"]: if not _object.get("rule"): continue rule = _object["rule"] if nft_input_chain and rule["chain"] == nft_input_chain: if rule.get("comment") and rule["comment"] == "mailcow": input_jump_found = True if nft_forward_chain and rule["chain"] == nft_forward_chain: if rule.get("comment") and rule["comment"] == "mailcow": forward_jump_found = True if not input_jump_found: command = self.get_mailcow_jump_rule_dict(_family, nft_input_chain) self.nft_exec_dict(command) if not forward_jump_found: command = self.get_mailcow_jump_rule_dict(_family, nft_forward_chain) self.nft_exec_dict(command) def delete_nat_rule(self, _family:str, _chain: str, _handle:str): delete_command = self.get_base_dict() _rule_opts = {'family': _family, 'table': 'nat', 'chain': _chain, 'handle': _handle } _delete = {'delete': {'rule': _rule_opts} } delete_command["nftables"].append(_delete) return self.nft_exec_dict(delete_command) def delete_filter_rule(self, _family:str, _chain: str, _handle:str): delete_command = self.get_base_dict() _rule_opts = {'family': _family, 'table': 'filter', 'chain': _chain, 'handle': _handle } _delete = {'delete': {'rule': _rule_opts} } delete_command["nftables"].append(_delete) return self.nft_exec_dict(delete_command) def snat_rule(self, _family: str, snat_target: str, source_address: str): chain_name = self.nft_chain_names[_family]['nat']['postrouting'] # no postrouting chain, may occur if docker has ipv6 disabled. if not chain_name: return # Command: nft list chain nat _chain_opts = {'family': _family, 'table': 'nat', 'name': chain_name} _list = {'list':{'chain': _chain_opts} } command = self.get_base_dict() command['nftables'].append(_list) kernel_ruleset = self.nft_exec_dict(command) if not kernel_ruleset: return rule_position = 0 rule_handle = None rule_found = False for _object in kernel_ruleset["nftables"]: if not _object.get("rule"): continue rule = _object["rule"] if not rule.get("comment") or not rule["comment"] == "mailcow": rule_position +=1 continue rule_found = True rule_handle = rule["handle"] break dest_net = ipaddress.ip_network(source_address, strict=False) target_net = ipaddress.ip_network(snat_target, strict=False) if rule_found: saddr_ip = rule["expr"][0]["match"]["right"]["prefix"]["addr"] saddr_len = int(rule["expr"][0]["match"]["right"]["prefix"]["len"]) daddr_ip = rule["expr"][1]["match"]["right"]["prefix"]["addr"] daddr_len = int(rule["expr"][1]["match"]["right"]["prefix"]["len"]) target_ip = rule["expr"][3]["snat"]["addr"] saddr_net = ipaddress.ip_network(saddr_ip + '/' + str(saddr_len), strict=False) daddr_net = ipaddress.ip_network(daddr_ip + '/' + str(daddr_len), strict=False) current_target_net = ipaddress.ip_network(target_ip, strict=False) match = all(( dest_net == saddr_net, dest_net == daddr_net, target_net == current_target_net )) try: if rule_position == 0: if not match: # Position 0 , it is a mailcow rule , but it does not have the same parameters if self.delete_nat_rule(_family, chain_name, rule_handle): self.logger.logInfo(f'Remove rule for source network {saddr_net} to SNAT target {target_net} from {_family} nat {chain_name} chain, rule does not match configured parameters') else: # Position > 0 and is mailcow rule if self.delete_nat_rule(_family, chain_name, rule_handle): self.logger.logInfo(f'Remove rule for source network {saddr_net} to SNAT target {target_net} from {_family} nat {chain_name} chain, rule is at position {rule_position}') except: self.logger.logCrit(f"Error running SNAT on {_family}, retrying..." ) else: # rule not found json_command = self.get_base_dict() try: snat_dict = {'snat': {'addr': str(target_net.network_address)} } expr_counter = {'family': _family, 'table': 'nat', 'packets': 0, 'bytes': 0} counter_dict = {'counter': expr_counter} prefix_dict = {'prefix': {'addr': str(dest_net.network_address), 'len': int(dest_net.prefixlen)} } payload_dict = {'payload': {'protocol': _family, 'field': "saddr"} } match_dict1 = {'match': {'op': '==', 'left': payload_dict, 'right': prefix_dict} } payload_dict2 = {'payload': {'protocol': _family, 'field': "daddr"} } match_dict2 = {'match': {'op': '!=', 'left': payload_dict2, 'right': prefix_dict } } expr_list = [ match_dict1, match_dict2, counter_dict, snat_dict ] rule_fields = {'family': _family, 'table': 'nat', 'chain': chain_name, 'comment': "mailcow", 'expr': expr_list } insert_dict = {'insert': {'rule': rule_fields} } json_command["nftables"].append(insert_dict) if self.nft_exec_dict(json_command): self.logger.logInfo(f'Added {_family} nat {chain_name} rule for source network {dest_net} to {target_net}') except: self.logger.logCrit(f"Error running SNAT on {_family}, retrying...") def get_chain_handle(self, _family: str, _table: str, chain_name: str): chain_handle = None # Command: 'nft list chains {family}' _list = {'list': {'chains': {'family': _family} } } command = self.get_base_dict() command['nftables'].append(_list) kernel_ruleset = self.nft_exec_dict(command) if kernel_ruleset: for _object in kernel_ruleset["nftables"]: if not _object.get("chain"): continue chain = _object["chain"] if chain["family"] == _family and chain["table"] == _table and chain["name"] == chain_name: chain_handle = chain["handle"] break return chain_handle def get_rules_handle(self, _family: str, _table: str, chain_name: str, _comment_filter = "mailcow"): rule_handle = [] # Command: 'nft list chain {family} {table} {chain_name}' _chain_opts = {'family': _family, 'table': _table, 'name': chain_name} _list = {'list': {'chain': _chain_opts} } command = self.get_base_dict() command['nftables'].append(_list) kernel_ruleset = self.nft_exec_dict(command) if kernel_ruleset: for _object in kernel_ruleset["nftables"]: if not _object.get("rule"): continue rule = _object["rule"] if rule["family"] == _family and rule["table"] == _table and rule["chain"] == chain_name: if rule.get("comment") and rule["comment"] == _comment_filter: rule_handle.append(rule["handle"]) return rule_handle def get_ban_ip_dict(self, ipaddr: str, _family: str): json_command = self.get_base_dict() expr_opt = [] ipaddr_net = ipaddress.ip_network(ipaddr, strict=False) right_dict = {'prefix': {'addr': str(ipaddr_net.network_address), 'len': int(ipaddr_net.prefixlen) } } left_dict = {'payload': {'protocol': _family, 'field': 'saddr'} } match_dict = {'op': '==', 'left': left_dict, 'right': right_dict } expr_opt.append({'match': match_dict}) counter_dict = {'counter': {'family': _family, 'table': "filter", 'packets': 0, 'bytes': 0} } expr_opt.append(counter_dict) expr_opt.append({'drop': "null"}) rule_dict = {'family': _family, 'table': "filter", 'chain': self.chain_name, 'expr': expr_opt} base_dict = {'insert': {'rule': rule_dict} } json_command["nftables"].append(base_dict) return json_command def get_unban_ip_dict(self, ipaddr:str, _family: str): json_command = self.get_base_dict() # Command: 'nft list chain {s_family} filter MAILCOW' _chain_opts = {'family': _family, 'table': 'filter', 'name': self.chain_name} _list = {'list': {'chain': _chain_opts} } command = self.get_base_dict() command['nftables'].append(_list) kernel_ruleset = self.nft_exec_dict(command) rule_handle = None if kernel_ruleset: for _object in kernel_ruleset["nftables"]: if not _object.get("rule"): continue rule = _object["rule"]["expr"][0]["match"] if not "payload" in rule["left"]: continue left_opt = rule["left"]["payload"] if not left_opt["protocol"] == _family: continue if not left_opt["field"] =="saddr": continue # ip currently banned rule_right = rule["right"] if isinstance(rule_right, dict): current_rule_ip = rule_right["prefix"]["addr"] + '/' + str(rule_right["prefix"]["len"]) else: current_rule_ip = rule_right current_rule_net = ipaddress.ip_network(current_rule_ip) # ip to ban candidate_net = ipaddress.ip_network(ipaddr, strict=False) if current_rule_net == candidate_net: rule_handle = _object["rule"]["handle"] break if rule_handle is not None: mailcow_rule = {'family': _family, 'table': 'filter', 'chain': self.chain_name, 'handle': rule_handle} delete_rule = {'delete': {'rule': mailcow_rule} } json_command["nftables"].append(delete_rule) else: return False return json_command def check_mailcow_chains(self, family: str, chain: str): position = 0 rule_found = False chain_name = self.nft_chain_names[family]['filter'][chain] if not chain_name: return None _chain_opts = {'family': family, 'table': 'filter', 'name': chain_name} _list = {'list': {'chain': _chain_opts}} command = self.get_base_dict() command['nftables'].append(_list) kernel_ruleset = self.nft_exec_dict(command) if kernel_ruleset: for _object in kernel_ruleset["nftables"]: if not _object.get("rule"): continue rule = _object["rule"] if rule.get("comment") and rule["comment"] == "mailcow": rule_found = True break position+=1 return position if rule_found else False def create_mailcow_isolation_rule(self, _interface:str, _dports:list, _allow:str = ""): family = "ip" table = "filter" comment_filter_drop = "mailcow isolation" comment_filter_allow = "mailcow isolation allow" json_command = self.get_base_dict() # Delete old mailcow isolation rules handles = self.get_rules_handle(family, table, self.chain_name, comment_filter_drop) for handle in handles: self.delete_filter_rule(family, self.chain_name, handle) handles = self.get_rules_handle(family, table, self.chain_name, comment_filter_allow) for handle in handles: self.delete_filter_rule(family, self.chain_name, handle) # insert mailcow isolation rule _match_dict_drop = [ { "match": { "op": "!=", "left": { "meta": { "key": "iifname" } }, "right": _interface } }, { "match": { "op": "==", "left": { "meta": { "key": "oifname" } }, "right": _interface } }, { "match": { "op": "==", "left": { "payload": { "protocol": "tcp", "field": "dport" } }, "right": { "set": _dports } } }, { "counter": { "packets": 0, "bytes": 0 } }, { "drop": None } ] rule_drop = { "insert": { "rule": { "family": family, "table": table, "chain": self.chain_name, "comment": comment_filter_drop, "expr": _match_dict_drop }}} json_command["nftables"].append(rule_drop) # insert mailcow isolation allow rule if _allow != "": _match_dict_allow = [ { "match": { "op": "==", "left": { "payload": { "protocol": "ip", "field": "saddr" } }, "right": _allow } }, { "match": { "op": "!=", "left": { "meta": { "key": "iifname" } }, "right": _interface } }, { "match": { "op": "==", "left": { "meta": { "key": "oifname" } }, "right": _interface } }, { "match": { "op": "==", "left": { "payload": { "protocol": "tcp", "field": "dport" } }, "right": { "set": _dports } } }, { "counter": { "packets": 0, "bytes": 0 } }, { "accept": None } ] rule_allow = { "insert": { "rule": { "family": family, "table": table, "chain": self.chain_name, "comment": comment_filter_allow, "expr": _match_dict_allow }}} json_command["nftables"].append(rule_allow) success = self.nft_exec_dict(json_command) if success == False: self.logger.logCrit(f"Error adding {self.chain_name} isolation") return False return True ================================================ FILE: data/Dockerfiles/netfilter/modules/__init__.py ================================================ ================================================ FILE: data/Dockerfiles/nginx/Dockerfile ================================================ FROM nginx:alpine LABEL maintainer "The Infrastructure Company GmbH " ENV PIP_BREAK_SYSTEM_PACKAGES=1 RUN apk add --no-cache nginx \ python3 \ py3-pip && \ pip install --upgrade pip && \ pip install Jinja2 RUN mkdir -p /etc/nginx/includes COPY ./bootstrap.py / COPY ./docker-entrypoint.sh / ENTRYPOINT ["/docker-entrypoint.sh"] CMD ["nginx", "-g", "daemon off;"] ================================================ FILE: data/Dockerfiles/nginx/bootstrap.py ================================================ import os import subprocess from jinja2 import Environment, FileSystemLoader def includes_conf(env, template_vars): server_name = "server_name.active" listen_plain = "listen_plain.active" listen_ssl = "listen_ssl.active" server_name_config = f"server_name {template_vars['MAILCOW_HOSTNAME']} autodiscover.* autoconfig.* {' '.join(template_vars['ADDITIONAL_SERVER_NAMES'])};" listen_plain_config = f"listen {template_vars['HTTP_PORT']};" listen_ssl_config = f"listen {template_vars['HTTPS_PORT']};" if template_vars['ENABLE_IPV6']: listen_plain_config += f"\nlisten [::]:{template_vars['HTTP_PORT']};" listen_ssl_config += f"\nlisten [::]:{template_vars['HTTPS_PORT']} ssl;" listen_ssl_config += "\nhttp2 on;" with open(f"/etc/nginx/conf.d/{server_name}", "w") as f: f.write(server_name_config) with open(f"/etc/nginx/conf.d/{listen_plain}", "w") as f: f.write(listen_plain_config) with open(f"/etc/nginx/conf.d/{listen_ssl}", "w") as f: f.write(listen_ssl_config) def sites_default_conf(env, template_vars): config_name = "sites-default.conf" template = env.get_template(f"{config_name}.j2") config = template.render(template_vars) with open(f"/etc/nginx/includes/{config_name}", "w") as f: f.write(config) def nginx_conf(env, template_vars): config_name = "nginx.conf" template = env.get_template(f"{config_name}.j2") config = template.render(template_vars) with open(f"/etc/nginx/{config_name}", "w") as f: f.write(config) def prepare_template_vars(): ipv4_network = os.getenv("IPV4_NETWORK", "172.22.1") additional_server_names = os.getenv("ADDITIONAL_SERVER_NAMES", "") trusted_proxies = os.getenv("TRUSTED_PROXIES", "") template_vars = { 'IPV4_NETWORK': ipv4_network, 'TRUSTED_PROXIES': [item.strip() for item in trusted_proxies.split(",") if item.strip()], 'SKIP_RSPAMD': os.getenv("SKIP_RSPAMD", "n").lower() in ("y", "yes"), 'SKIP_SOGO': os.getenv("SKIP_SOGO", "n").lower() in ("y", "yes"), 'NGINX_USE_PROXY_PROTOCOL': os.getenv("NGINX_USE_PROXY_PROTOCOL", "n").lower() in ("y", "yes"), 'MAILCOW_HOSTNAME': os.getenv("MAILCOW_HOSTNAME", ""), 'ADDITIONAL_SERVER_NAMES': [item.strip() for item in additional_server_names.split(",") if item.strip()], 'HTTP_PORT': os.getenv("HTTP_PORT", "80"), 'HTTPS_PORT': os.getenv("HTTPS_PORT", "443"), 'SOGOHOST': os.getenv("SOGOHOST", ipv4_network + ".248"), 'RSPAMDHOST': os.getenv("RSPAMDHOST", "rspamd-mailcow"), 'PHPFPMHOST': os.getenv("PHPFPMHOST", "php-fpm-mailcow"), 'ENABLE_IPV6': os.getenv("ENABLE_IPV6", "true").lower() != "false", 'HTTP_REDIRECT': os.getenv("HTTP_REDIRECT", "n").lower() in ("y", "yes"), } ssl_dir = '/etc/ssl/mail/' template_vars['valid_cert_dirs'] = [] for d in os.listdir(ssl_dir): full_path = os.path.join(ssl_dir, d) if not os.path.isdir(full_path): continue cert_path = os.path.join(full_path, 'cert.pem') key_path = os.path.join(full_path, 'key.pem') domains_path = os.path.join(full_path, 'domains') if os.path.isfile(cert_path) and os.path.isfile(key_path) and os.path.isfile(domains_path): with open(domains_path, 'r') as file: domains = file.read().strip() domains_list = domains.split() if domains_list and template_vars["MAILCOW_HOSTNAME"] not in domains_list: template_vars['valid_cert_dirs'].append({ 'cert_path': full_path + '/', 'domains': domains }) return template_vars def main(): env = Environment(loader=FileSystemLoader('./etc/nginx/conf.d/templates')) # Render config print("Render config") template_vars = prepare_template_vars() sites_default_conf(env, template_vars) nginx_conf(env, template_vars) includes_conf(env, template_vars) if __name__ == "__main__": main() ================================================ FILE: data/Dockerfiles/nginx/docker-entrypoint.sh ================================================ #!/bin/sh PHPFPMHOST=${PHPFPMHOST:-"php-fpm-mailcow"} SOGOHOST=${SOGOHOST:-"$IPV4_NETWORK.248"} RSPAMDHOST=${RSPAMDHOST:-"rspamd-mailcow"} until ping ${PHPFPMHOST} -c1 > /dev/null; do echo "Waiting for PHP..." sleep 1 done if ! printf "%s\n" "${SKIP_SOGO}" | grep -E '^([yY][eE][sS]|[yY])+$' >/dev/null; then until ping ${SOGOHOST} -c1 > /dev/null; do echo "Waiting for SOGo..." sleep 1 done fi if ! printf "%s\n" "${SKIP_RSPAMD}" | grep -E '^([yY][eE][sS]|[yY])+$' >/dev/null; then until ping ${RSPAMDHOST} -c1 > /dev/null; do echo "Waiting for Rspamd..." sleep 1 done fi python3 /bootstrap.py exec "$@" ================================================ FILE: data/Dockerfiles/olefy/Dockerfile ================================================ FROM alpine:3.21 LABEL maintainer = "The Infrastructure Company GmbH " ARG PIP_BREAK_SYSTEM_PACKAGES=1 WORKDIR /app #RUN addgroup -S olefy && adduser -S olefy -G olefy \ RUN apk add --virtual .build-deps gcc musl-dev python3-dev libffi-dev openssl-dev cargo \ && apk add --update --no-cache python3 py3-pip openssl tzdata libmagic \ && pip3 install --upgrade pip \ && pip3 install --upgrade asyncio python-magic \ && pip3 install --upgrade https://github.com/decalage2/oletools/archive/master.zip \ && apk del .build-deps # && sed -i 's/template_injection_detected = True/template_injection_detected = False/g' /usr/lib/python3.9/site-packages/oletools/olevba.py ADD olefy.py /app/ RUN chown -R nobody:nobody /app /tmp USER nobody CMD ["python3", "-u", "/app/olefy.py"] ================================================ FILE: data/Dockerfiles/olefy/olefy.py ================================================ #!/usr/bin/env python3 # -*- coding: utf-8 -*- # Copyright (c) 2020, Dennis Kalbhen # Copyright (c) 2020, Carsten Rosenberg # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. ### # # olefy is a little helper socket to use oletools with rspamd. (https://rspamd.com) # Please find updates and issues here: https://github.com/HeinleinSupport/olefy # ### from subprocess import Popen, PIPE import sys import os import logging import asyncio import time import magic import re skip_olefy = os.getenv('SKIP_OLEFY', '') if skip_olefy.lower() in ['yes', 'y']: print("SKIP_OLEFY=y, skipping Olefy...") time.sleep(365 * 24 * 60 * 60) sys.exit(0) # merge variables from /etc/olefy.conf and the defaults olefy_listen_addr_string = os.getenv('OLEFY_BINDADDRESS', '127.0.0.1,::1') olefy_listen_port = int(os.getenv('OLEFY_BINDPORT', '10050')) olefy_tmp_dir = os.getenv('OLEFY_TMPDIR', '/tmp') olefy_python_path = os.getenv('OLEFY_PYTHON_PATH', '/usr/bin/python3') olefy_olevba_path = os.getenv('OLEFY_OLEVBA_PATH', '/usr/local/bin/olevba3') # 10:DEBUG, 20:INFO, 30:WARNING, 40:ERROR, 50:CRITICAL olefy_loglvl = int(os.getenv('OLEFY_LOGLVL', 20)) olefy_min_length = int(os.getenv('OLEFY_MINLENGTH', 500)) olefy_del_tmp = int(os.getenv('OLEFY_DEL_TMP', 1)) olefy_del_tmp_failed = int(os.getenv('OLEFY_DEL_TMP_FAILED', 1)) # internal used variables request_time = '0000000000.000000' olefy_protocol = 'OLEFY' olefy_ping = 'PING' olefy_protocol_sep = '\n\n' olefy_headers = {} # init logging logger = logging.getLogger('olefy') logging.basicConfig(stream=sys.stdout, level=olefy_loglvl, format='olefy %(levelname)s %(funcName)s %(message)s') logger.debug('olefy listen address string: {} (type {})'.format(olefy_listen_addr_string, type(olefy_listen_addr_string))) if not olefy_listen_addr_string: olefy_listen_addr = "" else: addr_re = re.compile('[\[" \]]') olefy_listen_addr = addr_re.sub('', olefy_listen_addr_string.replace("'", "")).split(',') # log runtime variables logger.info('olefy listen address: {} (type: {})'.format(olefy_listen_addr, type(olefy_listen_addr))) logger.info('olefy listen port: {}'.format(olefy_listen_port)) logger.info('olefy tmp dir: {}'.format(olefy_tmp_dir)) logger.info('olefy python path: {}'.format(olefy_python_path)) logger.info('olefy olvba path: {}'.format(olefy_olevba_path)) logger.info('olefy log level: {}'.format(olefy_loglvl)) logger.info('olefy min file length: {}'.format(olefy_min_length)) logger.info('olefy delete tmp file: {}'.format(olefy_del_tmp)) logger.info('olefy delete tmp file when failed: {}'.format(olefy_del_tmp_failed)) if not os.path.isfile(olefy_python_path): logger.critical('python path not found: {}'.format(olefy_python_path)) exit(1) if not os.path.isfile(olefy_olevba_path): logger.critical('olevba path not found: {}'.format(olefy_olevba_path)) exit(1) # olefy protocol function def protocol_split( olefy_line ): header_lines = olefy_line.split('\n') for line in header_lines: if line == 'OLEFY/1.0': olefy_headers['olefy'] = line elif line != '': kv = line.split(': ') if kv[0] != '' and kv[1] != '': olefy_headers[kv[0]] = kv[1] logger.debug('olefy_headers: {}'.format(olefy_headers)) # calling oletools def oletools( stream, tmp_file_name, lid ): if olefy_min_length > stream.__len__(): logger.error('{} {} bytes (Not Scanning! File smaller than {!r})'.format(lid, stream.__len__(), olefy_min_length)) out = b'[ { "error": "File too small" } ]' else: tmp_file = open(tmp_file_name, 'wb') tmp_file.write(stream) tmp_file.close() file_magic = magic.Magic(mime=True, uncompress=True) file_mime = file_magic.from_file(tmp_file_name) logger.info('{} {} (libmagic output)'.format(lid, file_mime)) # do the olefy cmd_tmp = Popen([olefy_python_path, olefy_olevba_path, '-a', '-j' , '-l', 'error', tmp_file_name], stdout=PIPE, stderr=PIPE) out, err = cmd_tmp.communicate() out = bytes(out.decode('utf-8', 'ignore').replace(' ', ' ').replace('\t', '').replace('\n', '').replace('XLMMacroDeobfuscator: pywin32 is not installed (only is required if you want to use MS Excel)', ''), encoding="utf-8") failed = False if out.__len__() < 30: logger.error('{} olevba returned <30 chars - rc: {!r}, response: {!r}, error: {!r}'.format(lid,cmd_tmp.returncode, out.decode('utf-8', 'ignore'), err.decode('utf-8', 'ignore'))) out = b'[ { "error": "Unhandled error - too short olevba response" } ]' failed = True elif err.__len__() > 10 and cmd_tmp.returncode == 9: logger.error("{} olevba stderr >10 chars - rc: {!r}, response: {!r}".format(lid, cmd_tmp.returncode, err.decode("utf-8", "ignore"))) out = b'[ { "error": "Decrypt failed" } ]' failed = True elif err.__len__() > 10 and cmd_tmp.returncode > 9: logger.error('{} olevba stderr >10 chars - rc: {!r}, response: {!r}'.format(lid, cmd_tmp.returncode, err.decode('utf-8', 'ignore'))) out = b'[ { "error": "Unhandled oletools error" } ]' failed = True elif cmd_tmp.returncode != 0: logger.error('{} olevba exited with code {!r}; err: {!r}'.format(lid, cmd_tmp.returncode, err.decode('utf-8', 'ignore'))) failed = True if failed and olefy_del_tmp_failed == 0: logger.debug('{} {} FAILED: not deleting tmp file'.format(lid, tmp_file_name)) elif olefy_del_tmp == 1: logger.debug('{} {} deleting tmp file'.format(lid, tmp_file_name)) os.remove(tmp_file_name) logger.debug('{} response: {}'.format(lid, out.decode('utf-8', 'ignore'))) return out + b'\t\n\n\t' # Asyncio data handling, default AIO-Functions class AIO(asyncio.Protocol): def __init__(self): self.extra = bytearray() def connection_made(self, transport): global request_time peer = transport.get_extra_info('peername') logger.debug('{} new connection was made'.format(peer)) self.transport = transport request_time = str(time.time()) def data_received(self, request, msgid=1): peer = self.transport.get_extra_info('peername') logger.debug('{} data received from new connection'.format(peer)) self.extra.extend(request) def eof_received(self): peer = self.transport.get_extra_info('peername') olefy_protocol_err = False proto_ck = self.extra[0:2000].decode('utf-8', 'ignore') headers = proto_ck[0:proto_ck.find(olefy_protocol_sep)] if olefy_protocol == headers[0:5]: self.extra = bytearray(self.extra[len(headers)+2:len(self.extra)]) protocol_split(headers) else: olefy_protocol_err = True if olefy_ping == headers[0:4]: is_ping = True else: is_ping = False rspamd_id = olefy_headers['Rspamd-ID'][:6] or '' lid = 'Rspamd-ID' in olefy_headers and '<'+rspamd_id+'>' tmp_file_name = olefy_tmp_dir+'/'+request_time+'.'+str(peer[1])+'.'+rspamd_id logger.debug('{} {} choosen as tmp filename'.format(lid, tmp_file_name)) if not is_ping or olefy_loglvl == 10: logger.info('{} {} bytes (stream size)'.format(lid, self.extra.__len__())) if olefy_ping == headers[0:4]: logger.debug('{} PING request'.format(peer)) out = b'PONG' elif olefy_protocol_err == True or olefy_headers['olefy'] != 'OLEFY/1.0': logger.error('{} Protocol ERROR: no OLEFY/1.0 found'.format(lid)) out = b'[ { "error": "Protocol error" } ]' elif 'Method' in olefy_headers: if olefy_headers['Method'] == 'oletools': out = oletools(self.extra, tmp_file_name, lid) else: logger.error('Protocol ERROR: Method header not found') out = b'[ { "error": "Protocol error: Method header not found" } ]' self.transport.write(out) if not is_ping or olefy_loglvl == 10: logger.info('{} {} response send: {!r}'.format(lid, peer, out)) self.transport.close() # start the listeners loop = asyncio.get_event_loop() # each client connection will create a new protocol instance coro = loop.create_server(AIO, olefy_listen_addr, olefy_listen_port) server = loop.run_until_complete(coro) for sockets in server.sockets: logger.info('serving on {}'.format(sockets.getsockname())) # XXX serve requests until KeyboardInterrupt, not needed for production try: loop.run_forever() except KeyboardInterrupt: pass # graceful shutdown/reload server.close() loop.run_until_complete(server.wait_closed()) loop.close() logger.info('stopped serving') ================================================ FILE: data/Dockerfiles/phpfpm/Dockerfile ================================================ FROM php:8.2-fpm-alpine3.21 LABEL maintainer = "The Infrastructure Company GmbH " # renovate: datasource=github-tags depName=krakjoe/apcu versioning=semver-coerced extractVersion=^v(?.*)$ ARG APCU_PECL_VERSION=5.1.28 # renovate: datasource=github-tags depName=Imagick/imagick versioning=semver-coerced extractVersion=(?.*)$ ARG IMAGICK_PECL_VERSION=3.8.1 # renovate: datasource=github-tags depName=php/pecl-mail-mailparse versioning=semver-coerced extractVersion=^v(?.*)$ ARG MAILPARSE_PECL_VERSION=3.1.9 # renovate: datasource=github-tags depName=php-memcached-dev/php-memcached versioning=semver-coerced extractVersion=^v(?.*)$ ARG MEMCACHED_PECL_VERSION=3.4.0 # renovate: datasource=github-tags depName=phpredis/phpredis versioning=semver-coerced extractVersion=(?.*)$ ARG REDIS_PECL_VERSION=6.3.0 # renovate: datasource=github-tags depName=composer/composer versioning=semver-coerced extractVersion=(?.*)$ ARG COMPOSER_VERSION=2.9.5 RUN apk add -U --no-cache autoconf \ aspell-dev \ aspell-libs \ bash \ c-client \ cyrus-sasl-dev \ freetype \ freetype-dev \ g++ \ git \ gettext \ gettext-dev \ gmp-dev \ gnupg \ icu-dev \ icu-libs \ imagemagick \ imagemagick-dev \ imap-dev \ jq \ libavif \ libavif-dev \ libjpeg-turbo \ libjpeg-turbo-dev \ libmemcached \ libmemcached-dev \ libpng \ libpng-dev \ libressl \ libressl-dev \ librsvg \ libtool \ libwebp-dev \ libxml2-dev \ libxpm \ libxpm-dev \ libzip \ libzip-dev \ linux-headers \ make \ mysql-client \ openldap-dev \ pcre-dev \ re2c \ redis \ samba-client \ zlib-dev \ tzdata \ && pecl install APCu-${APCU_PECL_VERSION} \ && pecl install imagick-${IMAGICK_PECL_VERSION} \ && pecl install mailparse-${MAILPARSE_PECL_VERSION} \ && pecl install memcached-${MEMCACHED_PECL_VERSION} \ && pecl install redis-${REDIS_PECL_VERSION} \ && docker-php-ext-enable apcu imagick memcached mailparse redis \ && pecl clear-cache \ && docker-php-ext-configure intl \ && docker-php-ext-configure exif \ && docker-php-ext-configure gd --with-freetype=/usr/include/ \ --with-jpeg=/usr/include/ \ --with-webp \ --with-xpm \ --with-avif \ && docker-php-ext-install -j 4 exif gd gettext intl ldap opcache pcntl pdo pdo_mysql pspell soap sockets zip bcmath gmp \ && docker-php-ext-configure imap --with-imap --with-imap-ssl \ && docker-php-ext-install -j 4 imap \ && curl --silent --show-error https://getcomposer.org/installer | php -- --version=${COMPOSER_VERSION} \ && mv composer.phar /usr/local/bin/composer \ && chmod +x /usr/local/bin/composer \ && apk del --purge autoconf \ aspell-dev \ cyrus-sasl-dev \ freetype-dev \ g++ \ gettext-dev \ icu-dev \ imagemagick-dev \ imap-dev \ libavif-dev \ libjpeg-turbo-dev \ libmemcached-dev \ libpng-dev \ libressl-dev \ libwebp-dev \ libxml2-dev \ libxpm-dev \ libzip-dev \ linux-headers \ make \ openldap-dev \ pcre-dev \ zlib-dev COPY ./docker-entrypoint.sh / ENTRYPOINT ["/docker-entrypoint.sh"] CMD ["php-fpm"] ================================================ FILE: data/Dockerfiles/phpfpm/docker-entrypoint.sh ================================================ #!/bin/bash function array_by_comma { local IFS=","; echo "$*"; } # Wait for containers while ! mariadb-admin status --ssl=false --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do echo "Waiting for SQL..." sleep 2 done # Do not attempt to write to slave if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then REDIS_HOST=$REDIS_SLAVEOF_IP REDIS_PORT=$REDIS_SLAVEOF_PORT else REDIS_HOST="redis" REDIS_PORT="6379" fi REDIS_CMDLINE="redis-cli -h ${REDIS_HOST} -p ${REDIS_PORT} -a ${REDISPASS} --no-auth-warning" until [[ $(${REDIS_CMDLINE} PING) == "PONG" ]]; do echo "Waiting for Redis..." sleep 2 done # Set redis session store echo -n ' session.save_handler = redis session.save_path = "tcp://'${REDIS_HOST}':'${REDIS_PORT}'?auth='${REDISPASS}'" ' > /usr/local/etc/php/conf.d/session_store.ini # Check mysql_upgrade (master and slave) CONTAINER_ID= until [[ ! -z "${CONTAINER_ID}" ]] && [[ "${CONTAINER_ID}" =~ ^[[:alnum:]]*$ ]]; do CONTAINER_ID=$(curl --silent --insecure https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], project: .Config.Labels[\"com.docker.compose.project\"], id: .Id}" 2> /dev/null | jq -rc "select( .name | tostring | contains(\"mysql-mailcow\")) | select( .project | tostring | contains(\"${COMPOSE_PROJECT_NAME,,}\")) | .id" 2> /dev/null) echo "Could not get mysql-mailcow container id... trying again" sleep 2 done echo "MySQL @ ${CONTAINER_ID}" SQL_LOOP_C=0 SQL_CHANGED=0 until [[ ${SQL_UPGRADE_STATUS} == 'success' ]]; do if [ ${SQL_LOOP_C} -gt 4 ]; then echo "Tried to upgrade MySQL and failed, giving up after ${SQL_LOOP_C} retries and starting container (oops, not good)" break fi SQL_FULL_UPGRADE_RETURN=$(curl --silent --insecure -XPOST https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/${CONTAINER_ID}/exec -d '{"cmd":"system", "task":"mysql_upgrade"}' --silent -H 'Content-type: application/json') SQL_UPGRADE_STATUS=$(echo ${SQL_FULL_UPGRADE_RETURN} | jq -r .type) SQL_LOOP_C=$((SQL_LOOP_C+1)) echo "SQL upgrade iteration #${SQL_LOOP_C}" if [[ ${SQL_UPGRADE_STATUS} == 'warning' ]]; then SQL_CHANGED=1 echo "MySQL applied an upgrade, debug output:" echo ${SQL_FULL_UPGRADE_RETURN} sleep 3 while ! mariadb-admin status --ssl=false --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do echo "Waiting for SQL to return, please wait" sleep 2 done continue elif [[ ${SQL_UPGRADE_STATUS} == 'success' ]]; then echo "MySQL is up-to-date - debug output:" echo ${SQL_FULL_UPGRADE_RETURN} else echo "No valid reponse for mysql_upgrade was received, debug output:" echo ${SQL_FULL_UPGRADE_RETURN} fi done # doing post-installation stuff, if SQL was upgraded (master and slave) if [ ${SQL_CHANGED} -eq 1 ]; then POSTFIX=$(curl --silent --insecure https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], project: .Config.Labels[\"com.docker.compose.project\"], id: .Id}" 2> /dev/null | jq -rc "select( .name | tostring | contains(\"postfix-mailcow\")) | select( .project | tostring | contains(\"${COMPOSE_PROJECT_NAME,,}\")) | .id" 2> /dev/null) if [[ -z "${POSTFIX}" ]] || ! [[ "${POSTFIX}" =~ ^[[:alnum:]]*$ ]]; then echo "Could not determine Postfix container ID, skipping Postfix restart." else echo "Restarting Postfix" curl -X POST --silent --insecure https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/${POSTFIX}/restart | jq -r '.msg' echo "Sleeping 5 seconds..." sleep 5 fi fi # Check mysql tz import (master and slave) TZ_CHECK=$(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT CONVERT_TZ('2019-11-02 23:33:00','Europe/Berlin','UTC') AS time;" -BN 2> /dev/null) if [[ -z ${TZ_CHECK} ]] || [[ "${TZ_CHECK}" == "NULL" ]]; then SQL_FULL_TZINFO_IMPORT_RETURN=$(curl --silent --insecure -XPOST https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/${CONTAINER_ID}/exec -d '{"cmd":"system", "task":"mysql_tzinfo_to_sql"}' --silent -H 'Content-type: application/json') echo "MySQL mysql_tzinfo_to_sql - debug output:" echo ${SQL_FULL_TZINFO_IMPORT_RETURN} fi if [[ "${MASTER}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then echo "We are master, preparing..." # Set a default release format if [[ -z $(${REDIS_CMDLINE} --raw GET Q_RELEASE_FORMAT) ]]; then ${REDIS_CMDLINE} --raw SET Q_RELEASE_FORMAT raw fi # Set max age of q items - if unset if [[ -z $(${REDIS_CMDLINE} --raw GET Q_MAX_AGE) ]]; then ${REDIS_CMDLINE} --raw SET Q_MAX_AGE 365 fi # Set default password policy - if unset if [[ -z $(${REDIS_CMDLINE} --raw HGET PASSWD_POLICY length) ]]; then ${REDIS_CMDLINE} --raw HSET PASSWD_POLICY length 6 ${REDIS_CMDLINE} --raw HSET PASSWD_POLICY chars 0 ${REDIS_CMDLINE} --raw HSET PASSWD_POLICY special_chars 0 ${REDIS_CMDLINE} --raw HSET PASSWD_POLICY lowerupper 0 ${REDIS_CMDLINE} --raw HSET PASSWD_POLICY numbers 0 fi # Trigger db init echo "Running DB init..." php -c /usr/local/etc/php -f /web/inc/init_db.inc.php # Recreating domain map echo "Rebuilding domain map in Redis..." declare -a DOMAIN_ARR ${REDIS_CMDLINE} DEL DOMAIN_MAP > /dev/null while read line do DOMAIN_ARR+=("$line") done < <(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT domain FROM domain" -Bs) while read line do DOMAIN_ARR+=("$line") done < <(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT alias_domain FROM alias_domain" -Bs) if [[ ! -z ${DOMAIN_ARR} ]]; then for domain in "${DOMAIN_ARR[@]}"; do ${REDIS_CMDLINE} HSET DOMAIN_MAP ${domain} 1 > /dev/null done fi # Set API options if env vars are not empty if [[ ${API_ALLOW_FROM} != "invalid" ]] && [[ ! -z ${API_ALLOW_FROM} ]]; then IFS=',' read -r -a API_ALLOW_FROM_ARR <<< "${API_ALLOW_FROM}" declare -a VALIDATED_API_ALLOW_FROM_ARR REGEX_IP6='^([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4}(/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))?$' REGEX_IP4='^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+(/([0-9]|[1-2][0-9]|3[0-2]))?$' for IP in "${API_ALLOW_FROM_ARR[@]}"; do if [[ ${IP} =~ ${REGEX_IP6} ]] || [[ ${IP} =~ ${REGEX_IP4} ]]; then VALIDATED_API_ALLOW_FROM_ARR+=("${IP}") fi done VALIDATED_IPS=$(array_by_comma ${VALIDATED_API_ALLOW_FROM_ARR[*]}) if [[ ! -z ${VALIDATED_IPS} ]]; then if [[ ${API_KEY} != "invalid" ]] && [[ ! -z ${API_KEY} ]]; then mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF DELETE FROM api WHERE access = 'rw'; INSERT INTO api (api_key, active, allow_from, access) VALUES ("${API_KEY}", "1", "${VALIDATED_IPS}", "rw"); EOF fi if [[ ${API_KEY_READ_ONLY} != "invalid" ]] && [[ ! -z ${API_KEY_READ_ONLY} ]]; then mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF DELETE FROM api WHERE access = 'ro'; INSERT INTO api (api_key, active, allow_from, access) VALUES ("${API_KEY_READ_ONLY}", "1", "${VALIDATED_IPS}", "ro"); EOF fi fi fi # Create events (master only, STATUS for event on slave will be SLAVESIDE_DISABLED) mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF DROP EVENT IF EXISTS clean_spamalias; DELIMITER // CREATE EVENT clean_spamalias ON SCHEDULE EVERY 1 DAY DO BEGIN DELETE FROM spamalias WHERE validity < UNIX_TIMESTAMP() AND permanent = 0; END; // DELIMITER ; DROP EVENT IF EXISTS clean_oauth2; DELIMITER // CREATE EVENT clean_oauth2 ON SCHEDULE EVERY 1 DAY DO BEGIN DELETE FROM oauth_refresh_tokens WHERE expires < NOW(); DELETE FROM oauth_access_tokens WHERE expires < NOW(); DELETE FROM oauth_authorization_codes WHERE expires < NOW(); END; // DELIMITER ; DROP EVENT IF EXISTS clean_sasl_log; DELIMITER // CREATE EVENT clean_sasl_log ON SCHEDULE EVERY 1 DAY DO BEGIN DELETE sasl_log.* FROM sasl_log LEFT JOIN ( SELECT username, service, MAX(datetime) AS lastdate FROM sasl_log GROUP BY username, service ) AS last ON sasl_log.username = last.username AND sasl_log.service = last.service WHERE datetime < DATE_SUB(NOW(), INTERVAL 31 DAY) AND datetime < lastdate; DELETE FROM sasl_log WHERE username NOT IN (SELECT username FROM mailbox) AND datetime < DATE_SUB(NOW(), INTERVAL 31 DAY); END; // DELIMITER ; EOF fi # Create dummy for custom overrides of mailcow style [[ ! -f /web/css/build/0081-custom-mailcow.css ]] && echo '/* Autogenerated by mailcow */' > /web/css/build/0081-custom-mailcow.css # Fix permissions for global filters chown -R 82:82 /global_sieve/* # Fix permissions on twig cache folder chown -R 82:82 /web/templates/cache # Clear cache find /web/templates/cache/* -not -name '.gitkeep' -delete # Run hooks for file in /hooks/*; do if [ -x "${file}" ]; then echo "Running hook ${file}" "${file}" fi done exec "$@" ================================================ FILE: data/Dockerfiles/postfix/Dockerfile ================================================ FROM debian:bookworm-slim LABEL maintainer="The Infrastructure Company GmbH " ARG DEBIAN_FRONTEND=noninteractive ENV LC_ALL=C RUN dpkg-divert --local --rename --add /sbin/initctl \ && ln -sf /bin/true /sbin/initctl \ && dpkg-divert --local --rename --add /usr/bin/ischroot \ && ln -sf /bin/true /usr/bin/ischroot # Add groups and users before installing Postfix to not break compatibility RUN groupadd -g 102 postfix \ && groupadd -g 103 postdrop \ && useradd -g postfix -u 101 -d /var/spool/postfix -s /usr/sbin/nologin postfix \ && apt-get update && apt-get install -y --no-install-recommends \ ca-certificates \ curl \ dirmngr \ dnsutils \ gnupg \ libsasl2-modules \ mariadb-client \ perl \ postfix \ postfix-mysql \ postfix-pcre \ redis-tools \ sasl2-bin \ sudo \ supervisor \ syslog-ng \ syslog-ng-core \ syslog-ng-mod-redis \ tzdata \ && rm -rf /var/lib/apt/lists/* \ && touch /etc/default/locale \ && printf '#!/bin/bash\n/usr/sbin/postconf -c /opt/postfix/conf "$@"' > /usr/local/sbin/postconf \ && chmod +x /usr/local/sbin/postconf COPY supervisord.conf /etc/supervisor/supervisord.conf COPY syslog-ng.conf /etc/syslog-ng/syslog-ng.conf COPY syslog-ng-redis_slave.conf /etc/syslog-ng/syslog-ng-redis_slave.conf COPY postfix.sh /opt/postfix.sh COPY rspamd-pipe-ham /usr/local/bin/rspamd-pipe-ham COPY rspamd-pipe-spam /usr/local/bin/rspamd-pipe-spam COPY whitelist_forwardinghosts.sh /usr/local/bin/whitelist_forwardinghosts.sh COPY stop-supervisor.sh /usr/local/sbin/stop-supervisor.sh COPY docker-entrypoint.sh /docker-entrypoint.sh RUN chmod +x /opt/postfix.sh \ /usr/local/bin/rspamd-pipe-ham \ /usr/local/bin/rspamd-pipe-spam \ /usr/local/bin/whitelist_forwardinghosts.sh \ /usr/local/sbin/stop-supervisor.sh RUN rm -rf /tmp/* /var/tmp/* EXPOSE 588 ENTRYPOINT ["/docker-entrypoint.sh"] CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/supervisord.conf"] ================================================ FILE: data/Dockerfiles/postfix/docker-entrypoint.sh ================================================ #!/bin/bash # Run hooks for file in /hooks/*; do if [ -x "${file}" ]; then echo "Running hook ${file}" "${file}" fi done if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then cp /etc/syslog-ng/syslog-ng-redis_slave.conf /etc/syslog-ng/syslog-ng.conf fi # Fix OpenSSL 3.X TLS1.0, 1.1 support (https://community.mailcow.email/d/4062-hi-all/20) if grep -qE '\!SSLv2|\!SSLv3|>=TLSv1(\.[0-1])?$' /opt/postfix/conf/main.cf /opt/postfix/conf/extra.cf; then sed -i '/\[openssl_init\]/a ssl_conf = ssl_configuration' /etc/ssl/openssl.cnf echo "[ssl_configuration]" >> /etc/ssl/openssl.cnf echo "system_default = tls_system_default" >> /etc/ssl/openssl.cnf echo "[tls_system_default]" >> /etc/ssl/openssl.cnf echo "MinProtocol = TLSv1" >> /etc/ssl/openssl.cnf echo "CipherString = DEFAULT@SECLEVEL=0" >> /etc/ssl/openssl.cnf fi exec "$@" ================================================ FILE: data/Dockerfiles/postfix/postfix.sh ================================================ #!/bin/bash trap "postfix stop" EXIT [[ ! -d /opt/postfix/conf/sql/ ]] && mkdir -p /opt/postfix/conf/sql/ # Wait for MySQL to warm-up while ! mariadb-admin status --ssl=false --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do echo "Waiting for database to come up..." sleep 2 done until dig +short mailcow.email > /dev/null; do echo "Waiting for DNS..." sleep 1 done cat < /etc/aliases # Autogenerated by mailcow null: /dev/null watchdog: /dev/null ham: "|/usr/local/bin/rspamd-pipe-ham" spam: "|/usr/local/bin/rspamd-pipe-spam" EOF newaliases; # create sni configuration if [[ "${SKIP_LETS_ENCRYPT}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then echo -n "" > /opt/postfix/conf/sni.map else echo -n "" > /opt/postfix/conf/sni.map; for cert_dir in /etc/ssl/mail/*/ ; do if [[ ! -f ${cert_dir}domains ]] || [[ ! -f ${cert_dir}cert.pem ]] || [[ ! -f ${cert_dir}key.pem ]]; then continue; fi IFS=" " read -r -a domains <<< "$(cat "${cert_dir}domains")" for domain in "${domains[@]}"; do echo -n "${domain} ${cert_dir}key.pem ${cert_dir}cert.pem" >> /opt/postfix/conf/sni.map; echo "" >> /opt/postfix/conf/sni.map; done done fi postmap -F hash:/opt/postfix/conf/sni.map; cat < /opt/postfix/conf/sql/mysql_relay_ne.cf # Autogenerated by mailcow user = ${DBUSER} password = ${DBPASS} hosts = unix:/var/run/mysqld/mysqld.sock dbname = ${DBNAME} query = SELECT IF(EXISTS(SELECT address, domain FROM alias WHERE address = '%s' AND domain IN ( SELECT domain FROM domain WHERE backupmx = '1' AND relay_all_recipients = '1' AND relay_unknown_only = '1') ), 'lmtp:inet:dovecot:24', NULL) AS 'transport' EOF cat < /opt/postfix/conf/sql/mysql_relay_recipient_maps.cf # Autogenerated by mailcow user = ${DBUSER} password = ${DBPASS} hosts = unix:/var/run/mysqld/mysqld.sock dbname = ${DBNAME} query = SELECT DISTINCT CASE WHEN '%d' IN ( SELECT domain FROM domain WHERE relay_all_recipients=1 AND domain='%d' AND backupmx=1 ) THEN '%s' ELSE ( SELECT goto FROM alias WHERE address='%s' AND active='1' ) END AS result; EOF cat < /opt/postfix/conf/sql/mysql_tls_policy_override_maps.cf # Autogenerated by mailcow user = ${DBUSER} password = ${DBPASS} hosts = unix:/var/run/mysqld/mysqld.sock dbname = ${DBNAME} query = SELECT CONCAT(policy, ' ', parameters) AS tls_policy FROM tls_policy_override WHERE active = '1' AND dest = '%s' EOF cat < /opt/postfix/conf/sql/mysql_tls_enforce_in_policy.cf # Autogenerated by mailcow user = ${DBUSER} password = ${DBPASS} hosts = unix:/var/run/mysqld/mysqld.sock dbname = ${DBNAME} query = SELECT IF(EXISTS( SELECT 'TLS_ACTIVE' FROM alias LEFT OUTER JOIN mailbox ON mailbox.username = alias.goto WHERE (address='%s' OR address IN ( SELECT CONCAT('%u', '@', target_domain) FROM alias_domain WHERE alias_domain='%d' ) ) AND JSON_UNQUOTE(JSON_VALUE(attributes, '$.tls_enforce_in')) = '1' AND mailbox.active = '1' ), 'reject_plaintext_session', NULL) AS 'tls_enforce_in'; EOF cat < /opt/postfix/conf/sql/mysql_sender_dependent_default_transport_maps.cf # Autogenerated by mailcow user = ${DBUSER} password = ${DBPASS} hosts = unix:/var/run/mysqld/mysqld.sock dbname = ${DBNAME} query = SELECT GROUP_CONCAT(transport SEPARATOR '') AS transport_maps FROM ( SELECT IF(EXISTS(SELECT 'smtp_type' FROM alias LEFT OUTER JOIN mailbox ON mailbox.username = alias.goto WHERE (address = '%s' OR address IN ( SELECT CONCAT('%u', '@', target_domain) FROM alias_domain WHERE alias_domain = '%d' ) ) AND JSON_UNQUOTE(JSON_VALUE(attributes, '$.tls_enforce_out')) = '1' AND mailbox.active = '1' ), 'smtp_enforced_tls:', 'smtp:') AS 'transport' UNION ALL SELECT COALESCE( (SELECT hostname FROM relayhosts LEFT OUTER JOIN mailbox ON JSON_UNQUOTE(JSON_VALUE(mailbox.attributes, '$.relayhost')) = relayhosts.id WHERE relayhosts.active = '1' AND ( mailbox.username IN (SELECT alias.goto from alias JOIN mailbox ON mailbox.username = alias.goto WHERE alias.active = '1' AND alias.address = '%s' AND alias.address NOT LIKE '@%%' ) ) ), (SELECT hostname FROM relayhosts LEFT OUTER JOIN domain ON domain.relayhost = relayhosts.id WHERE relayhosts.active = '1' AND (domain.domain = '%d' OR domain.domain IN ( SELECT target_domain FROM alias_domain WHERE alias_domain = '%d' ) ) ) ) ) AS transport_view; EOF cat < /opt/postfix/conf/sql/mysql_transport_maps.cf # Autogenerated by mailcow user = ${DBUSER} password = ${DBPASS} hosts = unix:/var/run/mysqld/mysqld.sock dbname = ${DBNAME} query = SELECT CONCAT('smtp_via_transport_maps:', nexthop) AS transport FROM transports WHERE active = '1' AND destination = '%s'; EOF cat < /opt/postfix/conf/sql/mysql_virtual_resource_maps.cf # Autogenerated by mailcow user = ${DBUSER} password = ${DBPASS} hosts = unix:/var/run/mysqld/mysqld.sock dbname = ${DBNAME} query = SELECT 'null@localhost' FROM mailbox WHERE kind REGEXP 'location|thing|group' AND username = '%s'; EOF cat < /opt/postfix/conf/sql/mysql_sasl_passwd_maps_sender_dependent.cf # Autogenerated by mailcow user = ${DBUSER} password = ${DBPASS} hosts = unix:/var/run/mysqld/mysqld.sock dbname = ${DBNAME} query = SELECT CONCAT_WS(':', username, password) AS auth_data FROM relayhosts WHERE id IN ( SELECT COALESCE( (SELECT id FROM relayhosts LEFT OUTER JOIN domain ON domain.relayhost = relayhosts.id WHERE relayhosts.active = '1' AND (domain.domain = '%d' OR domain.domain IN ( SELECT target_domain FROM alias_domain WHERE alias_domain = '%d' ) ) ), (SELECT id FROM relayhosts LEFT OUTER JOIN mailbox ON JSON_UNQUOTE(JSON_VALUE(mailbox.attributes, '$.relayhost')) = relayhosts.id WHERE relayhosts.active = '1' AND ( mailbox.username IN ( SELECT alias.goto from alias JOIN mailbox ON mailbox.username = alias.goto WHERE alias.active = '1' AND alias.address = '%s' AND alias.address NOT LIKE '@%%' ) ) ) ) ) AND active = '1' AND username != ''; EOF cat < /opt/postfix/conf/sql/mysql_sasl_passwd_maps_transport_maps.cf # Autogenerated by mailcow user = ${DBUSER} password = ${DBPASS} hosts = unix:/var/run/mysqld/mysqld.sock dbname = ${DBNAME} query = SELECT CONCAT_WS(':', username, password) AS auth_data FROM transports WHERE nexthop = '%s' AND active = '1' AND username != '' LIMIT 1; EOF cat < /opt/postfix/conf/sql/mysql_virtual_alias_domain_maps.cf # Autogenerated by mailcow user = ${DBUSER} password = ${DBPASS} hosts = unix:/var/run/mysqld/mysqld.sock dbname = ${DBNAME} query = SELECT username FROM mailbox, alias_domain WHERE alias_domain.alias_domain = '%d' AND mailbox.username = CONCAT('%u', '@', alias_domain.target_domain) AND (mailbox.active = '1' OR mailbox.active = '2') AND alias_domain.active='1' EOF cat < /opt/postfix/conf/sql/mysql_virtual_alias_maps.cf # Autogenerated by mailcow user = ${DBUSER} password = ${DBPASS} hosts = unix:/var/run/mysqld/mysqld.sock dbname = ${DBNAME} query = SELECT goto FROM alias WHERE address='%s' AND (active='1' OR active='2'); EOF cat < /opt/postfix/conf/sql/mysql_recipient_bcc_maps.cf # Autogenerated by mailcow user = ${DBUSER} password = ${DBPASS} hosts = unix:/var/run/mysqld/mysqld.sock dbname = ${DBNAME} query = SELECT bcc_dest FROM bcc_maps WHERE local_dest='%s' AND type='rcpt' AND active='1'; EOF cat < /opt/postfix/conf/sql/mysql_sender_bcc_maps.cf # Autogenerated by mailcow user = ${DBUSER} password = ${DBPASS} hosts = unix:/var/run/mysqld/mysqld.sock dbname = ${DBNAME} query = SELECT bcc_dest FROM bcc_maps WHERE local_dest='%s' AND type='sender' AND active='1'; EOF cat < /opt/postfix/conf/sql/mysql_recipient_canonical_maps.cf # Autogenerated by mailcow user = ${DBUSER} password = ${DBPASS} hosts = unix:/var/run/mysqld/mysqld.sock dbname = ${DBNAME} query = SELECT new_dest FROM recipient_maps WHERE old_dest='%s' AND active='1'; EOF cat < /opt/postfix/conf/sql/mysql_virtual_domains_maps.cf # Autogenerated by mailcow user = ${DBUSER} password = ${DBPASS} hosts = unix:/var/run/mysqld/mysqld.sock dbname = ${DBNAME} query = SELECT alias_domain from alias_domain WHERE alias_domain='%s' AND active='1' UNION SELECT domain FROM domain WHERE domain='%s' AND active = '1' AND backupmx = '0' EOF cat < /opt/postfix/conf/sql/mysql_virtual_mailbox_maps.cf # Autogenerated by mailcow user = ${DBUSER} password = ${DBPASS} hosts = unix:/var/run/mysqld/mysqld.sock dbname = ${DBNAME} query = SELECT CONCAT(JSON_UNQUOTE(JSON_VALUE(attributes, '$.mailbox_format')), mailbox_path_prefix, '%d/%u/') FROM mailbox WHERE username='%s' AND (active = '1' OR active = '2') EOF cat < /opt/postfix/conf/sql/mysql_virtual_relay_domain_maps.cf # Autogenerated by mailcow user = ${DBUSER} password = ${DBPASS} hosts = unix:/var/run/mysqld/mysqld.sock dbname = ${DBNAME} query = SELECT domain FROM domain WHERE domain='%s' AND backupmx = '1' AND active = '1' EOF cat < /opt/postfix/conf/sql/mysql_virtual_sender_acl.cf # Autogenerated by mailcow user = ${DBUSER} password = ${DBPASS} hosts = unix:/var/run/mysqld/mysqld.sock dbname = ${DBNAME} # First select queries domain and alias_domain to determine if domains are active. query = SELECT goto FROM alias WHERE id IN ( SELECT COALESCE ( ( SELECT id FROM alias WHERE address='%s' AND (active='1' OR active='2') AND sender_allowed='1' ), ( SELECT id FROM alias WHERE address='@%d' AND (active='1' OR active='2') AND sender_allowed='1' ) ) ) AND active='1' AND sender_allowed='1' AND (domain IN (SELECT domain FROM domain WHERE domain='%d' AND active='1') OR domain in ( SELECT alias_domain FROM alias_domain WHERE alias_domain='%d' AND active='1' ) ) UNION SELECT logged_in_as FROM sender_acl WHERE send_as='@%d' OR send_as='%s' OR send_as='*' OR send_as IN ( SELECT CONCAT('@',target_domain) FROM alias_domain WHERE alias_domain = '%d') OR send_as IN ( SELECT CONCAT('%u','@',target_domain) FROM alias_domain WHERE alias_domain = '%d') AND logged_in_as NOT IN ( SELECT goto FROM alias WHERE address='%s') UNION SELECT username FROM mailbox, alias_domain WHERE alias_domain.alias_domain = '%d' AND mailbox.username = CONCAT('%u','@',alias_domain.target_domain) AND (mailbox.active = '1' OR mailbox.active ='2') AND alias_domain.active='1'; EOF # MX based routing cat < /opt/postfix/conf/sql/mysql_mbr_access_maps.cf # Autogenerated by mailcow user = ${DBUSER} password = ${DBPASS} hosts = unix:/var/run/mysqld/mysqld.sock dbname = ${DBNAME} query = SELECT CONCAT('FILTER smtp_via_transport_maps:', nexthop) as transport FROM transports WHERE '%s' REGEXP destination AND active='1' AND is_mx_based='1'; EOF cat < /opt/postfix/conf/sql/mysql_virtual_spamalias_maps.cf # Autogenerated by mailcow user = ${DBUSER} password = ${DBPASS} hosts = unix:/var/run/mysqld/mysqld.sock dbname = ${DBNAME} query = SELECT goto FROM spamalias WHERE address='%s' AND (validity >= UNIX_TIMESTAMP() OR permanent != 0) EOF if [ ! -f /opt/postfix/conf/dns_blocklists.cf ]; then cat < /opt/postfix/conf/dns_blocklists.cf # This file can be edited. # Delete this file and restart postfix container to revert any changes. postscreen_dnsbl_sites = wl.mailspike.net=127.0.0.[18;19;20]*-2 hostkarma.junkemailfilter.com=127.0.0.1*-2 list.dnswl.org=127.0.[0..255].0*-2 list.dnswl.org=127.0.[0..255].1*-4 list.dnswl.org=127.0.[0..255].2*-6 list.dnswl.org=127.0.[0..255].3*-8 bl.spamcop.net*2 bl.suomispam.net*2 hostkarma.junkemailfilter.com=127.0.0.2*3 hostkarma.junkemailfilter.com=127.0.0.4*2 hostkarma.junkemailfilter.com=127.0.1.2*1 backscatter.spameatingmonkey.net*2 bl.ipv6.spameatingmonkey.net*2 bl.spameatingmonkey.net*2 b.barracudacentral.org=127.0.0.2*7 bl.mailspike.net=127.0.0.2*5 bl.mailspike.net=127.0.0.[10;11;12]*4 EOF fi # Remove discontinued DNSBLs from existing dns_blocklists.cf sed -i '/ix\.dnsbl\.manitu\.net\*2/d' /opt/postfix/conf/dns_blocklists.cf # Nixspam DNSBL_CONFIG=$(grep -v '^#' /opt/postfix/conf/dns_blocklists.cf | grep '\S') if [ ! -z "$DNSBL_CONFIG" ]; then echo -e "\e[33mChecking if ASN for your IP is listed for Spamhaus Bad ASN List...\e[0m" if [ -n "$SPAMHAUS_DQS_KEY" ]; then echo -e "\e[32mDetected SPAMHAUS_DQS_KEY variable from mailcow.conf...\e[0m" echo -e "\e[33mUsing DQS Blocklists from Spamhaus!\e[0m" SPAMHAUS_DNSBL_CONFIG=$(cat < /opt/postfix/conf/dnsbl_reply.map # Autogenerated by mailcow, using Spamhaus DQS reply domains ${SPAMHAUS_DQS_KEY}.sbl.dq.spamhaus.net sbl.spamhaus.org ${SPAMHAUS_DQS_KEY}.xbl.dq.spamhaus.net xbl.spamhaus.org ${SPAMHAUS_DQS_KEY}.pbl.dq.spamhaus.net pbl.spamhaus.org ${SPAMHAUS_DQS_KEY}.zen.dq.spamhaus.net zen.spamhaus.org ${SPAMHAUS_DQS_KEY}.dbl.dq.spamhaus.net dbl.spamhaus.org ${SPAMHAUS_DQS_KEY}.zrd.dq.spamhaus.net zrd.spamhaus.org EOF ) else if [ -f "/opt/postfix/conf/dnsbl_reply.map" ]; then rm /opt/postfix/conf/dnsbl_reply.map fi response=$(curl --connect-timeout 15 --max-time 30 -s -o /dev/null -w "%{http_code}" "https://asn-check.mailcow.email") if [ "$response" -eq 503 ]; then echo -e "\e[31mThe AS of your IP is listed as a banned AS from Spamhaus!\e[0m" echo -e "\e[33mNo SPAMHAUS_DQS_KEY found... Skipping Spamhaus blocklists entirely!\e[0m" SPAMHAUS_DNSBL_CONFIG="" elif [ "$response" -eq 200 ]; then echo -e "\e[32mThe AS of your IP is NOT listed as a banned AS from Spamhaus!\e[0m" echo -e "\e[33mUsing the open Spamhaus blocklists.\e[0m" SPAMHAUS_DNSBL_CONFIG=$(cat <> /opt/postfix/conf/main.cf # Append postscreen dnsbl sites to main.cf if [ ! -z "$DNSBL_CONFIG" ]; then echo -e "${DNSBL_CONFIG}\n${SPAMHAUS_DNSBL_CONFIG}" >> /opt/postfix/conf/main.cf fi # Append user overrides echo -e "\n# User Overrides" >> /opt/postfix/conf/main.cf touch /opt/postfix/conf/extra.cf sed -i '/\$myhostname/! { /myhostname/d }' /opt/postfix/conf/extra.cf echo -e "myhostname = ${MAILCOW_HOSTNAME}\n$(cat /opt/postfix/conf/extra.cf)" > /opt/postfix/conf/extra.cf cat /opt/postfix/conf/extra.cf >> /opt/postfix/conf/main.cf if [ ! -f /opt/postfix/conf/custom_transport.pcre ]; then echo "Creating dummy custom_transport.pcre" touch /opt/postfix/conf/custom_transport.pcre fi if [[ ! -f /opt/postfix/conf/custom_postscreen_whitelist.cidr ]]; then echo "Creating dummy custom_postscreen_whitelist.cidr" cat < /opt/postfix/conf/custom_postscreen_whitelist.cidr # Autogenerated by mailcow # Rules are evaluated in the order as specified. # Blacklist 192.168.* except 192.168.0.1. # 192.168.0.1 permit # 192.168.0.0/16 reject EOF fi # Fix Postfix permissions chown -R root:postfix /opt/postfix/conf/sql/ /opt/postfix/conf/custom_transport.pcre chmod 640 /opt/postfix/conf/sql/*.cf /opt/postfix/conf/custom_transport.pcre chgrp -R postdrop /var/spool/postfix/public chgrp -R postdrop /var/spool/postfix/maildrop postfix set-permissions # Checking if there is a leftover of a crashed postfix container before starting a new one if [ -e /var/spool/postfix/pid/master.pid ]; then rm -rf /var/spool/postfix/pid/master.pid fi # Check Postfix configuration postconf -c /opt/postfix/conf > /dev/null if [[ $? != 0 ]]; then echo "Postfix configuration error, refusing to start." exit 1 else postfix -c /opt/postfix/conf start sleep 126144000 fi ================================================ FILE: data/Dockerfiles/postfix/rspamd-pipe-ham ================================================ #!/bin/bash FILE=/tmp/mail$$ cat > $FILE trap "/bin/rm -f $FILE" 0 1 2 3 13 15 cat ${FILE} | /usr/bin/curl -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd/learnham cat ${FILE} | /usr/bin/curl -H "Flag: 13" -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd/fuzzyadd exit 0 ================================================ FILE: data/Dockerfiles/postfix/rspamd-pipe-spam ================================================ #!/bin/bash FILE=/tmp/mail$$ cat > $FILE trap "/bin/rm -f $FILE" 0 1 2 3 13 15 cat ${FILE} | /usr/bin/curl -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd/learnspam cat ${FILE} | /usr/bin/curl -H "Flag: 11" -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd/fuzzyadd exit 0 ================================================ FILE: data/Dockerfiles/postfix/stop-supervisor.sh ================================================ #!/bin/bash printf "READY\n"; while read line; do echo "Processing Event: $line" >&2; kill -3 $(cat "/var/run/supervisord.pid") done < /dev/stdin ================================================ FILE: data/Dockerfiles/postfix/supervisord.conf ================================================ [supervisord] pidfile=/var/run/supervisord.pid nodaemon=true user=root [program:syslog-ng] command=/usr/sbin/syslog-ng --foreground --no-caps stdout_logfile=/dev/stdout stdout_logfile_maxbytes=0 stderr_logfile=/dev/stderr stderr_logfile_maxbytes=0 autostart=true [program:postfix] command=/opt/postfix.sh stdout_logfile=/dev/stdout stdout_logfile_maxbytes=0 stderr_logfile=/dev/stderr stderr_logfile_maxbytes=0 autorestart=true startsecs=10 [eventlistener:processes] command=/usr/local/sbin/stop-supervisor.sh events=PROCESS_STATE_STOPPED, PROCESS_STATE_EXITED, PROCESS_STATE_FATAL ================================================ FILE: data/Dockerfiles/postfix/syslog-ng-redis_slave.conf ================================================ @version: 3.38 @include "scl.conf" options { chain_hostnames(off); flush_lines(0); use_dns(no); dns_cache(no); use_fqdn(no); owner("root"); group("adm"); perm(0640); stats_freq(0); bad_hostname("^gconfd$"); }; source s_src { unix-stream("/dev/log"); internal(); }; destination d_stdout { pipe("/dev/stdout"); }; destination d_redis_ui_log { redis( host("`REDIS_SLAVEOF_IP`") persist-name("redis1") port(`REDIS_SLAVEOF_PORT`) auth("`REDISPASS`") command("LPUSH" "POSTFIX_MAILLOG" "$(format-json time=\"$S_UNIXTIME\" priority=\"$PRIORITY\" program=\"$PROGRAM\" message=\"$MESSAGE\")\n") ); }; destination d_redis_f2b_channel { redis( host("`REDIS_SLAVEOF_IP`") persist-name("redis2") port(`REDIS_SLAVEOF_PORT`) auth("`REDISPASS`") command("PUBLISH" "F2B_CHANNEL" "$(sanitize $MESSAGE)") ); }; filter f_mail { facility(mail); }; # start # overriding warnings are still displayed when the entrypoint runs its initial check # warnings logged by postfix-mailcow to syslog are hidden to reduce repeating msgs # Some other warnings are ignored filter f_ignore { not match("overriding earlier entry" value("MESSAGE")); not match("TLS SNI from checks.mailcow.email" value("MESSAGE")); not match("no SASL support" value("MESSAGE")); not facility (local0, local1, local2, local3, local4, local5, local6, local7); }; # end log { source(s_src); filter(f_ignore); destination(d_stdout); filter(f_mail); destination(d_redis_ui_log); destination(d_redis_f2b_channel); }; ================================================ FILE: data/Dockerfiles/postfix/syslog-ng.conf ================================================ @version: 3.38 @include "scl.conf" options { chain_hostnames(off); flush_lines(0); use_dns(no); dns_cache(no); use_fqdn(no); owner("root"); group("adm"); perm(0640); stats_freq(0); bad_hostname("^gconfd$"); }; source s_src { unix-stream("/dev/log"); internal(); }; destination d_stdout { pipe("/dev/stdout"); }; destination d_redis_ui_log { redis( host("redis-mailcow") persist-name("redis1") port(6379) auth("`REDISPASS`") command("LPUSH" "POSTFIX_MAILLOG" "$(format-json time=\"$S_UNIXTIME\" priority=\"$PRIORITY\" program=\"$PROGRAM\" message=\"$MESSAGE\")\n") ); }; destination d_redis_f2b_channel { redis( host("redis-mailcow") persist-name("redis2") port(6379) auth("`REDISPASS`") command("PUBLISH" "F2B_CHANNEL" "$(sanitize $MESSAGE)") ); }; filter f_mail { facility(mail); }; # start # overriding warnings are still displayed when the entrypoint runs its initial check # warnings logged by postfix-mailcow to syslog are hidden to reduce repeating msgs # Some other warnings are ignored filter f_ignore { not match("overriding earlier entry" value("MESSAGE")); not match("TLS SNI from checks.mailcow.email" value("MESSAGE")); not match("no SASL support" value("MESSAGE")); not facility (local0, local1, local2, local3, local4, local5, local6, local7); }; # end log { source(s_src); filter(f_ignore); destination(d_stdout); filter(f_mail); destination(d_redis_ui_log); destination(d_redis_f2b_channel); }; ================================================ FILE: data/Dockerfiles/postfix/whitelist_forwardinghosts.sh ================================================ #!/bin/bash while read QUERY; do QUERY=($QUERY) if [ "${QUERY[0]}" != "get" ]; then echo "500 dunno" continue fi result=$(curl -s http://nginx:8081/forwardinghosts.php?host=${QUERY[1]}) logger -t whitelist_forwardinghosts -p mail.info "Look up ${QUERY[1]} on whitelist, result $result" echo ${result} done ================================================ FILE: data/Dockerfiles/postfix-tlspol/Dockerfile ================================================ FROM golang:1.25-bookworm AS builder WORKDIR /src ENV CGO_ENABLED=0 \ GO111MODULE=on \ NOOPT=1 \ VERSION=1.8.22 RUN git clone --branch v${VERSION} https://github.com/Zuplu/postfix-tlspol && \ cd /src/postfix-tlspol && \ scripts/build.sh build-only FROM debian:bookworm-slim LABEL maintainer="The Infrastructure Company GmbH " ARG DEBIAN_FRONTEND=noninteractive ENV LC_ALL=C RUN apt-get update && apt-get install -y --no-install-recommends \ ca-certificates \ dirmngr \ dnsutils \ iputils-ping \ sudo \ supervisor \ redis-tools \ syslog-ng \ syslog-ng-core \ syslog-ng-mod-redis \ tzdata \ && rm -rf /var/lib/apt/lists/* \ && touch /etc/default/locale COPY supervisord.conf /etc/supervisor/supervisord.conf COPY syslog-ng.conf /etc/syslog-ng/syslog-ng.conf COPY syslog-ng-redis_slave.conf /etc/syslog-ng/syslog-ng-redis_slave.conf COPY postfix-tlspol.sh /opt/postfix-tlspol.sh COPY stop-supervisor.sh /usr/local/sbin/stop-supervisor.sh COPY docker-entrypoint.sh /docker-entrypoint.sh COPY --from=builder /src/postfix-tlspol/build/postfix-tlspol /usr/local/bin/postfix-tlspol RUN chmod +x /opt/postfix-tlspol.sh \ /usr/local/sbin/stop-supervisor.sh \ /docker-entrypoint.sh RUN rm -rf /tmp/* /var/tmp/* ENTRYPOINT ["/docker-entrypoint.sh"] CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/supervisord.conf"] ================================================ FILE: data/Dockerfiles/postfix-tlspol/docker-entrypoint.sh ================================================ #!/bin/bash if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then cp /etc/syslog-ng/syslog-ng-redis_slave.conf /etc/syslog-ng/syslog-ng.conf fi exec "$@" ================================================ FILE: data/Dockerfiles/postfix-tlspol/postfix-tlspol.sh ================================================ #!/bin/bash LOGLVL=info if [ ${DEV_MODE} != "n" ]; then echo -e "\e[31mEnabling debug mode\e[0m" set -x LOGLVL=debug fi [[ ! -d /etc/postfix-tlspol ]] && mkdir -p /etc/postfix-tlspol [[ ! -d /var/lib/postfix-tlspol ]] && mkdir -p /var/lib/postfix-tlspol until dig +short mailcow.email > /dev/null; do echo "Waiting for DNS..." sleep 1 done # Do not attempt to write to slave if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then export REDIS_CMDLINE="redis-cli -h ${REDIS_SLAVEOF_IP} -p ${REDIS_SLAVEOF_PORT} -a ${REDISPASS} --no-auth-warning" else export REDIS_CMDLINE="redis-cli -h redis -p 6379 -a ${REDISPASS} --no-auth-warning" fi until [[ $(${REDIS_CMDLINE} PING) == "PONG" ]]; do echo "Waiting for Redis..." sleep 2 done echo "Waiting for Postfix..." until ping postfix -c1 > /dev/null; do sleep 1 done echo "Postfix OK" cat < /etc/postfix-tlspol/config.yaml server: address: 0.0.0.0:8642 log-level: ${LOGLVL} prefetch: true cache-file: /var/lib/postfix-tlspol/cache.db dns: # must support DNSSEC address: 127.0.0.11:53 EOF /usr/local/bin/postfix-tlspol -config /etc/postfix-tlspol/config.yaml ================================================ FILE: data/Dockerfiles/postfix-tlspol/stop-supervisor.sh ================================================ #!/bin/bash printf "READY\n"; while read line; do echo "Processing Event: $line" >&2; kill -3 $(cat "/var/run/supervisord.pid") done < /dev/stdin ================================================ FILE: data/Dockerfiles/postfix-tlspol/supervisord.conf ================================================ [supervisord] pidfile=/var/run/supervisord.pid nodaemon=true user=root [program:syslog-ng] command=/usr/sbin/syslog-ng --foreground --no-caps stdout_logfile=/dev/stdout stdout_logfile_maxbytes=0 stderr_logfile=/dev/stderr stderr_logfile_maxbytes=0 autostart=true [program:postfix-tlspol] startsecs=10 autorestart=true command=/opt/postfix-tlspol.sh stdout_logfile=/dev/stdout stdout_logfile_maxbytes=0 stderr_logfile=/dev/stderr stderr_logfile_maxbytes=0 [eventlistener:processes] command=/usr/local/sbin/stop-supervisor.sh events=PROCESS_STATE_STOPPED, PROCESS_STATE_EXITED, PROCESS_STATE_FATAL ================================================ FILE: data/Dockerfiles/postfix-tlspol/syslog-ng-redis_slave.conf ================================================ @version: 3.38 @include "scl.conf" options { chain_hostnames(off); flush_lines(0); use_dns(no); dns_cache(no); use_fqdn(no); owner("root"); group("adm"); perm(0640); stats_freq(0); bad_hostname("^gconfd$"); }; source s_src { unix-stream("/dev/log"); internal(); }; destination d_stdout { pipe("/dev/stdout"); }; destination d_redis_ui_log { redis( host("`REDIS_SLAVEOF_IP`") persist-name("redis1") port(`REDIS_SLAVEOF_PORT`) auth("`REDISPASS`") command("LPUSH" "POSTFIX_MAILLOG" "$(format-json time=\"$S_UNIXTIME\" priority=\"$PRIORITY\" program=\"$PROGRAM\" message=\"$MESSAGE\")\n") ); }; filter f_mail { facility(mail); }; # start # overriding warnings are still displayed when the entrypoint runs its initial check # warnings logged by postfix-mailcow to syslog are hidden to reduce repeating msgs # Some other warnings are ignored filter f_ignore { not match("overriding earlier entry" value("MESSAGE")); not match("TLS SNI from checks.mailcow.email" value("MESSAGE")); not match("no SASL support" value("MESSAGE")); not facility (local0, local1, local2, local3, local4, local5, local6, local7); }; # end log { source(s_src); filter(f_ignore); destination(d_stdout); filter(f_mail); destination(d_redis_ui_log); }; ================================================ FILE: data/Dockerfiles/postfix-tlspol/syslog-ng.conf ================================================ @version: 3.38 @include "scl.conf" options { chain_hostnames(off); flush_lines(0); use_dns(no); dns_cache(no); use_fqdn(no); owner("root"); group("adm"); perm(0640); stats_freq(0); bad_hostname("^gconfd$"); }; source s_src { unix-stream("/dev/log"); internal(); }; destination d_stdout { pipe("/dev/stdout"); }; destination d_redis_ui_log { redis( host("redis-mailcow") persist-name("redis1") port(6379) auth("`REDISPASS`") command("LPUSH" "POSTFIX_MAILLOG" "$(format-json time=\"$S_UNIXTIME\" priority=\"$PRIORITY\" program=\"$PROGRAM\" message=\"$MESSAGE\")\n") ); }; filter f_mail { facility(mail); }; # start # overriding warnings are still displayed when the entrypoint runs its initial check # warnings logged by postfix-mailcow to syslog are hidden to reduce repeating msgs # Some other warnings are ignored filter f_ignore { not match("overriding earlier entry" value("MESSAGE")); not match("TLS SNI from checks.mailcow.email" value("MESSAGE")); not match("no SASL support" value("MESSAGE")); not facility (local0, local1, local2, local3, local4, local5, local6, local7); }; # end log { source(s_src); filter(f_ignore); destination(d_stdout); filter(f_mail); destination(d_redis_ui_log); }; ================================================ FILE: data/Dockerfiles/rspamd/Dockerfile ================================================ FROM debian:trixie-slim LABEL maintainer="The Infrastructure Company GmbH " ARG DEBIAN_FRONTEND=noninteractive ARG RSPAMD_VER=rspamd_3.14.3-1~236eb65 ARG CODENAME=trixie ENV LC_ALL=C RUN apt-get update && apt-get install -y --no-install-recommends \ tzdata \ ca-certificates \ gnupg2 \ apt-transport-https \ dnsutils \ netcat-traditional \ wget \ redis-tools \ procps \ nano \ lua-cjson \ && arch=$(arch | sed s/aarch64/arm64/ | sed s/x86_64/amd64/) \ && wget -P /tmp https://rspamd.com/apt-stable/pool/main/r/rspamd/${RSPAMD_VER}~${CODENAME}_${arch}.deb\ && apt install -y /tmp/${RSPAMD_VER}~${CODENAME}_${arch}.deb \ && rm -rf /var/lib/apt/lists/* /tmp/*\ && apt-get autoremove --purge \ && apt-get clean \ && mkdir -p /run/rspamd \ && chown _rspamd:_rspamd /run/rspamd \ && echo 'alias ll="ls -la --color"' >> ~/.bashrc \ && sed -i 's/#analysis_keyword_table > 0/analysis_cat_table.macro_exist == "M"/g' /usr/share/rspamd/lualib/lua_scanners/oletools.lua COPY settings.conf /etc/rspamd/settings.conf COPY set_worker_password.sh /set_worker_password.sh COPY docker-entrypoint.sh /docker-entrypoint.sh ENTRYPOINT ["/docker-entrypoint.sh"] STOPSIGNAL SIGTERM CMD ["/usr/bin/rspamd", "-f", "-u", "_rspamd", "-g", "_rspamd"] ================================================ FILE: data/Dockerfiles/rspamd/docker-entrypoint.sh ================================================ #!/bin/bash until nc phpfpm 9001 -z; do echo "Waiting for PHP on port 9001..." sleep 3 done until nc phpfpm 9002 -z; do echo "Waiting for PHP on port 9002..." sleep 3 done mkdir -p /etc/rspamd/plugins.d \ /etc/rspamd/custom touch /etc/rspamd/rspamd.conf.local \ /etc/rspamd/rspamd.conf.override chmod 755 /var/lib/rspamd [[ ! -f /etc/rspamd/override.d/worker-controller-password.inc ]] && echo '# Autogenerated by mailcow' > /etc/rspamd/override.d/worker-controller-password.inc echo ${IPV4_NETWORK}.0/24 > /etc/rspamd/custom/mailcow_networks.map echo ${IPV6_NETWORK} >> /etc/rspamd/custom/mailcow_networks.map DOVECOT_V4= DOVECOT_V6= until [[ ! -z ${DOVECOT_V4} ]]; do DOVECOT_V4=$(dig a dovecot +short) DOVECOT_V6=$(dig aaaa dovecot +short) [[ ! -z ${DOVECOT_V4} ]] && break; echo "Waiting for Dovecot..." sleep 3 done echo ${DOVECOT_V4}/32 > /etc/rspamd/custom/dovecot_trusted.map if [[ ! -z ${DOVECOT_V6} ]]; then echo ${DOVECOT_V6}/128 >> /etc/rspamd/custom/dovecot_trusted.map fi RSPAMD_V4= RSPAMD_V6= until [[ ! -z ${RSPAMD_V4} ]]; do RSPAMD_V4=$(dig a rspamd +short) RSPAMD_V6=$(dig aaaa rspamd +short) [[ ! -z ${RSPAMD_V4} ]] && break; echo "Waiting for Rspamd..." sleep 3 done echo ${RSPAMD_V4}/32 > /etc/rspamd/custom/rspamd_trusted.map if [[ ! -z ${RSPAMD_V6} ]]; then echo ${RSPAMD_V6}/128 >> /etc/rspamd/custom/rspamd_trusted.map fi if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then cat < /etc/rspamd/local.d/redis.conf read_servers = "redis:6379"; write_servers = "${REDIS_SLAVEOF_IP}:${REDIS_SLAVEOF_PORT}"; password = "${REDISPASS}"; timeout = 10; EOF until [[ $(redis-cli -h redis-mailcow -a ${REDISPASS} --no-auth-warning PING) == "PONG" ]]; do echo "Waiting for Redis @redis-mailcow..." sleep 2 done until [[ $(redis-cli -h ${REDIS_SLAVEOF_IP} -p ${REDIS_SLAVEOF_PORT} -a ${REDISPASS} --no-auth-warning PING) == "PONG" ]]; do echo "Waiting for Redis @${REDIS_SLAVEOF_IP}..." sleep 2 done redis-cli -h redis-mailcow -a ${REDISPASS} --no-auth-warning SLAVEOF ${REDIS_SLAVEOF_IP} ${REDIS_SLAVEOF_PORT} else cat < /etc/rspamd/local.d/redis.conf servers = "redis:6379"; password = "${REDISPASS}"; timeout = 10; EOF until [[ $(redis-cli -h redis-mailcow -a ${REDISPASS} --no-auth-warning PING) == "PONG" ]]; do echo "Waiting for Redis slave..." sleep 2 done redis-cli -h redis-mailcow -a ${REDISPASS} --no-auth-warning SLAVEOF NO ONE fi if [[ "${SKIP_OLEFY}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then if [[ -f /etc/rspamd/local.d/external_services.conf ]]; then rm /etc/rspamd/local.d/external_services.conf fi else if [[ ! -f /etc/rspamd/local.d/external_services.conf ]]; then cat < /etc/rspamd/local.d/external_services.conf oletools { # default olefy settings servers = "olefy:10055"; # needs to be set explicitly for Rspamd < 1.9.5 scan_mime_parts = true; # mime-part regex matching in content-type or filename # block all macros extended = true; max_size = 3145728; timeout = 20.0; retransmits = 1; } EOF fi fi # Provide additional lua modules ln -s /usr/lib/$(uname -m)-linux-gnu/liblua5.1-cjson.so.0.0.0 /usr/lib/rspamd/cjson.so chown -R _rspamd:_rspamd /var/lib/rspamd \ /etc/rspamd/local.d \ /etc/rspamd/override.d \ /etc/rspamd/rspamd.conf.local \ /etc/rspamd/rspamd.conf.override \ /etc/rspamd/plugins.d # Fix missing default global maps, if any # These exists in mailcow UI and should not be removed touch /etc/rspamd/custom/global_mime_from_blacklist.map \ /etc/rspamd/custom/global_rcpt_blacklist.map \ /etc/rspamd/custom/global_smtp_from_blacklist.map \ /etc/rspamd/custom/global_mime_from_whitelist.map \ /etc/rspamd/custom/global_rcpt_whitelist.map \ /etc/rspamd/custom/global_smtp_from_whitelist.map \ /etc/rspamd/custom/bad_languages.map \ /etc/rspamd/custom/sa-rules \ /etc/rspamd/custom/dovecot_trusted.map \ /etc/rspamd/custom/rspamd_trusted.map \ /etc/rspamd/custom/mailcow_networks.map \ /etc/rspamd/custom/ip_wl.map \ /etc/rspamd/custom/fishy_tlds.map \ /etc/rspamd/custom/bad_words.map \ /etc/rspamd/custom/bad_asn.map \ /etc/rspamd/custom/bad_words_de.map \ /etc/rspamd/custom/bulk_header.map \ /etc/rspamd/custom/bad_header.map # www-data (82) group needs to write to these files chown _rspamd:_rspamd /etc/rspamd/custom/ chmod 0755 /etc/rspamd/custom/. chown -R 82:82 /etc/rspamd/custom/* chmod 644 -R /etc/rspamd/custom/* # Run hooks for file in /hooks/*; do if [ -x "${file}" ]; then echo "Running hook ${file}" "${file}" fi done # If DQS KEY is set in mailcow.conf add Spamhaus DQS RBLs if [[ ! -z ${SPAMHAUS_DQS_KEY} ]]; then cat < /etc/rspamd/custom/dqs-rbl.conf # Autogenerated by mailcow. DO NOT TOUCH! spamhaus { rbl = "${SPAMHAUS_DQS_KEY}.zen.dq.spamhaus.net"; from = false; } spamhaus_from { from = true; received = false; rbl = "${SPAMHAUS_DQS_KEY}.zen.dq.spamhaus.net"; returncodes { SPAMHAUS_ZEN = [ "127.0.0.2", "127.0.0.3", "127.0.0.4", "127.0.0.5", "127.0.0.6", "127.0.0.7", "127.0.0.9", "127.0.0.10", "127.0.0.11" ]; } } spamhaus_authbl_received { # Check if the sender client is listed in AuthBL (AuthBL is *not* part of ZEN) rbl = "${SPAMHAUS_DQS_KEY}.authbl.dq.spamhaus.net"; from = false; received = true; ipv6 = true; returncodes { SH_AUTHBL_RECEIVED = "127.0.0.20" } } spamhaus_dbl { # Add checks on the HELO string rbl = "${SPAMHAUS_DQS_KEY}.dbl.dq.spamhaus.net"; helo = true; rdns = true; dkim = true; disable_monitoring = true; returncodes { RBL_DBL_SPAM = "127.0.1.2"; RBL_DBL_PHISH = "127.0.1.4"; RBL_DBL_MALWARE = "127.0.1.5"; RBL_DBL_BOTNET = "127.0.1.6"; RBL_DBL_ABUSED_SPAM = "127.0.1.102"; RBL_DBL_ABUSED_PHISH = "127.0.1.104"; RBL_DBL_ABUSED_MALWARE = "127.0.1.105"; RBL_DBL_ABUSED_BOTNET = "127.0.1.106"; RBL_DBL_DONT_QUERY_IPS = "127.0.1.255"; } } spamhaus_dbl_fullurls { ignore_defaults = true; no_ip = true; rbl = "${SPAMHAUS_DQS_KEY}.dbl.dq.spamhaus.net"; selector = 'urls:get_host' disable_monitoring = true; returncodes { DBLABUSED_SPAM_FULLURLS = "127.0.1.102"; DBLABUSED_PHISH_FULLURLS = "127.0.1.104"; DBLABUSED_MALWARE_FULLURLS = "127.0.1.105"; DBLABUSED_BOTNET_FULLURLS = "127.0.1.106"; } } spamhaus_zrd { # Add checks on the HELO string also for DQS rbl = "${SPAMHAUS_DQS_KEY}.zrd.dq.spamhaus.net"; helo = true; rdns = true; dkim = true; disable_monitoring = true; returncodes { RBL_ZRD_VERY_FRESH_DOMAIN = ["127.0.2.2", "127.0.2.3", "127.0.2.4"]; RBL_ZRD_FRESH_DOMAIN = [ "127.0.2.5", "127.0.2.6", "127.0.2.7", "127.0.2.8", "127.0.2.9", "127.0.2.10", "127.0.2.11", "127.0.2.12", "127.0.2.13", "127.0.2.14", "127.0.2.15", "127.0.2.16", "127.0.2.17", "127.0.2.18", "127.0.2.19", "127.0.2.20", "127.0.2.21", "127.0.2.22", "127.0.2.23", "127.0.2.24" ]; RBL_ZRD_DONT_QUERY_IPS = "127.0.2.255"; } } "SPAMHAUS_ZEN_URIBL" { enabled = true; rbl = "${SPAMHAUS_DQS_KEY}.zen.dq.spamhaus.net"; resolve_ip = true; checks = ['urls']; replyto = true; emails = true; ipv4 = true; ipv6 = true; emails_domainonly = true; returncodes { URIBL_SBL = "127.0.0.2"; URIBL_SBL_CSS = "127.0.0.3"; URIBL_XBL = ["127.0.0.4", "127.0.0.5", "127.0.0.6", "127.0.0.7"]; URIBL_PBL = ["127.0.0.10", "127.0.0.11"]; URIBL_DROP = "127.0.0.9"; } } SH_EMAIL_DBL { ignore_defaults = true; replyto = true; emails_domainonly = true; disable_monitoring = true; rbl = "${SPAMHAUS_DQS_KEY}.dbl.dq.spamhaus.net"; returncodes = { SH_EMAIL_DBL = [ "127.0.1.2", "127.0.1.4", "127.0.1.5", "127.0.1.6" ]; SH_EMAIL_DBL_ABUSED = [ "127.0.1.102", "127.0.1.104", "127.0.1.105", "127.0.1.106" ]; SH_EMAIL_DBL_DONT_QUERY_IPS = [ "127.0.1.255" ]; } } SH_EMAIL_ZRD { ignore_defaults = true; replyto = true; emails_domainonly = true; disable_monitoring = true; rbl = "${SPAMHAUS_DQS_KEY}.zrd.dq.spamhaus.net"; returncodes = { SH_EMAIL_ZRD_VERY_FRESH_DOMAIN = ["127.0.2.2", "127.0.2.3", "127.0.2.4"]; SH_EMAIL_ZRD_FRESH_DOMAIN = [ "127.0.2.5", "127.0.2.6", "127.0.2.7", "127.0.2.8", "127.0.2.9", "127.0.2.10", "127.0.2.11", "127.0.2.12", "127.0.2.13", "127.0.2.14", "127.0.2.15", "127.0.2.16", "127.0.2.17", "127.0.2.18", "127.0.2.19", "127.0.2.20", "127.0.2.21", "127.0.2.22", "127.0.2.23", "127.0.2.24" ]; SH_EMAIL_ZRD_DONT_QUERY_IPS = [ "127.0.2.255" ]; } } "DBL" { # override the defaults for DBL defined in modules.d/rbl.conf rbl = "${SPAMHAUS_DQS_KEY}.dbl.dq.spamhaus.net"; disable_monitoring = true; } "ZRD" { ignore_defaults = true; rbl = "${SPAMHAUS_DQS_KEY}.zrd.dq.spamhaus.net"; no_ip = true; dkim = true; emails = true; emails_domainonly = true; urls = true; returncodes = { ZRD_VERY_FRESH_DOMAIN = ["127.0.2.2", "127.0.2.3", "127.0.2.4"]; ZRD_FRESH_DOMAIN = ["127.0.2.5", "127.0.2.6", "127.0.2.7", "127.0.2.8", "127.0.2.9", "127.0.2.10", "127.0.2.11", "127.0.2.12", "127.0.2.13", "127.0.2.14", "127.0.2.15", "127.0.2.16", "127.0.2.17", "127.0.2.18", "127.0.2.19", "127.0.2.20", "127.0.2.21", "127.0.2.22", "127.0.2.23", "127.0.2.24"]; } } spamhaus_sbl_url { ignore_defaults = true rbl = "${SPAMHAUS_DQS_KEY}.sbl.dq.spamhaus.net"; checks = ['urls']; disable_monitoring = true; returncodes { SPAMHAUS_SBL_URL = "127.0.0.2"; } } SH_HBL_EMAIL { ignore_defaults = true; rbl = "_email.${SPAMHAUS_DQS_KEY}.hbl.dq.spamhaus.net"; emails_domainonly = false; selector = "from('smtp').lower;from('mime').lower"; ignore_whitelist = true; checks = ['emails', 'replyto']; hash = "sha1"; returncodes = { SH_HBL_EMAIL = [ "127.0.3.2" ]; } } spamhaus_dqs_hbl { symbol = "HBL_FILE_UNKNOWN"; rbl = "_file.${SPAMHAUS_DQS_KEY}.hbl.dq.spamhaus.net."; selector = "attachments('rbase32', 'sha256')"; ignore_whitelist = true; ignore_defaults = true; returncodes { SH_HBL_FILE_MALICIOUS = "127.0.3.10"; SH_HBL_FILE_SUSPICIOUS = "127.0.3.15"; } } EOF else rm -rf /etc/rspamd/custom/dqs-rbl.conf fi exec "$@" ================================================ FILE: data/Dockerfiles/rspamd/sa_trivial_convert.lua ================================================ local fun = require "fun" local rspamd_logger = require "rspamd_logger" local util = require "rspamd_util" local lua_util = require "lua_util" local rspamd_regexp = require "rspamd_regexp" local ucl = require "ucl" local complicated = {} local rules = {} local scores = {} local function words_to_re(words, start) return table.concat(fun.totable(fun.drop_n(start, words)), " "); end local function split(str, delim) local result = {} if not delim then delim = '[^%s]+' end for token in string.gmatch(str, delim) do table.insert(result, token) end return result end local function handle_header_def(hline, cur_rule) --Now check for modifiers inside header's name local hdrs = split(hline, '[^|]+') local hdr_params = {} local cur_param = {} -- Check if an re is an ordinary re local ordinary = true for _,h in ipairs(hdrs) do if h == 'ALL' or h == 'ALL:raw' then ordinary = false else local args = split(h, '[^:]+') cur_param['strong'] = false cur_param['raw'] = false cur_param['header'] = args[1] if args[2] then -- We have some ops that are required for the header, so it's not ordinary ordinary = false end fun.each(function(func) if func == 'addr' then cur_param['function'] = function(str) local addr_parsed = util.parse_addr(str) local ret = {} if addr_parsed then for _,elt in ipairs(addr_parsed) do if elt['addr'] then table.insert(ret, elt['addr']) end end end return ret end elseif func == 'name' then cur_param['function'] = function(str) local addr_parsed = util.parse_addr(str) local ret = {} if addr_parsed then for _,elt in ipairs(addr_parsed) do if elt['name'] then table.insert(ret, elt['name']) end end end return ret end elseif func == 'raw' then cur_param['raw'] = true elseif func == 'case' then cur_param['strong'] = true else rspamd_logger.warnx(rspamd_config, 'Function %1 is not supported in %2', func, cur_rule['symbol']) end end, fun.tail(args)) -- Some header rules require splitting to check of multiple headers if cur_param['header'] == 'MESSAGEID' then -- Special case for spamassassin ordinary = false elseif cur_param['header'] == 'ToCc' then ordinary = false else table.insert(hdr_params, cur_param) end end cur_rule['ordinary'] = ordinary and (not (#hdr_params > 1)) cur_rule['header'] = hdr_params end end local function process_sa_conf(f) local cur_rule = {} local valid_rule = false local function insert_cur_rule() if not rules[cur_rule.type] then rules[cur_rule.type] = {} end local target = rules[cur_rule.type] if cur_rule.type == 'header' then if not cur_rule.header[1].header then rspamd_logger.errx(rspamd_config, 'bad rule definition: %1', cur_rule) return end if not target[cur_rule.header[1].header] then target[cur_rule.header[1].header] = {} end target = target[cur_rule.header[1].header] end if not cur_rule['symbol'] then rspamd_logger.errx(rspamd_config, 'bad rule definition: %1', cur_rule) return end target[cur_rule['symbol']] = cur_rule cur_rule = {} valid_rule = false end local function parse_score(words) if #words == 3 then -- score rule return tonumber(words[3]) elseif #words == 6 then -- score rule -- we assume here that bayes and network are enabled and select return tonumber(words[6]) else rspamd_logger.errx(rspamd_config, 'invalid score for %1', words[2]) end return 0 end local skip_to_endif = false local if_nested = 0 for l in f:lines() do (function () l = lua_util.rspamd_str_trim(l) -- Replace bla=~/re/ with bla =~ /re/ (#2372) l = l:gsub('([^%s])%s*([=!]~)%s*([^%s])', '%1 %2 %3') if string.len(l) == 0 or string.sub(l, 1, 1) == '#' then return end -- Unbalanced if/endif if if_nested < 0 then if_nested = 0 end if skip_to_endif then if string.match(l, '^endif') then if_nested = if_nested - 1 if if_nested == 0 then skip_to_endif = false end elseif string.match(l, '^if') then if_nested = if_nested + 1 elseif string.match(l, '^else') then -- Else counterpart for if skip_to_endif = false end table.insert(complicated, l) return else if string.match(l, '^ifplugin') then skip_to_endif = true if_nested = if_nested + 1 table.insert(complicated, l) elseif string.match(l, '^if !plugin%(') then skip_to_endif = true if_nested = if_nested + 1 table.insert(complicated, l) elseif string.match(l, '^if') then -- Unknown if skip_to_endif = true if_nested = if_nested + 1 table.insert(complicated, l) elseif string.match(l, '^else') then -- Else counterpart for if skip_to_endif = true table.insert(complicated, l) elseif string.match(l, '^endif') then if_nested = if_nested - 1 table.insert(complicated, l) end end -- Skip comments local words = fun.totable(fun.take_while( function(w) return string.sub(w, 1, 1) ~= '#' end, fun.filter(function(w) return w ~= "" end, fun.iter(split(l))))) if words[1] == "header" then -- header SYMBOL Header ~= /regexp/ if valid_rule then insert_cur_rule() end if words[4] and (words[4] == '=~' or words[4] == '!~') then cur_rule['type'] = 'header' cur_rule['symbol'] = words[2] if words[4] == '!~' then table.insert(complicated, l) return end cur_rule['re_expr'] = words_to_re(words, 4) local unset_comp = string.find(cur_rule['re_expr'], '%s+%[if%-unset:') if unset_comp then table.insert(complicated, l) return end cur_rule['re'] = rspamd_regexp.create(cur_rule['re_expr']) if not cur_rule['re'] then rspamd_logger.warnx(rspamd_config, "Cannot parse regexp '%1' for %2", cur_rule['re_expr'], cur_rule['symbol']) table.insert(complicated, l) return else handle_header_def(words[3], cur_rule) if not cur_rule['ordinary'] then table.insert(complicated, l) return end end valid_rule = true else table.insert(complicated, l) return end elseif words[1] == "body" then -- body SYMBOL /regexp/ if valid_rule then insert_cur_rule() end cur_rule['symbol'] = words[2] if words[3] and (string.sub(words[3], 1, 1) == '/' or string.sub(words[3], 1, 1) == 'm') then cur_rule['type'] = 'sabody' cur_rule['re_expr'] = words_to_re(words, 2) cur_rule['re'] = rspamd_regexp.create(cur_rule['re_expr']) if cur_rule['re'] then valid_rule = true end else -- might be function table.insert(complicated, l) return end elseif words[1] == "rawbody" then -- body SYMBOL /regexp/ if valid_rule then insert_cur_rule() end cur_rule['symbol'] = words[2] if words[3] and (string.sub(words[3], 1, 1) == '/' or string.sub(words[3], 1, 1) == 'm') then cur_rule['type'] = 'sarawbody' cur_rule['re_expr'] = words_to_re(words, 2) cur_rule['re'] = rspamd_regexp.create(cur_rule['re_expr']) if cur_rule['re'] then valid_rule = true end else table.insert(complicated, l) return end elseif words[1] == "full" then -- body SYMBOL /regexp/ if valid_rule then insert_cur_rule() end cur_rule['symbol'] = words[2] if words[3] and (string.sub(words[3], 1, 1) == '/' or string.sub(words[3], 1, 1) == 'm') then cur_rule['type'] = 'message' cur_rule['re_expr'] = words_to_re(words, 2) cur_rule['re'] = rspamd_regexp.create(cur_rule['re_expr']) cur_rule['raw'] = true if cur_rule['re'] then valid_rule = true end else table.insert(complicated, l) return end elseif words[1] == "uri" then -- uri SYMBOL /regexp/ if valid_rule then insert_cur_rule() end cur_rule['type'] = 'uri' cur_rule['symbol'] = words[2] cur_rule['re_expr'] = words_to_re(words, 2) cur_rule['re'] = rspamd_regexp.create(cur_rule['re_expr']) if cur_rule['re'] and cur_rule['symbol'] then valid_rule = true else table.insert(complicated, l) return end elseif words[1] == "meta" then -- meta SYMBOL expression if valid_rule then insert_cur_rule() end table.insert(complicated, l) return elseif words[1] == "describe" and valid_rule then cur_rule['description'] = words_to_re(words, 2) elseif words[1] == "score" then scores[words[2]] = parse_score(words) else table.insert(complicated, l) return end end)() end if valid_rule then insert_cur_rule() end end for _,matched in ipairs(arg) do local f = io.open(matched, "r") if f then rspamd_logger.messagex(rspamd_config, 'loading SA rules from %s', matched) process_sa_conf(f) else rspamd_logger.errx(rspamd_config, "cannot open %1", matched) end end local multimap_conf = {} local function handle_rule(what, syms, hdr) local mtype local filter local fname local header local sym = what:upper() if what == 'sabody' then mtype = 'content' fname = 'body_re.map' filter = 'oneline' elseif what == 'sarawbody' then fname = 'raw_body_re.map' mtype = 'content' filter = 'rawtext' elseif what == 'full' then fname = 'full_re.map' mtype = 'content' filter = 'full' elseif what == 'uri' then fname = 'uri_re.map' mtype = 'url' filter = 'full' elseif what == 'header' then fname = ('hdr_' .. hdr .. '_re.map'):lower() mtype = 'header' header = hdr sym = sym .. '_' .. hdr:upper() else rspamd_logger.errx('unknown type: %s', what) return end local conf = { type = mtype, filter = filter, symbol = 'SA_MAP_AUTO_' .. sym, regexp = true, map = fname, header = header, symbols = {} } local re_file = io.open(fname, 'w') for k,r in pairs(syms) do local score = 0.0 if scores[k] then score = scores[k] end re_file:write(string.format('/%s/ %s:%f\n', tostring(r.re), k, score)) table.insert(conf.symbols, k) end re_file:close() multimap_conf[sym:lower()] = conf rspamd_logger.messagex('stored %s regexp in %s', sym:lower(), fname) end for k,v in pairs(rules) do if k == 'header' then for h,r in pairs(v) do handle_rule(k, r, h) end else handle_rule(k, v) end end local out = ucl.to_format(multimap_conf, 'ucl') local mmap_conf = io.open('auto_multimap.conf', 'w') mmap_conf:write(out) mmap_conf:close() rspamd_logger.messagex('stored multimap conf in %s', 'auto_multimap.conf') local sa_remain = io.open('auto_sa.conf', 'w') fun.each(function(l) sa_remain:write(l) sa_remain:write('\n') end, fun.filter(function(l) return not string.match(l, '^%s+$') end, complicated)) sa_remain:close() rspamd_logger.messagex('stored sa remains conf in %s', 'auto_sa.conf') ================================================ FILE: data/Dockerfiles/rspamd/set_worker_password.sh ================================================ #!/bin/bash password_file='/etc/rspamd/override.d/worker-controller-password.inc' password_hash=`/usr/bin/rspamadm pw -e -p $1` echo 'enable_password = "'$password_hash'";' > $password_file if grep -q "$password_hash" "$password_file"; then echo "OK" else echo "ERROR" fi ================================================ FILE: data/Dockerfiles/rspamd/settings.conf ================================================ settings = "http://nginx:8081/settings.php"; ================================================ FILE: data/Dockerfiles/sogo/Dockerfile ================================================ # SOGo built from source to enable security patch application # Repository: https://github.com/Alinto/sogo # Version: SOGo-5.12.4 # # Applied security patches: # - # # To add new patches, modify SOGO_SECURITY_PATCHES ARG below with space-separated commit hashes FROM debian:bookworm LABEL maintainer="The Infrastructure Company GmbH " ARG DEBIAN_FRONTEND=noninteractive ARG SOGO_VERSION=SOGo-5.12.5 ARG SOPE_VERSION=SOPE-5.12.5 # Security patches to apply (space-separated commit hashes) ARG SOGO_SECURITY_PATCHES="" # renovate: datasource=github-releases depName=tianon/gosu versioning=semver-coerced extractVersion=^(?.*)$ ARG GOSU_VERSION=1.19 ENV LC_ALL=C # Install dependencies, build SOPE and SOGo, then clean up (all in one layer to minimize image size) RUN apt-get update && apt-get install -y --no-install-recommends \ # Build dependencies git \ build-essential \ gobjc \ pkg-config \ gnustep-make \ gnustep-base-runtime \ libgnustep-base-dev \ libxml2-dev \ libldap2-dev \ libssl-dev \ zlib1g-dev \ libpq-dev \ libmariadb-dev-compat \ libmemcached-dev \ libsodium-dev \ libcurl4-openssl-dev \ libzip-dev \ libytnef0-dev \ libwbxml2-dev \ curl \ ca-certificates \ # Runtime dependencies apt-transport-https \ gettext \ gnupg \ mariadb-client \ rsync \ supervisor \ syslog-ng \ syslog-ng-core \ syslog-ng-mod-redis \ dirmngr \ netcat-traditional \ psmisc \ wget \ patch \ libobjc4 \ libxml2 \ libldap-2.5-0 \ libssl3 \ zlib1g \ libmariadb3 \ libmemcached11 \ libsodium23 \ libcurl4 \ libzip4 \ libytnef0 \ libwbxml2-1 \ # Download gosu && dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')" \ && wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch" \ && chmod +x /usr/local/bin/gosu \ && gosu nobody true \ # Build SOPE && git clone --depth 1 --branch ${SOPE_VERSION} https://github.com/Alinto/sope.git /tmp/sope \ && cd /tmp/sope \ && rm -rf .git \ && . /usr/share/GNUstep/Makefiles/GNUstep.sh \ && ./configure --prefix=/usr --disable-debug --disable-strip \ && make -j$(nproc) \ && make install \ && cd / \ && rm -rf /tmp/sope \ # Build SOGo with security patches && git clone --depth 1 --branch ${SOGO_VERSION} https://github.com/Alinto/sogo.git /tmp/sogo \ && cd /tmp/sogo \ && git config user.email "builder@mailcow.local" \ && git config user.name "SOGo Builder" \ && for patch in ${SOGO_SECURITY_PATCHES}; do \ echo "Applying security patch: ${patch}"; \ git fetch origin ${patch} && git cherry-pick ${patch}; \ done \ && rm -rf .git \ && . /usr/share/GNUstep/Makefiles/GNUstep.sh \ && ./configure --disable-debug --disable-strip \ && make -j$(nproc) \ && make install \ && cd /tmp/sogo/ActiveSync \ && . /usr/share/GNUstep/Makefiles/GNUstep.sh \ && make -j$(nproc) install \ && cd / \ && rm -rf /tmp/sogo \ # Strip binaries && strip --strip-unneeded /usr/local/sbin/sogod 2>/dev/null || true \ && strip --strip-unneeded /usr/local/sbin/sogo-tool 2>/dev/null || true \ && strip --strip-unneeded /usr/local/sbin/sogo-ealarms-notify 2>/dev/null || true \ && strip --strip-unneeded /usr/local/sbin/sogo-slapd-sockd 2>/dev/null || true \ # Remove build dependencies and clean up && apt-get purge -y --auto-remove \ git \ build-essential \ gobjc \ gnustep-make \ libgnustep-base-dev \ libxml2-dev \ libldap2-dev \ libssl-dev \ zlib1g-dev \ libpq-dev \ libmariadb-dev-compat \ libmemcached-dev \ libsodium-dev \ libcurl4-openssl-dev \ libzip-dev \ libytnef0-dev \ curl \ && apt-get autoremove -y \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* \ && rm -rf /usr/share/doc/* \ && rm -rf /usr/share/man/* \ && rm -rf /var/cache/debconf/* \ && rm -rf /tmp/* \ && rm -rf /root/.cache \ && find /usr/local/lib -name '*.a' -delete \ && find /usr/lib -name '*.a' -delete \ && mkdir -p /usr/share/doc/sogo \ && touch /usr/share/doc/sogo/empty.sh \ && touch /etc/default/locale # Configure library paths RUN echo "/usr/lib64" > /etc/ld.so.conf.d/sogo.conf \ && echo "/usr/local/lib/sogo" >> /etc/ld.so.conf.d/sogo.conf \ && echo "/usr/local/lib/GNUstep/Frameworks/SOGo.framework/Versions/5/sogo" >> /etc/ld.so.conf.d/sogo.conf \ && ldconfig # Create sogo user and group RUN groupadd -r -g 999 sogo \ && useradd -r -u 999 -g sogo -d /var/lib/sogo -s /bin/bash -c "SOGo Daemon" sogo \ && mkdir -p /var/lib/sogo /var/run/sogo /var/log/sogo /var/spool/sogo \ && chown -R sogo:sogo /var/lib/sogo /var/run/sogo /var/log/sogo /var/spool/sogo # Create symlinks for SOGo binaries RUN ln -s /usr/local/sbin/sogod /usr/sbin/sogod \ && ln -s /usr/local/sbin/sogo-tool /usr/sbin/sogo-tool \ && ln -s /usr/local/sbin/sogo-ealarms-notify /usr/sbin/sogo-ealarms-notify \ && ln -s /usr/local/sbin/sogo-slapd-sockd /usr/sbin/sogo-slapd-sockd # Copy configuration files and scripts COPY ./bootstrap-sogo.sh /bootstrap-sogo.sh COPY syslog-ng.conf /etc/syslog-ng/syslog-ng.conf COPY syslog-ng-redis_slave.conf /etc/syslog-ng/syslog-ng-redis_slave.conf COPY supervisord.conf /etc/supervisor/supervisord.conf COPY acl.diff /acl.diff COPY navMailcowBtns.diff /navMailcowBtns.diff COPY stop-supervisor.sh /usr/local/sbin/stop-supervisor.sh COPY docker-entrypoint.sh / RUN chmod +x /bootstrap-sogo.sh \ /usr/local/sbin/stop-supervisor.sh ENTRYPOINT ["/docker-entrypoint.sh"] CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/supervisord.conf"] ================================================ FILE: data/Dockerfiles/sogo/acl.diff ================================================ --- /usr/local/lib/GNUstep/SOGo/Templates/UIxAclEditor.wox 2018-08-17 18:29:57.987504204 +0200 +++ /usr/local/lib/GNUstep/SOGo/Templates/UIxAclEditor.wox 2018-08-17 18:29:35.918291298 +0200 @@ -46,7 +46,7 @@ - ================================================ FILE: data/Dockerfiles/sogo/bootstrap-sogo.sh ================================================ #!/bin/bash # Wait for MySQL to warm-up while ! mariadb-admin status --ssl=false --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do echo "Waiting for database to come up..." sleep 2 done # Wait until port becomes free and send sig until ! nc -z sogo-mailcow 20000; do killall -TERM sogod sleep 3 done # Wait for updated schema DBV_NOW=$(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT version FROM versions WHERE application = 'db_schema';" -BN) DBV_NEW=$(grep -oE '\$db_version = .*;' init_db.inc.php | sed 's/$db_version = //g;s/;//g' | cut -d \" -f2) while [[ "${DBV_NOW}" != "${DBV_NEW}" ]]; do echo "Waiting for schema update..." DBV_NOW=$(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT version FROM versions WHERE application = 'db_schema';" -BN) DBV_NEW=$(grep -oE '\$db_version = .*;' init_db.inc.php | sed 's/$db_version = //g;s/;//g' | cut -d \" -f2) sleep 5 done echo "DB schema is ${DBV_NOW}" if [[ "${MASTER}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "DROP TRIGGER IF EXISTS sogo_update_password" fi # cat /dev/urandom seems to hang here occasionally and is not recommended anyway, better use openssl RAND_PASS=$(openssl rand -base64 16 | tr -dc _A-Z-a-z-0-9) # Generate plist header with timezone data mkdir -p /var/lib/sogo/GNUstep/Defaults/ cat < /var/lib/sogo/GNUstep/Defaults/sogod.plist OCSAclURL mysql://${DBUSER}:${DBPASS}@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/${DBNAME}/sogo_acl SOGoIMAPServer imap://${IPV4_NETWORK}.250:143/?TLS=YES&tlsVerifyMode=none SOGoSieveServer sieve://${IPV4_NETWORK}.250:4190/?TLS=YES&tlsVerifyMode=none SOGoSMTPServer smtp://${IPV4_NETWORK}.253:588/?TLS=YES&tlsVerifyMode=none SOGoTrustProxyAuthentication YES SOGoEncryptionKey ${RAND_PASS} OCSAdminURL mysql://${DBUSER}:${DBPASS}@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/${DBNAME}/sogo_admin OCSCacheFolderURL mysql://${DBUSER}:${DBPASS}@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/${DBNAME}/sogo_cache_folder OCSEMailAlarmsFolderURL mysql://${DBUSER}:${DBPASS}@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/${DBNAME}/sogo_alarms_folder OCSFolderInfoURL mysql://${DBUSER}:${DBPASS}@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/${DBNAME}/sogo_folder_info OCSSessionsFolderURL mysql://${DBUSER}:${DBPASS}@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/${DBNAME}/sogo_sessions_folder OCSStoreURL mysql://${DBUSER}:${DBPASS}@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/${DBNAME}/sogo_store SOGoProfileURL mysql://${DBUSER}:${DBPASS}@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/${DBNAME}/sogo_user_profile SOGoTimeZone ${TZ} domains EOF # Generate multi-domain setup while read -r line gal do echo " ${line} SOGoMailDomain ${line} SOGoUserSources MailFieldNames aliases ad_aliases ext_acl KindFieldName kind DomainFieldName domain MultipleBookingsFieldName multiple_bookings listRequiresDot NO canAuthenticate YES displayName GAL ${line} id ${line} isAddressBook ${gal} type sql userPasswordAlgorithm ${MAILCOW_PASS_SCHEME} prependPasswordScheme YES viewURL mysql://${DBUSER}:${DBPASS}@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/${DBNAME}/_sogo_static_view " >> /var/lib/sogo/GNUstep/Defaults/sogod.plist # Generate alternative LDAP authentication dict, when SQL authentication fails # This will nevertheless read attributes from LDAP /etc/sogo/plist_ldap.sh ${line} ${gal} >> /var/lib/sogo/GNUstep/Defaults/sogod.plist echo " " >> /var/lib/sogo/GNUstep/Defaults/sogod.plist done < <(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT domain, CASE gal WHEN '1' THEN 'YES' ELSE 'NO' END AS gal FROM domain;" -B -N) # Generate footer echo ' ' >> /var/lib/sogo/GNUstep/Defaults/sogod.plist # Fix permissions chown sogo:sogo -R /var/lib/sogo/ chmod 600 /var/lib/sogo/GNUstep/Defaults/sogod.plist # Patch ACLs #if [[ ${ACL_ANYONE} == 'allow' ]]; then # #enable any or authenticated targets for ACL # if patch -R -sfN --dry-run /usr/local/lib/GNUstep/SOGo/Templates/UIxAclEditor.wox < /acl.diff > /dev/null; then # patch -R /usr/local/lib/GNUstep/SOGo/Templates/UIxAclEditor.wox < /acl.diff; # fi #else # #disable any or authenticated targets for ACL # if patch -sfN --dry-run /usr/local/lib/GNUstep/SOGo/Templates/UIxAclEditor.wox < /acl.diff > /dev/null; then # patch /usr/local/lib/GNUstep/SOGo/Templates/UIxAclEditor.wox < /acl.diff; # fi #fi # Apply custom UI patch (reverse patch to ADD buttons) if patch -R -sfN --dry-run /usr/local/lib/GNUstep/SOGo/Templates/UIxTopnavToolbar.wox < /navMailcowBtns.diff > /dev/null; then echo "Applying navMailcowBtns patch (reverse to add buttons)..." patch -R /usr/local/lib/GNUstep/SOGo/Templates/UIxTopnavToolbar.wox < /navMailcowBtns.diff; else echo "navMailcowBtns patch already applied or cannot be applied" fi # Rename custom logo, if any [[ -f /etc/sogo/sogo-full.svg ]] && mv /etc/sogo/sogo-full.svg /etc/sogo/custom-fulllogo.svg # Rsync web content echo "Syncing web content with named volume" rsync -a /usr/local/lib/GNUstep/SOGo/. /sogo_web/ # Chown backup path chown -R sogo:sogo /sogo_backup exec gosu sogo /usr/sbin/sogod ================================================ FILE: data/Dockerfiles/sogo/docker-entrypoint.sh ================================================ #!/bin/bash if [[ "${SKIP_SOGO}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then echo "SKIP_SOGO=y, skipping SOGo..." sleep 365d exit 0 fi if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then cp /etc/syslog-ng/syslog-ng-redis_slave.conf /etc/syslog-ng/syslog-ng.conf fi echo "$TZ" > /etc/timezone # Run hooks for file in /hooks/*; do if [ -x "${file}" ]; then echo "Running hook ${file}" "${file}" fi done exec "$@" ================================================ FILE: data/Dockerfiles/sogo/navMailcowBtns.diff ================================================ 60,65d58 < var:ng-click="navButtonClick" < ng-href="/user"> < build < mailcow < < ng-show="::activeUser.path.logoff.length" 85c78 < ng-href="#"> --- > ng-href="{{::activeUser.path.logoff}}"> ================================================ FILE: data/Dockerfiles/sogo/stop-supervisor.sh ================================================ #!/bin/bash printf "READY\n"; while read line; do echo "Processing Event: $line" >&2; kill -3 $(cat "/var/run/supervisord.pid") done < /dev/stdin ================================================ FILE: data/Dockerfiles/sogo/supervisord.conf ================================================ [supervisord] nodaemon=true user=root [program:syslog-ng] command=/usr/sbin/syslog-ng --foreground --no-caps stdout_logfile=/dev/stdout stdout_logfile_maxbytes=0 stderr_logfile=/dev/stderr stderr_logfile_maxbytes=0 autostart=true priority=1 [program:bootstrap-sogo] command=/bootstrap-sogo.sh stdout_logfile=/dev/stdout stdout_logfile_maxbytes=0 stderr_logfile=/dev/stderr stderr_logfile_maxbytes=0 priority=2 startretries=10 autorestart=true stopwaitsecs=120 [eventlistener:processes] command=/usr/local/sbin/stop-supervisor.sh events=PROCESS_STATE_STOPPED, PROCESS_STATE_EXITED, PROCESS_STATE_FATAL ================================================ FILE: data/Dockerfiles/sogo/syslog-ng-redis_slave.conf ================================================ @version: 3.38 @include "scl.conf" options { chain_hostnames(off); flush_lines(0); use_dns(no); use_fqdn(no); owner("root"); group("adm"); perm(0640); stats_freq(0); bad_hostname("^gconfd$"); }; source s_src { unix-stream("/dev/log"); internal(); }; source s_sogo { pipe("/dev/sogo_log" owner(sogo) group(sogo)); }; destination d_stdout { pipe("/dev/stdout"); }; destination d_redis_ui_log { redis( host("`REDIS_SLAVEOF_IP`") persist-name("redis1") port(`REDIS_SLAVEOF_PORT`) auth("`REDISPASS`") command("LPUSH" "SOGO_LOG" "$(format-json time=\"$S_UNIXTIME\" priority=\"$PRIORITY\" program=\"$PROGRAM\" message=\"$MESSAGE\")\n") ); }; destination d_redis_f2b_channel { redis( host("`REDIS_SLAVEOF_IP`") persist-name("redis2") port(`REDIS_SLAVEOF_PORT`) auth("`REDISPASS`") command("PUBLISH" "F2B_CHANNEL" "$(sanitize $MESSAGE)") ); }; log { source(s_sogo); destination(d_redis_ui_log); destination(d_redis_f2b_channel); }; log { source(s_sogo); source(s_src); destination(d_stdout); }; ================================================ FILE: data/Dockerfiles/sogo/syslog-ng.conf ================================================ @version: 3.38 @include "scl.conf" options { chain_hostnames(off); flush_lines(0); use_dns(no); use_fqdn(no); owner("root"); group("adm"); perm(0640); stats_freq(0); bad_hostname("^gconfd$"); }; source s_src { unix-stream("/dev/log"); internal(); }; source s_sogo { pipe("/dev/sogo_log" owner(sogo) group(sogo)); }; destination d_stdout { pipe("/dev/stdout"); }; destination d_redis_ui_log { redis( host("redis-mailcow") persist-name("redis1") port(6379) auth("`REDISPASS`") command("LPUSH" "SOGO_LOG" "$(format-json time=\"$S_UNIXTIME\" priority=\"$PRIORITY\" program=\"$PROGRAM\" message=\"$MESSAGE\")\n") ); }; destination d_redis_f2b_channel { redis( host("redis-mailcow") persist-name("redis2") port(6379) auth("`REDISPASS`") command("PUBLISH" "F2B_CHANNEL" "$(sanitize $MESSAGE)") ); }; log { source(s_sogo); destination(d_redis_ui_log); destination(d_redis_f2b_channel); }; log { source(s_sogo); source(s_src); destination(d_stdout); }; ================================================ FILE: data/Dockerfiles/unbound/Dockerfile ================================================ FROM alpine:3.23 LABEL maintainer = "The Infrastructure Company GmbH " RUN apk add --update --no-cache \ curl \ bind-tools \ coreutils \ unbound \ bash \ openssl \ drill \ tzdata \ syslog-ng \ supervisor \ && curl -o /etc/unbound/root.hints https://www.internic.net/domain/named.cache \ && chown root:unbound /etc/unbound \ && adduser unbound tty \ && chmod 775 /etc/unbound EXPOSE 53/udp 53/tcp COPY docker-entrypoint.sh /docker-entrypoint.sh # healthcheck (dig, ping) COPY healthcheck.sh /healthcheck.sh COPY syslog-ng.conf /etc/syslog-ng/syslog-ng.conf COPY supervisord.conf /etc/supervisor/supervisord.conf COPY stop-supervisor.sh /usr/local/sbin/stop-supervisor.sh RUN chmod +x /healthcheck.sh HEALTHCHECK --interval=30s --timeout=10s \ CMD sh -c '[ -f /tmp/healthcheck_status ] && [ "$(cat /tmp/healthcheck_status)" -eq 0 ] || exit 1' ENTRYPOINT ["/docker-entrypoint.sh"] CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/supervisord.conf"] ================================================ FILE: data/Dockerfiles/unbound/docker-entrypoint.sh ================================================ #!/bin/bash echo "Setting console permissions..." chown root:tty /dev/console chmod g+rw /dev/console echo "Receiving anchor key..." /usr/sbin/unbound-anchor -a /etc/unbound/trusted-key.key echo "Receiving root hints..." curl -#o /etc/unbound/root.hints https://www.internic.net/domain/named.cache /usr/sbin/unbound-control-setup # Run hooks for file in /hooks/*; do if [ -x "${file}" ]; then echo "Running hook ${file}" "${file}" fi done exec "$@" ================================================ FILE: data/Dockerfiles/unbound/healthcheck.sh ================================================ #!/bin/bash STATUS_FILE="/tmp/healthcheck_status" RUNS=0 # Declare log function for logfile to stdout function log_to_stdout() { echo "$(date +"%Y-%m-%d %H:%M:%S"): $1" } # General Ping function to check general pingability function check_ping() { declare -a ipstoping=("1.1.1.1" "8.8.8.8" "9.9.9.9") local fail_tolerance=1 local failures=0 for ip in "${ipstoping[@]}" ; do success=false for ((i=1; i<=3; i++)); do ping -q -c 3 -w 5 "$ip" > /dev/null if [ $? -eq 0 ]; then success=true break else log_to_stdout "Healthcheck: Failed to ping $ip on attempt $i. Trying again..." fi done if [ "$success" = false ]; then log_to_stdout "Healthcheck: Couldn't ping $ip after 3 attempts. Marking this IP as failed." ((failures++)) fi done if [ $failures -gt $fail_tolerance ]; then log_to_stdout "Healthcheck: Too many ping failures ($fail_tolerance failures allowed, you got $failures failures), marking Healthcheck as unhealthy..." return 1 fi return 0 } # General DNS Resolve Check against Unbound Resolver himself function check_dns() { declare -a domains=("fuzzy.mailcow.email" "github.com" "hub.docker.com") local fail_tolerance=1 local failures=0 for domain in "${domains[@]}" ; do success=false for ((i=1; i<=3; i++)); do dig_output=$(dig +short +timeout=2 +tries=1 "$domain" @127.0.0.1 2>/dev/null) dig_rc=$? if [ $dig_rc -ne 0 ] || [ -z "$dig_output" ]; then log_to_stdout "Healthcheck: DNS Resolution Failed on attempt $i for $domain! Trying again..." else success=true break fi done if [ "$success" = false ]; then log_to_stdout "Healthcheck: DNS Resolution not possible after 3 attempts for $domain... Gave up!" ((failures++)) fi done if [ $failures -gt $fail_tolerance ]; then log_to_stdout "Healthcheck: Too many DNS failures ($fail_tolerance failures allowed, you got $failures failures), marking Healthcheck as unhealthy..." return 1 fi return 0 } while true; do if [[ ${SKIP_UNBOUND_HEALTHCHECK} == "y" ]]; then log_to_stdout "Healthcheck: ALL CHECKS WERE SKIPPED! Unbound is healthy!" echo "0" > $STATUS_FILE sleep 365d fi # run checks, if check is not returning 0 (return value if check is ok), healthcheck will exit with 1 (marked in docker as unhealthy) check_ping PING_STATUS=$? check_dns DNS_STATUS=$? if [ $PING_STATUS -ne 0 ] || [ $DNS_STATUS -ne 0 ]; then echo "1" > $STATUS_FILE else echo "0" > $STATUS_FILE fi sleep 30 done ================================================ FILE: data/Dockerfiles/unbound/stop-supervisor.sh ================================================ #!/bin/bash printf "READY\n"; while read line; do echo "Processing Event: $line" >&2; kill -3 $(cat "/var/run/supervisord.pid") done < /dev/stdin rm -rf /tmp/healthcheck_status ================================================ FILE: data/Dockerfiles/unbound/supervisord.conf ================================================ [supervisord] nodaemon=true user=root pidfile=/var/run/supervisord.pid [program:syslog-ng] command=/usr/sbin/syslog-ng --foreground --no-caps stdout_logfile=/dev/stdout stdout_logfile_maxbytes=0 stderr_logfile=/dev/stderr stderr_logfile_maxbytes=0 autostart=true [program:unbound] command=/usr/sbin/unbound stdout_logfile=/dev/stdout stdout_logfile_maxbytes=0 stderr_logfile=/dev/stderr stderr_logfile_maxbytes=0 autorestart=true [program:unbound-healthcheck] command=/bin/bash /healthcheck.sh stdout_logfile=/dev/stdout stdout_logfile_maxbytes=0 stderr_logfile=/dev/stderr stderr_logfile_maxbytes=0 autorestart=true [eventlistener:processes] command=/usr/local/sbin/stop-supervisor.sh events=PROCESS_STATE_STOPPED, PROCESS_STATE_EXITED, PROCESS_STATE_FATAL ================================================ FILE: data/Dockerfiles/unbound/syslog-ng.conf ================================================ @version: 4.5 @include "scl.conf" options { chain_hostnames(off); flush_lines(0); use_dns(no); use_fqdn(no); owner("root"); group("adm"); perm(0640); stats(freq(0)); keep_timestamp(no); bad_hostname("^gconfd$"); }; source s_dgram { unix-dgram("/dev/log"); internal(); }; destination d_stdout { pipe("/dev/stdout"); }; log { source(s_dgram); destination(d_stdout); }; ================================================ FILE: data/Dockerfiles/watchdog/Dockerfile ================================================ FROM alpine:3.23 LABEL maintainer = "The Infrastructure Company GmbH " # Installation RUN apk add --update \ && apk add --no-cache nagios-plugins-smtp \ nagios-plugins-tcp \ nagios-plugins-http \ nagios-plugins-ping \ mariadb-client \ curl \ bash \ coreutils \ jq \ fcgi \ openssl \ nagios-plugins-mysql \ nagios-plugins-disk \ bind-tools \ redis \ perl \ perl-net-dns \ perl-io-socket-ssl \ perl-io-socket-inet6 \ perl-socket \ perl-socket6 \ perl-mime-lite \ perl-term-readkey \ tini \ tzdata \ whois \ && curl https://raw.githubusercontent.com/mludvig/smtp-cli/v3.10/smtp-cli -o /smtp-cli \ && chmod +x smtp-cli \ && mkdir /usr/lib/mailcow COPY watchdog.sh /watchdog.sh COPY check_mysql_slavestatus.sh /usr/lib/nagios/plugins/check_mysql_slavestatus.sh COPY check_dns.sh /usr/lib/mailcow/check_dns.sh COPY client.cnf /etc/my.cnf.d/client.cnf CMD ["/watchdog.sh"] ================================================ FILE: data/Dockerfiles/watchdog/check_dns.sh ================================================ #!/bin/sh while getopts "H:s:" opt; do case "$opt" in H) HOST="$OPTARG" ;; s) SERVER="$OPTARG" ;; *) echo "Usage: $0 -H host -s server"; exit 3 ;; esac done if [ -z "$SERVER" ]; then echo "No DNS Server provided" exit 3 fi if [ -z "$HOST" ]; then echo "No host to test provided" exit 3 fi # run dig and measure the time it takes to run START_TIME=$(perl -MTime::HiRes -e 'print Time::HiRes::time') dig_output=$(dig +short +timeout=2 +tries=1 "$HOST" @"$SERVER" 2>/dev/null) dig_rc=$? END_TIME=$(perl -MTime::HiRes -e 'print Time::HiRes::time') dig_output_ips=$(echo "$dig_output" | grep -E '^[0-9.]+$' | sort | paste -sd ',' -) ELAPSED_TIME=$(perl -e "printf('%.3f', $END_TIME - $START_TIME)") # validate and perform nagios like output and exit codes if [ $dig_rc -ne 0 ] || [ -z "$dig_output" ]; then echo "Domain $HOST was not found by the server" exit 2 elif [ $dig_rc -eq 0 ]; then echo "DNS OK: $ELAPSED_TIME seconds response time. $HOST returns $dig_output_ips" exit 0 else echo "Unknown error" exit 3 fi ================================================ FILE: data/Dockerfiles/watchdog/check_mysql_slavestatus.sh ================================================ #!/bin/bash ######################################################################### # Script: check_mysql_slavestatus.sh # # Author: Claudio Kuenzler www.claudiokuenzler.com # # Purpose: Monitor MySQL Replication status with Nagios # # Description: Connects to given MySQL hosts and checks for running # # SLAVE state and delivers additional info # # Original: This script is a modified version of # # check mysql slave sql running written by dhirajt # # Thanks to: Victor Balada Diaz for his ideas added on 20080930 # # Soren Klintrup for stuff added on 20081015 # # Marc Feret for Slave_IO_Running check 20111227 # # Peter Lecki for his mods added on 20120803 # # Serge Victor for his mods added on 20131223 # # Omri Bahumi for his fix added on 20131230 # # Marc Falzon for his option mods added on 20190822 # # Andreas Pfeiffer for adding socket option on 20190822 # # History: # # 2008041700 Original Script modified # # 2008041701 Added additional info if status OK # # 2008041702 Added usage of script with params -H -u -p # # 2008041703 Added bindir variable for multiple platforms # # 2008041704 Added help because mankind needs help # # 2008093000 Using /bin/sh instead of /bin/bash # # 2008093001 Added port for MySQL server # # 2008093002 Added mysqldir if mysql binary is elsewhere # # 2008101501 Changed bindir/mysqldir to use PATH # # 2008101501 Use $() instead of `` to avoid forks # # 2008101501 Use ${} for variables to prevent problems # # 2008101501 Check if required commands exist # # 2008101501 Check if mysql connection works # # 2008101501 Exit with unknown status at script end # # 2008101501 Also display help if no option is given # # 2008101501 Add warning/critical check to delay # # 2011062200 Add perfdata # # 2011122700 Checking Slave_IO_Running # # 2012080300 Changed to use only one mysql query # # 2012080301 Added warn and crit delay as optional args # # 2012080302 Added standard -h option for syntax help # # 2012080303 Added check for mandatory options passed in # # 2012080304 Added error output from mysql # # 2012080305 Changed from 'cut' to 'awk' (eliminate ws) # # 2012111600 Do not show password in error output # # 2013042800 Changed PATH to use existing PATH, too # # 2013050800 Bugfix in PATH export # # 2013092700 Bugfix in PATH export # # 2013092701 Bugfix in getopts # # 2013101600 Rewrite of threshold logic and handling # # 2013101601 Optical clean up # # 2013101602 Rewrite help output # # 2013101700 Handle Slave IO in 'Connecting' state # # 2013101701 Minor changes in output, handling UNKNOWN situations now # # 2013101702 Exit CRITICAL when Slave IO in Connecting state # # 2013123000 Slave_SQL_Running also matched Slave_SQL_Running_State # # 2015011600 Added 'moving' check to catch possible connection issues # # 2015011900 Use its own threshold for replication moving check # # 2019082200 Add support for mysql option file # # 2019082201 Improve password security (remove from mysql cli) # # 2019082202 Added socket parameter (-S) # # 2019082203 Use default port 3306, makes -P optional # # 2019082204 Fix moving subcheck, improve documentation # ######################################################################### # Usage: ./check_mysql_slavestatus.sh (-o file|(-H dbhost [-P port]|-S socket) -u dbuser -p dbpass) [-s connection] [-w integer] [-c integer] [-m integer] ######################################################################### help="\ncheck_mysql_slavestatus.sh (c) 2008-2019 GNU GPLv2 licence Usage: $0 (-o file|(-H dbhost [-P port]|-S socket) -u username -p password) [-s connection] [-w integer] [-c integer] [-m]\n Options:\n-o Path to option file containing connection settings (e.g. /home/nagios/.my.cnf). Note: If this option is used, -H, -u, -p parameters will become optional\n-H Hostname or IP of slave server\n-P MySQL Port of slave server (optional, defaults to 3306)\n-u Username of DB-user\n-p Password of DB-user\n-S database socket\n-s Connection name (optional, with multi-source replication)\n-w Replication delay in seconds for Warning status (optional)\n-c Replication delay in seconds for Critical status (optional)\n-m Threshold in seconds since when replication did not move (compares the slaves log position)\n Attention: The DB-user you type in must have CLIENT REPLICATION rights on the DB-server. Example:\n\tGRANT REPLICATION CLIENT on *.* TO 'nagios'@'%' IDENTIFIED BY 'secret';" STATE_OK=0 # define the exit code if status is OK STATE_WARNING=1 # define the exit code if status is Warning (not really used) STATE_CRITICAL=2 # define the exit code if status is Critical STATE_UNKNOWN=3 # define the exit code if status is Unknown export PATH=$PATH:/usr/local/bin:/usr/bin:/bin # Set path crit="No" # what is the answer of MySQL Slave_SQL_Running for a Critical status? ok="Yes" # what is the answer of MySQL Slave_SQL_Running for an OK status? port="-P 3306" # on which tcp port is the target MySQL slave listening? for cmd in mysql awk grep expr [ do if ! `which ${cmd} &>/dev/null` then echo "UNKNOWN: This script requires the command '${cmd}' but it does not exist; please check if command exists and PATH is correct" exit ${STATE_UNKNOWN} fi done # Check for people who need help ######################################################################### if [ "${1}" = "--help" -o "${#}" = "0" ]; then echo -e "${help}"; exit 1; fi # Important given variables for the DB-Connect ######################################################################### while getopts "H:P:u:p:S:s:w:c:o:m:h" Input; do case ${Input} in H) host="-h ${OPTARG}";slavetarget=${OPTARG};; P) port="-P ${OPTARG}";; u) user="-u ${OPTARG}";; p) password="${OPTARG}"; export MYSQL_PWD="${OPTARG}";; S) socket="-S ${OPTARG}";; s) connection=\"${OPTARG}\";; w) warn_delay=${OPTARG};; c) crit_delay=${OPTARG};; o) optfile="--defaults-extra-file=${OPTARG}";; m) moving=${OPTARG};; h) echo -e "${help}"; exit 1;; \?) echo "Wrong option given. Check help (-h, --help) for usage." exit 1 ;; esac done # Check if we can write to tmp ######################################################################### test -w /tmp && tmpfile="/tmp/mysql_slave_${slavetarget}_pos.txt" # Connect to the DB server and check for informations ######################################################################### # Check whether all required arguments were passed in (either option file or full connection settings) if [[ -z "${optfile}" && -z "${host}" && -z "${socket}" ]]; then echo -e "Missing required parameter(s)"; exit ${STATE_UNKNOWN} elif [[ -n "${host}" && (-z "${user}" || -z "${password}") ]]; then echo -e "Missing required parameter(s)"; exit ${STATE_UNKNOWN} elif [[ -n "${socket}" && (-z "${user}" || -z "${password}") ]]; then echo -e "Missing required parameter(s)"; exit ${STATE_UNKNOWN} fi # Connect to the DB server and store output in vars if [[ -n $socket ]]; then ConnectionResult=$(mariadb --skip-ssl ${optfile} ${socket} ${user} -e "show slave ${connection} status\G" 2>&1) else ConnectionResult=$(mariadb --skip-ssl ${optfile} ${host} ${port} ${user} -e "show slave ${connection} status\G" 2>&1) fi if [ -z "`echo "${ConnectionResult}" |grep Slave_IO_State`" ]; then echo -e "CRITICAL: Unable to connect to server" exit ${STATE_CRITICAL} fi check=`echo "${ConnectionResult}" |grep Slave_SQL_Running: | awk '{print $2}'` checkio=`echo "${ConnectionResult}" |grep Slave_IO_Running: | awk '{print $2}'` masterinfo=`echo "${ConnectionResult}" |grep Master_Host: | awk '{print $2}'` delayinfo=`echo "${ConnectionResult}" |grep Seconds_Behind_Master: | awk '{print $2}'` readpos=`echo "${ConnectionResult}" |grep Read_Master_Log_Pos: | awk '{print $2}'` execpos=`echo "${ConnectionResult}" |grep Exec_Master_Log_Pos: | awk '{print $2}'` # Output of different exit states ######################################################################### if [ ${check} = "NULL" ]; then echo "CRITICAL: Slave_SQL_Running is answering NULL"; exit ${STATE_CRITICAL}; fi if [ ${check} = ${crit} ]; then echo "CRITICAL: ${host}:${port} Slave_SQL_Running: ${check}"; exit ${STATE_CRITICAL}; fi if [ ${checkio} = ${crit} ]; then echo "CRITICAL: ${host} Slave_IO_Running: ${checkio}"; exit ${STATE_CRITICAL}; fi if [ ${checkio} = "Connecting" ]; then echo "CRITICAL: ${host} Slave_IO_Running: ${checkio}"; exit ${STATE_CRITICAL}; fi if [ ${check} = ${ok} ] && [ ${checkio} = ${ok} ]; then # Delay thresholds are set if [[ -n ${warn_delay} ]] && [[ -n ${crit_delay} ]]; then if ! [[ ${warn_delay} -gt 0 ]]; then echo "Warning threshold must be a valid integer greater than 0"; exit $STATE_UNKNOWN; fi if ! [[ ${crit_delay} -gt 0 ]]; then echo "Warning threshold must be a valid integer greater than 0"; exit $STATE_UNKNOWN; fi if [[ -z ${warn_delay} ]] || [[ -z ${crit_delay} ]]; then echo "Both warning and critical thresholds must be set"; exit $STATE_UNKNOWN; fi if [[ ${warn_delay} -gt ${crit_delay} ]]; then echo "Warning threshold cannot be greater than critical"; exit $STATE_UNKNOWN; fi if [[ ${delayinfo} -ge ${crit_delay} ]] then echo "CRITICAL: Slave is ${delayinfo} seconds behind Master | delay=${delayinfo}s"; exit ${STATE_CRITICAL} elif [[ ${delayinfo} -ge ${warn_delay} ]] then echo "WARNING: Slave is ${delayinfo} seconds behind Master | delay=${delayinfo}s"; exit ${STATE_WARNING} else # Everything looks OK here but now let us check if the replication is moving if [[ -n ${moving} ]] && [[ -n ${tmpfile} ]] && [[ $readpos -eq $execpos ]] then #echo "Debug: Read pos is $readpos - Exec pos is $execpos" # Check if tmp file exists curtime=`date +%s` if [[ -w $tmpfile ]] then tmpfiletime=`date +%s -r $tmpfile` if [[ `expr $curtime - $tmpfiletime` -gt ${moving} ]] then exectmp=`cat $tmpfile` #echo "Debug: Exec pos in tmpfile is $exectmp" if [[ $exectmp -eq $execpos ]] then # The value read from the tmp file and from db are the same. Replication hasnt moved! echo "WARNING: Slave replication has not moved in ${moving} seconds. Manual check required."; exit ${STATE_WARNING} else # Replication has moved since the tmp file was written. Delete tmp file and output OK. rm $tmpfile echo "OK: Slave SQL running: ${check} Slave IO running: ${checkio} / master: ${masterinfo} / slave is ${delayinfo} seconds behind master | delay=${delayinfo}s"; exit ${STATE_OK}; fi else echo "OK: Slave SQL running: ${check} Slave IO running: ${checkio} / master: ${masterinfo} / slave is ${delayinfo} seconds behind master | delay=${delayinfo}s"; exit ${STATE_OK}; fi else echo "$execpos" > $tmpfile echo "OK: Slave SQL running: ${check} Slave IO running: ${checkio} / master: ${masterinfo} / slave is ${delayinfo} seconds behind master | delay=${delayinfo}s"; exit ${STATE_OK}; fi else # Everything OK (no additional moving check) echo "OK: Slave SQL running: ${check} Slave IO running: ${checkio} / master: ${masterinfo} / slave is ${delayinfo} seconds behind master | delay=${delayinfo}s"; exit ${STATE_OK}; fi fi else # Without delay thresholds echo "OK: Slave SQL running: ${check} Slave IO running: ${checkio} / master: ${masterinfo} / slave is ${delayinfo} seconds behind master | delay=${delayinfo}s" exit ${STATE_OK}; fi fi echo "UNKNOWN: should never reach this part (Slave_SQL_Running is ${check}, Slave_IO_Running is ${checkio})" exit ${STATE_UNKNOWN} ================================================ FILE: data/Dockerfiles/watchdog/client.cnf ================================================ [client] ssl = false ssl-verify-server-cert = false ================================================ FILE: data/Dockerfiles/watchdog/watchdog.sh ================================================ #!/bin/bash if [ "${DEV_MODE}" != "n" ]; then echo -e "\e[31mEnabled Debug Mode\e[0m" set -x fi trap "exit" INT TERM trap "kill 0" EXIT # Prepare BACKGROUND_TASKS=() echo "Waiting for containers to settle..." for i in {30..1}; do echo "${i}" sleep 1 done if [[ "${USE_WATCHDOG}" =~ ^([nN][oO]|[nN])+$ ]]; then echo -e "$(date) - USE_WATCHDOG=n, skipping watchdog..." sleep 365d exec $(readlink -f "$0") fi if [[ "${WATCHDOG_VERBOSE}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then SMTP_VERBOSE="--verbose" CURL_VERBOSE="--verbose" set -xv else SMTP_VERBOSE="" CURL_VERBOSE="" exec 2>/dev/null fi # Checks pipe their corresponding container name in this pipe if [[ ! -p /tmp/com_pipe ]]; then mkfifo /tmp/com_pipe fi # Wait for containers while ! mariadb-admin status --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do echo "Waiting for SQL..." sleep 2 done # Do not attempt to write to slave if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then REDIS_CMDLINE="redis-cli -h ${REDIS_SLAVEOF_IP} -p ${REDIS_SLAVEOF_PORT} -a ${REDISPASS} --no-auth-warning" else REDIS_CMDLINE="redis-cli -h redis -p 6379 -a ${REDISPASS} --no-auth-warning" fi until [[ $(${REDIS_CMDLINE} PING) == "PONG" ]]; do echo "Waiting for Redis..." sleep 2 done ${REDIS_CMDLINE} DEL F2B_RES > /dev/null # Common functions get_ipv6(){ local IPV6= local IPV6_SRCS= local TRY= IPV6_SRCS[0]="ip6.mailcow.email" IPV6_SRCS[1]="ip6.nevondo.com" until [[ ! -z ${IPV6} ]] || [[ ${TRY} -ge 10 ]]; do IPV6=$(curl --connect-timeout 3 -m 10 -L6s ${IPV6_SRCS[$RANDOM % ${#IPV6_SRCS[@]} ]} | grep "^\([0-9a-fA-F]\{0,4\}:\)\{1,7\}[0-9a-fA-F]\{0,4\}$") [[ ! -z ${TRY} ]] && sleep 1 TRY=$((TRY+1)) done echo ${IPV6} } array_diff() { # https://stackoverflow.com/questions/2312762, Alex Offshore eval local ARR1=\(\"\${$2[@]}\"\) eval local ARR2=\(\"\${$3[@]}\"\) local IFS=$'\n' mapfile -t $1 < <(comm -23 <(echo "${ARR1[*]}" | sort) <(echo "${ARR2[*]}" | sort)) } progress() { SERVICE=${1} TOTAL=${2} CURRENT=${3} DIFF=${4} [[ -z ${DIFF} ]] && DIFF=0 [[ -z ${TOTAL} || -z ${CURRENT} ]] && return [[ ${CURRENT} -gt ${TOTAL} ]] && return [[ ${CURRENT} -lt 0 ]] && CURRENT=0 PERCENT=$(( 200 * ${CURRENT} / ${TOTAL} % 2 + 100 * ${CURRENT} / ${TOTAL} )) ${REDIS_CMDLINE} LPUSH WATCHDOG_LOG "{\"time\":\"$(date +%s)\",\"service\":\"${SERVICE}\",\"lvl\":\"${PERCENT}\",\"hpnow\":\"${CURRENT}\",\"hptotal\":\"${TOTAL}\",\"hpdiff\":\"${DIFF}\"}" > /dev/null log_msg "${SERVICE} health level: ${PERCENT}% (${CURRENT}/${TOTAL}), health trend: ${DIFF}" no_redis # Return 10 to indicate a dead service [ ${CURRENT} -le 0 ] && return 10 } log_msg() { if [[ ${2} != "no_redis" ]]; then ${REDIS_CMDLINE} LPUSH WATCHDOG_LOG "{\"time\":\"$(date +%s)\",\"message\":\"$(printf '%s' "${1}" | \ tr '\r\n%&;$"_[]{}-' ' ')\"}" > /dev/null fi echo $(date) $(printf '%s\n' "${1}") } function notify_error() { # Check if one of the notification options is enabled [[ -z ${WATCHDOG_NOTIFY_EMAIL} ]] && [[ -z ${WATCHDOG_NOTIFY_WEBHOOK} ]] && return 0 THROTTLE= [[ -z ${1} ]] && return 1 # If exists, body will be the content of "/tmp/${1}", even if ${2} is set [[ -z ${2} ]] && BODY="Service was restarted on $(date), please check your mailcow installation." || BODY="$(date) - ${2}" # If exists, mail will be throttled by argument in seconds [[ ! -z ${3} ]] && THROTTLE=${3} if [[ ! -z ${THROTTLE} ]]; then TTL_LEFT="$(${REDIS_CMDLINE} TTL THROTTLE_${1} 2> /dev/null)" if [[ "${TTL_LEFT}" == "-2" ]]; then # Delay key not found, setting a delay key now ${REDIS_CMDLINE} SET THROTTLE_${1} 1 EX ${THROTTLE} else log_msg "Not sending notification email now, blocked for ${TTL_LEFT} seconds..." return 1 fi fi WATCHDOG_NOTIFY_EMAIL=$(echo "${WATCHDOG_NOTIFY_EMAIL}" | sed 's/"//;s|"$||') # Some exceptions for subject and body formats if [[ ${1} == "fail2ban" ]]; then SUBJECT="${BODY}" BODY="Please see netfilter-mailcow for more details and triggered rules." else SUBJECT="${WATCHDOG_SUBJECT}: ${1}" fi # Send mail notification if enabled if [[ ! -z ${WATCHDOG_NOTIFY_EMAIL} ]]; then IFS=',' read -r -a MAIL_RCPTS <<< "${WATCHDOG_NOTIFY_EMAIL}" for rcpt in "${MAIL_RCPTS[@]}"; do RCPT_DOMAIN= RCPT_MX= RCPT_DOMAIN=$(echo ${rcpt} | awk -F @ {'print $NF'}) CHECK_FOR_VALID_MX=$(dig +short ${RCPT_DOMAIN} mx) if [[ -z ${CHECK_FOR_VALID_MX} ]]; then log_msg "Cannot determine MX for ${rcpt}, skipping email notification..." return 1 fi [ -f "/tmp/${1}" ] && BODY="/tmp/${1}" timeout 10s ./smtp-cli --missing-modules-ok \ "${SMTP_VERBOSE}" \ --charset=UTF-8 \ --subject="${SUBJECT}" \ --body-plain="${BODY}" \ --add-header="X-Priority: 1" \ --to=${rcpt} \ --from="watchdog@${MAILCOW_HOSTNAME}" \ --hello-host=${MAILCOW_HOSTNAME} \ --ipv4 if [[ $? -eq 1 ]]; then # exit code 1 is fine log_msg "Sent notification email to ${rcpt}" else if [[ "${SMTP_VERBOSE}" == "" ]]; then log_msg "Error while sending notification email to ${rcpt}. You can enable verbose logging by setting 'WATCHDOG_VERBOSE=y' in mailcow.conf." else log_msg "Error while sending notification email to ${rcpt}." fi fi done fi # Send webhook notification if enabled if [[ ! -z ${WATCHDOG_NOTIFY_WEBHOOK} ]]; then if [[ -z ${WATCHDOG_NOTIFY_WEBHOOK_BODY} ]]; then log_msg "No webhook body set, skipping webhook notification..." return 1 fi # Escape subject and body (https://stackoverflow.com/a/2705678) ESCAPED_SUBJECT=$(echo ${SUBJECT} | sed -e 's/[\/&]/\\&/g') ESCAPED_BODY=$(echo ${BODY} | sed -e 's/[\/&]/\\&/g') # Replace subject and body placeholders WEBHOOK_BODY=$(echo ${WATCHDOG_NOTIFY_WEBHOOK_BODY} | sed -e "s/\$SUBJECT\|\${SUBJECT}/$ESCAPED_SUBJECT/g" -e "s/\$BODY\|\${BODY}/$ESCAPED_BODY/g") # POST to webhook curl -X POST -H "Content-Type: application/json" ${CURL_VERBOSE} -d "${WEBHOOK_BODY}" ${WATCHDOG_NOTIFY_WEBHOOK} log_msg "Sent notification using webhook" fi } get_container_ip() { # ${1} is container CONTAINER_ID=() CONTAINER_IPS=() CONTAINER_IP= LOOP_C=1 until [[ ${CONTAINER_IP} =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]] || [[ ${LOOP_C} -gt 5 ]]; do if [ ${IP_BY_DOCKER_API} -eq 0 ]; then CONTAINER_IP=$(dig a "${1}" +short) else sleep 0.5 # get long container id for exact match CONTAINER_ID=($(curl --silent --insecure https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], project: .Config.Labels[\"com.docker.compose.project\"], id: .Id}" | jq -rc "select( .name | tostring == \"${1}\") | select( .project | tostring | contains(\"${COMPOSE_PROJECT_NAME,,}\")) | .id")) # returned id can have multiple elements (if scaled), shuffle for random test CONTAINER_ID=($(printf "%s\n" "${CONTAINER_ID[@]}" | shuf)) if [[ ! -z ${CONTAINER_ID} ]]; then for matched_container in "${CONTAINER_ID[@]}"; do CONTAINER_IPS=($(curl --silent --insecure https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/${matched_container}/json | jq -r '.NetworkSettings.Networks[].IPAddress')) for ip_match in "${CONTAINER_IPS[@]}"; do # grep will do nothing if one of these vars is empty [[ -z ${ip_match} ]] && continue [[ -z ${IPV4_NETWORK} ]] && continue # only return ips that are part of our network if ! grep -q ${IPV4_NETWORK} <(echo ${ip_match}); then continue else CONTAINER_IP=${ip_match} break fi done [[ ! -z ${CONTAINER_IP} ]] && break done fi fi LOOP_C=$((LOOP_C + 1)) done [[ ${LOOP_C} -gt 5 ]] && echo 240.0.0.0 || echo ${CONTAINER_IP} } # One-time check if grep -qi "$(echo ${IPV6_NETWORK} | cut -d: -f1-3)" <<< "$(ip a s)"; then if [[ -z "$(get_ipv6)" ]]; then notify_error "ipv6-config" "enable_ipv6 is true in docker-compose.yml, but an IPv6 link could not be established. Please verify your IPv6 connection." fi fi external_checks() { err_count=0 diff_c=0 THRESHOLD=${EXTERNAL_CHECKS_THRESHOLD} # Reduce error count by 2 after restarting an unhealthy container GUID=$(mariadb --skip-ssl -u${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT version FROM versions WHERE application = 'GUID'" -BN) trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1 while [ ${err_count} -lt ${THRESHOLD} ]; do err_c_cur=${err_count} CHECK_REPONSE="$(curl --connect-timeout 3 -m 10 -4 -s https://checks.mailcow.email -X POST -dguid=${GUID} 2> /dev/null)" if [[ ! -z "${CHECK_REPONSE}" ]] && [[ "$(echo ${CHECK_REPONSE} | jq -r .response)" == "critical" ]]; then echo ${CHECK_REPONSE} | jq -r .out > /tmp/external_checks err_count=$(( ${err_count} + 1 )) fi CHECK_REPONSE6="$(curl --connect-timeout 3 -m 10 -6 -s https://checks.mailcow.email -X POST -dguid=${GUID} 2> /dev/null)" if [[ ! -z "${CHECK_REPONSE6}" ]] && [[ "$(echo ${CHECK_REPONSE6} | jq -r .response)" == "critical" ]]; then echo ${CHECK_REPONSE} | jq -r .out > /tmp/external_checks err_count=$(( ${err_count} + 1 )) fi [ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1 [ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} )) progress "External checks" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c} if [[ $? == 10 ]]; then diff_c=0 sleep 60 else diff_c=0 sleep $(( ( RANDOM % 20 ) + 1800 )) fi done return 1 } nginx_checks() { err_count=0 diff_c=0 THRESHOLD=${NGINX_THRESHOLD} # Reduce error count by 2 after restarting an unhealthy container trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1 while [ ${err_count} -lt ${THRESHOLD} ]; do touch /tmp/nginx-mailcow; echo "$(tail -50 /tmp/nginx-mailcow)" > /tmp/nginx-mailcow host_ip=$(get_container_ip nginx-mailcow) err_c_cur=${err_count} /usr/lib/nagios/plugins/check_http -4 -H ${host_ip} -u / -p 8081 2>> /tmp/nginx-mailcow 1>&2; err_count=$(( ${err_count} + $? )) [ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1 [ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} )) progress "Nginx" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c} if [[ $? == 10 ]]; then diff_c=0 sleep 1 else diff_c=0 sleep $(( ( RANDOM % 60 ) + 20 )) fi done return 1 } unbound_checks() { err_count=0 diff_c=0 THRESHOLD=${UNBOUND_THRESHOLD} # Reduce error count by 2 after restarting an unhealthy container trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1 while [ ${err_count} -lt ${THRESHOLD} ]; do touch /tmp/unbound-mailcow; echo "$(tail -50 /tmp/unbound-mailcow)" > /tmp/unbound-mailcow host_ip=$(get_container_ip unbound-mailcow) err_c_cur=${err_count} /usr/lib/mailcow/check_dns.sh -s ${host_ip} -H stackoverflow.com 2>> /tmp/unbound-mailcow 1>&2; err_count=$(( ${err_count} + $? )) DNSSEC=$(dig com +dnssec | egrep 'flags:.+ad') if [[ -z ${DNSSEC} ]]; then echo "DNSSEC failure" 2>> /tmp/unbound-mailcow 1>&2 err_count=$(( ${err_count} + 1)) else echo "DNSSEC check succeeded" 2>> /tmp/unbound-mailcow 1>&2 fi [ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1 [ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} )) progress "Unbound" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c} if [[ $? == 10 ]]; then diff_c=0 sleep 1 else diff_c=0 sleep $(( ( RANDOM % 60 ) + 20 )) fi done return 1 } redis_checks() { # A check for the local redis container err_count=0 diff_c=0 THRESHOLD=${REDIS_THRESHOLD} # Reduce error count by 2 after restarting an unhealthy container trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1 while [ ${err_count} -lt ${THRESHOLD} ]; do touch /tmp/redis-mailcow; echo "$(tail -50 /tmp/redis-mailcow)" > /tmp/redis-mailcow host_ip=$(get_container_ip redis-mailcow) err_c_cur=${err_count} /usr/lib/nagios/plugins/check_tcp -4 -H redis-mailcow -p 6379 -E -s "AUTH ${REDISPASS}\nPING\n" -q "QUIT" -e "PONG" 2>> /tmp/redis-mailcow 1>&2; err_count=$(( ${err_count} + $? )) [ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1 [ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} )) progress "Redis" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c} if [[ $? == 10 ]]; then diff_c=0 sleep 1 else diff_c=0 sleep $(( ( RANDOM % 60 ) + 20 )) fi done return 1 } mysql_checks() { err_count=0 diff_c=0 THRESHOLD=${MYSQL_THRESHOLD} # Reduce error count by 2 after restarting an unhealthy container trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1 while [ ${err_count} -lt ${THRESHOLD} ]; do touch /tmp/mysql-mailcow; echo "$(tail -50 /tmp/mysql-mailcow)" > /tmp/mysql-mailcow err_c_cur=${err_count} /usr/lib/nagios/plugins/check_mysql -f /etc/my.cnf.d/client.cnf -s /var/run/mysqld/mysqld.sock -u ${DBUSER} -p ${DBPASS} -d ${DBNAME} 2>> /tmp/mysql-mailcow 1>&2; err_count=$(( ${err_count} + $? )) /usr/lib/nagios/plugins/check_mysql_query -f /etc/my.cnf.d/client.cnf -s /var/run/mysqld/mysqld.sock -u ${DBUSER} -p ${DBPASS} -d ${DBNAME} -q "SELECT COUNT(*) FROM information_schema.tables" 2>> /tmp/mysql-mailcow 1>&2; err_count=$(( ${err_count} + $? )) [ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1 [ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} )) progress "MySQL/MariaDB" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c} if [[ $? == 10 ]]; then diff_c=0 sleep 1 else diff_c=0 sleep $(( ( RANDOM % 60 ) + 20 )) fi done return 1 } mysql_repl_checks() { err_count=0 diff_c=0 THRESHOLD=${MYSQL_REPLICATION_THRESHOLD} # Reduce error count by 2 after restarting an unhealthy container trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1 while [ ${err_count} -lt ${THRESHOLD} ]; do touch /tmp/mysql_repl_checks; echo "$(tail -50 /tmp/mysql_repl_checks)" > /tmp/mysql_repl_checks err_c_cur=${err_count} /usr/lib/nagios/plugins/check_mysql_slavestatus.sh -o /etc/my.cnf.d/client.cnf -S /var/run/mysqld/mysqld.sock -u root -p ${DBROOT} 2>> /tmp/mysql_repl_checks 1>&2; err_count=$(( ${err_count} + $? )) [ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1 [ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} )) progress "MySQL/MariaDB replication" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c} if [[ $? == 10 ]]; then diff_c=0 sleep 60 else diff_c=0 sleep $(( ( RANDOM % 60 ) + 20 )) fi done return 1 } sogo_checks() { err_count=0 diff_c=0 THRESHOLD=${SOGO_THRESHOLD} # Reduce error count by 2 after restarting an unhealthy container trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1 while [ ${err_count} -lt ${THRESHOLD} ]; do touch /tmp/sogo-mailcow; echo "$(tail -50 /tmp/sogo-mailcow)" > /tmp/sogo-mailcow host_ip=$(get_container_ip sogo-mailcow) err_c_cur=${err_count} /usr/lib/nagios/plugins/check_http -4 -H ${host_ip} -u /SOGo.index/ -p 20000 2>> /tmp/sogo-mailcow 1>&2; err_count=$(( ${err_count} + $? )) [ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1 [ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} )) progress "SOGo" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c} if [[ $? == 10 ]]; then diff_c=0 sleep 1 else diff_c=0 sleep $(( ( RANDOM % 60 ) + 20 )) fi done return 1 } postfix_checks() { err_count=0 diff_c=0 THRESHOLD=${POSTFIX_THRESHOLD} # Reduce error count by 2 after restarting an unhealthy container trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1 while [ ${err_count} -lt ${THRESHOLD} ]; do touch /tmp/postfix-mailcow; echo "$(tail -50 /tmp/postfix-mailcow)" > /tmp/postfix-mailcow host_ip=$(get_container_ip postfix-mailcow) err_c_cur=${err_count} /usr/lib/nagios/plugins/check_smtp -4 -H ${host_ip} -p 589 -f "watchdog@invalid" -C "RCPT TO:watchdog@localhost" -C DATA -C . -R 250 2>> /tmp/postfix-mailcow 1>&2; err_count=$(( ${err_count} + $? )) /usr/lib/nagios/plugins/check_smtp -4 -H ${host_ip} -p 589 -S 2>> /tmp/postfix-mailcow 1>&2; err_count=$(( ${err_count} + $? )) [ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1 [ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} )) progress "Postfix" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c} if [[ $? == 10 ]]; then diff_c=0 sleep 1 else diff_c=0 sleep $(( ( RANDOM % 60 ) + 20 )) fi done return 1 } postfix-tlspol_checks() { err_count=0 diff_c=0 THRESHOLD=${POSTFIX_TLSPOL_THRESHOLD} # Reduce error count by 2 after restarting an unhealthy container trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1 while [ ${err_count} -lt ${THRESHOLD} ]; do touch /tmp/postfix-tlspol-mailcow; echo "$(tail -50 /tmp/postfix-tlspol-mailcow)" > /tmp/postfix-tlspol-mailcow host_ip=$(get_container_ip postfix-tlspol-mailcow) err_c_cur=${err_count} /usr/lib/nagios/plugins/check_tcp -4 -H ${host_ip} -p 8642 2>> /tmp/postfix-tlspol-mailcow 1>&2; err_count=$(( ${err_count} + $? )) [ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1 [ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} )) progress "Postfix TLS Policy companion" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c} if [[ $? == 10 ]]; then diff_c=0 sleep 1 else diff_c=0 sleep $(( ( RANDOM % 60 ) + 20 )) fi done return 1 } clamd_checks() { err_count=0 diff_c=0 THRESHOLD=${CLAMD_THRESHOLD} # Reduce error count by 2 after restarting an unhealthy container trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1 while [ ${err_count} -lt ${THRESHOLD} ]; do touch /tmp/clamd-mailcow; echo "$(tail -50 /tmp/clamd-mailcow)" > /tmp/clamd-mailcow host_ip=$(get_container_ip clamd-mailcow) err_c_cur=${err_count} /usr/lib/nagios/plugins/check_clamd -4 -H ${host_ip} 2>> /tmp/clamd-mailcow 1>&2; err_count=$(( ${err_count} + $? )) [ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1 [ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} )) progress "Clamd" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c} if [[ $? == 10 ]]; then diff_c=0 sleep 1 else diff_c=0 sleep $(( ( RANDOM % 120 ) + 20 )) fi done return 1 } dovecot_checks() { err_count=0 diff_c=0 THRESHOLD=${DOVECOT_THRESHOLD} # Reduce error count by 2 after restarting an unhealthy container trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1 while [ ${err_count} -lt ${THRESHOLD} ]; do touch /tmp/dovecot-mailcow; echo "$(tail -50 /tmp/dovecot-mailcow)" > /tmp/dovecot-mailcow host_ip=$(get_container_ip dovecot-mailcow) err_c_cur=${err_count} /usr/lib/nagios/plugins/check_smtp -4 -H ${host_ip} -p 24 -f "watchdog@invalid" -C "RCPT TO:" -L -R "User doesn't exist" 2>> /tmp/dovecot-mailcow 1>&2; err_count=$(( ${err_count} + $? )) /usr/lib/nagios/plugins/check_imap -4 -H ${host_ip} -p 993 -S -e "OK " 2>> /tmp/dovecot-mailcow 1>&2; err_count=$(( ${err_count} + $? )) /usr/lib/nagios/plugins/check_imap -4 -H ${host_ip} -p 143 -e "OK " 2>> /tmp/dovecot-mailcow 1>&2; err_count=$(( ${err_count} + $? )) /usr/lib/nagios/plugins/check_tcp -4 -H ${host_ip} -p 10001 -e "VERSION" 2>> /tmp/dovecot-mailcow 1>&2; err_count=$(( ${err_count} + $? )) /usr/lib/nagios/plugins/check_tcp -4 -H ${host_ip} -p 4190 -e "Dovecot ready" 2>> /tmp/dovecot-mailcow 1>&2; err_count=$(( ${err_count} + $? )) [ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1 [ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} )) progress "Dovecot" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c} if [[ $? == 10 ]]; then diff_c=0 sleep 1 else diff_c=0 sleep $(( ( RANDOM % 60 ) + 20 )) fi done return 1 } dovecot_repl_checks() { err_count=0 diff_c=0 THRESHOLD=${DOVECOT_REPL_THRESHOLD} D_REPL_STATUS=$(redis-cli -h redis -a ${REDISPASS} --no-auth-warning -r GET DOVECOT_REPL_HEALTH) # Reduce error count by 2 after restarting an unhealthy container trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1 while [ ${err_count} -lt ${THRESHOLD} ]; do err_c_cur=${err_count} D_REPL_STATUS=$(redis-cli --raw -h redis -a ${REDISPASS} --no-auth-warning GET DOVECOT_REPL_HEALTH) if [[ "${D_REPL_STATUS}" != "1" ]]; then err_count=$(( ${err_count} + 1 )) fi [ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1 [ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} )) progress "Dovecot replication" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c} if [[ $? == 10 ]]; then diff_c=0 sleep 60 else diff_c=0 sleep $(( ( RANDOM % 60 ) + 20 )) fi done return 1 } cert_checks() { err_count=0 diff_c=0 THRESHOLD=7 # Reduce error count by 2 after restarting an unhealthy container trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1 while [ ${err_count} -lt ${THRESHOLD} ]; do touch /tmp/certcheck; echo "$(tail -50 /tmp/certcheck)" > /tmp/certcheck host_ip_postfix=$(get_container_ip postfix) host_ip_dovecot=$(get_container_ip dovecot) err_c_cur=${err_count} /usr/lib/nagios/plugins/check_smtp -H ${host_ip_postfix} -p 589 -4 -S -D 7 2>> /tmp/certcheck 1>&2; err_count=$(( ${err_count} + $? )) /usr/lib/nagios/plugins/check_imap -H ${host_ip_dovecot} -p 993 -4 -S -D 7 2>> /tmp/certcheck 1>&2; err_count=$(( ${err_count} + $? )) [ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1 [ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} )) progress "Primary certificate expiry check" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c} # Always sleep 5 minutes, mail notifications are limited sleep 300 done return 1 } phpfpm_checks() { err_count=0 diff_c=0 THRESHOLD=${PHPFPM_THRESHOLD} # Reduce error count by 2 after restarting an unhealthy container trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1 while [ ${err_count} -lt ${THRESHOLD} ]; do touch /tmp/php-fpm-mailcow; echo "$(tail -50 /tmp/php-fpm-mailcow)" > /tmp/php-fpm-mailcow host_ip=$(get_container_ip php-fpm-mailcow) err_c_cur=${err_count} /usr/lib/nagios/plugins/check_tcp -H ${host_ip} -p 9001 2>> /tmp/php-fpm-mailcow 1>&2; err_count=$(( ${err_count} + $? )) /usr/lib/nagios/plugins/check_tcp -H ${host_ip} -p 9002 2>> /tmp/php-fpm-mailcow 1>&2; err_count=$(( ${err_count} + $? )) [ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1 [ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} )) progress "PHP-FPM" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c} if [[ $? == 10 ]]; then diff_c=0 sleep 1 else diff_c=0 sleep $(( ( RANDOM % 60 ) + 20 )) fi done return 1 } ratelimit_checks() { err_count=0 diff_c=0 THRESHOLD=${RATELIMIT_THRESHOLD} RL_LOG_STATUS=$(redis-cli -h redis -a ${REDISPASS} --no-auth-warning LRANGE RL_LOG 0 0 | jq .qid) # Reduce error count by 2 after restarting an unhealthy container trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1 while [ ${err_count} -lt ${THRESHOLD} ]; do err_c_cur=${err_count} RL_LOG_STATUS_PREV=${RL_LOG_STATUS} RL_LOG_STATUS=$(redis-cli -h redis -a ${REDISPASS} --no-auth-warning LRANGE RL_LOG 0 0 | jq .qid) if [[ ${RL_LOG_STATUS_PREV} != ${RL_LOG_STATUS} ]]; then err_count=$(( ${err_count} + 1 )) echo 'Last 10 applied ratelimits (may overlap with previous reports).' > /tmp/ratelimit echo 'Full ratelimit buckets can be emptied by deleting the ratelimit hash from within mailcow UI (see /debug -> Protocols -> Ratelimit):' >> /tmp/ratelimit echo >> /tmp/ratelimit redis-cli --raw -h redis -a ${REDISPASS} --no-auth-warning LRANGE RL_LOG 0 10 | jq . >> /tmp/ratelimit fi [ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1 [ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} )) progress "Ratelimit" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c} if [[ $? == 10 ]]; then diff_c=0 sleep 1 else diff_c=0 sleep $(( ( RANDOM % 60 ) + 20 )) fi done return 1 } mailq_checks() { err_count=0 diff_c=0 THRESHOLD=${MAILQ_THRESHOLD} # Reduce error count by 2 after restarting an unhealthy container trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1 while [ ${err_count} -lt ${THRESHOLD} ]; do touch /tmp/mail_queue_status; echo "$(tail -50 /tmp/mail_queue_status)" > /tmp/mail_queue_status MAILQ_LOG_STATUS=$(find /var/spool/postfix/deferred -type f | wc -l) echo "Mail queue contains ${MAILQ_LOG_STATUS} items (critical limit is ${MAILQ_CRIT}) at $(date)" >> /tmp/mail_queue_status err_c_cur=${err_count} if [ ${MAILQ_LOG_STATUS} -ge ${MAILQ_CRIT} ]; then err_count=$(( ${err_count} + 1 )) echo "Mail queue contains ${MAILQ_LOG_STATUS} items (critical limit is ${MAILQ_CRIT}) at $(date)" >> /tmp/mail_queue_status fi [ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1 [ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} )) progress "Mail queue" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c} if [[ $? == 10 ]]; then diff_c=0 sleep 60 else diff_c=0 sleep $(( ( RANDOM % 60 ) + 20 )) fi done return 1 } fail2ban_checks() { err_count=0 diff_c=0 THRESHOLD=${FAIL2BAN_THRESHOLD} F2B_LOG_STATUS=($(${REDIS_CMDLINE} --raw HKEYS F2B_ACTIVE_BANS)) F2B_RES= # Reduce error count by 2 after restarting an unhealthy container trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1 while [ ${err_count} -lt ${THRESHOLD} ]; do err_c_cur=${err_count} F2B_LOG_STATUS_PREV=(${F2B_LOG_STATUS[@]}) F2B_LOG_STATUS=($(${REDIS_CMDLINE} --raw HKEYS F2B_ACTIVE_BANS)) array_diff F2B_RES F2B_LOG_STATUS F2B_LOG_STATUS_PREV if [[ ! -z "${F2B_RES}" ]]; then err_count=$(( ${err_count} + 1 )) echo -n "${F2B_RES[@]}" | tr -cd "[a-fA-F0-9.:/] " | timeout 3s ${REDIS_CMDLINE} -x SET F2B_RES > /dev/null if [ $? -ne 0 ]; then ${REDIS_CMDLINE} -x DEL F2B_RES fi fi [ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1 [ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} )) progress "Fail2ban" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c} if [[ $? == 10 ]]; then diff_c=0 sleep 1 else diff_c=0 sleep $(( ( RANDOM % 60 ) + 20 )) fi done return 1 } acme_checks() { err_count=0 diff_c=0 THRESHOLD=${ACME_THRESHOLD} ACME_LOG_STATUS=$(redis-cli -h redis -a ${REDISPASS} --no-auth-warning GET ACME_FAIL_TIME) if [[ -z "${ACME_LOG_STATUS}" ]]; then ${REDIS_CMDLINE} SET ACME_FAIL_TIME 0 ACME_LOG_STATUS=0 fi # Reduce error count by 2 after restarting an unhealthy container trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1 while [ ${err_count} -lt ${THRESHOLD} ]; do err_c_cur=${err_count} ACME_LOG_STATUS_PREV=${ACME_LOG_STATUS} ACME_LC=0 until [[ ! -z ${ACME_LOG_STATUS} ]] || [ ${ACME_LC} -ge 3 ]; do ACME_LOG_STATUS=$(redis-cli -h redis -a ${REDISPASS} --no-auth-warning GET ACME_FAIL_TIME 2> /dev/null) sleep 3 ACME_LC=$((ACME_LC+1)) done if [[ ${ACME_LOG_STATUS_PREV} != ${ACME_LOG_STATUS} ]]; then err_count=$(( ${err_count} + 1 )) fi [ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1 [ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} )) progress "ACME" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c} if [[ $? == 10 ]]; then diff_c=0 sleep 1 else diff_c=0 sleep $(( ( RANDOM % 60 ) + 20 )) fi done return 1 } rspamd_checks() { err_count=0 diff_c=0 THRESHOLD=${RSPAMD_THRESHOLD} # Reduce error count by 2 after restarting an unhealthy container trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1 while [ ${err_count} -lt ${THRESHOLD} ]; do touch /tmp/rspamd-mailcow; echo "$(tail -50 /tmp/rspamd-mailcow)" > /tmp/rspamd-mailcow host_ip=$(get_container_ip rspamd-mailcow) err_c_cur=${err_count} SCORE=$(echo 'To: null@localhost From: watchdog@localhost Empty ' | usr/bin/curl --max-time 10 -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd.${COMPOSE_PROJECT_NAME}_mailcow-network/scan | jq -rc .default.required_score | sed 's/\..*//' ) if [[ ${SCORE} -ne 9999 ]]; then echo "Rspamd settings check failed, score returned: ${SCORE}" 2>> /tmp/rspamd-mailcow 1>&2 err_count=$(( ${err_count} + 1)) else echo "Rspamd settings check succeeded, score returned: ${SCORE}" 2>> /tmp/rspamd-mailcow 1>&2 fi # A dirty hack until a PING PONG event is implemented to worker proxy # We expect an empty response, not a timeout if [ "$(curl -s --max-time 10 ${host_ip}:9900 2> /dev/null ; echo $?)" == "28" ]; then echo "Milter check failed" 2>> /tmp/rspamd-mailcow 1>&2; err_count=$(( ${err_count} + 1 )); else echo "Milter check succeeded" 2>> /tmp/rspamd-mailcow 1>&2 fi [ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1 [ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} )) progress "Rspamd" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c} if [[ $? == 10 ]]; then diff_c=0 sleep 1 else diff_c=0 sleep $(( ( RANDOM % 60 ) + 20 )) fi done return 1 } olefy_checks() { err_count=0 diff_c=0 THRESHOLD=${OLEFY_THRESHOLD} # Reduce error count by 2 after restarting an unhealthy container trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1 while [ ${err_count} -lt ${THRESHOLD} ]; do touch /tmp/olefy-mailcow; echo "$(tail -50 /tmp/olefy-mailcow)" > /tmp/olefy-mailcow host_ip=$(get_container_ip olefy-mailcow) err_c_cur=${err_count} /usr/lib/nagios/plugins/check_tcp -4 -H ${host_ip} -p 10055 -s "PING\n" 2>> /tmp/olefy-mailcow 1>&2; err_count=$(( ${err_count} + $? )) [ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1 [ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} )) progress "Olefy" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c} if [[ $? == 10 ]]; then diff_c=0 sleep 1 else diff_c=0 sleep $(( ( RANDOM % 60 ) + 20 )) fi done return 1 } # Notify about start if [[ ${WATCHDOG_NOTIFY_START} =~ ^([yY][eE][sS]|[yY])+$ ]]; then notify_error "watchdog-mailcow" "Watchdog started monitoring mailcow." fi # Create watchdog agents ( while true; do if ! nginx_checks; then log_msg "Nginx hit error limit" echo nginx-mailcow > /tmp/com_pipe fi done ) & PID=$! echo "Spawned nginx_checks with PID ${PID}" BACKGROUND_TASKS+=(${PID}) if [[ ${WATCHDOG_EXTERNAL_CHECKS} =~ ^([yY][eE][sS]|[yY])+$ ]]; then ( while true; do if ! external_checks; then log_msg "External checks hit error limit" echo external_checks > /tmp/com_pipe fi done ) & PID=$! echo "Spawned external_checks with PID ${PID}" BACKGROUND_TASKS+=(${PID}) fi if [[ ${WATCHDOG_MYSQL_REPLICATION_CHECKS} =~ ^([yY][eE][sS]|[yY])+$ ]]; then ( while true; do if ! mysql_repl_checks; then log_msg "MySQL replication check hit error limit" echo mysql_repl_checks > /tmp/com_pipe fi done ) & PID=$! echo "Spawned mysql_repl_checks with PID ${PID}" BACKGROUND_TASKS+=(${PID}) fi ( while true; do if ! mysql_checks; then log_msg "MySQL hit error limit" echo mysql-mailcow > /tmp/com_pipe fi done ) & PID=$! echo "Spawned mysql_checks with PID ${PID}" BACKGROUND_TASKS+=(${PID}) ( while true; do if ! redis_checks; then log_msg "Local Redis hit error limit" echo redis-mailcow > /tmp/com_pipe fi done ) & PID=$! echo "Spawned redis_checks with PID ${PID}" BACKGROUND_TASKS+=(${PID}) ( while true; do if ! phpfpm_checks; then log_msg "PHP-FPM hit error limit" echo php-fpm-mailcow > /tmp/com_pipe fi done ) & PID=$! echo "Spawned phpfpm_checks with PID ${PID}" BACKGROUND_TASKS+=(${PID}) if [[ "${SKIP_SOGO}" =~ ^([nN][oO]|[nN])+$ ]]; then ( while true; do if ! sogo_checks; then log_msg "SOGo hit error limit" echo sogo-mailcow > /tmp/com_pipe fi done ) & PID=$! echo "Spawned sogo_checks with PID ${PID}" BACKGROUND_TASKS+=(${PID}) fi if [ ${CHECK_UNBOUND} -eq 1 ]; then ( while true; do if ! unbound_checks; then log_msg "Unbound hit error limit" echo unbound-mailcow > /tmp/com_pipe fi done ) & PID=$! echo "Spawned unbound_checks with PID ${PID}" BACKGROUND_TASKS+=(${PID}) fi if [[ "${SKIP_CLAMD}" =~ ^([nN][oO]|[nN])+$ ]]; then ( while true; do if ! clamd_checks; then log_msg "Clamd hit error limit" echo clamd-mailcow > /tmp/com_pipe fi done ) & PID=$! echo "Spawned clamd_checks with PID ${PID}" BACKGROUND_TASKS+=(${PID}) fi ( while true; do if ! postfix_checks; then log_msg "Postfix hit error limit" echo postfix-mailcow > /tmp/com_pipe fi done ) & PID=$! echo "Spawned postfix_checks with PID ${PID}" BACKGROUND_TASKS+=(${PID}) ( while true; do if ! mailq_checks; then log_msg "Mail queue hit error limit" echo mail_queue_status > /tmp/com_pipe fi done ) & PID=$! echo "Spawned mailq_checks with PID ${PID}" BACKGROUND_TASKS+=(${PID}) ( while true; do if ! postfix-tlspol_checks; then log_msg "Postfix TLS Policy hit error limit" echo postfix-tlspol-mailcow > /tmp/com_pipe fi done ) & PID=$! echo "Spawned postfix-tlspol_checks with PID ${PID}" BACKGROUND_TASKS+=(${PID}) ( while true; do if ! dovecot_checks; then log_msg "Dovecot hit error limit" echo dovecot-mailcow > /tmp/com_pipe fi done ) & PID=$! echo "Spawned dovecot_checks with PID ${PID}" BACKGROUND_TASKS+=(${PID}) ( while true; do if ! dovecot_repl_checks; then log_msg "Dovecot hit error limit" echo dovecot_repl_checks > /tmp/com_pipe fi done ) & PID=$! echo "Spawned dovecot_repl_checks with PID ${PID}" BACKGROUND_TASKS+=(${PID}) ( while true; do if ! rspamd_checks; then log_msg "Rspamd hit error limit" echo rspamd-mailcow > /tmp/com_pipe fi done ) & PID=$! echo "Spawned rspamd_checks with PID ${PID}" BACKGROUND_TASKS+=(${PID}) ( while true; do if ! ratelimit_checks; then log_msg "Ratelimit hit error limit" echo ratelimit > /tmp/com_pipe fi done ) & PID=$! echo "Spawned ratelimit_checks with PID ${PID}" BACKGROUND_TASKS+=(${PID}) ( while true; do if ! fail2ban_checks; then log_msg "Fail2ban hit error limit" echo fail2ban > /tmp/com_pipe fi done ) & PID=$! echo "Spawned fail2ban_checks with PID ${PID}" BACKGROUND_TASKS+=(${PID}) ( while true; do if ! cert_checks; then log_msg "Cert check hit error limit" echo certcheck > /tmp/com_pipe fi done ) & PID=$! echo "Spawned cert_checks with PID ${PID}" BACKGROUND_TASKS+=(${PID}) if [[ "${SKIP_OLEFY}" =~ ^([nN][oO]|[nN])+$ ]]; then ( while true; do if ! olefy_checks; then log_msg "Olefy hit error limit" echo olefy-mailcow > /tmp/com_pipe fi done ) & PID=$! echo "Spawned olefy_checks with PID ${PID}" BACKGROUND_TASKS+=(${PID}) fi ( while true; do if ! acme_checks; then log_msg "ACME client hit error limit" echo acme-mailcow > /tmp/com_pipe fi done ) & PID=$! echo "Spawned acme_checks with PID ${PID}" BACKGROUND_TASKS+=(${PID}) # Monitor watchdog agents, stop script when agents fails and wait for respawn by Docker (restart:always:n) ( while true; do for bg_task in ${BACKGROUND_TASKS[*]}; do if ! kill -0 ${bg_task} 1>&2; then log_msg "Worker ${bg_task} died, stopping watchdog and waiting for respawn..." kill -TERM 1 fi sleep 10 done done ) & # Monitor dockerapi ( while true; do while nc -z dockerapi 443; do sleep 3 done log_msg "Cannot find dockerapi-mailcow, waiting to recover..." kill -STOP ${BACKGROUND_TASKS[*]} until nc -z dockerapi 443; do sleep 3 done kill -CONT ${BACKGROUND_TASKS[*]} kill -USR1 ${BACKGROUND_TASKS[*]} done ) & # Actions when threshold limit is reached while true; do CONTAINER_ID= HAS_INITDB= read com_pipe_answer /dev/null)) if [[ ! -z "${F2B_RES}" ]]; then ${REDIS_CMDLINE} DEL F2B_RES > /dev/null host= for host in "${F2B_RES[@]}"; do log_msg "Banned ${host}" rm /tmp/fail2ban 2> /dev/null timeout 2s whois "${host}" > /tmp/fail2ban [[ ${WATCHDOG_NOTIFY_BAN} =~ ^([yY][eE][sS]|[yY])+$ ]] && notify_error "${com_pipe_answer}" "IP ban: ${host}" done fi elif [[ ${com_pipe_answer} =~ .+-mailcow ]]; then kill -STOP ${BACKGROUND_TASKS[*]} sleep 10 CONTAINER_ID=$(curl --silent --insecure https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], project: .Config.Labels[\"com.docker.compose.project\"], id: .Id}" | jq -rc "select( .name | tostring | contains(\"${com_pipe_answer}\")) | select( .project | tostring | contains(\"${COMPOSE_PROJECT_NAME,,}\")) | .id") if [[ ! -z ${CONTAINER_ID} ]]; then if [[ "${com_pipe_answer}" == "php-fpm-mailcow" ]]; then HAS_INITDB=$(curl --silent --insecure -XPOST https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/${CONTAINER_ID}/top | jq '.msg.Processes[] | contains(["php -c /usr/local/etc/php -f /web/inc/init_db.inc.php"])' | grep true) fi S_RUNNING=$(($(date +%s) - $(curl --silent --insecure https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/${CONTAINER_ID}/json | jq .State.StartedAt | xargs -n1 date +%s -d))) if [ ${S_RUNNING} -lt 360 ]; then log_msg "Container is running for less than 360 seconds, skipping action..." elif [[ ! -z ${HAS_INITDB} ]]; then log_msg "Database is being initialized by php-fpm-mailcow, not restarting but delaying checks for a minute..." sleep 60 else log_msg "Sending restart command to ${CONTAINER_ID}..." curl --silent --insecure -XPOST https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/${CONTAINER_ID}/restart notify_error "${com_pipe_answer}" log_msg "Wait for restarted container to settle and continue watching..." sleep 35 fi fi kill -CONT ${BACKGROUND_TASKS[*]} sleep 1 kill -USR1 ${BACKGROUND_TASKS[*]} fi done ================================================ FILE: data/assets/mysql/docker-entrypoint.sh ================================================ #!/bin/bash set -eo pipefail shopt -s nullglob openssl req -x509 -sha256 -newkey rsa:2048 -keyout /var/lib/mysql/sql.key -out /var/lib/mysql/sql.crt -days 3650 -nodes -subj '/CN=mysql' # if command starts with an option, prepend mysqld if [ "${1:0:1}" = '-' ]; then set -- mysqld "$@" fi # skip setup if they want an option that stops mysqld wantHelp= for arg; do case "$arg" in -'?'|--help|--print-defaults|-V|--version) wantHelp=1 break ;; esac done # usage: file_env VAR [DEFAULT] # ie: file_env 'XYZ_DB_PASSWORD' 'example' # (will allow for "$XYZ_DB_PASSWORD_FILE" to fill in the value of # "$XYZ_DB_PASSWORD" from a file, especially for Docker's secrets feature) file_env() { local var="$1" local fileVar="${var}_FILE" local def="${2:-}" if [ "${!var:-}" ] && [ "${!fileVar:-}" ]; then echo >&2 "error: both $var and $fileVar are set (but are exclusive)" exit 1 fi local val="$def" if [ "${!var:-}" ]; then val="${!var}" elif [ "${!fileVar:-}" ]; then val="$(< "${!fileVar}")" fi export "$var"="$val" unset "$fileVar" } _check_config() { toRun=( "$@" --verbose --help --log-bin-index="$(mktemp -u)" ) if ! errors="$("${toRun[@]}" 2>&1 >/dev/null)"; then cat >&2 <<-EOM ERROR: mysqld failed while attempting to check config command was: "${toRun[*]}" $errors EOM exit 1 fi } # Fetch value from server config # We use mysqld --verbose --help instead of my_print_defaults because the # latter only show values present in config files, and not server defaults _get_config() { local conf="$1"; shift "$@" --verbose --help --log-bin-index="$(mktemp -u)" 2>/dev/null | awk '$1 == "'"$conf"'" { print $2; exit }' } # allow the container to be started with `--user` if [ "$1" = 'mysqld' -a -z "$wantHelp" -a "$(id -u)" = '0' ]; then _check_config "$@" DATADIR="$(_get_config 'datadir' "$@")" mkdir -p "$DATADIR" chown -R mysql:mysql "$DATADIR" exec gosu mysql "$BASH_SOURCE" "$@" fi if [ "$1" = 'mysqld' -a -z "$wantHelp" ]; then # still need to check config, container may have started with --user _check_config "$@" # Get config DATADIR="$(_get_config 'datadir' "$@")" if [ ! -d "$DATADIR/mysql" ]; then file_env 'MYSQL_ROOT_PASSWORD' if [ -z "$MYSQL_ROOT_PASSWORD" -a -z "$MYSQL_ALLOW_EMPTY_PASSWORD" -a -z "$MYSQL_RANDOM_ROOT_PASSWORD" ]; then echo >&2 'error: database is uninitialized and password option is not specified ' echo >&2 ' You need to specify one of MYSQL_ROOT_PASSWORD, MYSQL_ALLOW_EMPTY_PASSWORD and MYSQL_RANDOM_ROOT_PASSWORD' exit 1 fi mkdir -p "$DATADIR" echo 'Initializing database' # "Other options are passed to mysqld." (so we pass all "mysqld" arguments directly here) mysql_install_db --datadir="$DATADIR" --rpm "${@:2}" echo 'Database initialized' SOCKET="$(_get_config 'socket' "$@")" "$@" --skip-networking --socket="${SOCKET}" & pid="$!" mysql=( mysql --protocol=socket -uroot -hlocalhost --socket="${SOCKET}" ) for i in {30..0}; do if echo 'SELECT 1' | "${mysql[@]}" &> /dev/null; then break fi echo 'MySQL init process in progress...' sleep 1 done if [ "$i" = 0 ]; then echo >&2 'MySQL init process failed.' exit 1 fi if [ -z "$MYSQL_INITDB_SKIP_TZINFO" ]; then # sed is for https://bugs.mysql.com/bug.php?id=20545 mysql_tzinfo_to_sql /usr/share/zoneinfo | sed 's/Local time zone must be set--see zic manual page/FCTY/' | "${mysql[@]}" mysql fi if [ ! -z "$MYSQL_RANDOM_ROOT_PASSWORD" ]; then export MYSQL_ROOT_PASSWORD="$(pwgen -1 32)" echo "GENERATED ROOT PASSWORD: $MYSQL_ROOT_PASSWORD" fi rootCreate= # default root to listen for connections from anywhere file_env 'MYSQL_ROOT_HOST' '%' if [ ! -z "$MYSQL_ROOT_HOST" -a "$MYSQL_ROOT_HOST" != 'localhost' ]; then # no, we don't care if read finds a terminating character in this heredoc # https://unix.stackexchange.com/questions/265149/why-is-set-o-errexit-breaking-this-read-heredoc-expression/265151#265151 read -r -d '' rootCreate <<-EOSQL || true CREATE USER 'root'@'${MYSQL_ROOT_HOST}' IDENTIFIED BY '${MYSQL_ROOT_PASSWORD}' ; GRANT ALL ON *.* TO 'root'@'${MYSQL_ROOT_HOST}' WITH GRANT OPTION ; EOSQL fi "${mysql[@]}" <<-EOSQL -- What's done in this file shouldn't be replicated -- or products like mysql-fabric won't work SET @@SESSION.SQL_LOG_BIN=0; DELETE FROM mysql.user WHERE user NOT IN ('mysql.sys', 'mysqlxsys', 'root') OR host NOT IN ('localhost') ; SET PASSWORD FOR 'root'@'localhost'=PASSWORD('${MYSQL_ROOT_PASSWORD}') ; GRANT ALL ON *.* TO 'root'@'localhost' WITH GRANT OPTION ; ${rootCreate} DROP DATABASE IF EXISTS test ; FLUSH PRIVILEGES ; EOSQL if [ ! -z "$MYSQL_ROOT_PASSWORD" ]; then mysql+=( -p"${MYSQL_ROOT_PASSWORD}" ) fi file_env 'MYSQL_DATABASE' if [ "$MYSQL_DATABASE" ]; then echo "CREATE DATABASE IF NOT EXISTS \`$MYSQL_DATABASE\` ;" | "${mysql[@]}" mysql+=( "$MYSQL_DATABASE" ) fi file_env 'MYSQL_USER' file_env 'MYSQL_PASSWORD' if [ "$MYSQL_USER" -a "$MYSQL_PASSWORD" ]; then echo "CREATE USER '$MYSQL_USER'@'%' IDENTIFIED BY '$MYSQL_PASSWORD' ;" | "${mysql[@]}" if [ "$MYSQL_DATABASE" ]; then echo "GRANT ALL ON \`$MYSQL_DATABASE\`.* TO '$MYSQL_USER'@'%' ;" | "${mysql[@]}" fi fi echo for f in /docker-entrypoint-initdb.d/*; do case "$f" in *.sh) echo "$0: running $f"; . "$f" ;; *.sql) echo "$0: running $f"; "${mysql[@]}" < "$f"; echo ;; *.sql.gz) echo "$0: running $f"; gunzip -c "$f" | "${mysql[@]}"; echo ;; *) echo "$0: ignoring $f" ;; esac echo done if ! kill -s TERM "$pid" || ! wait "$pid"; then echo >&2 'MySQL init process failed.' exit 1 fi echo echo 'MySQL init process done. Ready for start up.' echo fi fi exec "$@" ================================================ FILE: data/assets/passwd/generate_passwords.sh ================================================ #!/bin/bash echo DBPASS=$(openssl rand -base64 32 | tr -dc _A-Z-a-z-0-9) echo DBROOT=$(openssl rand -base64 32 | tr -dc _A-Z-a-z-0-9) ================================================ FILE: data/assets/templates/pw_reset_html.tpl ================================================ Hello {{username2}},

Somebody requested a new password for the {{hostname}} account associated with {{username}}.
Date of the password reset request: {{date}}

You can reset your password by clicking the link below:
{{link}}

The link will be valid for the next {{token_lifetime}} minutes.

If you did not request a new password, please ignore this email.
================================================ FILE: data/assets/templates/pw_reset_text.tpl ================================================ Hello {{username2}}, Somebody requested a new password for the {{hostname}} account associated with {{username}}. Date of the password reset request: {{date}} You can reset your password by clicking the link below: {{link}} The link will be valid for the next {{token_lifetime}} minutes. If you did not request a new password, please ignore this email. ================================================ FILE: data/assets/templates/quarantine.tpl ================================================

Hi {{username}}!
{% if counter == 1 %} There is 1 new message waiting in quarantine:
{% else %} There are {{counter}} new messages waiting in quarantine:
{% endif %}

{% if quarantine_acl == 1 %}{% endif %} {% for line in meta|reverse %} {% if line.action == "reject" %} {% else %} {% endif %} {% if quarantine_acl == 1 %} {% if line.action == "reject" %} {% else %} {% endif %} {% endif %} {% endfor %}
SubjectSenderScoreActionArrived onActions
{{ line.subject|e }} {{ line.sender|e }} {{ line.score }}RejectedSent to Junk folder{{ line.created }}Release to inbox | deleteSend copy to inbox | delete

================================================ FILE: data/assets/templates/quota.tpl ================================================
Hi {{username}}!

Your mailbox is now {{percent}}% full, please consider deleting old messages to still be able to receive new mails in the future.

{{percent}}%
================================================ FILE: data/conf/acme/.gitkeep ================================================ ================================================ FILE: data/conf/clamav/clamd.conf ================================================ #Debug true #LogFile /dev/null LogTime yes LogClean yes ExtendedDetectionInfo yes PidFile /run/clamav/clamd.pid OfficialDatabaseOnly no LocalSocket /run/clamav/clamd.sock TCPSocket 3310 StreamMaxLength 25M MaxThreads 10 ReadTimeout 10 CommandReadTimeout 3 SendBufTimeout 200 MaxQueue 80 IdleTimeout 20 SelfCheck 3600 User clamav Foreground yes DetectPUA yes # See https://github.com/vrtadmin/clamav-faq/blob/master/faq/faq-pua.md #ExcludePUA NetTool #ExcludePUA PWTool #IncludePUA Spy #IncludePUA Scanner #IncludePUA RAT HeuristicAlerts yes ScanOLE2 yes AlertOLE2Macros no ScanPDF yes ScanSWF yes ScanXMLDOCS yes ScanHWP3 yes ScanMail yes PhishingSignatures no PhishingScanURLs no HeuristicScanPrecedence yes ScanHTML yes ScanArchive yes MaxScanSize 50M MaxFileSize 25M MaxRecursion 5 MaxFiles 200 Bytecode yes BytecodeSecurity TrustSigned BytecodeTimeout 1000 ConcurrentDatabaseReload no ================================================ FILE: data/conf/clamav/freshclam.conf ================================================ #UpdateLogFile /dev/console LogTime yes PidFile /run/clamav/freshclam.pid DatabaseOwner clamav DNSDatabaseInfo current.cvd.clamav.net DatabaseMirror db.uk.clamav.net DatabaseMirror db.nl.clamav.net DatabaseMirror db.fr.clamav.net DatabaseMirror db.ch.clamav.net MaxAttempts 4 ScriptedUpdates yes Checks 6 NotifyClamd /etc/clamav/clamd.conf Foreground yes ConnectTimeout 20 ReceiveTimeout 20 TestDatabases yes Bytecode yes ================================================ FILE: data/conf/dovecot/auth/mailcowauth.php ================================================ false); if(!isset($post['username']) || !isset($post['password']) || !isset($post['real_rip'])){ error_log("MAILCOWAUTH: Bad Request"); http_response_code(400); // Bad Request echo json_encode($return); exit(); } require_once('../../../web/inc/vars.inc.php'); if (file_exists('../../../web/inc/vars.local.inc.php')) { include_once('../../../web/inc/vars.local.inc.php'); } require_once '../../../web/inc/lib/vendor/autoload.php'; // Init Redis $redis = new Redis(); try { if (!empty(getenv('REDIS_SLAVEOF_IP'))) { $redis->connect(getenv('REDIS_SLAVEOF_IP'), getenv('REDIS_SLAVEOF_PORT')); } else { $redis->connect('redis-mailcow', 6379); } $redis->auth(getenv("REDISPASS")); } catch (Exception $e) { error_log("MAILCOWAUTH: " . $e . PHP_EOL); http_response_code(500); // Internal Server Error echo json_encode($return); exit; } // Init database $dsn = $database_type . ":unix_socket=" . $database_sock . ";dbname=" . $database_name; $opt = [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_EMULATE_PREPARES => false, ]; try { $pdo = new PDO($dsn, $database_user, $database_pass, $opt); } catch (PDOException $e) { error_log("MAILCOWAUTH: " . $e . PHP_EOL); http_response_code(500); // Internal Server Error echo json_encode($return); exit; } // Load core functions first require_once 'functions.inc.php'; require_once 'functions.auth.inc.php'; require_once 'sessions.inc.php'; require_once 'functions.mailbox.inc.php'; require_once 'functions.ratelimit.inc.php'; require_once 'functions.acl.inc.php'; $isSOGoRequest = $post['real_rip'] == getenv('IPV4_NETWORK') . '.248'; $result = false; if ($isSOGoRequest) { // This is a SOGo Auth request. First check for SSO password. $sogo_sso_pass = file_get_contents("/etc/sogo-sso/sogo-sso.pass"); if ($sogo_sso_pass === $post['password']){ error_log('MAILCOWAUTH: SOGo SSO auth for user ' . $post['username']); set_sasl_log($post['username'], $post['real_rip'], "SOGO"); $result = true; } } if ($result === false){ // If it's a SOGo Request, don't check for protocol access if ($isSOGoRequest) { $service = 'SOGO'; $post['service'] = 'NONE'; } else { $service = $post['service']; } $result = apppass_login($post['username'], $post['password'], array( 'service' => $post['service'], 'is_internal' => true, 'remote_addr' => $post['real_rip'] )); if ($result) { error_log('MAILCOWAUTH: App auth for user ' . $post['username'] . " with service " . $service . " from IP " . $post['real_rip']); set_sasl_log($post['username'], $post['real_rip'], $service); } } if ($result === false){ // Init Identity Provider $iam_provider = identity_provider('init'); $iam_settings = identity_provider('get'); $result = user_login($post['username'], $post['password'], array('is_internal' => true, 'service' => $post['service'])); if ($result) { error_log('MAILCOWAUTH: User auth for user ' . $post['username'] . " with service " . $post['service'] . " from IP " . $post['real_rip']); set_sasl_log($post['username'], $post['real_rip'], $post['service']); } } if ($result) { http_response_code(200); // OK $return['success'] = true; } else { error_log("MAILCOWAUTH: Login failed for user " . $post['username'] . " with service " . $post['service'] . " from IP " . $post['real_rip']); http_response_code(401); // Unauthorized } echo json_encode($return); session_destroy(); exit; ================================================ FILE: data/conf/dovecot/auth/passwd-verify.lua ================================================ function auth_password_verify(request, password) if request.domain == nil then return dovecot.auth.PASSDB_RESULT_USER_UNKNOWN, "No such user" end local json = require "cjson" local ltn12 = require "ltn12" local https = require "ssl.https" https.TIMEOUT = 30 local req = { username = request.user, password = password, real_rip = request.real_rip, service = request.service } local req_json = json.encode(req) local res = {} local b, c = https.request { method = "POST", url = "https://nginx:9082", source = ltn12.source.string(req_json), headers = { ["content-type"] = "application/json", ["content-length"] = tostring(#req_json) }, sink = ltn12.sink.table(res), insecure = true } -- Returning PASSDB_RESULT_PASSWORD_MISMATCH will reset the user's auth cache entry. -- Returning PASSDB_RESULT_INTERNAL_FAILURE keeps the existing cache entry, -- even if the TTL has expired. Useful to avoid cache eviction during backend issues. if c ~= 200 and c ~= 401 then dovecot.i_info("HTTP request failed with " .. c .. " for user " .. request.user) return dovecot.auth.PASSDB_RESULT_PASSWORD_MISMATCH, "Upstream error" end local response_str = table.concat(res) local is_response_valid, response_json = pcall(json.decode, response_str) if not is_response_valid then dovecot.i_info("Invalid JSON received: " .. response_str) return dovecot.auth.PASSDB_RESULT_PASSWORD_MISMATCH, "Invalid response format" end if response_json.success == true then return dovecot.auth.PASSDB_RESULT_OK, "" end return dovecot.auth.PASSDB_RESULT_PASSWORD_MISMATCH, "Failed to authenticate" end function auth_passdb_lookup(req) return dovecot.auth.PASSDB_RESULT_USER_UNKNOWN, "" end ================================================ FILE: data/conf/dovecot/dovecot.conf ================================================ # -------------------------------------------------------------------------- # Please create a file "extra.conf" for persistent overrides to dovecot.conf # -------------------------------------------------------------------------- # LDAP example: #passdb { # args = /etc/dovecot/ldap/passdb.conf # driver = ldap #} auth_mechanisms = plain login #mail_debug = yes #auth_debug = yes #log_debug = category=fts-flatcurve # Activate Logging for Flatcurve FTS Searchings log_path = syslog disable_plaintext_auth = yes # Uncomment on NFS share #mmap_disable = yes #mail_fsync = always #mail_nfs_index = yes #mail_nfs_storage = yes login_log_format_elements = "user=<%u> method=%m rip=%r lip=%l mpid=%e %c %k" mail_home = /var/vmail/%d/%n mail_location = maildir:~/ mail_plugins = !include_try /etc/dovecot/sni.conf !include_try /etc/dovecot/sogo_trusted_ip.conf !include_try /etc/dovecot/extra.conf !include_try /etc/dovecot/shared_namespace.conf !include_try /etc/dovecot/conf.d/fts.conf # default_client_limit = 10400 default_vsz_limit = 1024 M ================================================ FILE: data/conf/dovecot/dovecot.folders.conf ================================================ namespace inbox { inbox = yes location = separator = / mailbox "Trash" { auto = subscribe special_use = \Trash } mailbox "Deleted Messages" { special_use = \Trash } mailbox "Deleted Items" { special_use = \Trash } mailbox "Rubbish" { special_use = \Trash } mailbox "Gelöschte Objekte" { special_use = \Trash } mailbox "Gelöschte Elemente" { special_use = \Trash } mailbox "Papierkorb" { special_use = \Trash } mailbox "Itens Excluidos" { special_use = \Trash } mailbox "Itens Excluídos" { special_use = \Trash } mailbox "Lixeira" { special_use = \Trash } mailbox "Prullenbak" { special_use = \Trash } mailbox "Odstránené položky" { special_use = \Trash } mailbox "Koš" { special_use = \Trash } mailbox "Verwijderde items" { special_use = \Trash } mailbox "Удаленные" { special_use = \Trash } mailbox "Удаленные элементы" { special_use = \Trash } mailbox "Корзина" { special_use = \Trash } mailbox "Видалені" { special_use = \Trash } mailbox "Видалені елементи" { special_use = \Trash } mailbox "Кошик" { special_use = \Trash } mailbox "废件箱" { special_use = \Trash } mailbox "已删除消息" { special_use = \Trash } mailbox "已删除邮件" { special_use = \Trash } mailbox "Archive" { auto = subscribe special_use = \Archive } mailbox "Archiv" { special_use = \Archive } mailbox "Archives" { special_use = \Archive } mailbox "Arquivo" { special_use = \Archive } mailbox "Arquivos" { special_use = \Archive } mailbox "Archief" { special_use = \Archive } mailbox "Archív" { special_use = \Archive } mailbox "Archivovať" { special_use = \Archive } mailbox "归档" { special_use = \Archive } mailbox "Архив" { special_use = \Archive } mailbox "Архів" { special_use = \Archive } mailbox "Sent" { auto = subscribe special_use = \Sent } mailbox "Sent Messages" { special_use = \Sent } mailbox "Sent Items" { special_use = \Sent } mailbox "已发送" { special_use = \Sent } mailbox "已发送消息" { special_use = \Sent } mailbox "已发送邮件" { special_use = \Sent } mailbox "Отправленные" { special_use = \Sent } mailbox "Отправленные элементы" { special_use = \Sent } mailbox "Надіслані" { special_use = \Sent } mailbox "Надіслані елементи" { special_use = \Sent } mailbox "Gesendet" { special_use = \Sent } mailbox "Gesendete Objekte" { special_use = \Sent } mailbox "Gesendete Elemente" { special_use = \Sent } mailbox "Itens Enviados" { special_use = \Sent } mailbox "Enviados" { special_use = \Sent } mailbox "Verzonden items" { special_use = \Sent } mailbox "Verzonden" { special_use = \Sent } mailbox "Odoslaná pošta" { special_use = \Sent } mailbox "Odoslané" { special_use = \Sent } mailbox "Drafts" { auto = subscribe special_use = \Drafts } mailbox "Entwürfe" { special_use = \Drafts } mailbox "Rascunhos" { special_use = \Drafts } mailbox "Concepten" { special_use = \Drafts } mailbox "Koncepty" { special_use = \Drafts } mailbox "草稿" { special_use = \Drafts } mailbox "草稿箱" { special_use = \Drafts } mailbox "Черновики" { special_use = \Drafts } mailbox "Чернетки" { special_use = \Drafts } mailbox "Junk" { auto = subscribe special_use = \Junk } mailbox "Junk-E-Mail" { special_use = \Junk } mailbox "Junk E-Mail" { special_use = \Junk } mailbox "Spam" { special_use = \Junk } mailbox "Lixo Eletrônico" { special_use = \Junk } mailbox "Nevyžiadaná pošta" { special_use = \Junk } mailbox "Infikované položky" { special_use = \Junk } mailbox "Ongewenste e-mail" { special_use = \Junk } mailbox "垃圾" { special_use = \Junk } mailbox "垃圾箱" { special_use = \Junk } mailbox "Нежелательная почта" { special_use = \Junk } mailbox "Спам" { special_use = \Junk } mailbox "Небажана пошта" { special_use = \Junk } mailbox "Koncepty" { special_use = \Drafts } mailbox "Nevyžádaná pošta" { special_use = \Junk } mailbox "Odstraněná pošta" { special_use = \Trash } mailbox "Odeslaná pošta" { special_use = \Sent } mailbox "Skräp" { special_use = \Trash } mailbox "Borttagna Meddelanden" { special_use = \Trash } mailbox "Arkiv" { special_use = \Archive } mailbox "Arkeverat" { special_use = \Archive } mailbox "Skickat" { special_use = \Sent } mailbox "Skickade Meddelanden" { special_use = \Sent } mailbox "Utkast" { special_use = \Drafts } mailbox "Skraldespand" { special_use = \Trash } mailbox "Slettet mails" { special_use = \Trash } mailbox "Arkiv" { special_use = \Archive } mailbox "Arkiveret mails" { special_use = \Archive } mailbox "Sendt" { special_use = \Sent } mailbox "Sendte mails" { special_use = \Sent } mailbox "Udkast" { special_use = \Drafts } mailbox "Kladde" { special_use = \Drafts } mailbox "Πρόχειρα" { special_use = \Drafts } mailbox "Απεσταλμένα" { special_use = \Sent } mailbox "Κάδος απορριμάτων" { special_use = \Trash } mailbox "Ανεπιθύμητα" { special_use = \Junk } mailbox "Αρχειοθετημένα" { special_use = \Archive } prefix = } ================================================ FILE: data/conf/dovecot/ldap/passdb.conf ================================================ #hosts = 1.2.3.4 #dn = cn=admin,dc=example,dc=local #dnpass = password #ldap_version = 3 #base = ou=People,dc=example,dc=local #auth_bind = no #pass_filter = (&(objectClass=posixAccount)(mail=%u)) #pass_attrs = mail=user,userPassword=password #default_pass_scheme = SSHA ================================================ FILE: data/conf/mysql/my.cnf ================================================ [mysqld] character-set-client-handshake = FALSE character-set-server = utf8mb4 collation-server = utf8mb4_general_ci #innodb_file_per_table = TRUE #innodb_file_format = barracuda #innodb_large_prefix = TRUE #sql_mode=IGNORE_SPACE,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION max_allowed_packet = 192M max-connections = 550 key_buffer_size = 0 read_buffer_size = 192K sort_buffer_size = 2M innodb_buffer_pool_size = 24M read_rnd_buffer_size = 256K tmp_table_size = 24M performance_schema = 0 innodb-strict-mode = 0 thread_cache_size = 8 query_cache_type = 0 query_cache_size = 0 max_heap_table_size = 48M thread_stack = 256K skip-host-cache skip-name-resolve log-warnings = 0 event_scheduler = 1 interactive_timeout = 3610 wait_timeout = 3610 [client] default-character-set = utf8mb4 [mysql] default-character-set = utf8mb4 ================================================ FILE: data/conf/nginx/templates/nginx.conf.j2 ================================================ user nginx; worker_processes auto; error_log /var/log/nginx/error.log notice; pid /var/run/nginx.pid; events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; server_tokens off; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; sendfile on; #tcp_nopush on; keepalive_timeout 65; #gzip on; # map-size.conf: map_hash_max_size 256; map_hash_bucket_size 256; # site.conf: proxy_cache_path /tmp levels=1:2 keys_zone=sogo:10m inactive=24h max_size=1g; server_names_hash_max_size 512; server_names_hash_bucket_size 128; map $http_x_forwarded_proto $client_req_scheme { default $scheme; https https; } {% if HTTP_REDIRECT %} # HTTP to HTTPS redirect server { root /web; listen {{ HTTP_PORT }} default_server; listen [::]:{{ HTTP_PORT }} default_server; server_name {{ MAILCOW_HOSTNAME }} autodiscover.* autoconfig.* mta-sts.* {{ ADDITIONAL_SERVER_NAMES | join(' ') }}; if ( $request_uri ~* "%0A|%0D" ) { return 403; } location ^~ /.well-known/acme-challenge/ { allow all; default_type "text/plain"; } location ^~ /.well-known/mta-sts.txt { allow all; fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_pass {{ PHPFPMHOST }}:9002; include /etc/nginx/fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root/mta-sts.php; fastcgi_param PATH_INFO $fastcgi_path_info; } location / { return 301 https://$host$uri$is_args$args; } } {%endif%} # Default Server Name server { listen 127.0.0.1:65510; # sogo-auth verify internal {% if not HTTP_REDIRECT %} listen {{ HTTP_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%}; {%endif%} listen {{ HTTPS_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%} ssl; {% if ENABLE_IPV6 %} {% if not HTTP_REDIRECT %} listen [::]:{{ HTTP_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%}; {%endif%} listen [::]:{{ HTTPS_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%} ssl; {%endif%} http2 on; ssl_certificate /etc/ssl/mail/cert.pem; ssl_certificate_key /etc/ssl/mail/key.pem; server_name {{ MAILCOW_HOSTNAME }} autodiscover.* autoconfig.* mta-sts.*; include /etc/nginx/includes/sites-default.conf; } # Additional Server Names {% for SERVER_NAME in ADDITIONAL_SERVER_NAMES %} server { listen 127.0.0.1:65510; # sogo-auth verify internal {% if not HTTP_REDIRECT %} listen {{ HTTP_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%}; {%endif%} listen {{ HTTPS_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%} ssl; {% if ENABLE_IPV6 %} {% if not HTTP_REDIRECT %} listen [::]:{{ HTTP_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%}; {%endif%} listen [::]:{{ HTTPS_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%} ssl; {%endif%} http2 on; ssl_certificate /etc/ssl/mail/cert.pem; ssl_certificate_key /etc/ssl/mail/key.pem; server_name {{ SERVER_NAME }}; include /etc/nginx/includes/sites-default.conf; } {% endfor %} # rspamd dynmaps: server { listen 8081; {% if ENABLE_IPV6 %} listen [::]:8081; {%endif%} index index.php index.html; server_name _; error_log /var/log/nginx/error.log; access_log /var/log/nginx/access.log; root /dynmaps; location ~ \.php$ { try_files $uri =404; fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_pass {{ PHPFPMHOST }}:9001; fastcgi_index index.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param PATH_INFO $fastcgi_path_info; } } # rspamd meta_exporter: server { listen 9081; index index.php index.html; server_name _; error_log /var/log/nginx/error.log; access_log /var/log/nginx/access.log; root /meta_exporter; client_max_body_size 10M; location ~ \.php$ { client_max_body_size 10M; try_files $uri =404; fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_pass {{ PHPFPMHOST }}:9001; fastcgi_index pipe.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param PATH_INFO $fastcgi_path_info; } } server { listen 9082 ssl http2; ssl_certificate /etc/ssl/mail/cert.pem; ssl_certificate_key /etc/ssl/mail/key.pem; index mailcowauth.php; server_name _; error_log /var/log/nginx/error.log; access_log /var/log/nginx/access.log; root /mailcowauth; client_max_body_size 10M; location ~ \.php$ { client_max_body_size 10M; try_files $uri =404; fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_pass phpfpm:9001; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param PATH_INFO $fastcgi_path_info; } } include /etc/nginx/conf.d/*.conf; {% for cert in valid_cert_dirs %} server { {% if not HTTP_REDIRECT %} listen {{ HTTP_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%}; {%endif%} listen {{ HTTPS_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%} ssl; {% if ENABLE_IPV6 %} {% if not HTTP_REDIRECT %} listen [::]:{{ HTTP_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%}; {%endif%} listen [::]:{{ HTTPS_PORT }}{% if NGINX_USE_PROXY_PROTOCOL %} proxy_protocol{%endif%} ssl; {%endif%} http2 on; ssl_certificate {{ cert.cert_path }}cert.pem; ssl_certificate_key {{ cert.cert_path }}key.pem; server_name {{ cert.domains }}; include /etc/nginx/includes/sites-default.conf; } {% endfor %} } ================================================ FILE: data/conf/nginx/templates/sites-default.conf.j2 ================================================ include /etc/nginx/mime.types; charset utf-8; override_charset on; server_tokens off; ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers on; ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305; ssl_ecdh_curve X25519:X448:secp384r1:secp256k1; ssl_session_cache shared:SSL:50m; ssl_session_timeout 1d; ssl_session_tickets off; add_header Strict-Transport-Security "max-age=15768000;"; add_header X-Content-Type-Options nosniff; add_header X-Robots-Tag none; add_header X-Download-Options noopen; add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Permitted-Cross-Domain-Policies none; add_header Referrer-Policy strict-origin; index index.php index.html; client_max_body_size 0; gzip on; gzip_disable "msie6"; gzip_vary on; gzip_proxied off; gzip_comp_level 6; gzip_buffers 16 8k; gzip_http_version 1.1; gzip_min_length 256; gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript application/vnd.ms-fontobject application/x-font-ttf font/opentype image/svg+xml image/x-icon; location ~ ^/(fonts|js|css|img)/ { expires max; add_header Cache-Control public; } error_log /var/log/nginx/error.log; access_log /var/log/nginx/access.log; fastcgi_hide_header X-Powered-By; absolute_redirect off; root /web; # If behind reverse proxy, forwards the correct IP set_real_ip_from 10.0.0.0/8; set_real_ip_from 172.16.0.0/12; set_real_ip_from 192.168.0.0/16; set_real_ip_from fc00::/7; {% for TRUSTED_PROXY in TRUSTED_PROXIES %} set_real_ip_from {{ TRUSTED_PROXY }}; {% endfor %} {% if not NGINX_USE_PROXY_PROTOCOL %} real_ip_header X-Forwarded-For; {% else %} real_ip_header proxy_protocol; {% endif %} real_ip_recursive on; location @strip-ext { rewrite ^(.*)$ $1.php last; } location ^~ /inc/lib/ { deny all; return 403; } location ^~ /.well-known/acme-challenge/ { allow all; default_type "text/plain"; } location ^~ /.well-known/mta-sts.txt { allow all; fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_pass {{ PHPFPMHOST }}:9002; include /etc/nginx/fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root/mta-sts.php; fastcgi_param PATH_INFO $fastcgi_path_info; } rewrite ^/.well-known/caldav$ /SOGo/dav/ permanent; rewrite ^/.well-known/carddav$ /SOGo/dav/ permanent; location / { try_files $uri $uri/ @strip-ext; } location /qhandler { rewrite ^/qhandler/(.*)/(.*) /qhandler.php?action=$1&hash=$2; } location /edit { rewrite ^/edit/(.*)/(.*) /edit.php?$1=$2; } location ~ ^/api/v1/(.*)$ { try_files $uri $uri/ /json_api.php?query=$1&$args; } location ~ ^/cache/(.*)$ { try_files $uri $uri/ /resource.php?file=$1; } location ~ \.php$ { try_files $uri =404; fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_pass {{ PHPFPMHOST }}:9002; fastcgi_index index.php; include /etc/nginx/fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param PATH_INFO $fastcgi_path_info; fastcgi_read_timeout 3600; fastcgi_send_timeout 3600; } location ~* ^/Autodiscover/Autodiscover.xml { fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_pass {{ PHPFPMHOST }}:9002; include /etc/nginx/fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; try_files /autodiscover.php =404; } location ~* ^/Autodiscover/Autodiscover.json { fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_pass {{ PHPFPMHOST }}:9002; include /etc/nginx/fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; try_files /autodiscover-json.php =404; } location ~ /(?:m|M)ail/(?:c|C)onfig-v1.1.xml { fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_pass {{ PHPFPMHOST }}:9002; include /etc/nginx/fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; try_files /autoconfig.php =404; } {% if not SKIP_RSPAMD %} location /rspamd/ { location /rspamd/auth { # proxy_pass is not inherited proxy_pass http://{{ RSPAMDHOST }}:11334/auth; proxy_intercept_errors on; proxy_set_header Host $http_host; proxy_set_header X-Forwarded-For {% if not NGINX_USE_PROXY_PROTOCOL %}$proxy_add_x_forwarded_for{% else %}$proxy_protocol_addr{%endif%}; proxy_set_header X-Real-IP {% if not NGINX_USE_PROXY_PROTOCOL %}$remote_addr{% else %}$proxy_protocol_addr{%endif%}; proxy_redirect off; error_page 401 /_rspamderror.php; } proxy_pass http://{{ RSPAMDHOST }}:11334/; proxy_set_header Host $http_host; proxy_set_header X-Forwarded-For {% if not NGINX_USE_PROXY_PROTOCOL %}$proxy_add_x_forwarded_for{% else %}$proxy_protocol_addr{%endif%}; proxy_set_header X-Real-IP {% if not NGINX_USE_PROXY_PROTOCOL %}$remote_addr{% else %}$proxy_protocol_addr{%endif%}; proxy_redirect off; } {% endif %} {% if not SKIP_SOGO %} location ^~ /principals { return 301 /SOGo/dav; } location /sogo-auth-verify { internal; proxy_set_header X-Original-URI $request_uri; proxy_set_header X-Real-IP $remote_addr; proxy_set_header Host $http_host; proxy_set_header Content-Length ""; proxy_pass http://127.0.0.1:65510/sogo-auth; proxy_pass_request_body off; } location ^~ /Microsoft-Server-ActiveSync { auth_request /sogo-auth-verify; auth_request_set $user $upstream_http_x_user; auth_request_set $auth $upstream_http_x_auth; auth_request_set $auth_type $upstream_http_x_auth_type; proxy_set_header x-webobjects-remote-user "$user"; proxy_set_header Authorization "$auth"; proxy_set_header x-webobjects-auth-type "$auth_type"; proxy_pass http://{{ SOGOHOST }}:20000/SOGo/Microsoft-Server-ActiveSync; proxy_set_header X-Forwarded-For {% if not NGINX_USE_PROXY_PROTOCOL %}$proxy_add_x_forwarded_for{% else %}$proxy_protocol_addr{%endif%}; proxy_set_header X-Real-IP {% if not NGINX_USE_PROXY_PROTOCOL %}$remote_addr{% else %}$proxy_protocol_addr{%endif%}; proxy_connect_timeout 75; proxy_send_timeout 3600; proxy_read_timeout 3600; proxy_buffer_size 128k; proxy_buffers 64 512k; proxy_busy_buffers_size 512k; proxy_set_header Host $http_host; client_body_buffer_size 512k; client_max_body_size 0; } location ^~ /SOGo { location ~* ^/SOGo/so/.*\.(xml|js|html|xhtml)$ { auth_request /sogo-auth-verify; auth_request_set $user $upstream_http_x_user; auth_request_set $auth $upstream_http_x_auth; auth_request_set $auth_type $upstream_http_x_auth_type; proxy_set_header x-webobjects-remote-user "$user"; proxy_set_header Authorization "$auth"; proxy_set_header x-webobjects-auth-type "$auth_type"; proxy_pass http://{{ SOGOHOST }}:20000; proxy_set_header X-Forwarded-For {% if not NGINX_USE_PROXY_PROTOCOL %}$proxy_add_x_forwarded_for{% else %}$proxy_protocol_addr{%endif%}; proxy_set_header X-Real-IP {% if not NGINX_USE_PROXY_PROTOCOL %}$remote_addr{% else %}$proxy_protocol_addr{%endif%}; proxy_set_header Host $http_host; proxy_set_header x-webobjects-server-protocol HTTP/1.0; proxy_set_header x-webobjects-remote-host $remote_addr; proxy_set_header x-webobjects-server-name $server_name; proxy_set_header x-webobjects-server-url $client_req_scheme://$http_host; proxy_set_header x-webobjects-server-port $server_port; proxy_hide_header Content-Type; add_header Content-Type text/plain; break; } auth_request /sogo-auth-verify; auth_request_set $user $upstream_http_x_user; auth_request_set $auth $upstream_http_x_auth; auth_request_set $auth_type $upstream_http_x_auth_type; proxy_set_header x-webobjects-remote-user "$user"; proxy_set_header Authorization "$auth"; proxy_set_header x-webobjects-auth-type "$auth_type"; proxy_pass http://{{ SOGOHOST }}:20000; proxy_set_header X-Forwarded-For {% if not NGINX_USE_PROXY_PROTOCOL %}$proxy_add_x_forwarded_for{% else %}$proxy_protocol_addr{%endif%}; proxy_set_header X-Real-IP {% if not NGINX_USE_PROXY_PROTOCOL %}$remote_addr{% else %}$proxy_protocol_addr{%endif%}; proxy_set_header Host $http_host; proxy_set_header x-webobjects-server-protocol HTTP/1.0; proxy_set_header x-webobjects-remote-host $remote_addr; proxy_set_header x-webobjects-server-name $server_name; proxy_set_header x-webobjects-server-url $client_req_scheme://$http_host; proxy_set_header x-webobjects-server-port $server_port; proxy_buffer_size 128k; proxy_buffers 64 512k; proxy_busy_buffers_size 512k; proxy_send_timeout 3600; proxy_read_timeout 3600; client_body_buffer_size 128k; client_max_body_size 0; break; } location ~* /sogo$ { return 301 $client_req_scheme://$http_host/SOGo; } location /SOGo.woa/WebServerResources/ { alias /usr/local/lib/GNUstep/SOGo/WebServerResources/; } location /.woa/WebServerResources/ { alias /usr/local/lib/GNUstep/SOGo/WebServerResources/; } location /SOGo/WebServerResources/ { alias /usr/local/lib/GNUstep/SOGo/WebServerResources/; } location (^/SOGo/so/ControlPanel/Products/[^/]*UI/Resources/.*\.(jpg|png|gif|css|js)$) { alias /usr/local/lib/GNUstep/SOGo/$1.SOGo/Resources/$2; } {% endif %} include /etc/nginx/conf.d/site.*.custom; error_page 502 @awaitingupstream; location @awaitingupstream { rewrite ^(.*)$ /_status.502.html break; } location ~* \.php$ { return 404; } location ~* \.twig$ { return 404; } ================================================ FILE: data/conf/phpfpm/crons/keycloak-sync.php ================================================ PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_EMULATE_PREPARES => false, ]; try { $pdo = new PDO($dsn, $database_user, $database_pass, $opt); } catch (PDOException $e) { logMsg("err", $e->getMessage()); session_destroy(); exit; } // Init Redis $redis = new Redis(); try { if (!empty(getenv('REDIS_SLAVEOF_IP'))) { $redis->connect(getenv('REDIS_SLAVEOF_IP'), getenv('REDIS_SLAVEOF_PORT')); } else { $redis->connect('redis-mailcow', 6379); } $redis->auth(getenv("REDISPASS")); } catch (Exception $e) { echo "Exiting: " . $e->getMessage(); session_destroy(); exit; } function logMsg($priority, $message, $task = "Keycloak Sync") { global $redis; $finalMsg = array( "time" => time(), "priority" => $priority, "task" => $task, "message" => $message ); $redis->lPush('CRON_LOG', json_encode($finalMsg)); } // Load core functions first require_once __DIR__ . '/../web/inc/functions.inc.php'; require_once __DIR__ . '/../web/inc/functions.auth.inc.php'; require_once __DIR__ . '/../web/inc/sessions.inc.php'; require_once __DIR__ . '/../web/inc/functions.mailbox.inc.php'; require_once __DIR__ . '/../web/inc/functions.ratelimit.inc.php'; require_once __DIR__ . '/../web/inc/functions.acl.inc.php'; $_SESSION['mailcow_cc_username'] = "admin"; $_SESSION['mailcow_cc_role'] = "admin"; $_SESSION['acl']['tls_policy'] = "1"; $_SESSION['acl']['quarantine_notification'] = "1"; $_SESSION['acl']['quarantine_category'] = "1"; $_SESSION['acl']['ratelimit'] = "1"; $_SESSION['acl']['sogo_access'] = "1"; $_SESSION['acl']['protocol_access'] = "1"; $_SESSION['acl']['mailbox_relayhost'] = "1"; $_SESSION['acl']['unlimited_quota'] = "1"; $iam_settings = identity_provider('get'); if ($iam_settings['authsource'] != "keycloak" || (intval($iam_settings['periodic_sync']) != 1 && intval($iam_settings['import_users']) != 1)) { session_destroy(); exit; } // Set pagination variables $start = 0; $max = 100; // lock sync if already running $lock_file = '/tmp/iam-sync.lock'; if (file_exists($lock_file)) { $lock_file_parts = explode("\n", file_get_contents($lock_file)); $pid = $lock_file_parts[0]; if (count($lock_file_parts) > 1){ $last_execution = $lock_file_parts[1]; $elapsed_time = (time() - $last_execution) / 60; if ($elapsed_time < intval($iam_settings['sync_interval'])) { logMsg("warning", "Sync not ready (".number_format((float)$elapsed_time, 2, '.', '')."min / ".$iam_settings['sync_interval']."min)"); session_destroy(); exit; } } if (posix_kill($pid, 0)) { logMsg("warning", "Sync is already running"); session_destroy(); exit; } else { unlink($lock_file); } } $lock_file_handle = fopen($lock_file, 'w'); fwrite($lock_file_handle, getmypid()); fclose($lock_file_handle); // Init Keycloak Provider $iam_provider = identity_provider('init'); // Loop until all users have been retrieved while (true) { // Get admin access token $admin_token = identity_provider("get-keycloak-admin-token"); // Make the API request to retrieve the users $url = "{$iam_settings['server_url']}/admin/realms/{$iam_settings['realm']}/users?first=$start&max=$max"; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, [ "Content-Type: application/json", "Authorization: Bearer " . $admin_token ]); $response = curl_exec($ch); $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($code != 200){ logMsg("err", "Received HTTP {$code}"); session_destroy(); exit; } try { $response = json_decode($response, true); } catch (Exception $e) { logMsg("err", $e->getMessage()); break; } if (!is_array($response)){ logMsg("err", "Received malformed response from keycloak api"); break; } if (count($response) == 0) { break; } // Process the batch of users foreach ($response as $user) { if (empty($user['email'])){ logMsg("warning", "No email address in keycloak found for user " . $user['name']); continue; } // try get mailbox user $stmt = $pdo->prepare("SELECT mailbox.*, domain.active AS d_active FROM `mailbox` INNER JOIN domain on mailbox.domain = domain.domain WHERE `kind` NOT REGEXP 'location|thing|group' AND `username` = :user"); $stmt->execute(array(':user' => $user['email'])); $row = $stmt->fetch(PDO::FETCH_ASSOC); // check if matching attribute mapping exists $user_template = $user['attributes']['mailcow_template'][0]; $mapper_key = array_search($user_template, $iam_settings['mappers']); $_SESSION['access_all_exception'] = '1'; if (!$row && intval($iam_settings['import_users']) == 1){ if ($mapper_key === false){ if (!empty($iam_settings['default_template'])) { $mbox_template = $iam_settings['default_template']; logMsg("warning", "Using default template for user " . $user['email']); } else { logMsg("warning", "No matching attribute mapping found for user " . $user['email']); continue; } } else { $mbox_template = $iam_settings['templates'][$mapper_key]; } // mailbox user does not exist, create... logMsg("info", "Creating user " . $user['email']); $create_res = mailbox('add', 'mailbox_from_template', array( 'domain' => explode('@', $user['email'])[1], 'local_part' => explode('@', $user['email'])[0], 'name' => $user['firstName'] . " " . $user['lastName'], 'authsource' => 'keycloak', 'template' => $mbox_template )); if (!$create_res){ logMsg("err", "Could not create user " . $user['email']); continue; } } else if ($row && intval($iam_settings['periodic_sync']) == 1 && $row['authsource'] == "keycloak") { if ($mapper_key === false){ logMsg("warning", "No matching attribute mapping found for user " . $user['email']); continue; } $mbox_template = $iam_settings['templates'][$mapper_key]; // mailbox user does exist, sync attribtues... logMsg("info", "Syncing attributes for user " . $user['email']); mailbox('edit', 'mailbox_from_template', array( 'username' => $user['email'], 'name' => $user['firstName'] . " " . $user['lastName'], 'template' => $mbox_template )); } else { // skip mailbox user logMsg("info", "Skipping user " . $user['email']); } $_SESSION['access_all_exception'] = '0'; sleep(0.025); } // Update the pagination variables for the next batch $start += $max; sleep(1); } logMsg("info", "DONE!"); // add last execution time to lock file $lock_file_handle = fopen($lock_file, 'w'); fwrite($lock_file_handle, getmypid() . "\n" . time()); fclose($lock_file_handle); session_destroy(); ================================================ FILE: data/conf/phpfpm/crons/ldap-sync.php ================================================ PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_EMULATE_PREPARES => false, ]; try { $pdo = new PDO($dsn, $database_user, $database_pass, $opt); } catch (PDOException $e) { logMsg("err", $e->getMessage()); session_destroy(); exit; } // Init Redis $redis = new Redis(); try { if (!empty(getenv('REDIS_SLAVEOF_IP'))) { $redis->connect(getenv('REDIS_SLAVEOF_IP'), getenv('REDIS_SLAVEOF_PORT')); } else { $redis->connect('redis-mailcow', 6379); } $redis->auth(getenv("REDISPASS")); } catch (Exception $e) { echo "Exiting: " . $e->getMessage(); session_destroy(); exit; } function logMsg($priority, $message, $task = "LDAP Sync") { global $redis; $finalMsg = array( "time" => time(), "priority" => $priority, "task" => $task, "message" => $message ); $redis->lPush('CRON_LOG', json_encode($finalMsg)); } // Load core functions first require_once __DIR__ . '/../web/inc/functions.inc.php'; require_once __DIR__ . '/../web/inc/functions.auth.inc.php'; require_once __DIR__ . '/../web/inc/sessions.inc.php'; require_once __DIR__ . '/../web/inc/functions.mailbox.inc.php'; require_once __DIR__ . '/../web/inc/functions.ratelimit.inc.php'; require_once __DIR__ . '/../web/inc/functions.acl.inc.php'; $_SESSION['mailcow_cc_username'] = "admin"; $_SESSION['mailcow_cc_role'] = "admin"; $_SESSION['acl']['tls_policy'] = "1"; $_SESSION['acl']['quarantine_notification'] = "1"; $_SESSION['acl']['quarantine_category'] = "1"; $_SESSION['acl']['ratelimit'] = "1"; $_SESSION['acl']['sogo_access'] = "1"; $_SESSION['acl']['protocol_access'] = "1"; $_SESSION['acl']['mailbox_relayhost'] = "1"; $_SESSION['acl']['unlimited_quota'] = "1"; $iam_settings = identity_provider('get'); if ($iam_settings['authsource'] != "ldap" || (intval($iam_settings['periodic_sync']) != 1 && intval($iam_settings['import_users']) != 1)) { session_destroy(); exit; } // Set pagination variables $start = 0; $max = 100; // lock sync if already running $lock_file = '/tmp/iam-sync.lock'; if (file_exists($lock_file)) { $lock_file_parts = explode("\n", file_get_contents($lock_file)); $pid = $lock_file_parts[0]; if (count($lock_file_parts) > 1){ $last_execution = $lock_file_parts[1]; $elapsed_time = (time() - $last_execution) / 60; if ($elapsed_time < intval($iam_settings['sync_interval'])) { logMsg("warning", "Sync not ready (".number_format((float)$elapsed_time, 2, '.', '')."min / ".$iam_settings['sync_interval']."min)"); session_destroy(); exit; } } if (posix_kill($pid, 0)) { logMsg("warning", "Sync is already running"); session_destroy(); exit; } else { unlink($lock_file); } } $lock_file_handle = fopen($lock_file, 'w'); fwrite($lock_file_handle, getmypid()); fclose($lock_file_handle); // Init Provider $iam_provider = identity_provider('init'); // Get ldap users $ldap_query = $iam_provider->query(); if (!empty($iam_settings['filter'])) { $ldap_query = $ldap_query->rawFilter($iam_settings['filter']); } $response = $ldap_query->where($iam_settings['username_field'], "*") ->where($iam_settings['attribute_field'], "*") ->select([$iam_settings['username_field'], $iam_settings['attribute_field'], 'displayname']) ->paginate($max); // Process the users foreach ($response as $user) { // try get mailbox user $stmt = $pdo->prepare("SELECT mailbox.*, domain.active AS d_active FROM `mailbox` INNER JOIN domain on mailbox.domain = domain.domain WHERE `kind` NOT REGEXP 'location|thing|group' AND `username` = :user"); $stmt->execute(array(':user' => $user[$iam_settings['username_field']][0])); $row = $stmt->fetch(PDO::FETCH_ASSOC); // check if matching attribute mapping exists $user_template = $user[$iam_settings['attribute_field']][0]; $mapper_key = array_search($user_template, $iam_settings['mappers']); if (empty($user[$iam_settings['username_field']][0])){ logMsg("warning", "Skipping user " . $user['displayname'][0] . " due to empty LDAP ". $iam_settings['username_field'] . " property."); continue; } $_SESSION['access_all_exception'] = '1'; if (!$row && intval($iam_settings['import_users']) == 1){ if ($mapper_key === false){ if (!empty($iam_settings['default_template'])) { $mbox_template = $iam_settings['default_template']; } else { logMsg("warning", "No matching attribute mapping found for user " . $user[$iam_settings['username_field']][0]); continue; } } else { $mbox_template = $iam_settings['templates'][$mapper_key]; } // mailbox user does not exist, create... logMsg("info", "Creating user " . $user[$iam_settings['username_field']][0]); $create_res = mailbox('add', 'mailbox_from_template', array( 'domain' => explode('@', $user[$iam_settings['username_field']][0])[1], 'local_part' => explode('@', $user[$iam_settings['username_field']][0])[0], 'name' => $user['displayname'][0], 'authsource' => 'ldap', 'template' => $mbox_template )); if (!$create_res){ logMsg("err", "Could not create user " . $user[$iam_settings['username_field']][0]); continue; } } else if ($row && intval($iam_settings['periodic_sync']) == 1 && $row['authsource'] == "ldap") { if ($mapper_key === false){ logMsg("warning", "No matching attribute mapping found for user " . $user[$iam_settings['username_field']][0]); continue; } $mbox_template = $iam_settings['templates'][$mapper_key]; // mailbox user does exist, sync attribtues... logMsg("info", "Syncing attributes for user " . $user[$iam_settings['username_field']][0]); mailbox('edit', 'mailbox_from_template', array( 'username' => $user[$iam_settings['username_field']][0], 'name' => $user['displayname'][0], 'template' => $mbox_template )); } else { // skip mailbox user logMsg("info", "Skipping user " . $user[$iam_settings['username_field']][0]); } $_SESSION['access_all_exception'] = '0'; sleep(0.025); } logMsg("info", "DONE!"); // add last execution time to lock file $lock_file_handle = fopen($lock_file, 'w'); fwrite($lock_file_handle, getmypid() . "\n" . time()); fclose($lock_file_handle); session_destroy(); ================================================ FILE: data/conf/phpfpm/php-conf.d/opcache-recommended.ini ================================================ ; NOTE: Restart phpfpm on ANY manual changes to PHP files! ; opcache opcache.enable=1 opcache.enable_cli=1 opcache.interned_strings_buffer=16 opcache.max_accelerated_files=10000 opcache.memory_consumption=128 opcache.save_comments=1 opcache.validate_timestamps=0 ; JIT ; Disabled for now due to some PHP segmentation faults observed ; in certain environments. Possibly some PHP or PHP extension bug. opcache.jit=disable opcache.jit_buffer_size=0 ================================================ FILE: data/conf/phpfpm/php-conf.d/other.ini ================================================ max_execution_time = 3600 max_input_time = 3600 memory_limit = 512M ================================================ FILE: data/conf/phpfpm/php-conf.d/upload.ini ================================================ file_uploads = On upload_max_filesize = 64M post_max_size = 64M ================================================ FILE: data/conf/phpfpm/php-fpm.d/pools.conf ================================================ [system-worker] user = www-data group = www-data pm = dynamic pm.max_children = 15 pm.start_servers = 2 pm.min_spare_servers = 2 pm.max_spare_servers = 4 listen = [::]:9001 access.log = /proc/self/fd/2 clear_env = no catch_workers_output = yes php_admin_value[memory_limit] = 256M php_admin_value[disable_functions] = show_source, highlight_file, apache_child_terminate, apache_get_modules, apache_note, apache_setenv, virtual, dl, disk_total_space, posix_getpwnam, posix_getpwuid, posix_mkfifo, posix_mknod, posix_setpgid, posix_setsid, posix_setuid, posix_uname, proc_nice, openlog, syslog, pfsockopen, system, shell_exec, passthru, popen, proc_open, exec, ini_alter, pcntl_exec, proc_close, proc_get_status, proc_terminate, symlink [web-worker] user = www-data group = www-data pm = dynamic pm.max_children = 50 pm.start_servers = 10 pm.min_spare_servers = 10 pm.max_spare_servers = 15 listen = [::]:9002 access.log = /proc/self/fd/2 clear_env = no catch_workers_output = yes php_admin_value[memory_limit] = 512M php_admin_value[disable_functions] = show_source, highlight_file, apache_child_terminate, apache_get_modules, apache_note, apache_setenv, virtual, dl, disk_total_space, posix_getpwnam, posix_getpwuid, posix_mkfifo, posix_mknod, posix_setpgid, posix_setsid, posix_setuid, posix_uname, proc_nice, openlog, syslog, pfsockopen, system, shell_exec, passthru, popen, proc_open, exec, ini_alter, pcntl_exec, proc_close, proc_get_status, proc_terminate, symlink ================================================ FILE: data/conf/phpfpm/sogo-sso/.gitkeep ================================================ ================================================ FILE: data/conf/postfix/anonymize_headers.pcre ================================================ if /^\s*Received:.*Authenticated sender.*\(Postcow\)/ #/^Received: from .*? \([\w-.]* \[.*?\]\)\s+\(Authenticated sender: (.+)\)\s+by.+\(Postcow\) with (E?SMTPS?A?) id ([A-F0-9]+).+;.*?/ /^Received: from .*? \([\w\-.]* \[.*?\]\)(.*|\n.*)\(Authenticated sender: (.+)\)\s+by.+\(Postcow\) with (.*)/ REPLACE Received: from [127.0.0.1] (localhost [127.0.0.1]) by localhost (Mailerdaemon) with $3 endif if /^\s*Received: from.* \(.*dovecot-mailcow.*mailcow-network.*\).*\(Postcow\)/ /^Received: from.* (.*|\n.*)\((.+) (.+)\)\s+by (.+) \(Postcow\) with (.*)/ REPLACE Received: from sieve (sieve $3) by $4 (Postcow) with $5 endif if /^\s*Received: from.* \(.*rspamd-mailcow.*mailcow-network.*\).*\(Postcow\)/ /^Received: from.* (.*|\n.*)\((.+) (.+)\)\s+by (.+) \(Postcow\) with (.*)/ REPLACE Received: from rspamd (rspamd $3) by $4 (Postcow) with $5 endif /^\s*X-Enigmail/ IGNORE # Not removing Mailer by default, might be signed #/^\s*X-Mailer/ IGNORE /^\s*X-Originating-IP/ IGNORE /^\s*X-Forward/ IGNORE # Not removing UA by default, might be signed #/^\s*User-Agent/ IGNORE ================================================ FILE: data/conf/postfix/local_transport ================================================ /watchdog@localhost$/ watchdog_discard: /localhost$/ local: ================================================ FILE: data/conf/postfix/main.cf ================================================ # -------------------------------------------------------------------------- # Please create a file "extra.cf" for persistent overrides to main.cf # -------------------------------------------------------------------------- biff = no append_dot_mydomain = no smtpd_tls_cert_file = /etc/ssl/mail/cert.pem smtpd_tls_key_file = /etc/ssl/mail/key.pem tls_server_sni_maps = hash:/opt/postfix/conf/sni.map smtpd_tls_received_header = yes smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache smtpd_relay_restrictions = permit_mynetworks, permit_sasl_authenticated, defer_unauth_destination smtpd_forbid_bare_newline = yes # alias maps are auto-generated in postfix.sh on startup alias_maps = hash:/etc/aliases alias_database = hash:/etc/aliases relayhost = mynetworks_style = subnet mailbox_size_limit = 0 recipient_delimiter = + inet_interfaces = all inet_protocols = all bounce_queue_lifetime = 1d broken_sasl_auth_clients = yes disable_vrfy_command = yes maximal_backoff_time = 1800s maximal_queue_lifetime = 5d delay_warning_time = 4h message_size_limit = 104857600 milter_default_action = tempfail milter_protocol = 6 minimal_backoff_time = 300s plaintext_reject_code = 550 postscreen_access_list = permit_mynetworks, cidr:/opt/postfix/conf/custom_postscreen_whitelist.cidr, cidr:/opt/postfix/conf/postscreen_access.cidr, tcp:127.0.0.1:10027 postscreen_bare_newline_enable = no postscreen_blacklist_action = drop postscreen_cache_cleanup_interval = 24h postscreen_cache_map = proxy:btree:$data_directory/postscreen_cache postscreen_dnsbl_action = enforce postscreen_dnsbl_threshold = 6 postscreen_dnsbl_ttl = 5m postscreen_greet_action = enforce postscreen_greet_banner = $smtpd_banner postscreen_greet_ttl = 2d postscreen_greet_wait = 3s postscreen_non_smtp_command_enable = no postscreen_pipelining_enable = no proxy_read_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_sasl_passwd_maps_transport_maps.cf, proxy:mysql:/opt/postfix/conf/sql/mysql_mbr_access_maps.cf, proxy:mysql:/opt/postfix/conf/sql/mysql_tls_enforce_in_policy.cf, $sender_dependent_default_transport_maps, $smtp_tls_policy_maps, $local_recipient_maps, $mydestination, $virtual_alias_maps, $virtual_alias_domains, $virtual_mailbox_maps, $virtual_mailbox_domains, $relay_recipient_maps, $relay_domains, $canonical_maps, $sender_canonical_maps, $sender_bcc_maps, $recipient_bcc_maps, $recipient_canonical_maps, $relocated_maps, $transport_maps, $mynetworks, $smtpd_sender_login_maps, $smtp_sasl_password_maps queue_run_delay = 300s relay_domains = proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_relay_domain_maps.cf relay_recipient_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_relay_recipient_maps.cf sender_dependent_default_transport_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_sender_dependent_default_transport_maps.cf smtp_tls_CAfile = /etc/ssl/certs/ca-certificates.crt smtp_tls_cert_file = /etc/ssl/mail/cert.pem smtp_tls_key_file = /etc/ssl/mail/key.pem smtp_tls_loglevel = 1 smtp_dns_support_level = dnssec smtp_tls_security_level = dane smtpd_data_restrictions = reject_unauth_pipelining, permit smtpd_delay_reject = yes smtpd_error_sleep_time = 10s smtpd_forbid_bare_newline = yes smtpd_hard_error_limit = ${stress?1}${stress:5} smtpd_helo_required = yes smtpd_proxy_timeout = 600s smtpd_recipient_restrictions = check_recipient_mx_access proxy:mysql:/opt/postfix/conf/sql/mysql_mbr_access_maps.cf, permit_sasl_authenticated, permit_mynetworks, check_recipient_access proxy:mysql:/opt/postfix/conf/sql/mysql_tls_enforce_in_policy.cf, reject_invalid_helo_hostname, reject_unauth_destination smtpd_sasl_auth_enable = yes smtpd_sasl_authenticated_header = yes smtpd_sasl_path = inet:dovecot:10001 smtpd_sasl_type = dovecot smtpd_sender_login_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_sender_acl.cf smtpd_sender_restrictions = reject_authenticated_sender_login_mismatch, permit_mynetworks, permit_sasl_authenticated, reject_unlisted_sender, reject_unknown_sender_domain smtpd_soft_error_limit = 3 smtpd_tls_auth_only = yes smtpd_tls_dh1024_param_file = /etc/ssl/mail/dhparams.pem smtpd_tls_eecdh_grade = auto smtpd_tls_exclude_ciphers = ECDHE-RSA-RC4-SHA, RC4, aNULL, DES-CBC3-SHA, ECDHE-RSA-DES-CBC3-SHA, EDH-RSA-DES-CBC3-SHA smtpd_tls_loglevel = 1 # Mandatory protocols and ciphers are used when a connections is enforced to use TLS # Does _not_ apply to enforced incoming TLS settings per mailbox smtp_tls_mandatory_protocols = >=TLSv1.2 lmtp_tls_mandatory_protocols = >=TLSv1.2 smtpd_tls_mandatory_protocols = >=TLSv1.2 smtpd_tls_mandatory_ciphers = high smtp_tls_protocols = >=TLSv1.2 lmtp_tls_protocols = >=TLSv1.2 smtpd_tls_protocols = >=TLSv1.2 smtpd_tls_security_level = may tls_preempt_cipherlist = yes tls_ssl_options = NO_COMPRESSION, NO_RENEGOTIATION virtual_alias_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_alias_maps.cf, proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_resource_maps.cf, proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_spamalias_maps.cf, proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_alias_domain_maps.cf virtual_gid_maps = static:5000 virtual_mailbox_base = /var/vmail/ virtual_mailbox_domains = proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_domains_maps.cf # -- moved to rspamd on 2021-06-01 #recipient_bcc_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_recipient_bcc_maps.cf #sender_bcc_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_sender_bcc_maps.cf recipient_canonical_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_recipient_canonical_maps.cf recipient_canonical_classes = envelope_recipient virtual_mailbox_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_mailbox_maps.cf virtual_minimum_uid = 104 virtual_transport = lmtp:inet:dovecot:24 virtual_uid_maps = static:5000 smtpd_milters = inet:rspamd:9900 non_smtpd_milters = inet:rspamd:9900 milter_mail_macros = i {mail_addr} {client_addr} {client_name} {auth_authen} mydestination = localhost.localdomain, localhost smtp_address_preference = any smtp_sender_dependent_authentication = yes smtp_sasl_auth_enable = yes smtp_sasl_password_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_sasl_passwd_maps_sender_dependent.cf smtp_sasl_security_options = smtp_sasl_mechanism_filter = plain, login smtp_tls_policy_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_tls_policy_override_maps.cf socketmap:inet:postfix-tlspol:8642:QUERY smtp_header_checks = pcre:/opt/postfix/conf/anonymize_headers.pcre mail_name = Postcow # local_transport map catches local destinations and prevents routing local dests when the next map would route "*" # Use custom_transport.pcre for custom transports transport_maps = pcre:/opt/postfix/conf/custom_transport.pcre, pcre:/opt/postfix/conf/local_transport, proxy:mysql:/opt/postfix/conf/sql/mysql_relay_ne.cf, proxy:mysql:/opt/postfix/conf/sql/mysql_transport_maps.cf smtp_sasl_auth_soft_bounce = no postscreen_discard_ehlo_keywords = chunking, silent-discard, smtputf8, dsn smtpd_discard_ehlo_keywords = chunking, silent-discard, smtputf8 compatibility_level = 3.7 # Define protocols for SMTPS and submission service submission_smtpd_tls_mandatory_protocols = >=TLSv1.2 smtps_smtpd_tls_mandatory_protocols = >=TLSv1.2 parent_domain_matches_subdomains = debug_peer_list,fast_flush_domains,mynetworks,qmqpd_authorized_clients # This Option is added to correctly set the X-Original-To Header when mails are send to lmtp (dovecot) lmtp_destination_recipient_limit=1 # DO NOT EDIT ANYTHING BELOW # # Overrides # ================================================ FILE: data/conf/postfix/master.cf ================================================ # inter-mx with postscreen on 25/tcp smtp inet n - n - 1 postscreen 10025 inet n - n - 1 postscreen -o postscreen_upstream_proxy_protocol=haproxy -o syslog_name=haproxy smtpd pass - - n - - smtpd -o smtpd_sasl_auth_enable=no -o smtpd_sender_restrictions=permit_mynetworks,reject_unlisted_sender,reject_unknown_sender_domain # smtpd tls-wrapped (smtps) on 465/tcp # TLS protocol can be modified by setting smtps_smtpd_tls_mandatory_protocols in extra.cf smtps inet n - n - - smtpd -o smtpd_tls_wrappermode=yes -o smtpd_client_restrictions=permit_mynetworks,permit_sasl_authenticated,reject -o smtpd_tls_mandatory_protocols=$smtps_smtpd_tls_mandatory_protocols -o tls_preempt_cipherlist=yes -o cleanup_service_name=smtp_sender_cleanup -o syslog_name=postfix/smtps 10465 inet n - n - - smtpd -o smtpd_upstream_proxy_protocol=haproxy -o smtpd_tls_wrappermode=yes -o smtpd_client_restrictions=permit_mynetworks,permit_sasl_authenticated,reject -o smtpd_tls_mandatory_protocols=$smtps_smtpd_tls_mandatory_protocols -o tls_preempt_cipherlist=yes -o cleanup_service_name=smtp_sender_cleanup -o syslog_name=postfix/smtps-haproxy # smtpd with starttls on 587/tcp # TLS protocol can be modified by setting submission_smtpd_tls_mandatory_protocols in extra.cf submission inet n - n - - smtpd -o smtpd_client_restrictions=permit_mynetworks,permit_sasl_authenticated,reject -o smtpd_enforce_tls=yes -o smtpd_tls_security_level=encrypt -o smtpd_tls_mandatory_protocols=$submission_smtpd_tls_mandatory_protocols -o tls_preempt_cipherlist=yes -o cleanup_service_name=smtp_sender_cleanup -o syslog_name=postfix/submission 10587 inet n - n - - smtpd -o smtpd_upstream_proxy_protocol=haproxy -o smtpd_client_restrictions=permit_mynetworks,permit_sasl_authenticated,reject -o smtpd_enforce_tls=yes -o smtpd_tls_security_level=encrypt -o smtpd_tls_mandatory_protocols=$submission_smtpd_tls_mandatory_protocols -o tls_preempt_cipherlist=yes -o cleanup_service_name=smtp_sender_cleanup -o syslog_name=postfix/submission-haproxy # used by SOGo # smtpd_sender_restrictions should match main.cf, but with check_sasl_access prepended for login-as-mailbox-user function 588 inet n - n - - smtpd -o smtpd_client_restrictions=permit_mynetworks,permit_sasl_authenticated,reject -o smtpd_tls_auth_only=no -o smtpd_sender_restrictions=check_sasl_access,regexp:/opt/postfix/conf/allow_mailcow_local.regexp,reject_authenticated_sender_login_mismatch,permit_mynetworks,permit_sasl_authenticated,reject_unlisted_sender,reject_unknown_sender_domain -o cleanup_service_name=smtp_sender_cleanup -o syslog_name=postfix/sogo # used to reinject quarantine mails 590 inet n - n - - smtpd -o smtpd_helo_restrictions= -o smtpd_client_restrictions=permit_mynetworks,reject -o smtpd_tls_auth_only=no -o smtpd_milters= -o non_smtpd_milters= -o syslog_name=postfix/quarantine # used to send bcc mails 591 inet n - n - - smtpd -o smtpd_helo_restrictions= -o smtpd_client_restrictions=permit_mynetworks,reject -o smtpd_tls_auth_only=no -o smtpd_milters= -o non_smtpd_milters= -o syslog_name=postfix/bcc # enforced smtp connector smtp_enforced_tls unix - - n - - smtp -o smtp_tls_security_level=encrypt -o syslog_name=enforced-tls-smtp -o smtp_delivery_status_filter=pcre:/opt/postfix/conf/smtp_dsn_filter # smtp connector used, when a transport map matched # this helps to have different sasl maps than we have with sender dependent transport maps smtp_via_transport_maps unix - - n - - smtp -o smtp_sasl_password_maps=proxy:mysql:/opt/postfix/conf/sql/mysql_sasl_passwd_maps_transport_maps.cf tlsproxy unix - - n - 0 tlsproxy dnsblog unix - - n - 0 dnsblog pickup fifo n - n 60 1 pickup cleanup unix n - n - 0 cleanup qmgr fifo n - n 300 1 qmgr tlsmgr unix - - n 1000? 1 tlsmgr rewrite unix - - n - - trivial-rewrite bounce unix - - n - 0 bounce defer unix - - n - 0 bounce trace unix - - n - 0 bounce verify unix - - n - 1 verify flush unix n - n 1000? 0 flush proxymap unix - - n - - proxymap proxywrite unix - - n - 1 proxymap smtp unix - - n - - smtp relay unix - - n - - smtp showq unix n - n - - showq error unix - - n - - error retry unix - - n - - error discard unix - - n - - discard local unix - n n - - local virtual unix - n n - - virtual lmtp unix - - n - - lmtp flags=O anvil unix - - n - 1 anvil scache unix - - n - 1 scache maildrop unix - n n - - pipe flags=DRhu user=vmail argv=/usr/bin/maildrop -d ${recipient} # used to anonymize sender IP smtp_sender_cleanup unix n - y - 0 cleanup -o header_checks=$smtp_header_checks # start whitelist_fwd 127.0.0.1:10027 inet n n n - 0 spawn user=nobody argv=/usr/local/bin/whitelist_forwardinghosts.sh # end whitelist_fwd # start watchdog-specific # logs to local7 (hidden) 589 inet n - n - - smtpd -o smtpd_client_restrictions=permit_mynetworks,reject -o syslog_name=watchdog -o syslog_facility=local7 -o smtpd_milters= -o cleanup_service_name=watchdog_cleanup -o non_smtpd_milters= watchdog_cleanup unix n - n - 0 cleanup -o syslog_name=watchdog -o syslog_facility=local7 -o queue_service_name=watchdog_qmgr watchdog_qmgr fifo n - n 300 1 qmgr -o syslog_facility=local7 -o syslog_name=watchdog -o rewrite_service_name=watchdog_rewrite watchdog_rewrite unix - - n - - trivial-rewrite -o syslog_facility=local7 -o syslog_name=watchdog -o local_transport=watchdog_discard watchdog_discard unix - - n - - discard -o syslog_facility=local7 -o syslog_name=watchdog # end watchdog-specific ================================================ FILE: data/conf/postfix/postscreen_access.cidr ================================================ # Whitelist generated by Postwhite v3.4 on Sun Mar 1 00:29:01 UTC 2026 # https://github.com/stevejenkins/postwhite/ # 2174 total rules 2a00:1450:4000::/36 permit 2a01:111:f400::/48 permit 2a01:111:f403:2800::/53 permit 2a01:111:f403:8000::/51 permit 2a01:111:f403::/49 permit 2a01:111:f403:c000::/51 permit 2a01:111:f403:d000::/53 permit 2a01:111:f403:f000::/52 permit 2a01:238:20a:202:5370::1 permit 2a01:238:20a:202:5372::1 permit 2a01:238:20a:202:5373::1 permit 2a01:238:400:101:53::1 permit 2a01:238:400:102:53::1 permit 2a01:238:400:103:53::1 permit 2a01:238:400:301:53::1 permit 2a01:238:400:302:53::1 permit 2a01:238:400:303:53::1 permit 2a01:238:400:470:53::1 permit 2a01:238:400:471:53::1 permit 2a01:238:400:472:53::1 permit 2a01:b747:3000:200::/56 permit 2a01:b747:3001:200::/56 permit 2a01:b747:3002:200::/56 permit 2a01:b747:3003:200::/56 permit 2a01:b747:3004:200::/56 permit 2a01:b747:3005:200::/56 permit 2a01:b747:3006:200::/56 permit 2a02:a60:0:5::/64 permit 2a0f:f640::/56 permit 2c0f:fb50:4000::/36 permit 2.207.151.53 permit 2.207.217.30 permit 3.64.237.68 permit 3.65.3.180 permit 3.70.123.177 permit 3.72.182.33 permit 3.74.81.189 permit 3.74.125.228 permit 3.75.33.185 permit 3.93.157.0/24 permit 3.94.40.108 permit 3.121.107.214 permit 3.129.120.190 permit 3.210.190.0/24 permit 3.211.80.218 permit 3.216.221.67 permit 3.221.209.22 permit 8.20.114.31 permit 8.25.194.0/23 permit 8.25.196.0/23 permit 8.36.116.0/24 permit 8.39.54.0/23 permit 8.39.54.250/31 permit 8.39.144.0/24 permit 8.40.222.0/23 permit 8.40.222.250/31 permit 12.130.86.238 permit 13.107.213.51 permit 13.107.246.51 permit 13.108.16.0/20 permit 13.110.208.0/21 permit 13.110.209.0/24 permit 13.110.216.0/22 permit 13.110.224.0/20 permit 13.111.0.0/16 permit 13.111.191.0/24 permit 13.216.7.111 permit 13.216.54.180 permit 13.247.164.219 permit 15.200.21.50 permit 15.200.44.248 permit 15.200.201.185 permit 17.41.0.0/16 permit 17.57.155.0/24 permit 17.57.156.0/24 permit 17.58.0.0/16 permit 17.142.0.0/15 permit 18.97.0.8/30 permit 18.97.1.184/29 permit 18.97.2.64/26 permit 18.156.89.250 permit 18.156.205.64 permit 18.157.70.148 permit 18.157.114.255 permit 18.157.243.190 permit 18.158.153.154 permit 18.194.95.56 permit 18.197.217.180 permit 18.198.96.88 permit 18.199.210.3 permit 18.207.52.234 permit 18.208.124.128/25 permit 18.216.232.154 permit 18.235.27.253 permit 18.236.40.242 permit 20.51.6.32/30 permit 20.52.52.2 permit 20.52.128.133 permit 20.59.80.4/30 permit 20.63.210.192/28 permit 20.69.8.108/30 permit 20.83.222.104/30 permit 20.88.157.184/30 permit 20.94.180.64/28 permit 20.97.34.220/30 permit 20.98.148.156/30 permit 20.98.194.68/30 permit 20.105.209.76/30 permit 20.107.239.64/30 permit 20.118.139.208/30 permit 20.141.10.196 permit 20.185.214.0/27 permit 20.185.214.32/27 permit 20.185.214.64/27 permit 23.103.224.0/19 permit 23.249.208.0/20 permit 23.251.224.0/19 permit 23.253.141.0/24 permit 23.253.182.0/23 permit 23.253.182.103 permit 23.253.183.145 permit 23.253.183.146 permit 23.253.183.147 permit 23.253.183.148 permit 23.253.183.150 permit 24.110.64.0/18 permit 27.123.204.128/30 permit 27.123.204.132/31 permit 27.123.204.148/30 permit 27.123.204.152 permit 27.123.204.168/30 permit 27.123.204.172 permit 27.123.204.188/30 permit 27.123.204.192 permit 27.123.206.50/31 permit 27.123.206.56/29 permit 27.123.206.76/30 permit 27.123.206.80/28 permit 31.47.251.17 permit 31.186.239.0/24 permit 34.2.64.0/22 permit 34.2.68.0/23 permit 34.2.70.0/23 permit 34.2.71.64/26 permit 34.2.72.0/22 permit 34.2.75.0/26 permit 34.2.78.0/23 permit 34.2.80.0/23 permit 34.2.82.0/23 permit 34.2.84.0/24 permit 34.2.84.64/26 permit 34.2.85.0/24 permit 34.2.85.64/26 permit 34.2.86.0/23 permit 34.2.88.0/23 permit 34.2.90.0/23 permit 34.2.92.0/23 permit 34.2.94.0/23 permit 34.70.158.162 permit 34.74.74.140 permit 34.83.159.189 permit 34.141.160.224 permit 34.193.58.168 permit 34.195.217.107 permit 34.197.10.50 permit 34.197.254.9 permit 34.198.94.229 permit 34.198.218.121 permit 34.212.163.75 permit 34.215.104.144 permit 34.218.115.239 permit 34.225.212.172 permit 34.241.242.183 permit 35.83.148.184 permit 35.155.198.111 permit 35.158.23.94 permit 35.161.32.253 permit 35.162.73.231 permit 35.167.93.243 permit 35.174.145.124 permit 35.176.132.251 permit 35.205.92.9 permit 35.228.216.85 permit 35.242.169.159 permit 37.188.97.188 permit 37.218.248.47 permit 37.218.249.47 permit 37.218.251.62 permit 39.156.163.64/29 permit 40.92.0.0/15 permit 40.92.0.0/16 permit 40.107.0.0/16 permit 40.112.65.63 permit 40.233.64.216 permit 40.233.83.78 permit 40.233.88.28 permit 43.239.212.33 permit 44.206.138.57 permit 44.210.169.44 permit 44.217.45.156 permit 44.236.56.93 permit 44.238.220.251 permit 44.245.243.92 permit 44.246.1.125 permit 44.246.68.102 permit 44.246.77.92 permit 45.14.148.0/22 permit 45.143.132.0/24 permit 45.143.133.0/24 permit 45.143.134.0/24 permit 45.143.135.0/24 permit 46.226.48.0/21 permit 46.228.36.37 permit 46.228.36.38/31 permit 46.228.36.212/30 permit 46.228.36.216/31 permit 46.228.37.1 permit 46.228.37.2 permit 46.228.37.3 permit 46.228.37.4 permit 46.228.37.5 permit 46.228.37.6/31 permit 46.228.37.8/31 permit 46.228.37.10/31 permit 46.228.37.12/30 permit 46.228.37.16/30 permit 46.228.37.20/30 permit 46.228.37.24/30 permit 46.228.37.28/31 permit 46.228.37.30/31 permit 46.228.37.32/29 permit 46.228.37.40/29 permit 46.228.37.48/31 permit 46.228.37.50/31 permit 46.228.37.52/30 permit 46.228.37.56/31 permit 46.228.38.33 permit 46.228.38.34/31 permit 46.228.38.36/31 permit 46.228.38.38 permit 46.228.38.53 permit 46.228.38.54/31 permit 46.228.38.56/31 permit 46.228.38.58 permit 46.228.38.101 permit 46.228.38.102/31 permit 46.228.38.128/28 permit 46.228.38.144/29 permit 46.228.38.152/31 permit 46.228.38.154 permit 46.228.39.64/27 permit 46.228.39.96/30 permit 46.228.39.100/30 permit 46.228.39.104/29 permit 46.228.39.112/31 permit 46.243.88.174 permit 46.243.88.175 permit 46.243.88.176 permit 46.243.88.177 permit 46.243.95.179 permit 46.243.95.180 permit 50.16.246.183 permit 50.18.45.249 permit 50.18.121.236 permit 50.18.121.248 permit 50.18.123.221 permit 50.18.124.70 permit 50.18.125.97 permit 50.18.125.237 permit 50.18.126.162 permit 50.31.32.0/19 permit 50.31.36.205 permit 50.56.130.220 permit 50.56.130.221 permit 50.56.130.222 permit 50.112.246.219 permit 52.1.14.157 permit 52.5.230.59 permit 52.6.74.205 permit 52.12.53.23 permit 52.13.214.179 permit 52.26.1.71 permit 52.27.5.72 permit 52.27.28.47 permit 52.28.63.81 permit 52.28.197.132 permit 52.34.181.151 permit 52.35.192.45 permit 52.36.138.31 permit 52.37.142.146 permit 52.42.203.116 permit 52.50.24.208 permit 52.57.120.243 permit 52.58.216.183 permit 52.59.143.3 permit 52.60.41.5 permit 52.60.115.116 permit 52.61.91.9 permit 52.71.0.205 permit 52.94.124.0/28 permit 52.95.48.152/29 permit 52.95.49.88/29 permit 52.100.0.0/15 permit 52.102.0.0/16 permit 52.103.0.0/17 permit 52.119.213.144/28 permit 52.185.106.240/28 permit 52.200.59.0/24 permit 52.207.191.216 permit 52.222.62.51 permit 52.222.73.83 permit 52.222.73.120 permit 52.222.75.85 permit 52.222.89.228 permit 52.234.172.96/28 permit 52.235.253.128 permit 52.236.28.240/28 permit 54.90.148.255 permit 54.165.19.38 permit 54.174.52.0/24 permit 54.174.57.0/24 permit 54.174.59.0/24 permit 54.174.60.0/23 permit 54.174.63.0/24 permit 54.186.193.102 permit 54.191.223.56 permit 54.211.126.101 permit 54.213.20.246 permit 54.214.39.184 permit 54.240.0.0/18 permit 54.240.64.0/18 permit 54.241.16.209 permit 54.244.54.130 permit 54.244.242.0/24 permit 54.255.61.23 permit 56.124.6.228 permit 57.103.64.0/18 permit 57.129.93.249 permit 62.13.128.0/24 permit 62.13.129.128/25 permit 62.13.136.0/21 permit 62.13.144.0/21 permit 62.13.152.0/21 permit 62.17.146.128/26 permit 62.201.172.0/27 permit 62.201.172.32/27 permit 62.253.227.114 permit 63.80.14.0/23 permit 63.128.21.0/24 permit 63.143.57.128/25 permit 63.143.59.128/25 permit 63.176.194.123 permit 63.178.132.221 permit 63.178.143.178 permit 64.18.0.0/20 permit 64.20.241.45 permit 64.69.212.0/24 permit 64.71.149.160/28 permit 64.79.155.0/24 permit 64.79.155.192 permit 64.79.155.193 permit 64.79.155.205 permit 64.79.155.206 permit 64.127.115.252 permit 64.132.88.0/23 permit 64.132.92.0/24 permit 64.181.194.190 permit 64.207.219.7 permit 64.207.219.8 permit 64.207.219.9 permit 64.207.219.10 permit 64.207.219.11 permit 64.207.219.12 permit 64.207.219.13 permit 64.207.219.14 permit 64.207.219.15 permit 64.207.219.71 permit 64.207.219.72 permit 64.207.219.73 permit 64.207.219.74 permit 64.207.219.75 permit 64.207.219.76 permit 64.207.219.77 permit 64.207.219.78 permit 64.207.219.79 permit 64.207.219.135 permit 64.207.219.136 permit 64.207.219.137 permit 64.207.219.138 permit 64.207.219.139 permit 64.207.219.140 permit 64.207.219.141 permit 64.207.219.142 permit 64.207.219.143 permit 64.233.160.0/19 permit 65.52.80.137 permit 65.55.29.77 permit 65.55.42.224/28 permit 65.110.161.77 permit 65.123.29.213 permit 65.123.29.220 permit 65.154.166.0/24 permit 65.212.180.36 permit 66.102.0.0/20 permit 66.119.150.192/26 permit 66.163.184.0/24 permit 66.163.185.0/24 permit 66.163.186.0/24 permit 66.163.187.0/24 permit 66.163.188.0/24 permit 66.163.189.0/24 permit 66.163.190.0/24 permit 66.163.191.0/24 permit 66.170.126.97 permit 66.196.80.112/28 permit 66.196.80.144/29 permit 66.196.80.193 permit 66.196.81.104/29 permit 66.196.81.112/29 permit 66.196.81.120 permit 66.196.81.125 permit 66.196.81.126/31 permit 66.196.81.128/28 permit 66.196.81.144/29 permit 66.196.81.152/31 permit 66.196.81.154 permit 66.196.81.225 permit 66.196.81.226/31 permit 66.196.81.228/30 permit 66.196.81.232/31 permit 66.196.81.234 permit 66.211.170.88/29 permit 66.211.184.0/23 permit 66.218.74.64/30 permit 66.218.74.68/31 permit 66.218.75.112/30 permit 66.218.75.116/31 permit 66.218.75.144/31 permit 66.218.75.146 permit 66.218.75.152 permit 66.218.75.192/30 permit 66.218.75.196/31 permit 66.218.75.203 permit 66.218.75.212/30 permit 66.218.75.216/31 permit 66.218.75.223 permit 66.218.75.232/30 permit 66.218.75.236/31 permit 66.218.75.243 permit 66.218.75.252/31 permit 66.218.75.254 permit 66.220.144.128/25 permit 66.220.155.0/24 permit 66.220.157.0/25 permit 66.231.80.0/20 permit 66.240.227.0/24 permit 66.249.80.0/20 permit 67.23.31.6 permit 67.72.99.26 permit 67.195.22.113 permit 67.195.22.116/30 permit 67.195.23.144/30 permit 67.195.23.148 permit 67.195.60.45 permit 67.195.60.46/31 permit 67.195.60.48/31 permit 67.195.60.50 permit 67.195.60.146 permit 67.195.60.155 permit 67.195.60.156 permit 67.195.87.64 permit 67.195.87.81 permit 67.195.87.82/31 permit 67.195.87.84/31 permit 67.195.87.86 permit 67.195.87.128 permit 67.195.87.145 permit 67.195.87.146/31 permit 67.195.87.192 permit 67.195.87.209 permit 67.195.87.210/31 permit 67.195.124.137 permit 67.195.124.139 permit 67.195.124.141 permit 67.195.124.143 permit 67.195.124.145 permit 67.195.124.147 permit 67.219.240.0/20 permit 67.221.168.65 permit 67.228.2.24/30 permit 67.228.21.184/29 permit 67.228.37.4/30 permit 67.231.145.42 permit 67.231.153.30 permit 68.142.230.64/31 permit 68.142.230.69 permit 68.142.230.70/31 permit 68.142.230.72/30 permit 68.142.230.76/31 permit 68.142.230.78 permit 68.232.140.138 permit 68.232.157.143 permit 68.232.192.0/20 permit 69.63.178.128/25 permit 69.63.181.0/24 permit 69.63.184.0/25 permit 69.65.42.195 permit 69.65.49.192/29 permit 69.72.32.0/20 permit 69.72.40.93 permit 69.72.40.94/31 permit 69.72.40.96/30 permit 69.72.47.205 permit 69.147.84.227 permit 69.162.98.0/24 permit 69.169.224.0/20 permit 69.171.232.0/24 permit 69.171.244.0/23 permit 70.42.149.35 permit 72.3.185.0/24 permit 72.14.192.0/18 permit 72.21.192.0/19 permit 72.21.217.142 permit 72.30.234.152/29 permit 72.30.236.160/30 permit 72.30.236.164/31 permit 72.30.236.180/30 permit 72.30.236.184/31 permit 72.30.236.208/30 permit 72.30.236.212/31 permit 72.30.236.224/30 permit 72.30.236.228/31 permit 72.30.236.244/30 permit 72.30.236.248/31 permit 72.30.237.32/30 permit 72.30.237.36/31 permit 72.30.237.52/30 permit 72.30.237.56/31 permit 72.30.237.80/31 permit 72.30.237.96/30 permit 72.30.237.100/31 permit 72.30.237.116/30 permit 72.30.237.120/31 permit 72.30.237.144/30 permit 72.30.237.148/31 permit 72.30.237.160/30 permit 72.30.237.164/31 permit 72.30.237.180/30 permit 72.30.237.184/31 permit 72.30.237.204/30 permit 72.30.238.116/30 permit 72.30.238.120/31 permit 72.30.238.128 permit 72.30.238.133 permit 72.30.238.168/30 permit 72.30.238.172/31 permit 72.30.238.188/30 permit 72.30.238.192 permit 72.30.238.197 permit 72.30.238.224/31 permit 72.30.238.240/30 permit 72.30.238.244/31 permit 72.30.239.5 permit 72.30.239.37 permit 72.30.239.38/31 permit 72.30.239.40/31 permit 72.30.239.42 permit 72.30.239.57 permit 72.30.239.64 permit 72.30.239.69 permit 72.30.239.89 permit 72.30.239.90/31 permit 72.30.239.92/31 permit 72.30.239.94 permit 72.30.239.128 permit 72.30.239.198 permit 72.30.239.224/30 permit 72.30.239.228/31 permit 72.30.239.244/30 permit 72.30.239.248/31 permit 72.32.154.0/24 permit 72.32.217.0/24 permit 72.32.243.0/24 permit 72.52.72.32/28 permit 74.6.128.0/24 permit 74.6.129.0/24 permit 74.6.130.0/24 permit 74.6.131.0/24 permit 74.6.132.0/24 permit 74.6.133.0/24 permit 74.6.134.0/24 permit 74.6.135.0/24 permit 74.63.212.0/24 permit 74.63.234.75 permit 74.63.236.0/24 permit 74.86.113.28/30 permit 74.86.129.240/30 permit 74.86.131.208/30 permit 74.86.132.208/30 permit 74.86.160.160/30 permit 74.86.164.188/30 permit 74.86.171.192/30 permit 74.86.195.28/30 permit 74.86.207.36/30 permit 74.86.226.216/30 permit 74.86.236.240/30 permit 74.86.241.250/31 permit 74.112.67.243 permit 74.125.0.0/16 permit 74.208.4.200 permit 74.208.4.201 permit 74.208.4.220 permit 74.208.4.221 permit 74.209.250.0/24 permit 76.223.128.0/19 permit 76.223.176.0/20 permit 77.238.176.0/24 permit 77.238.177.0/24 permit 77.238.178.0/24 permit 77.238.179.0/24 permit 77.238.189.21 permit 77.238.189.23 permit 77.238.189.35 permit 77.238.189.39 permit 77.238.189.58/31 permit 77.238.189.60/30 permit 77.238.189.64/29 permit 77.238.189.76/31 permit 77.238.189.128/30 permit 77.238.189.132/31 permit 77.238.189.137 permit 77.238.189.138/31 permit 77.238.189.140/31 permit 77.238.189.142 permit 77.238.189.146/31 permit 77.238.189.148/30 permit 79.135.106.0/24 permit 79.135.107.0/24 permit 81.169.146.243 permit 81.169.146.245 permit 81.169.146.246 permit 81.223.46.0/27 permit 82.165.159.2 permit 82.165.159.3 permit 82.165.159.4 permit 82.165.159.12 permit 82.165.159.13 permit 82.165.159.14 permit 82.165.159.34 permit 82.165.159.35 permit 82.165.159.40 permit 82.165.159.41 permit 82.165.159.42 permit 82.165.159.45 permit 82.165.159.130 permit 82.165.159.131 permit 85.9.206.169 permit 85.9.210.45 permit 85.158.136.0/21 permit 85.215.255.39 permit 85.215.255.40 permit 85.215.255.41 permit 85.215.255.45 permit 85.215.255.46 permit 85.215.255.47 permit 85.215.255.48 permit 85.215.255.49 permit 86.61.88.25 permit 87.238.80.0/21 permit 87.248.103.12 permit 87.248.103.21 permit 87.248.103.23 permit 87.248.103.27 permit 87.248.103.29 permit 87.248.103.36 permit 87.248.103.42/31 permit 87.248.103.44 permit 87.248.103.64 permit 87.248.103.76 permit 87.248.103.83 permit 87.248.103.92/31 permit 87.248.103.94 permit 87.248.103.107 permit 87.248.103.113 permit 87.248.103.122 permit 87.248.110.0/24 permit 87.248.117.30 permit 87.248.117.65 permit 87.248.117.67 permit 87.248.117.70 permit 87.248.117.73 permit 87.248.117.74 permit 87.248.117.86 permit 87.248.117.97 permit 87.248.117.98 permit 87.248.117.104 permit 87.248.117.119 permit 87.248.117.124 permit 87.248.117.185 permit 87.248.117.196 permit 87.248.117.198 permit 87.248.117.201 permit 87.248.117.202 permit 87.248.117.205 permit 87.253.232.0/21 permit 89.22.108.0/24 permit 91.198.2.177 permit 91.198.2.217 permit 91.198.2.222 permit 91.211.240.0/22 permit 94.236.119.0/26 permit 95.131.104.0/21 permit 95.217.114.154 permit 96.43.144.0/20 permit 96.43.144.64/28 permit 96.43.144.64/31 permit 96.43.148.64/28 permit 96.43.148.64/31 permit 96.43.151.64/28 permit 98.97.248.0/21 permit 98.136.44.181 permit 98.136.44.182/31 permit 98.136.44.184 permit 98.136.164.36/31 permit 98.136.164.64/29 permit 98.136.164.72/30 permit 98.136.164.76/31 permit 98.136.164.78 permit 98.136.172.32/30 permit 98.136.172.36/31 permit 98.136.185.29 permit 98.136.185.42/31 permit 98.136.185.46 permit 98.136.185.115 permit 98.136.214.28/30 permit 98.136.214.97 permit 98.136.214.98/31 permit 98.136.214.100 permit 98.136.214.116/30 permit 98.136.214.120/31 permit 98.136.214.157 permit 98.136.214.158/31 permit 98.136.214.160/31 permit 98.136.214.162 permit 98.136.214.190/31 permit 98.136.214.192/30 permit 98.136.214.238/31 permit 98.136.214.255 permit 98.136.215.0/31 permit 98.136.215.2 permit 98.136.215.3 permit 98.136.215.4/31 permit 98.136.215.6 permit 98.136.215.114/31 permit 98.136.215.116/31 permit 98.136.215.118 permit 98.136.215.136/30 permit 98.136.215.148/30 permit 98.136.215.152/31 permit 98.136.215.179 permit 98.136.215.180/30 permit 98.136.215.184 permit 98.136.215.208/30 permit 98.136.215.212/31 permit 98.136.217.1 permit 98.136.217.2 permit 98.136.217.3 permit 98.136.217.4/30 permit 98.136.217.8/31 permit 98.136.217.10/31 permit 98.136.217.12/30 permit 98.136.217.16/30 permit 98.136.217.20/30 permit 98.136.218.39 permit 98.136.218.40/29 permit 98.136.218.48/28 permit 98.136.218.67 permit 98.136.218.68/30 permit 98.136.218.72/30 permit 98.137.12.48/30 permit 98.137.12.52/31 permit 98.137.12.54 permit 98.137.12.177 permit 98.137.12.178/31 permit 98.137.12.180/31 permit 98.137.12.182 permit 98.137.12.192/27 permit 98.137.12.224/28 permit 98.137.12.240/29 permit 98.137.12.248/30 permit 98.137.12.252/31 permit 98.137.12.254 permit 98.137.13.81 permit 98.137.13.82 permit 98.137.13.87 permit 98.137.13.88 permit 98.137.13.91 permit 98.137.13.92 permit 98.137.13.97 permit 98.137.13.98 permit 98.137.13.101 permit 98.137.13.102 permit 98.137.13.107 permit 98.137.13.108 permit 98.137.13.111 permit 98.137.13.112 permit 98.137.13.117 permit 98.137.13.118 permit 98.137.13.121 permit 98.137.13.122 permit 98.137.13.127 permit 98.137.13.128 permit 98.137.13.131 permit 98.137.13.132 permit 98.137.13.137 permit 98.137.13.138 permit 98.137.64.0/24 permit 98.137.65.0/24 permit 98.137.66.0/24 permit 98.137.67.0/24 permit 98.137.68.0/24 permit 98.137.69.0/24 permit 98.137.70.0/24 permit 98.137.71.0/24 permit 98.137.176.58/31 permit 98.137.177.247 permit 98.138.31.128/30 permit 98.138.31.132/31 permit 98.138.31.192/30 permit 98.138.31.196/31 permit 98.138.31.202/31 permit 98.138.31.204/30 permit 98.138.31.232/30 permit 98.138.31.236/31 permit 98.138.82.208/30 permit 98.138.82.212/31 permit 98.138.83.176/31 permit 98.138.83.179 permit 98.138.83.180/31 permit 98.138.84.37 permit 98.138.84.38/31 permit 98.138.84.40/29 permit 98.138.85.128/30 permit 98.138.85.132/31 permit 98.138.85.148/30 permit 98.138.85.152/31 permit 98.138.85.168/30 permit 98.138.85.172/31 permit 98.138.85.188/30 permit 98.138.85.192/31 permit 98.138.85.208/30 permit 98.138.85.212/31 permit 98.138.85.228/30 permit 98.138.85.232/31 permit 98.138.85.248/30 permit 98.138.85.252/31 permit 98.138.86.69 permit 98.138.86.156/31 permit 98.138.86.192/30 permit 98.138.86.196/31 permit 98.138.87.1 permit 98.138.87.2/31 permit 98.138.87.4/31 permit 98.138.87.6 permit 98.138.87.7 permit 98.138.87.8/31 permit 98.138.87.10/31 permit 98.138.87.12 permit 98.138.87.16/30 permit 98.138.87.64/30 permit 98.138.87.68/31 permit 98.138.87.73 permit 98.138.87.74/31 permit 98.138.87.76/31 permit 98.138.87.78 permit 98.138.87.144/30 permit 98.138.87.148/31 permit 98.138.87.192/30 permit 98.138.87.196/31 permit 98.138.88.105 permit 98.138.88.106 permit 98.138.88.128/30 permit 98.138.88.132/31 permit 98.138.88.148/30 permit 98.138.88.152/31 permit 98.138.88.232/29 permit 98.138.89.160/28 permit 98.138.89.192/29 permit 98.138.89.232/31 permit 98.138.89.234 permit 98.138.89.240 permit 98.138.89.244/31 permit 98.138.89.246 permit 98.138.89.248/30 permit 98.138.89.252/31 permit 98.138.89.254 permit 98.138.90.64/28 permit 98.138.90.80/29 permit 98.138.90.88/30 permit 98.138.90.92/31 permit 98.138.90.95 permit 98.138.90.96/30 permit 98.138.90.100 permit 98.138.90.104/30 permit 98.138.90.108/31 permit 98.138.90.113 permit 98.138.90.114/31 permit 98.138.90.116/31 permit 98.138.90.118 permit 98.138.90.122/31 permit 98.138.90.124/30 permit 98.138.90.131 permit 98.138.90.132/30 permit 98.138.90.136 permit 98.138.91.1 permit 98.138.91.2/31 permit 98.138.91.4/31 permit 98.138.91.6 permit 98.138.100.220/30 permit 98.138.100.224/30 permit 98.138.100.228/31 permit 98.138.101.160/28 permit 98.138.101.176/29 permit 98.138.104.96/30 permit 98.138.104.100 permit 98.138.104.112/30 permit 98.138.104.116 permit 98.138.120.36/30 permit 98.138.120.48/28 permit 98.138.197.46/31 permit 98.138.197.48/30 permit 98.138.197.169 permit 98.138.197.176 permit 98.138.197.180 permit 98.138.198.52/31 permit 98.138.198.54 permit 98.138.198.56 permit 98.138.198.61 permit 98.138.198.190/31 permit 98.138.198.239 permit 98.138.199.6 permit 98.138.199.9 permit 98.138.199.48 permit 98.138.199.83 permit 98.138.199.84 permit 98.138.199.86/31 permit 98.138.199.94 permit 98.138.199.116 permit 98.138.199.208/30 permit 98.138.199.212/31 permit 98.138.199.218/31 permit 98.138.199.220/30 permit 98.138.199.236 permit 98.138.206.67 permit 98.138.206.73 permit 98.138.206.143 permit 98.138.206.167 permit 98.138.206.169 permit 98.138.206.173 permit 98.138.207.9 permit 98.138.207.10/31 permit 98.138.207.12/31 permit 98.138.207.14 permit 98.138.210.39 permit 98.138.210.64/30 permit 98.138.210.68/31 permit 98.138.210.74/31 permit 98.138.210.76/30 permit 98.138.210.84 permit 98.138.210.86 permit 98.138.210.89 permit 98.138.210.93 permit 98.138.210.94 permit 98.138.210.96 permit 98.138.210.98 permit 98.138.210.101 permit 98.138.210.111 permit 98.138.210.115 permit 98.138.210.122 permit 98.138.210.136 permit 98.138.210.146 permit 98.138.210.148 permit 98.138.210.155 permit 98.138.210.158/31 permit 98.138.210.166 permit 98.138.210.169 permit 98.138.210.175 permit 98.138.210.177 permit 98.138.210.181 permit 98.138.210.182/31 permit 98.138.210.187 permit 98.138.210.189 permit 98.138.210.201 permit 98.138.210.203 permit 98.138.210.210 permit 98.138.210.240/30 permit 98.138.210.244/31 permit 98.138.211.122/31 permit 98.138.211.124/30 permit 98.138.211.128/30 permit 98.138.211.132/31 permit 98.138.211.148/30 permit 98.138.211.152/31 permit 98.138.211.216/30 permit 98.138.211.220/31 permit 98.138.213.128/30 permit 98.138.213.132/31 permit 98.138.213.138/31 permit 98.138.213.140/30 permit 98.138.213.148/30 permit 98.138.213.152/31 permit 98.138.213.158/31 permit 98.138.213.160/30 permit 98.138.213.168/30 permit 98.138.213.172/31 permit 98.138.213.237 permit 98.138.213.238/31 permit 98.138.213.240/31 permit 98.138.213.242 permit 98.138.215.12/30 permit 98.138.215.16/28 permit 98.138.217.216/30 permit 98.138.217.220/31 permit 98.138.226.30/31 permit 98.138.226.56/29 permit 98.138.226.64/30 permit 98.138.226.68/31 permit 98.138.226.74/31 permit 98.138.226.76/30 permit 98.138.226.84/30 permit 98.138.226.88/31 permit 98.138.226.124/30 permit 98.138.226.128/30 permit 98.138.226.132/31 permit 98.138.226.160/29 permit 98.138.226.168/31 permit 98.138.226.240/30 permit 98.138.226.244 permit 98.138.227.32/30 permit 98.138.227.36/31 permit 98.138.227.56/30 permit 98.138.227.60/31 permit 98.138.227.80/30 permit 98.138.227.84/31 permit 98.138.227.104/30 permit 98.138.227.108/31 permit 98.138.227.128/30 permit 98.138.227.132/31 permit 98.138.229.24/29 permit 98.138.229.32/31 permit 98.138.229.122/31 permit 98.138.229.138/31 permit 98.138.229.154/31 permit 98.138.229.170/31 permit 98.139.164.96/30 permit 98.139.164.100/30 permit 98.139.164.104/29 permit 98.139.164.112/30 permit 98.139.172.112/30 permit 98.139.172.116/31 permit 98.139.175.65 permit 98.139.175.66/31 permit 98.139.175.68/30 permit 98.139.175.72/29 permit 98.139.175.80/29 permit 98.139.175.88/30 permit 98.139.175.92/31 permit 98.139.175.94 permit 98.139.210.112/30 permit 98.139.210.128/30 permit 98.139.210.132/31 permit 98.139.210.138/31 permit 98.139.210.140/30 permit 98.139.210.148/30 permit 98.139.210.152/31 permit 98.139.210.170/31 permit 98.139.210.172/30 permit 98.139.210.180/30 permit 98.139.210.184/31 permit 98.139.210.190/31 permit 98.139.210.192/30 permit 98.139.210.196/31 permit 98.139.210.202/31 permit 98.139.210.204/30 permit 98.139.211.160/30 permit 98.139.211.192/28 permit 98.139.212.160/28 permit 98.139.212.176/29 permit 98.139.212.184/30 permit 98.139.212.188/31 permit 98.139.212.192/27 permit 98.139.212.224/28 permit 98.139.212.240/29 permit 98.139.212.248/30 permit 98.139.213.8/31 permit 98.139.213.10/31 permit 98.139.213.12/30 permit 98.139.214.155 permit 98.139.214.156/30 permit 98.139.214.221 permit 98.139.215.228/31 permit 98.139.215.230 permit 98.139.215.248/30 permit 98.139.215.252/31 permit 98.139.215.254 permit 98.139.218.53 permit 98.139.218.61 permit 98.139.218.63 permit 98.139.219.128 permit 98.139.219.138 permit 98.139.219.159 permit 98.139.219.177 permit 98.139.219.184 permit 98.139.219.193 permit 98.139.219.194 permit 98.139.219.196 permit 98.139.219.203 permit 98.139.219.209 permit 98.139.219.215 permit 98.139.219.216/31 permit 98.139.219.231 permit 98.139.220.43 permit 98.139.220.50 permit 98.139.220.57 permit 98.139.220.63 permit 98.139.220.71 permit 98.139.220.73 permit 98.139.220.85 permit 98.139.220.96 permit 98.139.220.100 permit 98.139.220.103 permit 98.139.220.104 permit 98.139.220.106/31 permit 98.139.220.110/31 permit 98.139.220.112/31 permit 98.139.220.144 permit 98.139.220.152 permit 98.139.220.157 permit 98.139.220.158 permit 98.139.220.163 permit 98.139.220.164 permit 98.139.220.170 permit 98.139.220.182 permit 98.139.220.187 permit 98.139.220.189 permit 98.139.220.193 permit 98.139.220.198 permit 98.139.220.203 permit 98.139.220.208/31 permit 98.139.220.212 permit 98.139.220.214/31 permit 98.139.220.219 permit 98.139.220.223 permit 98.139.220.232 permit 98.139.220.238 permit 98.139.220.243 permit 98.139.220.245 permit 98.139.220.253 permit 98.139.221.43 permit 98.139.221.60/30 permit 98.139.221.156/30 permit 98.139.221.232/30 permit 98.139.221.236/31 permit 98.139.221.250 permit 98.139.244.47 permit 98.139.244.49 permit 98.139.244.50/31 permit 98.139.244.52/30 permit 98.139.244.57 permit 98.139.244.58/31 permit 98.139.244.60/31 permit 98.139.244.62 permit 98.139.244.132 permit 98.139.244.141 permit 98.139.244.142/31 permit 98.139.244.144/31 permit 98.139.244.146 permit 98.139.244.152 permit 98.139.244.161 permit 98.139.244.162/31 permit 98.139.244.164/31 permit 98.139.244.166 permit 98.139.244.172 permit 98.139.244.181 permit 98.139.244.182/31 permit 98.139.244.184/31 permit 98.139.244.186 permit 98.139.244.192 permit 98.139.244.201 permit 98.139.244.202/31 permit 98.139.244.204/31 permit 98.139.244.206 permit 98.139.244.212 permit 98.139.244.221 permit 98.139.244.222/31 permit 98.139.244.224/31 permit 98.139.244.226 permit 98.139.244.232 permit 98.139.245.176/30 permit 98.139.245.180/31 permit 98.139.245.208/30 permit 98.139.245.212/31 permit 99.78.197.208/28 permit 103.9.96.0/22 permit 103.28.42.0/24 permit 103.84.217.15 permit 103.84.217.238 permit 103.89.75.238 permit 103.151.192.0/23 permit 103.168.172.128/27 permit 103.237.104.0/22 permit 104.43.243.237 permit 104.44.112.128/25 permit 104.47.0.0/17 permit 104.130.96.0/28 permit 104.130.122.0/23 permit 106.10.144.64/27 permit 106.10.144.100/31 permit 106.10.144.103 permit 106.10.144.112/28 permit 106.10.144.128 permit 106.10.144.133 permit 106.10.144.138 permit 106.10.144.143 permit 106.10.144.148 permit 106.10.144.153 permit 106.10.144.158 permit 106.10.144.163 permit 106.10.144.168 permit 106.10.145.33 permit 106.10.145.34/31 permit 106.10.145.160/31 permit 106.10.145.162 permit 106.10.145.192/30 permit 106.10.145.196/31 permit 106.10.145.224/30 permit 106.10.145.228/31 permit 106.10.146.48/30 permit 106.10.146.52/31 permit 106.10.146.224/30 permit 106.10.146.228/31 permit 106.10.148.48/30 permit 106.10.148.52/31 permit 106.10.148.68/30 permit 106.10.148.80/28 permit 106.10.148.100/30 permit 106.10.148.124/31 permit 106.10.148.252/31 permit 106.10.148.254 permit 106.10.149.28/31 permit 106.10.149.30 permit 106.10.149.160/30 permit 106.10.149.164/31 permit 106.10.150.23 permit 106.10.150.24/30 permit 106.10.150.28/31 permit 106.10.150.32/30 permit 106.10.150.36/31 permit 106.10.150.52/30 permit 106.10.150.56/31 permit 106.10.150.72/30 permit 106.10.150.76/31 permit 106.10.150.92/30 permit 106.10.150.96/31 permit 106.10.150.112/30 permit 106.10.150.116/31 permit 106.10.150.132/30 permit 106.10.150.136/31 permit 106.10.150.172/30 permit 106.10.151.15 permit 106.10.151.16/31 permit 106.10.151.18 permit 106.10.151.19 permit 106.10.151.20 permit 106.10.151.21 permit 106.10.151.22/31 permit 106.10.151.24/31 permit 106.10.151.26/31 permit 106.10.151.28/30 permit 106.10.151.122/31 permit 106.10.151.138/31 permit 106.10.151.154/31 permit 106.10.151.170/31 permit 106.10.151.186/31 permit 106.10.151.202/31 permit 106.10.151.218/31 permit 106.10.151.234/31 permit 106.10.151.239 permit 106.10.151.250/31 permit 106.10.151.252/31 permit 106.10.151.254 permit 106.10.167.72 permit 106.10.167.128/27 permit 106.10.167.160/28 permit 106.10.167.176/31 permit 106.10.167.244/31 permit 106.10.167.246 permit 106.10.169.16/30 permit 106.10.169.20 permit 106.10.169.21 permit 106.10.169.208/31 permit 106.10.169.210/31 permit 106.10.169.233 permit 106.10.169.234/31 permit 106.10.169.236/31 permit 106.10.169.238 permit 106.10.169.253 permit 106.10.169.254 permit 106.10.174.118/31 permit 106.10.174.120/30 permit 106.10.174.154/31 permit 106.10.174.156/30 permit 106.10.176.32/29 permit 106.10.176.48 permit 106.10.176.112 permit 106.10.176.128/29 permit 106.10.176.136 permit 106.10.176.255 permit 106.10.177.0 permit 106.10.177.108/30 permit 106.10.177.128/30 permit 106.10.177.184/30 permit 106.10.177.188/31 permit 106.10.177.217 permit 106.10.177.218/31 permit 106.10.177.220/31 permit 106.10.177.222 permit 106.10.196.43 permit 106.10.196.44/30 permit 106.10.196.48 permit 106.10.240.0/24 permit 106.10.241.0/24 permit 106.10.242.0/24 permit 106.10.243.0/24 permit 106.10.244.0/24 permit 106.39.212.64/29 permit 106.50.16.0/28 permit 107.20.18.111 permit 107.20.210.250 permit 107.22.191.150 permit 108.174.0.0/24 permit 108.174.0.215 permit 108.174.3.0/24 permit 108.174.3.215 permit 108.174.6.0/24 permit 108.174.6.215 permit 108.175.18.45 permit 108.175.30.45 permit 108.179.144.0/20 permit 109.224.244.0/24 permit 109.237.142.0/24 permit 112.19.199.64/29 permit 112.19.242.64/29 permit 116.214.12.47 permit 116.214.12.48/31 permit 116.214.12.56/31 permit 116.214.12.58 permit 116.214.12.73 permit 116.214.12.74/31 permit 116.214.12.76/31 permit 116.214.12.78 permit 116.214.12.93 permit 116.214.12.94/31 permit 116.214.12.96/31 permit 116.214.12.98 permit 117.120.16.0/21 permit 119.42.242.52/31 permit 119.42.242.156 permit 121.244.91.48 permit 121.244.91.52 permit 122.15.156.182 permit 123.126.78.64/29 permit 124.108.96.24/31 permit 124.108.96.28/31 permit 124.108.96.70/31 permit 124.108.96.72/31 permit 128.17.0.0/20 permit 128.17.64.0/20 permit 128.17.128.0/20 permit 128.17.192.0/20 permit 128.127.70.0/26 permit 128.245.0.0/20 permit 128.245.64.0/20 permit 128.245.176.0/20 permit 128.245.240.0/24 permit 128.245.241.0/24 permit 128.245.242.0/24 permit 128.245.242.16 permit 128.245.242.17 permit 128.245.242.18 permit 128.245.243.0/24 permit 128.245.244.0/24 permit 128.245.245.0/24 permit 128.245.246.0/24 permit 128.245.247.0/24 permit 128.245.248.0/21 permit 129.41.77.70 permit 129.41.169.249 permit 129.77.16.0/20 permit 129.80.5.164 permit 129.80.64.36 permit 129.80.67.121 permit 129.80.145.156 permit 129.145.74.12 permit 129.146.88.28 permit 129.146.147.105 permit 129.146.236.58 permit 129.151.67.221 permit 129.153.62.216 permit 129.153.104.71 permit 129.153.168.146 permit 129.153.190.200 permit 129.153.194.228 permit 129.154.255.129 permit 129.158.56.255 permit 129.158.62.153 permit 129.159.22.159 permit 129.159.87.137 permit 129.213.195.191 permit 130.61.9.72 permit 130.162.39.83 permit 130.248.172.0/24 permit 130.248.173.0/24 permit 131.253.30.0/24 permit 131.253.121.0/26 permit 132.145.13.209 permit 132.226.26.225 permit 132.226.49.32 permit 132.226.56.24 permit 134.128.64.0/19 permit 134.128.96.0/19 permit 134.170.27.8 permit 134.170.113.0/26 permit 134.170.141.64/26 permit 134.170.143.0/24 permit 134.170.174.0/24 permit 135.84.80.0/24 permit 135.84.81.0/24 permit 135.84.82.0/24 permit 135.84.83.0/24 permit 135.84.216.0/22 permit 136.143.160.0/24 permit 136.143.161.0/24 permit 136.143.162.0/24 permit 136.143.176.0/24 permit 136.143.177.0/24 permit 136.143.178.49 permit 136.143.182.0/23 permit 136.143.184.0/24 permit 136.143.188.0/24 permit 136.143.190.0/23 permit 136.146.128.0/20 permit 136.147.128.0/20 permit 136.147.135.0/24 permit 136.147.176.0/20 permit 136.147.176.0/24 permit 136.147.182.0/24 permit 136.147.224.0/20 permit 136.179.50.206 permit 139.60.152.0/22 permit 139.138.35.44 permit 139.138.46.121 permit 139.138.46.176 permit 139.138.46.219 permit 139.138.57.55 permit 139.138.58.119 permit 139.167.79.86 permit 139.180.17.0/24 permit 140.238.148.191 permit 141.148.55.217 permit 141.148.91.244 permit 141.148.159.229 permit 141.193.32.0/23 permit 141.193.184.32/27 permit 141.193.184.64/26 permit 141.193.184.128/25 permit 141.193.185.32/27 permit 141.193.185.64/26 permit 141.193.185.128/25 permit 143.47.120.152 permit 143.55.224.0/21 permit 143.55.232.0/22 permit 143.55.236.0/22 permit 143.244.80.0/20 permit 144.24.6.140 permit 144.34.8.247 permit 144.34.9.247 permit 144.34.32.247 permit 144.34.33.247 permit 144.178.36.0/24 permit 144.178.38.0/24 permit 145.253.228.160/29 permit 145.253.239.128/29 permit 146.20.14.104 permit 146.20.14.105 permit 146.20.14.106 permit 146.20.14.107 permit 146.20.112.0/26 permit 146.20.113.0/24 permit 146.20.191.0/24 permit 146.20.215.0/24 permit 146.20.215.182 permit 146.88.28.0/24 permit 146.148.116.76 permit 147.154.32.0/25 permit 147.243.1.47 permit 147.243.1.48 permit 147.243.1.153 permit 147.243.128.24 permit 147.243.128.26 permit 148.105.0.0/16 permit 148.105.8.0/21 permit 149.72.0.0/16 permit 149.72.234.184 permit 149.72.248.236 permit 149.97.173.180 permit 150.136.21.199 permit 150.230.98.160 permit 151.145.38.14 permit 152.67.105.195 permit 152.69.200.236 permit 152.70.155.126 permit 155.248.208.51 permit 155.248.220.138 permit 155.248.234.149 permit 155.248.237.141 permit 157.56.120.128/26 permit 157.58.30.128/25 permit 157.58.196.96/29 permit 157.58.249.3 permit 157.151.208.65 permit 157.255.1.64/29 permit 158.101.211.207 permit 158.247.16.0/20 permit 159.92.154.0/24 permit 159.92.155.0/24 permit 159.92.157.0/24 permit 159.92.157.16 permit 159.92.157.17 permit 159.92.157.18 permit 159.92.158.0/24 permit 159.92.159.0/24 permit 159.92.160.0/24 permit 159.92.161.0/24 permit 159.92.162.0/24 permit 159.92.163.0/24 permit 159.92.164.0/22 permit 159.92.168.0/21 permit 159.112.240.0/20 permit 159.112.242.162 permit 159.135.132.128/25 permit 159.135.140.80/29 permit 159.135.224.0/20 permit 159.135.228.10 permit 159.183.0.0/16 permit 159.183.14.233 permit 159.183.68.71 permit 159.183.79.38 permit 159.183.121.182 permit 159.183.129.172 permit 160.1.62.192 permit 161.38.192.0/20 permit 161.38.204.0/22 permit 161.71.32.0/19 permit 161.71.64.0/20 permit 162.88.4.0/23 permit 162.88.8.0/24 permit 162.88.24.0/24 permit 162.88.25.0/24 permit 162.88.36.0/24 permit 162.247.216.0/22 permit 163.47.180.0/22 permit 163.114.130.16 permit 163.114.132.120 permit 163.114.134.16 permit 163.114.135.16 permit 163.116.128.0/17 permit 163.192.116.87 permit 163.192.125.176 permit 163.192.196.146 permit 163.192.204.161 permit 164.152.23.32 permit 164.152.25.241 permit 164.177.132.168/30 permit 165.173.128.0/24 permit 165.173.180.1 permit 165.173.180.250/31 permit 165.173.182.250/31 permit 166.78.68.0/22 permit 166.78.68.221 permit 166.78.69.169 permit 166.78.69.170 permit 166.78.71.131 permit 167.89.0.0/17 permit 167.89.46.159 permit 167.89.54.103 permit 167.89.60.95 permit 167.89.64.9 permit 167.89.65.0 permit 167.89.65.53 permit 167.89.65.100 permit 167.89.74.233 permit 167.89.75.33 permit 167.89.75.126 permit 167.89.75.136 permit 167.89.75.164 permit 167.89.101.2 permit 167.89.101.192/28 permit 167.220.67.232/29 permit 168.138.5.36 permit 168.138.73.51 permit 168.138.77.31 permit 168.138.237.153 permit 168.245.0.0/17 permit 168.245.12.252 permit 168.245.46.9 permit 168.245.127.231 permit 169.148.129.0/24 permit 169.148.131.0/24 permit 169.148.138.0/24 permit 169.148.142.10 permit 169.148.142.33 permit 169.148.144.0/25 permit 169.148.144.10 permit 169.148.146.0/23 permit 169.148.175.3 permit 169.148.179.3 permit 169.148.188.0/24 permit 169.148.188.182 permit 170.9.232.254 permit 170.10.128.0/24 permit 170.10.129.0/24 permit 170.10.132.56/29 permit 170.10.132.64/29 permit 170.10.133.0/24 permit 173.0.84.0/29 permit 173.0.84.224/27 permit 173.0.94.244/30 permit 173.194.0.0/16 permit 173.203.79.182 permit 173.203.81.39 permit 173.224.161.128/25 permit 173.224.165.0/26 permit 174.36.84.8/29 permit 174.36.84.16/29 permit 174.36.84.32/29 permit 174.36.84.144/29 permit 174.36.84.240/29 permit 174.36.85.248/30 permit 174.36.114.128/30 permit 174.36.114.140/30 permit 174.36.114.148/30 permit 174.36.114.152/29 permit 174.37.67.28/30 permit 175.41.215.51 permit 176.32.105.0/24 permit 176.32.127.0/24 permit 178.236.10.128/26 permit 182.50.76.0/22 permit 182.50.78.64/28 permit 183.240.219.64/29 permit 185.4.120.0/22 permit 185.11.255.144 permit 185.12.80.0/22 permit 185.28.196.0/22 permit 185.58.84.93 permit 185.70.40.0/24 permit 185.70.41.0/24 permit 185.70.43.0/24 permit 185.80.93.204 permit 185.80.93.227 permit 185.80.95.31 permit 185.90.20.0/22 permit 185.138.56.128/25 permit 185.189.236.0/22 permit 185.211.120.0/22 permit 185.233.188.68 permit 185.233.188.75 permit 185.233.188.84 permit 185.233.188.160 permit 185.233.188.176 permit 185.233.188.247 permit 185.233.189.44 permit 185.233.189.98 permit 185.233.189.122 permit 185.233.189.228 permit 185.250.236.0/22 permit 185.250.239.148 permit 185.250.239.168 permit 185.250.239.190 permit 188.125.68.132 permit 188.125.68.152/31 permit 188.125.68.156 permit 188.125.68.163 permit 188.125.68.172/31 permit 188.125.68.176 permit 188.125.68.179 permit 188.125.68.184 permit 188.125.68.186 permit 188.125.68.192 permit 188.125.69.105 permit 188.125.69.110 permit 188.125.69.112 permit 188.125.69.120 permit 188.125.69.126 permit 188.125.69.131 permit 188.125.69.132/31 permit 188.125.69.137 permit 188.125.69.139 permit 188.125.69.144/30 permit 188.125.69.148/31 permit 188.125.83.88/30 permit 188.125.83.92/31 permit 188.125.83.96/30 permit 188.125.83.100/31 permit 188.125.83.116/30 permit 188.125.83.120/31 permit 188.125.83.140/30 permit 188.125.83.144/31 permit 188.125.83.160/30 permit 188.125.83.164/31 permit 188.125.84.32/30 permit 188.125.84.36 permit 188.125.84.122/31 permit 188.125.84.124/30 permit 188.125.84.244/30 permit 188.125.84.248/31 permit 188.125.85.116/30 permit 188.125.85.120/31 permit 188.125.85.130/31 permit 188.125.85.132/30 permit 188.125.85.202/31 permit 188.125.85.204/31 permit 188.125.85.206 permit 188.125.85.233 permit 188.125.85.234/31 permit 188.125.85.236/31 permit 188.125.85.238 permit 188.165.51.139 permit 188.172.128.0/20 permit 192.0.64.0/18 permit 192.18.139.154 permit 192.18.145.36 permit 192.18.152.58 permit 192.28.128.0/18 permit 192.29.103.128/25 permit 192.30.252.0/22 permit 192.161.144.0/20 permit 192.162.87.0/24 permit 192.237.158.0/23 permit 192.237.159.42 permit 192.237.159.43 permit 192.254.112.0/20 permit 192.254.112.60 permit 192.254.112.98/31 permit 192.254.113.10 permit 192.254.113.101 permit 192.254.114.176 permit 193.109.254.0/23 permit 193.122.128.100 permit 193.123.56.63 permit 193.142.157.15 permit 193.142.157.125 permit 193.142.157.158 permit 193.142.157.191 permit 193.142.157.198 permit 194.19.134.0/25 permit 194.25.134.16/28 permit 194.25.134.80/28 permit 194.64.234.129 permit 194.97.196.0/24 permit 194.97.196.3 permit 194.97.196.4 permit 194.97.196.11 permit 194.97.196.12 permit 194.97.204.0/24 permit 194.97.204.3 permit 194.97.204.4 permit 194.97.204.11 permit 194.97.204.12 permit 194.97.212.0/24 permit 194.97.212.3 permit 194.97.212.4 permit 194.97.212.11 permit 194.97.212.12 permit 194.106.220.0/23 permit 194.113.24.0/22 permit 194.113.42.0/26 permit 194.154.193.192/27 permit 195.4.92.0/23 permit 195.54.172.0/23 permit 195.234.109.226 permit 195.245.230.0/23 permit 198.2.128.0/18 permit 198.21.0.0/21 permit 198.37.144.0/20 permit 198.37.152.186 permit 198.61.254.0/23 permit 198.61.254.21 permit 198.61.254.231 permit 198.178.234.57 permit 198.202.211.1 permit 198.244.48.0/20 permit 198.244.56.107 permit 198.244.56.108 permit 198.244.56.109 permit 198.244.56.111 permit 198.244.56.112 permit 198.244.56.113 permit 198.244.56.114 permit 198.244.56.115 permit 198.244.59.30 permit 198.244.59.33 permit 198.244.59.35 permit 198.244.60.0/22 permit 198.245.80.0/20 permit 198.245.81.0/24 permit 199.15.212.0/22 permit 199.15.213.187 permit 199.15.226.37 permit 199.16.156.0/22 permit 199.33.145.1 permit 199.33.145.32 permit 199.34.22.36 permit 199.59.148.0/22 permit 199.67.80.2 permit 199.67.80.20 permit 199.67.82.2 permit 199.67.82.20 permit 199.67.84.0/24 permit 199.67.86.0/24 permit 199.67.88.0/24 permit 199.67.90.0/24 permit 199.101.161.130 permit 199.101.162.0/25 permit 199.122.120.0/21 permit 199.122.123.0/24 permit 199.127.232.0/22 permit 199.255.192.0/22 permit 202.12.124.128/27 permit 202.129.242.0/23 permit 202.165.102.47 permit 202.177.148.100 permit 202.177.148.110 permit 203.32.4.25 permit 203.55.21.0/24 permit 203.81.17.0/24 permit 203.122.32.250 permit 203.145.57.160/27 permit 203.188.194.32 permit 203.188.194.151 permit 203.188.194.203 permit 203.188.194.204 permit 203.188.194.213 permit 203.188.194.251 permit 203.188.195.240/30 permit 203.188.195.244/31 permit 203.188.197.193 permit 203.188.197.194/31 permit 203.188.197.196/30 permit 203.188.197.209 permit 203.188.197.210/31 permit 203.188.197.212/30 permit 203.188.197.216/29 permit 203.188.197.232/29 permit 203.188.197.240/29 permit 203.188.200.56/31 permit 203.188.200.58 permit 203.188.200.60/30 permit 203.188.200.83 permit 203.188.200.100/31 permit 203.188.200.102 permit 203.188.200.108/31 permit 203.188.200.160/31 permit 203.188.200.208/30 permit 203.188.200.212/31 permit 203.188.201.12/30 permit 203.209.230.75 permit 203.209.230.76/31 permit 204.11.168.0/21 permit 204.13.11.48/29 permit 204.14.232.0/21 permit 204.14.232.64/28 permit 204.14.234.64/28 permit 204.75.142.0/24 permit 204.92.114.187 permit 204.92.114.203 permit 204.92.114.204/31 permit 204.141.32.0/23 permit 204.141.42.0/23 permit 204.216.164.202 permit 204.220.160.0/21 permit 204.220.168.0/21 permit 204.220.176.0/20 permit 204.220.181.105 permit 204.232.168.0/24 permit 205.139.110.0/24 permit 205.201.128.0/20 permit 205.201.137.229 permit 205.207.104.0/22 permit 205.220.167.17 permit 205.220.167.98 permit 205.220.179.17 permit 205.220.179.98 permit 205.251.233.32 permit 205.251.233.36 permit 206.25.247.143 permit 206.25.247.155 permit 206.55.144.0/20 permit 206.165.246.80/29 permit 206.191.224.0/19 permit 206.246.157.1 permit 207.46.22.35 permit 207.46.50.72 permit 207.46.50.82 permit 207.46.52.71 permit 207.46.52.79 permit 207.67.38.0/24 permit 207.67.98.192/27 permit 207.97.204.96/29 permit 207.126.144.0/20 permit 207.171.160.0/19 permit 207.211.30.64/26 permit 207.211.30.128/25 permit 207.211.31.0/25 permit 207.211.41.113 permit 207.218.90.0/24 permit 207.218.90.122 permit 207.250.68.0/24 permit 208.40.232.70 permit 208.43.21.28/30 permit 208.43.21.64/29 permit 208.43.21.72/30 permit 208.64.132.0/22 permit 208.71.40.63 permit 208.71.40.64/31 permit 208.71.40.174/31 permit 208.71.40.185 permit 208.71.40.186/31 permit 208.71.40.202/31 permit 208.71.40.204/31 permit 208.71.40.219 permit 208.71.41.21 permit 208.71.41.22/31 permit 208.71.41.24/31 permit 208.71.41.42/31 permit 208.71.41.64/30 permit 208.71.41.68/31 permit 208.71.41.171 permit 208.71.41.172/31 permit 208.71.41.188/30 permit 208.71.41.192/31 permit 208.71.42.190/31 permit 208.71.42.192/28 permit 208.71.42.208/30 permit 208.71.42.212/31 permit 208.71.42.214 permit 208.72.249.240/29 permit 208.75.120.0/22 permit 208.76.62.0/24 permit 208.76.63.0/24 permit 208.82.237.96/29 permit 208.82.237.104/31 permit 208.82.238.96/29 permit 208.82.238.104/31 permit 208.85.50.137 permit 208.117.48.0/20 permit 208.185.229.45 permit 208.201.241.163 permit 209.43.22.0/28 permit 209.46.117.168 permit 209.46.117.179 permit 209.61.151.0/24 permit 209.61.151.236 permit 209.61.151.249 permit 209.61.151.251 permit 209.67.98.46 permit 209.67.98.59 permit 209.85.128.0/17 permit 212.82.96.32/27 permit 212.82.96.64/29 permit 212.82.98.32/29 permit 212.82.98.64/27 permit 212.82.98.96/30 permit 212.82.98.100/30 permit 212.82.98.104/29 permit 212.82.98.112/29 permit 212.82.98.120/30 permit 212.82.98.144/28 permit 212.82.105.240/30 permit 212.82.108.112/29 permit 212.82.108.120/30 permit 212.82.108.124/31 permit 212.82.108.126 permit 212.82.108.132/30 permit 212.82.108.136/30 permit 212.82.108.208/30 permit 212.82.108.212/31 permit 212.82.108.224/30 permit 212.82.108.232/29 permit 212.82.108.240/29 permit 212.82.108.248/30 permit 212.82.108.252/31 permit 212.82.108.254 permit 212.82.109.128/31 permit 212.82.109.132 permit 212.82.111.131 permit 212.82.111.133 permit 212.82.111.135 permit 212.82.111.137 permit 212.82.111.139 permit 212.82.111.141 permit 212.82.111.225 permit 212.82.111.226/31 permit 212.82.111.228/31 permit 212.82.111.230 permit 212.123.28.40 permit 212.227.15.3 permit 212.227.15.4 permit 212.227.15.5 permit 212.227.15.6 permit 212.227.15.7 permit 212.227.15.8 permit 212.227.15.14 permit 212.227.15.15 permit 212.227.15.18 permit 212.227.15.19 permit 212.227.15.25 permit 212.227.15.26 permit 212.227.15.29 permit 212.227.15.44 permit 212.227.15.45 permit 212.227.15.46 permit 212.227.15.47 permit 212.227.15.50 permit 212.227.15.52 permit 212.227.15.53 permit 212.227.15.54 permit 212.227.15.55 permit 212.227.17.1 permit 212.227.17.2 permit 212.227.17.7 permit 212.227.17.11 permit 212.227.17.12 permit 212.227.17.16 permit 212.227.17.17 permit 212.227.17.18 permit 212.227.17.19 permit 212.227.17.20 permit 212.227.17.21 permit 212.227.17.22 permit 212.227.17.26 permit 212.227.17.27 permit 212.227.17.28 permit 212.227.17.29 permit 212.227.126.206 permit 212.227.126.207 permit 212.227.126.208 permit 212.227.126.209 permit 212.227.126.220 permit 212.227.126.221 permit 212.227.126.222 permit 212.227.126.223 permit 212.227.126.224 permit 212.227.126.225 permit 212.227.126.226 permit 212.227.126.227 permit 213.199.128.139 permit 213.199.128.145 permit 213.199.138.181 permit 213.199.138.191 permit 216.17.150.242 permit 216.17.150.251 permit 216.24.224.0/20 permit 216.27.86.152/31 permit 216.39.60.154/31 permit 216.39.60.156/30 permit 216.39.60.160/30 permit 216.39.60.164 permit 216.39.60.192/28 permit 216.39.60.208/29 permit 216.39.60.216 permit 216.39.60.218 permit 216.39.60.230/31 permit 216.39.60.232/29 permit 216.39.60.240/29 permit 216.39.60.248/30 permit 216.39.60.252/31 permit 216.39.60.254 permit 216.39.61.170 permit 216.39.61.175 permit 216.39.61.238/31 permit 216.39.62.32/28 permit 216.39.62.48/29 permit 216.39.62.56/30 permit 216.39.62.60/31 permit 216.39.62.136/29 permit 216.39.62.144/31 permit 216.66.217.240/29 permit 216.71.138.33 permit 216.71.152.207 permit 216.71.154.29 permit 216.71.155.89 permit 216.74.162.13 permit 216.74.162.14 permit 216.82.240.0/20 permit 216.98.158.0/24 permit 216.99.5.67 permit 216.99.5.68 permit 216.109.114.32/27 permit 216.109.114.64/29 permit 216.113.162.65 permit 216.113.163.65 permit 216.128.126.97 permit 216.136.162.65 permit 216.136.162.120/29 permit 216.136.168.80/28 permit 216.139.64.0/19 permit 216.145.221.0/24 permit 216.146.32.0/24 permit 216.146.33.0/24 permit 216.198.0.0/18 permit 216.203.30.55 permit 216.203.33.178/31 permit 216.205.24.0/24 permit 216.221.160.0/19 permit 216.239.32.0/19 permit 217.72.192.77 permit 217.72.192.78 permit 217.77.141.52 permit 217.77.141.59 permit 217.175.194.0/24 permit 222.73.195.64/29 permit 223.165.113.0/24 permit 223.165.115.0/24 permit 223.165.118.0/23 permit 223.165.120.0/23 permit 2001:0868:0100:0600::/64 permit 2001:4860:4000::/36 permit 2001:748:100:40::2:0/112 permit 2001:748:400:1300::3 permit 2001:748:400:1300::4 permit 2001:748:400:1301::0/64 permit 2001:748:400:1301::3 permit 2001:748:400:1301::4 permit 2001:748:400:2300::3 permit 2001:748:400:2300::4 permit 2001:748:400:2301::0/64 permit 2001:748:400:2301::3 permit 2001:748:400:2301::4 permit 2001:748:400:3300::3 permit 2001:748:400:3300::4 permit 2001:748:400:3301::0/64 permit 2001:748:400:3301::3 permit 2001:748:400:3301::4 permit 2404:6800:4000::/36 permit 2607:13c0:0001:0000:0000:0000:0000:7000/116 permit 2607:13c0:0002:0000:0000:0000:0000:1000/116 permit 2607:13c0:0004:0000:0000:0000:0000:0000/116 permit 2607:f8b0:4000::/36 permit 2620:109:c003:104::/64 permit 2620:109:c003:104::215 permit 2620:109:c006:104::/64 permit 2620:109:c006:104::215 permit 2620:109:c00d:104::/64 permit 2620:10d:c090:400::8:1 permit 2620:10d:c091:400::8:1 permit 2620:10d:c09b:400::8:1 permit 2620:10d:c09c:400::8:1 permit 2620:119:50c0:207::/64 permit 2620:119:50c0:207::215 permit 2620:1ec:46::51 permit 2620:1ec:bdf::51 permit 2800:3f0:4000::/36 permit 49.12.4.251 permit # checks.mailcow.email 2a01:4f8:c17:7906::10 permit # checks.mailcow.email ================================================ FILE: data/conf/postfix/smtp_dsn_filter ================================================ /^4(\.\d+\.\d+ TLS is required, but host \S+ refused to start TLS: .+)/ 5$1 /^4(\.\d+\.\d+ TLS is required, but was not offered by host .+)/ 5$1 /^4.7.5(.*)/ 5.7.5$1 ================================================ FILE: data/conf/redis/redis-conf.sh ================================================ #!/bin/sh cat < /redis.conf requirepass $REDISPASS user quota_notify on nopass ~QW_* -@all +get +hget +ping EOF if [ -n "$REDISMASTERPASS" ]; then echo "masterauth $REDISMASTERPASS" >> /redis.conf fi exec redis-server /redis.conf ================================================ FILE: data/conf/rspamd/dynmaps/aliasexp.php ================================================ PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_EMULATE_PREPARES => false, ]; try { $pdo = new PDO($dsn, $database_user, $database_pass, $opt); } catch (PDOException $e) { error_log("ALIASEXP: " . $e . PHP_EOL); http_response_code(501); exit; } // Init Redis $redis = new Redis(); $redis->connect('redis-mailcow', 6379); $redis->auth(getenv("REDISPASS")); function parse_email($email) { if(!filter_var($email, FILTER_VALIDATE_EMAIL)) return false; $a = strrpos($email, '@'); return array('local' => substr($email, 0, $a), 'domain' => substr(substr($email, $a), 1)); } if (!function_exists('getallheaders')) { function getallheaders() { if (!is_array($_SERVER)) { return array(); } $headers = array(); foreach ($_SERVER as $name => $value) { if (substr($name, 0, 5) == 'HTTP_') { $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value; } } return $headers; } } // Read headers $headers = getallheaders(); // Get rcpt $rcpt = $headers['Rcpt']; // Remove tag $rcpt = preg_replace('/^(.*?)\+.*(@.*)$/', '$1$2', $rcpt); // Parse email address $parsed_rcpt = parse_email($rcpt); // Create array of final mailboxes $rcpt_final_mailboxes = array(); // Skip if not a mailcow handled domain try { if (!$redis->hGet('DOMAIN_MAP', $parsed_rcpt['domain'])) { exit; } } catch (RedisException $e) { error_log("ALIASEXP: " . $e . PHP_EOL); http_response_code(504); exit; } // Always assume rcpt is not a final mailbox but an alias for a mailbox or further aliases // // rcpt // | // mailbox <-- goto ---> alias1, alias2, mailbox2 // | | // mailbox3 | // | // alias3 ---> mailbox4 // try { $stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :rcpt AND `active` = '1'"); $stmt->execute(array( ':rcpt' => $rcpt )); $gotos = $stmt->fetch(PDO::FETCH_ASSOC)['goto']; if (empty($gotos)) { $stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :rcpt AND `active` = '1'"); $stmt->execute(array( ':rcpt' => '@' . $parsed_rcpt['domain'] )); $gotos = $stmt->fetch(PDO::FETCH_ASSOC)['goto']; } if (empty($gotos)) { $stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :rcpt AND `active` = '1'"); $stmt->execute(array(':rcpt' => $parsed_rcpt['domain'])); $goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain']; if ($goto_branch) { $gotos = $parsed_rcpt['local'] . '@' . $goto_branch; } } $gotos_array = explode(',', $gotos); $loop_c = 0; while (count($gotos_array) != 0 && $loop_c <= 20) { // Loop through all found gotos foreach ($gotos_array as $index => &$goto) { error_log("ALIAS EXPANDER: http pipe: query " . $goto . " as username from mailbox" . PHP_EOL); $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `username` = :goto AND (`active`= '1' OR `active`= '2');"); $stmt->execute(array(':goto' => $goto)); $username = $stmt->fetch(PDO::FETCH_ASSOC)['username']; if (!empty($username)) { error_log("ALIAS EXPANDER: http pipe: mailbox found: " . $username . PHP_EOL); // Current goto is a mailbox, save to rcpt_final_mailboxes if not a duplicate if (!in_array($username, $rcpt_final_mailboxes)) { $rcpt_final_mailboxes[] = $username; } } else { $parsed_goto = parse_email($goto); if (!$redis->hGet('DOMAIN_MAP', $parsed_goto['domain'])) { error_log("ALIAS EXPANDER:" . $goto . " is not a mailcow handled mailbox or alias address" . PHP_EOL); } else { $stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :goto AND `active` = '1'"); $stmt->execute(array(':goto' => $goto)); $goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['goto']; if ($goto_branch) { error_log("ALIAS EXPANDER: http pipe: goto address " . $goto . " is an alias branch for " . $goto_branch . PHP_EOL); $goto_branch_array = explode(',', $goto_branch); } else { $stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain AND `active` = '1'"); $stmt->execute(array(':domain' => $parsed_goto['domain'])); $goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain']; if ($goto_branch) { error_log("ALIAS EXPANDER: http pipe: goto domain " . $parsed_goto['domain'] . " is a domain alias branch for " . $goto_branch . PHP_EOL); $goto_branch_array = array($parsed_goto['local'] . '@' . $goto_branch); } } } } // goto item was processed, unset unset($gotos_array[$index]); } // Merge goto branch array derived from previous loop (if any), filter duplicates and unset goto branch array if (!empty($goto_branch_array)) { $gotos_array = array_unique(array_merge($gotos_array, $goto_branch_array)); unset($goto_branch_array); } // Reindex array $gotos_array = array_values($gotos_array); // Force exit if loop cannot be solved // Postfix does not allow for alias loops, so this should never happen. $loop_c++; error_log("ALIAS EXPANDER: http pipe: goto array count on loop #". $loop_c . " is " . count($gotos_array) . PHP_EOL); } } catch (PDOException $e) { error_log("ALIAS EXPANDER: " . $e->getMessage() . PHP_EOL); http_response_code(502); exit; } // Does also return the mailbox name if question == answer (query == mailbox) if (count($rcpt_final_mailboxes) == 1) { error_log("ALIASEXP: direct alias " . $rcpt . " expanded to " . $rcpt_final_mailboxes[0] . PHP_EOL); echo trim($rcpt_final_mailboxes[0]); } ================================================ FILE: data/conf/rspamd/dynmaps/bcc.php ================================================ PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_EMULATE_PREPARES => false, ]; try { $pdo = new PDO($dsn, $database_user, $database_pass, $opt); } catch (PDOException $e) { error_log("BCC MAP SQL ERROR: " . $e . PHP_EOL); http_response_code(501); exit; } function parse_email($email) { if(!filter_var($email, FILTER_VALIDATE_EMAIL)) return false; $a = strrpos($email, '@'); return array('local' => substr($email, 0, $a), 'domain' => substr(substr($email, $a), 1)); } if (!function_exists('getallheaders')) { function getallheaders() { if (!is_array($_SERVER)) { return array(); } $headers = array(); foreach ($_SERVER as $name => $value) { if (substr($name, 0, 5) == 'HTTP_') { $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value; } } return $headers; } } // Read headers $headers = getallheaders(); // Get rcpt $rcpt = $headers['Rcpt']; // Get from $from = $headers['From']; // Remove tags $rcpt = preg_replace('/^(.*?)\+.*(@.*)$/', '$1$2', $rcpt); $from = preg_replace('/^(.*?)\+.*(@.*)$/', '$1$2', $from); try { if (!empty($rcpt)) { $stmt = $pdo->prepare("SELECT `bcc_dest` FROM `bcc_maps` WHERE `type` = 'rcpt' AND `local_dest` = :local_dest AND `active` = '1'"); $stmt->execute(array( ':local_dest' => $rcpt )); $bcc_dest = $stmt->fetch(PDO::FETCH_ASSOC)['bcc_dest']; if (!empty($bcc_dest) && filter_var($bcc_dest, FILTER_VALIDATE_EMAIL)) { error_log("BCC MAP: returning ". $bcc_dest . " for " . $rcpt . PHP_EOL); http_response_code(201); echo trim($bcc_dest); exit; } } if (!empty($from)) { $stmt = $pdo->prepare("SELECT `bcc_dest` FROM `bcc_maps` WHERE `type` = 'sender' AND `local_dest` = :local_dest AND `active` = '1'"); $stmt->execute(array( ':local_dest' => $from )); $bcc_dest = $stmt->fetch(PDO::FETCH_ASSOC)['bcc_dest']; if (!empty($bcc_dest) && filter_var($bcc_dest, FILTER_VALIDATE_EMAIL)) { error_log("BCC MAP: returning ". $bcc_dest . " for " . $from . PHP_EOL); http_response_code(201); echo trim($bcc_dest); exit; } } } catch (PDOException $e) { error_log("BCC MAP SQL ERROR: " . $e->getMessage() . PHP_EOL); http_response_code(502); exit; } ================================================ FILE: data/conf/rspamd/dynmaps/footer.php ================================================ PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_EMULATE_PREPARES => false, ]; try { $pdo = new PDO($dsn, $database_user, $database_pass, $opt); } catch (PDOException $e) { error_log("FOOTER: " . $e . PHP_EOL); http_response_code(501); exit; } if (!function_exists('getallheaders')) { function getallheaders() { if (!is_array($_SERVER)) { return array(); } $headers = array(); foreach ($_SERVER as $name => $value) { if (substr($name, 0, 5) == 'HTTP_') { $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value; } } return $headers; } } // Read headers $headers = getallheaders(); // Get Domain $domain = $headers['Domain']; // Get Username $username = $headers['Username']; // Get From $from = $headers['From']; // define empty footer $empty_footer = json_encode(array( 'html' => '', 'plain' => '', 'skip_replies' => 0, 'vars' => array() )); error_log("FOOTER: checking for domain " . $domain . ", user " . $username . " and address " . $from . PHP_EOL); try { // try get $target_domain if $domain is an alias_domain $stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :alias_domain"); $stmt->execute(array( ':alias_domain' => $domain )); $alias_domain = $stmt->fetch(PDO::FETCH_ASSOC); if (!$alias_domain) { $target_domain = $domain; } else { $target_domain = $alias_domain['target_domain']; } // get footer associated with the domain $stmt = $pdo->prepare("SELECT `plain`, `html`, `mbox_exclude`, `alias_domain_exclude`, `skip_replies` FROM `domain_wide_footer` WHERE `domain` = :domain"); $stmt->execute(array( ':domain' => $target_domain )); $footer = $stmt->fetch(PDO::FETCH_ASSOC); // check if the sender is excluded if (in_array($from, json_decode($footer['mbox_exclude']))){ $footer = false; } if (in_array($domain, json_decode($footer['alias_domain_exclude']))){ $footer = false; } if (empty($footer)){ echo $empty_footer; exit; } error_log("FOOTER: " . json_encode($footer) . PHP_EOL); // footer will be applied // get custom mailbox attributes to insert into the footer $stmt = $pdo->prepare("SELECT `custom_attributes` FROM `mailbox` WHERE `username` = :username"); $stmt->execute(array( ':username' => $username )); $custom_attributes = $stmt->fetch(PDO::FETCH_ASSOC)['custom_attributes']; if (empty($custom_attributes)){ $custom_attributes = (object)array(); } } catch (Exception $e) { error_log("FOOTER: " . $e->getMessage() . PHP_EOL); http_response_code(502); exit; } // return footer $footer["vars"] = $custom_attributes; echo json_encode($footer); ================================================ FILE: data/conf/rspamd/dynmaps/forwardinghosts.php ================================================ connect('redis-mailcow', 6379); $redis->auth(getenv("REDISPASS")); function in_net($addr, $net) { $net = explode('/', $net); if (count($net) > 1) { $mask = $net[1]; } $net = inet_pton($net[0]); $addr = inet_pton($addr); $length = strlen($net); // 4 for IPv4, 16 for IPv6 if (strlen($net) != strlen($addr)) { return false; } if (!isset($mask)) { $mask = $length * 8; } $addr_bin = ''; $net_bin = ''; for ($i = 0; $i < $length; ++$i) { $addr_bin .= str_pad(decbin(ord(substr($addr, $i, $i+1))), 8, '0', STR_PAD_LEFT); $net_bin .= str_pad(decbin(ord(substr($net, $i, $i+1))), 8, '0', STR_PAD_LEFT); } return substr($addr_bin, 0, $mask) == substr($net_bin, 0, $mask); } if (isset($_GET['host'])) { try { foreach ($redis->hGetAll('WHITELISTED_FWD_HOST') as $host => $source) { if (in_net($_GET['host'], $host)) { echo '200 PERMIT'; exit; } } echo '200 DUNNO'; } catch (RedisException $e) { echo '200 DUNNO'; exit; } } else { try { echo '240.240.240.240' . PHP_EOL; foreach ($redis->hGetAll('WHITELISTED_FWD_HOST') as $host => $source) { echo $host . PHP_EOL; } } catch (RedisException $e) { echo '240.240.240.240' . PHP_EOL; exit; } } ?> ================================================ FILE: data/conf/rspamd/dynmaps/index.html ================================================ ================================================ FILE: data/conf/rspamd/dynmaps/sasl_logs.php ================================================ PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_EMULATE_PREPARES => false, ]; try { $pdo = new PDO($dsn, $database_user, $database_pass, $opt); $stmt = $pdo->query("SELECT '1' FROM `filterconf`"); } catch (PDOException $e) { echo 'settings { }'; exit; } // Check if db changed and return header $stmt = $pdo->prepare("SELECT GREATEST(COALESCE(MAX(UNIX_TIMESTAMP(UPDATE_TIME)), 1), COALESCE(MAX(UNIX_TIMESTAMP(CREATE_TIME)), 1)) AS `db_update_time` FROM `information_schema`.`tables` WHERE (`TABLE_NAME` = 'filterconf' OR `TABLE_NAME` = 'settingsmap' OR `TABLE_NAME` = 'sogo_quick_contact' OR `TABLE_NAME` = 'alias') AND TABLE_SCHEMA = :dbname;"); $stmt->execute(array( ':dbname' => $database_name )); $db_update_time = $stmt->fetch(PDO::FETCH_ASSOC)['db_update_time']; if (empty($db_update_time)) { $db_update_time = 1572048000; } if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && (strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) == $db_update_time)) { header('Last-Modified: '.gmdate('D, d M Y H:i:s', $db_update_time).' GMT', true, 304); exit; } else { header('Last-Modified: '.gmdate('D, d M Y H:i:s', $db_update_time).' GMT', true, 200); } function parse_email($email) { if (!filter_var($email, FILTER_VALIDATE_EMAIL)) return false; $a = strrpos($email, '@'); return array('local' => substr($email, 0, $a), 'domain' => substr($email, $a)); } function normalize_email($email) { $email = strtolower(str_replace('/', '\/', $email)); $gm = "@gmail.com"; if (substr_compare($email, $gm, -strlen($gm)) == 0) { $email = explode('@', $email); $email[0] = str_replace('.', '', $email[0]); $email = implode('@', $email); } $gm_alt = "@googlemail.com"; if (substr_compare($email, $gm_alt, -strlen($gm_alt)) == 0) { $email = explode('@', $email); $email[0] = str_replace('.', '', $email[0]); $email[1] = str_replace('@', '', $gm); $email = implode('@', $email); } if (str_contains($email, "+")) { $email = explode('@', $email); $user = explode('+', $email[0]); $email[0] = $user[0]; $email = implode('@', $email); } return $email; } function wl_by_sogo() { global $pdo; $rcpt = array(); $stmt = $pdo->query("SELECT DISTINCT(`sogo_folder_info`.`c_path2`) AS `user`, GROUP_CONCAT(`sogo_quick_contact`.`c_mail`) AS `contacts` FROM `sogo_folder_info` INNER JOIN `sogo_quick_contact` ON `sogo_quick_contact`.`c_folder_id` = `sogo_folder_info`.`c_folder_id` GROUP BY `c_path2`"); $sogo_contacts = $stmt->fetchAll(PDO::FETCH_ASSOC); while ($row = array_shift($sogo_contacts)) { foreach (explode(',', $row['contacts']) as $contact) { if (!filter_var($contact, FILTER_VALIDATE_EMAIL)) { continue; } // Explicit from, no mime_from, no regex - envelope must match // mailcow white and blacklists also cover mime_from $rcpt[$row['user']][] = normalize_email($contact); } } return $rcpt; } function ucl_rcpts($object, $type) { global $pdo; $rcpt = array(); if ($type == 'mailbox') { // Standard aliases $stmt = $pdo->prepare("SELECT `address` FROM `alias` WHERE `goto` = :object_goto AND `address` NOT LIKE '@%'"); $stmt->execute(array( ':object_goto' => $object )); $standard_aliases = $stmt->fetchAll(PDO::FETCH_ASSOC); while ($row = array_shift($standard_aliases)) { $local = parse_email($row['address'])['local']; $domain = parse_email($row['address'])['domain']; if (!empty($local) && !empty($domain)) { $rcpt[] = '/^' . str_replace('/', '\/', $local) . '[+].*' . str_replace('/', '\/', $domain) . '$/i'; } $rcpt[] = str_replace('/', '\/', $row['address']); } // Aliases by alias domains $stmt = $pdo->prepare("SELECT CONCAT(`local_part`, '@', `alias_domain`.`alias_domain`) AS `alias` FROM `mailbox` LEFT OUTER JOIN `alias_domain` ON `mailbox`.`domain` = `alias_domain`.`target_domain` WHERE `mailbox`.`username` = :object"); $stmt->execute(array( ':object' => $object )); $by_domain_aliases = $stmt->fetchAll(PDO::FETCH_ASSOC); array_filter($by_domain_aliases); while ($row = array_shift($by_domain_aliases)) { if (!empty($row['alias'])) { $local = parse_email($row['alias'])['local']; $domain = parse_email($row['alias'])['domain']; if (!empty($local) && !empty($domain)) { $rcpt[] = '/^' . str_replace('/', '\/', $local) . '[+].*' . str_replace('/', '\/', $domain) . '$/i'; } $rcpt[] = str_replace('/', '\/', $row['alias']); } } } elseif ($type == 'domain') { // Domain self $rcpt[] = '/.*@' . $object . '/i'; $stmt = $pdo->prepare("SELECT `alias_domain` FROM `alias_domain` WHERE `target_domain` = :object"); $stmt->execute(array(':object' => $object)); $alias_domains = $stmt->fetchAll(PDO::FETCH_ASSOC); array_filter($alias_domains); while ($row = array_shift($alias_domains)) { $rcpt[] = '/.*@' . $row['alias_domain'] . '/i'; } } return $rcpt; } ?> settings { watchdog { priority = 10; rcpt_mime = "/null@localhost/i"; from_mime = "/watchdog@localhost/i"; apply "default" { symbols_disabled = ["HISTORY_SAVE", "ARC", "ARC_SIGNED", "DKIM", "DKIM_SIGNED", "CLAM_VIRUS"]; want_spam = yes; actions { reject = 9999.0; greylist = 9998.0; "add header" = 9997.0; } } } query("SELECT DISTINCT `object` FROM `filterconf` WHERE `option` = 'highspamlevel' OR `option` = 'lowspamlevel'"); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); while ($row = array_shift($rows)) { $username_sane = preg_replace("/[^a-zA-Z0-9]+/", "", $row['object']); ?> score_ { priority = 4; rcpt = ; prepare("SELECT `option`, `value` FROM `filterconf` WHERE (`option` = 'highspamlevel' OR `option` = 'lowspamlevel') AND `object`= :object"); $stmt->execute(array(':object' => $row['object'])); $spamscore = $stmt->fetchAll(PDO::FETCH_COLUMN|PDO::FETCH_GROUP); ?> apply "default" { actions { reject = ; greylist = ; "add header" = ; } } } $contacts) { $username_sane = preg_replace("/[^a-zA-Z0-9]+/", "", $user); ?> whitelist_sogo_ { from = ; priority = 4; rcpt = ; apply "default" { SOGO_CONTACT = -99.0; } symbols [ "SOGO_CONTACT" ] } query("SELECT DISTINCT `object` FROM `filterconf` WHERE `option` = 'whitelist_from'"); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); while ($row = array_shift($rows)) { $username_sane = preg_replace("/[^a-zA-Z0-9]+/", "", $row['object']); ?> whitelist_ { prepare("SELECT `value` FROM `filterconf` WHERE `object`= :object AND `option` = 'whitelist_from'"); $stmt->execute(array(':object' => $row['object'])); $list_items = $stmt->fetchAll(PDO::FETCH_ASSOC); foreach ($list_items as $item) { ?> from = "//i"; priority = 5; rcpt = ; priority = 6; rcpt = ; apply "default" { MAILCOW_WHITE = -999.0; } symbols [ "MAILCOW_WHITE" ] } whitelist_mime_ { from_mime = "//i"; priority = 5; rcpt = ; priority = 6; rcpt = ; apply "default" { MAILCOW_WHITE = -999.0; } symbols [ "MAILCOW_WHITE" ] } query("SELECT DISTINCT `object` FROM `filterconf` WHERE `option` = 'blacklist_from'"); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); while ($row = array_shift($rows)) { $username_sane = preg_replace("/[^a-zA-Z0-9]+/", "", $row['object']); ?> blacklist_ { prepare("SELECT `value` FROM `filterconf` WHERE `object`= :object AND `option` = 'blacklist_from'"); $stmt->execute(array(':object' => $row['object'])); $list_items = $stmt->fetchAll(PDO::FETCH_ASSOC); foreach ($list_items as $item) { ?> from = "//i"; priority = 5; rcpt = ; priority = 6; rcpt = ; apply "default" { MAILCOW_BLACK = 999.0; } symbols [ "MAILCOW_BLACK" ] } blacklist_header_ { from_mime = "//i"; priority = 5; rcpt = ; priority = 6; rcpt = ; apply "default" { MAILCOW_BLACK = 999.0; } symbols [ "MAILCOW_BLACK" ] } ham_trap { rcpt = ; priority = 9; apply "default" { symbols_enabled = ["HISTORY_SAVE"]; } symbols [ "HAM_TRAP" ] } spam_trap { rcpt = ; priority = 9; apply "default" { symbols_enabled = ["HISTORY_SAVE"]; } symbols [ "SPAM_TRAP" ] } query("SELECT `id`, `content` FROM `settingsmap` WHERE `active` = '1'"); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); while ($row = array_shift($rows)) { $username_sane = preg_replace("/[^a-zA-Z0-9]+/", "", $row['id']); ?> additional_settings_ { } query("SELECT `id`, `address`, `domain` FROM `alias` WHERE `active` = '1' AND `internal` = '1'"); $aliases = $stmt->fetchAll(PDO::FETCH_ASSOC); while ($alias = array_shift($aliases)) { // build allowed_domains regex and add target domain and alias domains $stmt = $pdo->prepare("SELECT `alias_domain` FROM `alias_domain` WHERE `active` = '1' AND `target_domain` = :target_domain"); $stmt->execute(array(':target_domain' => $alias['domain'])); $allowed_domains = $stmt->fetchAll(PDO::FETCH_ASSOC); $allowed_domains = array_map(function($item) { return str_replace('.', '\.', $item['alias_domain']); }, $allowed_domains); $allowed_domains[] = str_replace('.', '\.', $alias['domain']); $allowed_domains = implode('|', $allowed_domains); ?> internal_alias_ { priority = 10; rcpt = ""; from = "/^((?!.*@()).)*$/"; apply "default" { MAILCOW_INTERNAL_ALIAS = 9999.0; } symbols [ "MAILCOW_INTERNAL_ALIAS" ] } } ================================================ FILE: data/conf/rspamd/dynmaps/vars.inc.php ================================================ ================================================ FILE: data/conf/rspamd/lua/ratelimit.lua ================================================ local custom_keywords = {} custom_keywords.mailcow = function(task) local rspamd_logger = require "rspamd_logger" local dyn_rl_symbol = task:get_symbol("DYN_RL") if dyn_rl_symbol then local rl_value = dyn_rl_symbol[1].options[1] local rl_object = dyn_rl_symbol[1].options[2] if rl_value and rl_object then rspamd_logger.infox(rspamd_config, "DYN_RL symbol has value %s for object %s, returning %s...", rl_value, rl_object, "rs_dynrl_" .. rl_object) return "rs_dynrl_" .. rl_object, rl_value end end end return custom_keywords ================================================ FILE: data/conf/rspamd/lua/rspamd.local.lua ================================================ rspamd_config.MAILCOW_AUTH = { callback = function(task) local uname = task:get_user() if uname then return 1 end end } local monitoring_hosts = rspamd_config:add_map{ url = "/etc/rspamd/custom/monitoring_nolog.map", description = "Monitoring hosts", type = "regexp" } rspamd_config:register_symbol({ name = 'SMTP_ACCESS', type = 'postfilter', callback = function(task) local util = require("rspamd_util") local rspamd_logger = require "rspamd_logger" local rspamd_ip = require 'rspamd_ip' local uname = task:get_user() local limited_access = task:get_symbol("SMTP_LIMITED_ACCESS") if not uname then return false end if not limited_access then return false end local hash_key = 'SMTP_ALLOW_NETS_' .. uname local redis_params = rspamd_parse_redis_server('smtp_access') local ip = task:get_from_ip() if ip == nil or not ip:is_valid() then return false end local from_ip_string = tostring(ip) smtp_access_table = {from_ip_string} local maxbits = 128 local minbits = 32 if ip:get_version() == 4 then maxbits = 32 minbits = 8 end for i=maxbits,minbits,-1 do local nip = ip:apply_mask(i):to_string() .. "/" .. i table.insert(smtp_access_table, nip) end local function smtp_access_cb(err, data) if err then rspamd_logger.infox(rspamd_config, "smtp_access query request for ip %s returned invalid or empty data (\"%s\") or error (\"%s\")", ip, data, err) return false else rspamd_logger.infox(rspamd_config, "checking ip %s for smtp_access in %s", from_ip_string, hash_key) for k,v in pairs(data) do if (v and v ~= userdata and v == '1') then rspamd_logger.infox(rspamd_config, "found ip in smtp_access map") task:insert_result(true, 'SMTP_ACCESS', 0.0, from_ip_string) return true end end rspamd_logger.infox(rspamd_config, "couldnt find ip in smtp_access map") task:insert_result(true, 'SMTP_ACCESS', 999.0, from_ip_string) return true end end table.insert(smtp_access_table, 1, hash_key) local redis_ret_user = rspamd_redis_make_request(task, redis_params, -- connect params hash_key, -- hash key false, -- is write smtp_access_cb, --callback 'HMGET', -- command smtp_access_table -- arguments ) if not redis_ret_user then rspamd_logger.infox(rspamd_config, "cannot check smtp_access redis map") end end, priority = 10 }) rspamd_config:register_symbol({ name = 'POSTMASTER_HANDLER', type = 'prefilter', callback = function(task) local rcpts = task:get_recipients('smtp') local rspamd_logger = require "rspamd_logger" local lua_util = require "lua_util" local from = task:get_from(1) -- not applying to mails with more than one rcpt to avoid bypassing filters by addressing postmaster if rcpts and #rcpts == 1 then for _,rcpt in ipairs(rcpts) do local rcpt_split = rspamd_str_split(rcpt['addr'], '@') if #rcpt_split == 2 then if rcpt_split[1] == 'postmaster' then task:set_pre_result('accept', 'whitelisting postmaster smtp rcpt', 'postmaster') return end end end end if from then for _,fr in ipairs(from) do local fr_split = rspamd_str_split(fr['addr'], '@') if #fr_split == 2 then if fr_split[1] == 'postmaster' and task:get_user() then -- no whitelist, keep signatures task:insert_result(true, 'POSTMASTER_FROM', -2500.0) return end end end end end, priority = 10 }) rspamd_config:register_symbol({ name = 'KEEP_SPAM', type = 'prefilter', callback = function(task) local util = require("rspamd_util") local rspamd_logger = require "rspamd_logger" local rspamd_ip = require 'rspamd_ip' local uname = task:get_user() if uname then return false end local redis_params = rspamd_parse_redis_server('keep_spam') local ip = task:get_from_ip() if ip == nil or not ip:is_valid() then return false end -- Helper function to parse IPv6 into 8 segments local function ipv6_to_segments(ip_str) -- Remove zone identifier if present (e.g., %eth0) ip_str = ip_str:gsub("%%.*$", "") local segments = {} -- Handle :: compression if ip_str:find('::') then local before, after = ip_str:match('^(.*)::(.*)$') before = before or '' after = after or '' local before_parts = {} local after_parts = {} if before ~= '' then for seg in before:gmatch('[^:]+') do table.insert(before_parts, tonumber(seg, 16) or 0) end end if after ~= '' then for seg in after:gmatch('[^:]+') do table.insert(after_parts, tonumber(seg, 16) or 0) end end -- Add before segments for _, seg in ipairs(before_parts) do table.insert(segments, seg) end -- Add compressed zeros local zeros_needed = 8 - #before_parts - #after_parts for i = 1, zeros_needed do table.insert(segments, 0) end -- Add after segments for _, seg in ipairs(after_parts) do table.insert(segments, seg) end else -- No compression for seg in ip_str:gmatch('[^:]+') do table.insert(segments, tonumber(seg, 16) or 0) end end -- Ensure we have exactly 8 segments while #segments < 8 do table.insert(segments, 0) end return segments end -- Generate all common IPv6 notations local function get_ipv6_variants(ip_str) local variants = {} local seen = {} local function add_variant(v) if v and not seen[v] then table.insert(variants, v) seen[v] = true end end -- For IPv4, just return the original if not ip_str:find(':') then add_variant(ip_str) return variants end local segments = ipv6_to_segments(ip_str) -- 1. Fully expanded form (all zeros shown as 0000) local expanded_parts = {} for _, seg in ipairs(segments) do table.insert(expanded_parts, string.format('%04x', seg)) end add_variant(table.concat(expanded_parts, ':')) -- 2. Standard form (no leading zeros, but all segments present) local standard_parts = {} for _, seg in ipairs(segments) do table.insert(standard_parts, string.format('%x', seg)) end add_variant(table.concat(standard_parts, ':')) -- 3. Find all possible :: compressions -- RFC 5952: compress the longest run of consecutive zeros -- But we need to check all possibilities since Redis might have any form -- Find all zero runs local zero_runs = {} local in_run = false local run_start = 0 local run_length = 0 for i = 1, 8 do if segments[i] == 0 then if not in_run then in_run = true run_start = i run_length = 1 else run_length = run_length + 1 end else if in_run then if run_length >= 1 then -- Allow single zero compression too table.insert(zero_runs, {start = run_start, length = run_length}) end in_run = false end end end -- Don't forget the last run if in_run and run_length >= 1 then table.insert(zero_runs, {start = run_start, length = run_length}) end -- Generate variant for each zero run compression for _, run in ipairs(zero_runs) do local parts = {} -- Before compression for i = 1, run.start - 1 do table.insert(parts, string.format('%x', segments[i])) end -- The compression if run.start == 1 then table.insert(parts, '') table.insert(parts, '') elseif run.start + run.length - 1 == 8 then table.insert(parts, '') table.insert(parts, '') else table.insert(parts, '') end -- After compression for i = run.start + run.length, 8 do table.insert(parts, string.format('%x', segments[i])) end local compressed = table.concat(parts, ':'):gsub('::+', '::') add_variant(compressed) end return variants end local from_ip_string = tostring(ip) local ip_check_table = {} -- Add all variants of the exact IP for _, variant in ipairs(get_ipv6_variants(from_ip_string)) do table.insert(ip_check_table, variant) end local maxbits = 128 local minbits = 32 if ip:get_version() == 4 then maxbits = 32 minbits = 8 end -- Add all CIDR notations with variants for i=maxbits,minbits,-1 do local masked_ip = ip:apply_mask(i) local cidr_base = masked_ip:to_string() for _, variant in ipairs(get_ipv6_variants(cidr_base)) do local cidr = variant .. "/" .. i table.insert(ip_check_table, cidr) end end local function keep_spam_cb(err, data) if err then rspamd_logger.infox(rspamd_config, "keep_spam query request for ip %s returned invalid or empty data (\"%s\") or error (\"%s\")", ip, data, err) return false else for k,v in pairs(data) do if (v and v ~= userdata and v == '1') then rspamd_logger.infox(rspamd_config, "found ip %s (checked as: %s) in keep_spam map, setting pre-result accept", from_ip_string, ip_check_table[k]) task:set_pre_result('accept', 'ip matched with forward hosts', 'keep_spam') task:set_flag('no_stat') return end end end end table.insert(ip_check_table, 1, 'KEEP_SPAM') local redis_ret_user = rspamd_redis_make_request(task, redis_params, -- connect params 'KEEP_SPAM', -- hash key false, -- is write keep_spam_cb, --callback 'HMGET', -- command ip_check_table -- arguments ) if not redis_ret_user then rspamd_logger.infox(rspamd_config, "cannot check keep_spam redis map") end end, priority = 19 }) rspamd_config:register_symbol({ name = 'TLS_HEADER', type = 'postfilter', callback = function(task) local rspamd_logger = require "rspamd_logger" local tls_tag = task:get_request_header('TLS-Version') if type(tls_tag) == 'nil' then task:set_milter_reply({ add_headers = {['X-Last-TLS-Session-Version'] = 'None'} }) else task:set_milter_reply({ add_headers = {['X-Last-TLS-Session-Version'] = tostring(tls_tag)} }) end end, priority = 12 }) rspamd_config:register_symbol({ name = 'TAG_MOO', type = 'postfilter', flags = 'ignore_passthrough', callback = function(task) local util = require("rspamd_util") local rspamd_logger = require "rspamd_logger" local redis_params = rspamd_parse_redis_server('taghandler') local rspamd_http = require "rspamd_http" local rcpts = task:get_recipients('smtp') local lua_util = require "lua_util" local tagged_rcpt = task:get_symbol("TAGGED_RCPT") local function remove_moo_tag() local moo_tag_header = task:get_header('X-Moo-Tag', false) if moo_tag_header then task:set_milter_reply({ remove_headers = {['X-Moo-Tag'] = 0}, }) end return true end -- Check if we have exactly one recipient if not (rcpts and #rcpts == 1) then rspamd_logger.infox("TAG_MOO: not exactly one rcpt (%s), removing moo tag", rcpts and #rcpts or 0) remove_moo_tag() return end local rcpt_addr = rcpts[1]['addr'] local rcpt_user = rcpts[1]['user'] local rcpt_domain = rcpts[1]['domain'] -- Check if recipient has a tag (contains '+') local tag = nil if tagged_rcpt ~= nil then tag = tagged_rcpt rspamd_logger.infox("TAG_MOO: found tag in recipient: %s (base: %s, tag: %s)", rcpt_addr, base_user, tag) end if not tag then rspamd_logger.infox("TAG_MOO: no tag found in recipient %s, removing moo tag", rcpt_addr) remove_moo_tag() return end -- Optional: Check if domain is a mailcow domain -- When KEEP_SPAM is active, RCPT_MAILCOW_DOMAIN might not be set -- If the mail is being delivered, we can assume it's valid local mailcow_domain = task:get_symbol("RCPT_MAILCOW_DOMAIN") if not mailcow_domain then rspamd_logger.infox("TAG_MOO: RCPT_MAILCOW_DOMAIN not set (possibly due to pre-result), proceeding anyway for domain %s", rcpt_domain) end local action = task:get_metric_action('default') rspamd_logger.infox("TAG_MOO: metric action: %s", action) -- Check if we have a pre-result (e.g., from KEEP_SPAM or POSTMASTER_HANDLER) local allow_processing = false if task.has_pre_result then local has_pre, pre_action = task:has_pre_result() if has_pre then rspamd_logger.infox("TAG_MOO: pre-result detected: %s", tostring(pre_action)) if pre_action == 'accept' then allow_processing = true rspamd_logger.infox("TAG_MOO: pre-result is accept, will process") end end end -- Allow processing for mild actions or when we have pre-result accept if not allow_processing and action ~= 'no action' and action ~= 'greylist' then rspamd_logger.infox("TAG_MOO: skipping tag handler for action: %s", action) remove_moo_tag() return true end rspamd_logger.infox("TAG_MOO: processing allowed") local function http_callback(err_message, code, body, headers) if body ~= nil and body ~= "" then rspamd_logger.infox(rspamd_config, "TAG_MOO: expanding rcpt to \"%s\"", body) local function tag_callback_subject(err, data) if err or type(data) ~= 'string' or data == '' then rspamd_logger.infox(rspamd_config, "TAG_MOO: subject tag handler rcpt %s returned invalid or empty data (\"%s\") or error (\"%s\") - trying subfolder tag handler...", body, data, err) local function tag_callback_subfolder(err, data) if err or type(data) ~= 'string' or data == '' then rspamd_logger.infox(rspamd_config, "TAG_MOO: subfolder tag handler for rcpt %s returned invalid or empty data (\"%s\") or error (\"%s\")", body, data, err) remove_moo_tag() else rspamd_logger.infox("TAG_MOO: User wants subfolder tag, adding X-Moo-Tag header") task:set_milter_reply({ add_headers = {['X-Moo-Tag'] = 'YES'} }) end end local redis_ret_subfolder = rspamd_redis_make_request(task, redis_params, -- connect params body, -- hash key false, -- is write tag_callback_subfolder, --callback 'HGET', -- command {'RCPT_WANTS_SUBFOLDER_TAG', body} -- arguments ) if not redis_ret_subfolder then rspamd_logger.infox(rspamd_config, "TAG_MOO: cannot make request to load tag handler for rcpt") remove_moo_tag() end else rspamd_logger.infox("TAG_MOO: user wants subject modified for tagged mail") local sbj = task:get_header('Subject') or '' local tag_value = tag[1] and tag[1].options and tag[1].options[1] or '' new_sbj = '=?UTF-8?B?' .. tostring(util.encode_base64('[' .. tag_value .. '] ' .. sbj)) .. '?=' task:set_milter_reply({ remove_headers = { ['Subject'] = 1, ['X-Moo-Tag'] = 0 }, add_headers = {['Subject'] = new_sbj} }) end end local redis_ret_subject = rspamd_redis_make_request(task, redis_params, -- connect params body, -- hash key false, -- is write tag_callback_subject, --callback 'HGET', -- command {'RCPT_WANTS_SUBJECT_TAG', body} -- arguments ) if not redis_ret_subject then rspamd_logger.infox(rspamd_config, "TAG_MOO: cannot make request to load tag handler for rcpt") remove_moo_tag() end else rspamd_logger.infox("TAG_MOO: alias expansion returned empty body") remove_moo_tag() end end local rcpt_split = rspamd_str_split(rcpt_addr, '@') if #rcpt_split == 2 then if rcpt_split[1]:match('^postmaster') then rspamd_logger.infox(rspamd_config, "TAG_MOO: not expanding postmaster alias") remove_moo_tag() else rspamd_logger.infox("TAG_MOO: requesting alias expansion for %s", rcpt_addr) rspamd_http.request({ task=task, url='http://nginx:8081/aliasexp.php', body='', callback=http_callback, headers={Rcpt=rcpt_addr}, }) end else rspamd_logger.infox("TAG_MOO: invalid rcpt format") remove_moo_tag() end end, priority = 19 }) rspamd_config:register_symbol({ name = 'BCC', type = 'postfilter', flags = 'ignore_passthrough', callback = function(task) local util = require("rspamd_util") local rspamd_http = require "rspamd_http" local rspamd_logger = require "rspamd_logger" local from_table = {} local rcpt_table = {} if task:has_symbol('ENCRYPTED_CHAT') then return -- stop end local send_mail = function(task, bcc_dest) local lua_smtp = require "lua_smtp" local function sendmail_cb(ret, err) if not ret then rspamd_logger.errx(task, 'BCC SMTP ERROR: %s', err) else rspamd_logger.infox(rspamd_config, "BCC SMTP SUCCESS TO %s", bcc_dest) end end if not bcc_dest then return -- stop end -- dot stuff content before sending local email_content = tostring(task:get_content()) email_content = string.gsub(email_content, "\r\n%.", "\r\n..") -- send mail local from_smtp = task:get_from('smtp') local from_addr = (from_smtp and from_smtp[1] and from_smtp[1].addr) or 'mailer-daemon@localhost' lua_smtp.sendmail({ task = task, host = os.getenv("IPV4_NETWORK") .. '.253', port = 591, from = from_addr, recipients = bcc_dest, helo = 'bcc', timeout = 20, }, email_content, sendmail_cb) end -- determine from local from = task:get_from('smtp') if from then for _, a in ipairs(from) do table.insert(from_table, a['addr']) -- add this rcpt to table table.insert(from_table, '@' .. a['domain']) -- add this rcpts domain to table end else return -- stop end -- determine rcpts local rcpts = task:get_recipients('smtp') if rcpts then for _, a in ipairs(rcpts) do table.insert(rcpt_table, a['addr']) -- add this rcpt to table table.insert(rcpt_table, '@' .. a['domain']) -- add this rcpts domain to table end else return -- stop end local action = task:get_metric_action('default') rspamd_logger.infox("BCC: metric action: %s", action) -- Check for pre-result accept (e.g., from KEEP_SPAM) local allow_bcc = false if task.has_pre_result then local has_pre, pre_action = task:has_pre_result() if has_pre and pre_action == 'accept' then allow_bcc = true rspamd_logger.infox("BCC: pre-result accept detected, will send BCC") end end -- Allow BCC for mild actions or when we have pre-result accept if not allow_bcc and action ~= 'no action' and action ~= 'add header' and action ~= 'rewrite subject' then rspamd_logger.infox("BCC: skipping for action: %s", action) return end local function rcpt_callback(err_message, code, body, headers) if err_message == nil and code == 201 and body ~= nil then rspamd_logger.infox("BCC: sending BCC to %s for rcpt match", body) send_mail(task, body) end end local function from_callback(err_message, code, body, headers) if err_message == nil and code == 201 and body ~= nil then rspamd_logger.infox("BCC: sending BCC to %s for from match", body) send_mail(task, body) end end if rcpt_table then for _,e in ipairs(rcpt_table) do rspamd_logger.infox(rspamd_config, "BCC: checking bcc for rcpt address %s", e) rspamd_http.request({ task=task, url='http://nginx:8081/bcc.php', body='', callback=rcpt_callback, headers={Rcpt=e} }) end end if from_table then for _,e in ipairs(from_table) do rspamd_logger.infox(rspamd_config, "BCC: checking bcc for from address %s", e) rspamd_http.request({ task=task, url='http://nginx:8081/bcc.php', body='', callback=from_callback, headers={From=e} }) end end -- Don't return true to avoid symbol being logged end, priority = 20 }) rspamd_config:register_symbol({ name = 'DYN_RL_CHECK', type = 'prefilter', callback = function(task) local util = require("rspamd_util") local redis_params = rspamd_parse_redis_server('dyn_rl') local rspamd_logger = require "rspamd_logger" local envfrom = task:get_from(1) local envrcpt = task:get_recipients(1) or {} local uname = task:get_user() if not envfrom or not uname then return false end local uname = uname:lower() if #envrcpt == 1 and envrcpt[1].addr:lower() == uname then return false end local env_from_domain = envfrom[1].domain:lower() -- get smtp from domain in lower case local function redis_cb_user(err, data) if err or type(data) ~= 'string' then rspamd_logger.infox(rspamd_config, "dynamic ratelimit request for user %s returned invalid or empty data (\"%s\") or error (\"%s\") - trying dynamic ratelimit for domain...", uname, data, err) local function redis_key_cb_domain(err, data) if err or type(data) ~= 'string' then rspamd_logger.infox(rspamd_config, "dynamic ratelimit request for domain %s returned invalid or empty data (\"%s\") or error (\"%s\")", env_from_domain, data, err) else rspamd_logger.infox(rspamd_config, "found dynamic ratelimit in redis for domain %s with value %s", env_from_domain, data) task:insert_result('DYN_RL', 0.0, data, env_from_domain) end end local redis_ret_domain = rspamd_redis_make_request(task, redis_params, -- connect params env_from_domain, -- hash key false, -- is write redis_key_cb_domain, --callback 'HGET', -- command {'RL_VALUE', env_from_domain} -- arguments ) if not redis_ret_domain then rspamd_logger.infox(rspamd_config, "cannot make request to load ratelimit for domain") end else rspamd_logger.infox(rspamd_config, "found dynamic ratelimit in redis for user %s with value %s", uname, data) task:insert_result('DYN_RL', 0.0, data, uname) end end local redis_ret_user = rspamd_redis_make_request(task, redis_params, -- connect params uname, -- hash key false, -- is write redis_cb_user, --callback 'HGET', -- command {'RL_VALUE', uname} -- arguments ) if not redis_ret_user then rspamd_logger.infox(rspamd_config, "cannot make request to load ratelimit for user") end return true end, flags = 'empty', priority = 20 }) rspamd_config:register_symbol({ name = 'NO_LOG_STAT', type = 'postfilter', callback = function(task) local from = task:get_header('From') if from and (monitoring_hosts:get_key(from) or from == "watchdog@localhost") then task:set_flag('no_log') task:set_flag('no_stat') end end }) rspamd_config:register_symbol({ name = 'MOO_FOOTER', type = 'prefilter', callback = function(task) local cjson = require "cjson" local lua_mime = require "lua_mime" local lua_util = require "lua_util" local rspamd_logger = require "rspamd_logger" local rspamd_http = require "rspamd_http" local envfrom = task:get_from(1) local uname = task:get_user() if not envfrom or not uname then return false end local uname = uname:lower() local env_from_domain = envfrom[1].domain:lower() local env_from_addr = envfrom[1].addr:lower() -- determine newline type local function newline(task) local t = task:get_newlines_type() if t == 'cr' then return '\r' elseif t == 'lf' then return '\n' end return '\r\n' end -- retrieve footer local function footer_cb(err_message, code, data, headers) if err or type(data) ~= 'string' then rspamd_logger.infox(rspamd_config, "domain wide footer request for user %s returned invalid or empty data (\"%s\") or error (\"%s\")", uname, data, err) else -- parse json string local footer = cjson.decode(data) if not footer then rspamd_logger.infox(rspamd_config, "parsing domain wide footer for user %s returned invalid or empty data (\"%s\") or error (\"%s\")", uname, data, err) else if footer and type(footer) == "table" and (footer.html and footer.html ~= "" or footer.plain and footer.plain ~= "") then rspamd_logger.infox(rspamd_config, "found domain wide footer for user %s: html=%s, plain=%s, vars=%s", uname, footer.html, footer.plain, footer.vars) if footer.skip_replies ~= 0 then in_reply_to = task:get_header_raw('in-reply-to') if in_reply_to then rspamd_logger.infox(rspamd_config, "mail is a reply - skip footer") return end end local envfrom_mime = task:get_from(2) local from_name = "" if envfrom_mime and envfrom_mime[1].name then from_name = envfrom_mime[1].name elseif envfrom and envfrom[1].name then from_name = envfrom[1].name end -- default replacements local replacements = { auth_user = uname, from_user = envfrom[1].user, from_name = from_name, from_addr = envfrom[1].addr, from_domain = envfrom[1].domain:lower() } -- add custom mailbox attributes if footer.vars and type(footer.vars) == "string" then local footer_vars = cjson.decode(footer.vars) if type(footer_vars) == "table" then for key, value in pairs(footer_vars) do replacements[key] = value end end end if footer.html and footer.html ~= "" then footer.html = lua_util.jinja_template(footer.html, replacements, true) end if footer.plain and footer.plain ~= "" then footer.plain = lua_util.jinja_template(footer.plain, replacements, true) end -- add footer local out = {} local rewrite = lua_mime.add_text_footer(task, footer.html, footer.plain) or {} local seen_cte local newline_s = newline(task) local function rewrite_ct_cb(name, hdr) if rewrite.need_rewrite_ct then if name:lower() == 'content-type' then -- include boundary if present local boundary_part = rewrite.new_ct.boundary and string.format('; boundary="%s"', rewrite.new_ct.boundary) or '' local nct = string.format('%s: %s/%s; charset=utf-8%s', 'Content-Type', rewrite.new_ct.type, rewrite.new_ct.subtype, boundary_part) out[#out + 1] = nct -- update Content-Type header (include boundary if present) task:set_milter_reply({ remove_headers = {['Content-Type'] = 0}, }) task:set_milter_reply({ add_headers = {['Content-Type'] = string.format('%s/%s; charset=utf-8%s', rewrite.new_ct.type, rewrite.new_ct.subtype, boundary_part)} }) return elseif name:lower() == 'content-transfer-encoding' then out[#out + 1] = string.format('%s: %s', 'Content-Transfer-Encoding', 'quoted-printable') -- update Content-Transfer-Encoding header task:set_milter_reply({ remove_headers = {['Content-Transfer-Encoding'] = 0}, }) task:set_milter_reply({ add_headers = {['Content-Transfer-Encoding'] = 'quoted-printable'} }) seen_cte = true return end end out[#out + 1] = hdr.raw:gsub('\r?\n?$', '') end task:headers_foreach(rewrite_ct_cb, {full = true}) if not seen_cte and rewrite.need_rewrite_ct then out[#out + 1] = string.format('%s: %s', 'Content-Transfer-Encoding', 'quoted-printable') end -- End of headers out[#out + 1] = newline_s if rewrite.out then for _,o in ipairs(rewrite.out) do out[#out + 1] = o end else out[#out + 1] = task:get_rawbody() end local out_parts = {} for _,o in ipairs(out) do if type(o) ~= 'table' then out_parts[#out_parts + 1] = o out_parts[#out_parts + 1] = newline_s else local removePrefix = "--\x0D\x0AContent-Type" if string.lower(string.sub(tostring(o[1]), 1, string.len(removePrefix))) == string.lower(removePrefix) then o[1] = string.sub(tostring(o[1]), string.len("--\x0D\x0A") + 1) end out_parts[#out_parts + 1] = o[1] if o[2] then out_parts[#out_parts + 1] = newline_s end end end task:set_message(out_parts) else rspamd_logger.infox(rspamd_config, "domain wide footer request for user %s returned invalid or empty data (\"%s\")", uname, data) end end end end -- fetch footer rspamd_http.request({ task=task, url='http://nginx:8081/footer.php', body='', callback=footer_cb, headers={Domain=env_from_domain,Username=uname,From=env_from_addr}, }) return true end, priority = 1 }) ================================================ FILE: data/conf/rspamd/meta_exporter/pipe.php ================================================ PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_EMULATE_PREPARES => false, ]; try { $pdo = new PDO($dsn, $database_user, $database_pass, $opt); } catch (PDOException $e) { error_log("QUARANTINE: " . $e . PHP_EOL); http_response_code(501); exit; } // Init Redis $redis = new Redis(); $redis->connect('redis-mailcow', 6379); $redis->auth(getenv("REDISPASS")); // Functions function parse_email($email) { if(!filter_var($email, FILTER_VALIDATE_EMAIL)) return false; $a = strrpos($email, '@'); return array('local' => substr($email, 0, $a), 'domain' => substr(substr($email, $a), 1)); } if (!function_exists('getallheaders')) { function getallheaders() { if (!is_array($_SERVER)) { return array(); } $headers = array(); foreach ($_SERVER as $name => $value) { if (substr($name, 0, 5) == 'HTTP_') { $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value; } } return $headers; } } $raw_data_content = file_get_contents('php://input'); $raw_data = mb_convert_encoding($raw_data_content, 'HTML-ENTITIES', "UTF-8"); $headers = getallheaders(); $qid = $headers['X-Rspamd-Qid']; $fuzzy = $headers['X-Rspamd-Fuzzy']; $subject = iconv_mime_decode($headers['X-Rspamd-Subject']); $score = $headers['X-Rspamd-Score']; $rcpts = $headers['X-Rspamd-Rcpt']; $user = $headers['X-Rspamd-User']; $ip = $headers['X-Rspamd-Ip']; $action = $headers['X-Rspamd-Action']; $sender = $headers['X-Rspamd-From']; $symbols = $headers['X-Rspamd-Symbols']; $raw_size = (int)$_SERVER['CONTENT_LENGTH']; if (empty($sender)) { error_log("QUARANTINE: Unknown sender, assuming empty-env-from@localhost" . PHP_EOL); $sender = 'empty-env-from@localhost'; } if ($fuzzy == 'unknown') { $fuzzy = '[]'; } try { $max_size = (int)$redis->Get('Q_MAX_SIZE'); if (($max_size * 1048576) < $raw_size) { error_log(sprintf("QUARANTINE: Message too large: %d b exceeds %d b", $raw_size, ($max_size * 1048576)) . PHP_EOL); http_response_code(505); exit; } if ($exclude_domains = $redis->Get('Q_EXCLUDE_DOMAINS')) { $exclude_domains = json_decode($exclude_domains, true); } $retention_size = (int)$redis->Get('Q_RETENTION_SIZE'); } catch (RedisException $e) { error_log("QUARANTINE: " . $e . PHP_EOL); http_response_code(504); exit; } $rcpt_final_mailboxes = array(); // Loop through all rcpts foreach (json_decode($rcpts, true) as $rcpt) { // Remove tag $rcpt = preg_replace('/^(.*?)\+.*(@.*)$/', '$1$2', $rcpt); // Break rcpt into local part and domain part $parsed_rcpt = parse_email($rcpt); // Skip if not a mailcow handled domain try { if (!$redis->hGet('DOMAIN_MAP', $parsed_rcpt['domain'])) { continue; } } catch (RedisException $e) { error_log("QUARANTINE: " . $e . PHP_EOL); http_response_code(504); exit; } // Skip if domain is excluded if (in_array($parsed_rcpt['domain'], $exclude_domains)) { error_log(sprintf("QUARANTINE: Skipped domain %s", $parsed_rcpt['domain']) . PHP_EOL); continue; } // Always assume rcpt is not a final mailbox but an alias for a mailbox or further aliases // // rcpt // | // mailbox <-- goto ---> alias1, alias2, mailbox2 // | | // mailbox3 | // | // alias3 ---> mailbox4 // try { $stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :rcpt AND `active` = '1'"); $stmt->execute(array( ':rcpt' => $rcpt )); $gotos = $stmt->fetch(PDO::FETCH_ASSOC)['goto']; if (empty($gotos)) { $stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :rcpt AND `active` = '1'"); $stmt->execute(array( ':rcpt' => '@' . $parsed_rcpt['domain'] )); $gotos = $stmt->fetch(PDO::FETCH_ASSOC)['goto']; } if (empty($gotos)) { $stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :rcpt AND `active` = '1'"); $stmt->execute(array(':rcpt' => $parsed_rcpt['domain'])); $goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain']; if ($goto_branch) { $gotos = $parsed_rcpt['local'] . '@' . $goto_branch; } } $gotos_array = explode(',', $gotos); $loop_c = 0; while (count($gotos_array) != 0 && $loop_c <= 20) { // Loop through all found gotos foreach ($gotos_array as $index => &$goto) { error_log("RCPT RESOVLER: http pipe: query " . $goto . " as username from mailbox" . PHP_EOL); $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `username` = :goto AND (`active`= '1' OR `active`= '2');"); $stmt->execute(array(':goto' => $goto)); $username = $stmt->fetch(PDO::FETCH_ASSOC)['username']; if (!empty($username)) { error_log("RCPT RESOVLER: http pipe: mailbox found: " . $username . PHP_EOL); // Current goto is a mailbox, save to rcpt_final_mailboxes if not a duplicate if (!in_array($username, $rcpt_final_mailboxes)) { $rcpt_final_mailboxes[] = $username; } } else { $parsed_goto = parse_email($goto); if (!$redis->hGet('DOMAIN_MAP', $parsed_goto['domain'])) { error_log("RCPT RESOVLER:" . $goto . " is not a mailcow handled mailbox or alias address" . PHP_EOL); } else { $stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :goto AND `active` = '1'"); $stmt->execute(array(':goto' => $goto)); $goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['goto']; if ($goto_branch) { error_log("RCPT RESOVLER: http pipe: goto address " . $goto . " is an alias branch for " . $goto_branch . PHP_EOL); $goto_branch_array = explode(',', $goto_branch); } else { $stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain AND `active` = '1'"); $stmt->execute(array(':domain' => $parsed_goto['domain'])); $goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain']; if ($goto_branch) { error_log("RCPT RESOVLER: http pipe: goto domain " . $parsed_goto['domain'] . " is a domain alias branch for " . $goto_branch . PHP_EOL); $goto_branch_array = array($parsed_goto['local'] . '@' . $goto_branch); } } } } // goto item was processed, unset unset($gotos_array[$index]); } // Merge goto branch array derived from previous loop (if any), filter duplicates and unset goto branch array if (!empty($goto_branch_array)) { $gotos_array = array_unique(array_merge($gotos_array, $goto_branch_array)); unset($goto_branch_array); } // Reindex array $gotos_array = array_values($gotos_array); // Force exit if loop cannot be solved // Postfix does not allow for alias loops, so this should never happen. $loop_c++; error_log("RCPT RESOVLER: http pipe: goto array count on loop #". $loop_c . " is " . count($gotos_array) . PHP_EOL); } } catch (PDOException $e) { error_log("RCPT RESOVLER: " . $e->getMessage() . PHP_EOL); http_response_code(502); exit; } } foreach ($rcpt_final_mailboxes as $rcpt_final) { error_log("QUARANTINE: quarantine pipe: processing quarantine message for rcpt " . $rcpt_final . PHP_EOL); try { $stmt = $pdo->prepare("INSERT INTO `quarantine` (`qid`, `subject`, `score`, `sender`, `rcpt`, `symbols`, `user`, `ip`, `msg`, `action`, `fuzzy_hashes`) VALUES (:qid, :subject, :score, :sender, :rcpt, :symbols, :user, :ip, :msg, :action, :fuzzy_hashes)"); $stmt->execute(array( ':qid' => $qid, ':subject' => $subject, ':score' => $score, ':sender' => $sender, ':rcpt' => $rcpt_final, ':symbols' => $symbols, ':user' => $user, ':ip' => $ip, ':msg' => $raw_data, ':action' => $action, ':fuzzy_hashes' => $fuzzy )); $lastId = $pdo->lastInsertId(); $stmt_update = $pdo->prepare("UPDATE `quarantine` SET `qhash` = SHA2(CONCAT(`id`, `qid`), 256) WHERE `id` = :id"); $stmt_update->execute(array(':id' => $lastId)); $stmt = $pdo->prepare('DELETE FROM `quarantine` WHERE `rcpt` = :rcpt AND `id` NOT IN ( SELECT `id` FROM ( SELECT `id` FROM `quarantine` WHERE `rcpt` = :rcpt2 ORDER BY id DESC LIMIT :retention_size ) x );'); $stmt->execute(array( ':rcpt' => $rcpt_final, ':rcpt2' => $rcpt_final, ':retention_size' => $retention_size )); } catch (PDOException $e) { error_log("QUARANTINE: " . $e->getMessage() . PHP_EOL); http_response_code(503); exit; } } ================================================ FILE: data/conf/rspamd/meta_exporter/pipe_rl.php ================================================ connect(getenv('REDIS_SLAVEOF_IP'), getenv('REDIS_SLAVEOF_PORT')); } else { $redis->connect('redis-mailcow', 6379); } $redis->auth(getenv("REDISPASS")); } catch (Exception $e) { exit; } $raw_data_content = file_get_contents('php://input'); $raw_data_decoded = json_decode($raw_data_content, true); $data['time'] = time(); $data['rcpt'] = implode(', ', $raw_data_decoded['rcpt']); $data['from'] = $raw_data_decoded['from']; $data['user'] = $raw_data_decoded['user']; $symbol_rl_key = array_search('RATELIMITED', array_column($raw_data_decoded['symbols'], 'name')); $data['rl_info'] = implode($raw_data_decoded['symbols'][$symbol_rl_key]['options']); preg_match('/(.+)\((.+)\)/i', $data['rl_info'], $rl_matches); if (!empty($rl_matches[1]) && !empty($rl_matches[2])) { $data['rl_name'] = $rl_matches[1]; $data['rl_hash'] = $rl_matches[2]; } else { $data['rl_name'] = 'err'; $data['rl_hash'] = 'err'; } $data['qid'] = $raw_data_decoded['qid']; $data['ip'] = $raw_data_decoded['ip']; $data['message_id'] = $raw_data_decoded['message_id']; $data['header_subject'] = implode(' ', $raw_data_decoded['header_subject']); $data['header_from'] = implode(', ', $raw_data_decoded['header_from']); $redis->lpush('RL_LOG', json_encode($data)); exit; ================================================ FILE: data/conf/rspamd/meta_exporter/pushover.php ================================================ PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_EMULATE_PREPARES => false, ]; try { $pdo = new PDO($dsn, $database_user, $database_pass, $opt); } catch (PDOException $e) { error_log("NOTIFY: " . $e . PHP_EOL); http_response_code(501); exit; } // Init Redis $redis = new Redis(); $redis->connect('redis-mailcow', 6379); $redis->auth(getenv("REDISPASS")); // Functions function parse_email($email) { if(!filter_var($email, FILTER_VALIDATE_EMAIL)) return false; $a = strrpos($email, '@'); return array('local' => substr($email, 0, $a), 'domain' => substr(substr($email, $a), 1)); } if (!function_exists('getallheaders')) { function getallheaders() { if (!is_array($_SERVER)) { return array(); } $headers = array(); foreach ($_SERVER as $name => $value) { if (substr($name, 0, 5) == 'HTTP_') { $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value; } } return $headers; } } $headers = getallheaders(); $json_body = json_decode(file_get_contents('php://input')); $qid = $headers['X-Rspamd-Qid']; $rcpts = $headers['X-Rspamd-Rcpt']; $sender = $headers['X-Rspamd-From']; $ip = $headers['X-Rspamd-Ip']; $subject = iconv_mime_decode($headers['X-Rspamd-Subject']); $messageid= $json_body->message_id; $priority = 0; $symbols_array = json_decode($headers['X-Rspamd-Symbols'], true); if (is_array($symbols_array)) { foreach ($symbols_array as $symbol) { if ($symbol['name'] == 'HAS_X_PRIO_ONE') { $priority = 1; break; } } } $sender_address = $json_body->header_from[0]; $sender_name = '-'; if (preg_match('/(?.*?)<(?
.*?)>/i', $sender_address, $matches)) { $sender_address = $matches['address']; $sender_name = trim($matches['name'], '"\' '); } $to_address = $json_body->header_to[0]; $to_name = '-'; if (preg_match('/(?.*?)<(?
.*?)>/i', $to_address, $matches)) { $to_address = $matches['address']; $to_name = trim($matches['name'], '"\' '); } $rcpt_final_mailboxes = array(); // Loop through all rcpts foreach (json_decode($rcpts, true) as $rcpt) { // Remove tag $rcpt = preg_replace('/^(.*?)\+.*(@.*)$/', '$1$2', $rcpt); // Break rcpt into local part and domain part $parsed_rcpt = parse_email($rcpt); // Skip if not a mailcow handled domain try { if (!$redis->hGet('DOMAIN_MAP', $parsed_rcpt['domain'])) { continue; } } catch (RedisException $e) { error_log("NOTIFY: " . $e . PHP_EOL); http_response_code(504); exit; } // Always assume rcpt is not a final mailbox but an alias for a mailbox or further aliases // // rcpt // | // mailbox <-- goto ---> alias1, alias2, mailbox2 // | | // mailbox3 | // | // alias3 ---> mailbox4 // try { $stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :rcpt AND `active` = '1'"); $stmt->execute(array( ':rcpt' => $rcpt )); $gotos = $stmt->fetch(PDO::FETCH_ASSOC)['goto']; if (empty($gotos)) { $stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :rcpt AND `active` = '1'"); $stmt->execute(array( ':rcpt' => '@' . $parsed_rcpt['domain'] )); $gotos = $stmt->fetch(PDO::FETCH_ASSOC)['goto']; } if (empty($gotos)) { $stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :rcpt AND `active` = '1'"); $stmt->execute(array(':rcpt' => $parsed_rcpt['domain'])); $goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain']; if ($goto_branch) { $gotos = $parsed_rcpt['local'] . '@' . $goto_branch; } } $gotos_array = explode(',', $gotos); $loop_c = 0; while (count($gotos_array) != 0 && $loop_c <= 20) { // Loop through all found gotos foreach ($gotos_array as $index => &$goto) { error_log("RCPT RESOVLER: http pipe: query " . $goto . " as username from mailbox" . PHP_EOL); $stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `username` = :goto AND (`active`= '1' OR `active`= '2');"); $stmt->execute(array(':goto' => $goto)); $username = $stmt->fetch(PDO::FETCH_ASSOC)['username']; if (!empty($username)) { error_log("RCPT RESOVLER: http pipe: mailbox found: " . $username . PHP_EOL); // Current goto is a mailbox, save to rcpt_final_mailboxes if not a duplicate if (!in_array($username, $rcpt_final_mailboxes)) { $rcpt_final_mailboxes[] = $username; } } else { $parsed_goto = parse_email($goto); if (!$redis->hGet('DOMAIN_MAP', $parsed_goto['domain'])) { error_log("RCPT RESOVLER:" . $goto . " is not a mailcow handled mailbox or alias address" . PHP_EOL); } else { $stmt = $pdo->prepare("SELECT `goto` FROM `alias` WHERE `address` = :goto AND `active` = '1'"); $stmt->execute(array(':goto' => $goto)); $goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['goto']; if ($goto_branch) { error_log("RCPT RESOVLER: http pipe: goto address " . $goto . " is an alias branch for " . $goto_branch . PHP_EOL); $goto_branch_array = explode(',', $goto_branch); } else { $stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain AND `active` = '1'"); $stmt->execute(array(':domain' => $parsed_goto['domain'])); $goto_branch = $stmt->fetch(PDO::FETCH_ASSOC)['target_domain']; if ($goto_branch) { error_log("RCPT RESOVLER: http pipe: goto domain " . $parsed_goto['domain'] . " is a domain alias branch for " . $goto_branch . PHP_EOL); $goto_branch_array = array($parsed_goto['local'] . '@' . $goto_branch); } } } } // goto item was processed, unset unset($gotos_array[$index]); } // Merge goto branch array derived from previous loop (if any), filter duplicates and unset goto branch array if (!empty($goto_branch_array)) { $gotos_array = array_unique(array_merge($gotos_array, $goto_branch_array)); unset($goto_branch_array); } // Reindex array $gotos_array = array_values($gotos_array); // Force exit if loop cannot be solved // Postfix does not allow for alias loops, so this should never happen. $loop_c++; error_log("RCPT RESOVLER: http pipe: goto array count on loop #". $loop_c . " is " . count($gotos_array) . PHP_EOL); } } catch (PDOException $e) { error_log("RCPT RESOVLER: " . $e->getMessage() . PHP_EOL); http_response_code(502); exit; } } foreach ($rcpt_final_mailboxes as $rcpt_final) { error_log("NOTIFY: pushover pipe: processing pushover message for rcpt " . $rcpt_final . PHP_EOL); $stmt = $pdo->prepare("SELECT * FROM `pushover` WHERE `username` = :username AND `active` = '1'"); $stmt->execute(array( ':username' => $rcpt_final )); $api_data = $stmt->fetch(PDO::FETCH_ASSOC); if (isset($api_data['key']) && isset($api_data['token'])) { $title = (!empty($api_data['title'])) ? $api_data['title'] : 'Mail'; $text = (!empty($api_data['text'])) ? $api_data['text'] : 'You\'ve got mail 📧'; $attributes = json_decode($api_data['attributes'], true); $senders = explode(',', $api_data['senders']); $senders = array_filter($senders); $senders_regex = $api_data['senders_regex']; $sender_validated = false; if (empty($senders) && empty($senders_regex)) { $sender_validated = true; } else { if (!empty($senders)) { if (in_array($sender, $senders)) { $sender_validated = true; } } if (!empty($senders_regex) && $sender_validated !== true) { if (preg_match($senders_regex, $sender)) { $sender_validated = true; } } } if ($sender_validated === false) { error_log("NOTIFY: pushover pipe: skipping unwanted sender " . $sender); continue; } if ($attributes['only_x_prio'] == "1" && $priority == 0) { error_log("NOTIFY: pushover pipe: mail has no X-Priority: 1 header, skipping"); continue; } $post_fields = array( "token" => $api_data['token'], "user" => $api_data['key'], "title" => sprintf("%s", str_replace( array('{SUBJECT}', '{SENDER}', '{SENDER_NAME}', '{SENDER_ADDRESS}', '{TO_NAME}', '{TO_ADDRESS}', '{MSG_ID}'), array($subject, $sender, $sender_name, $sender_address, $to_name, $to_address, $messageid), $title) ), "priority" => $priority, "message" => sprintf("%s", str_replace( array('{SUBJECT}', '{SENDER}', '{SENDER_NAME}', '{SENDER_ADDRESS}', '{TO_NAME}', '{TO_ADDRESS}', '{MSG_ID}', '\n'), array($subject, $sender, $sender_name, $sender_address, $to_name, $to_address, $messageid, PHP_EOL), $text) ), "sound" => $attributes['sound'] ?? "pushover" ); if ($attributes['evaluate_x_prio'] == "1" && $priority == 1) { $post_fields['expire'] = 600; $post_fields['retry'] = 120; $post_fields['priority'] = 2; } curl_setopt_array($ch = curl_init(), array( CURLOPT_URL => "https://api.pushover.net/1/messages.json", CURLOPT_POSTFIELDS => $post_fields, CURLOPT_SAFE_UPLOAD => true, CURLOPT_RETURNTRANSFER => true, )); $result = curl_exec($ch); $httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); error_log("NOTIFY: result: " . $httpcode . PHP_EOL); } } ================================================ FILE: data/conf/rspamd/meta_exporter/vars.inc.php ================================================ ================================================ FILE: data/conf/rspamd/plugins.d/README.md ================================================ This is where you should copy any rspamd custom module ================================================ FILE: data/conf/rspamd/rspamd.conf.local ================================================ # rspamd.conf.local ================================================ FILE: data/conf/rspamd/rspamd.conf.override ================================================ # rspamd.conf.override ================================================ FILE: data/conf/sogo/custom-sogo.js ================================================ // redirect to mailcow login form document.addEventListener('DOMContentLoaded', function () { var loginForm = document.forms.namedItem("loginForm"); if (loginForm) { window.location.href = '/user'; } }); // logout function function mc_logout() { fetch("/", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: "logout=1" }).then(() => window.location.href = '/'); } // Custom SOGo JS // Change the visible font-size in the editor, this does not change the font of a html message by default CKEDITOR.addCss("body {font-size: 16px !important}"); // Enable scayt by default //CKEDITOR.config.scayt_autoStartup = true; ================================================ FILE: data/conf/sogo/sogo.conf ================================================ { SOGoCalendarDefaultRoles = ( PublicViewer, ConfidentialDAndTViewer, PrivateDAndTViewer ); WOWorkersCount = "20"; SOGoACLsSendEMailNotifications = YES; SOGoAppointmentSendEMailNotifications = YES; SOGoDraftsFolderName = "Drafts"; SOGoJunkFolderName= "Junk"; SOGoMailDomain = "sogo.local"; SOGoEnableEMailAlarms = YES; SOGoMailHideInlineAttachments = YES; SOGoFoldersSendEMailNotifications = YES; SOGoForwardEnabled = YES; // Added with SOGo 5.12 - Allows users to cleanup there maildirectories by deleting mails oder than X SOGoEnableMailCleaning = YES; // Fixes "MODIFICATION_FAILED" error (HTTP 412) in Clients when accepting invitations from external services SOGoDisableOrganizerEventCheck = YES; // Option to set Users as admin to globally manage calendar permissions etc. Disabled by default // SOGoSuperUsernames = ("moo@example.com"); SOGoUIAdditionalJSFiles = ( js/theme.js, js/custom-sogo.js ); SOGoEnablePublicAccess = YES; // Multi-domain setup // Domains are isolated, you can define visibility options here. // Example: // SOGoDomainsVisibility = ( // (domain1.tld, domain5.tld), // (domain3.tld, domain2.tld) // ); // self-signed is not trusted anymore WOPort = "0.0.0.0:20000"; SOGoMemcachedHost = "memcached"; SOGoLanguage = English; SOGoMailAuxiliaryUserAccountsEnabled = YES; // SOGoCreateIdentitiesDisabled = NO; SOGoMailCustomFromEnabled = YES; SOGoMailingMechanism = smtp; SOGoSMTPAuthenticationType = plain; SxVMemLimit = 384; SOGoMaximumPingInterval = 3540; SOGoInternalSyncInterval = 45; SOGoMaximumSyncInterval = 3540; // 100 seems to break some Android clients //SOGoMaximumSyncWindowSize = 99; // This should do the trick for Outlook 2016 SOGoMaximumSyncResponseSize = 512; WOWatchDogRequestTimeout = 30; WOListenQueueSize = 16; WONoDetach = YES; SOGoIMAPAclConformsToIMAPExt = Yes; SOGoPageTitle = "SOGo Groupware"; SOGoFirstDayOfWeek = "1"; SOGoSieveFolderEncoding = "UTF-8"; SOGoPasswordChangeEnabled = NO; SOGoSentFolderName = "Sent"; SOGoMailShowSubscribedFoldersOnly = NO; NGImap4ConnectionStringSeparator = "/"; SOGoSieveScriptsEnabled = YES; SOGoTrashFolderName = "Trash"; SOGoVacationEnabled = YES; SOGoCacheCleanupInterval = 900; SOGoMaximumFailedLoginCount = 10; SOGoMaximumFailedLoginInterval = 900; SOGoFailedLoginBlockInterval = 900; // Enable SOGo URL Description for GDPR compliance, this may cause some issues with calendars and contacts. Also uncomment the encryption key below to use it. //SOGoURLEncryptionEnabled = NO; // Set a 16 character encryption key for SOGo URL Description, change this to your own value //SOGoURLPathEncryptionKey = "SOGoSuperSecret0"; GCSChannelCollectionTimer = 60; GCSChannelExpireAge = 60; MySQL4Encoding = "utf8mb4"; //SOGoDebugRequests = YES; //SoDebugBaseURL = YES; //ImapDebugEnabled = YES; //SOGoEASDebugEnabled = YES; SOGoEASSearchInBody = YES; //LDAPDebugEnabled = YES; //PGDebugEnabled = YES; //MySQL4DebugEnabled = YES; //SOGoUIxDebugEnabled = YES; //WODontZipResponse = YES; WOLogFile = "/dev/sogo_log"; } ================================================ FILE: data/conf/unbound/unbound.conf ================================================ server: verbosity: 1 interface: 0.0.0.0 interface: ::0 logfile: /dev/console do-ip4: yes do-ip6: yes do-udp: yes do-tcp: yes do-daemonize: no #access-control: 0.0.0.0/0 allow access-control: 10.0.0.0/8 allow access-control: 172.16.0.0/12 allow access-control: 192.168.0.0/16 allow access-control: fc00::/7 allow access-control: fe80::/10 allow #access-control: ::0/0 allow directory: "/etc/unbound" username: unbound auto-trust-anchor-file: trusted-key.key #private-address: 10.0.0.0/8 #private-address: 172.16.0.0/12 #private-address: 192.168.0.0/16 #private-address: 169.254.0.0/16 #private-address: fc00::/7 #private-address: fe80::/10 # cache-min-ttl needs to be less or equal to cache-max-negative-ttl cache-min-ttl: 5 cache-max-negative-ttl: 60 root-hints: "/etc/unbound/root.hints" hide-identity: yes hide-version: yes max-udp-size: 4096 msg-buffer-size: 65552 unwanted-reply-threshold: 10000 ipsecmod-enabled: no remote-control: control-enable: yes control-interface: 127.0.0.1 control-port: 8953 server-key-file: "/etc/unbound/unbound_server.key" server-cert-file: "/etc/unbound/unbound_server.pem" control-key-file: "/etc/unbound/unbound_control.key" control-cert-file: "/etc/unbound/unbound_control.pem" ================================================ FILE: data/hooks/README.md ================================================ Place executable scripts in each directory to run a hook at the end of a docker-entrypoint.sh script. Only images with a docker-entrypoint are covered. ================================================ FILE: data/web/_rspamderror.php ================================================ connect(getenv('REDIS_SLAVEOF_IP'), getenv('REDIS_SLAVEOF_PORT')); } else { $redis->connect('redis-mailcow', 6379); } $redis->auth(getenv("REDISPASS")); } catch (Exception $e) { exit; } header('Content-Type: application/json'); echo '{"error":"Unauthorized"}'; error_log("Rspamd UI: Invalid password by " . $_SERVER['REMOTE_ADDR']); $redis->publish("F2B_CHANNEL", "Rspamd UI: Invalid password by " . $_SERVER['REMOTE_ADDR']); ================================================ FILE: data/web/_status.502.html ================================================ Preparing

What is happening?

Please do not stop the stack while we are initializing the database or do other preparations.

What is happening? - Nginx cannot connect to an upstream server or other services are not ready yet.
This is fine if mailcow was just installed or updated and can take a few minutes to complete.
Please check the logs or contact support if the error persists.

Quick debugging

Check Nginx and PHP logs:

docker compose logs --tail=200 php-fpm-mailcow nginx-mailcow

Make sure your SQL credentials in mailcow.conf (a link to .env) do fit your initialized SQL volume. If you see an access denied, you might have the wrong mailcow.conf:

source mailcow.conf ; docker compose exec mysql-mailcow mysql -u${DBUSER} -p${DBPASS} ${DBNAME}

In case of a previous failed installation, create a backup of your existing data, followed by removing all volumes and starting over (NEVER do this with a production system, it will remove ALL data):

BACKUP_LOCATION=/tmp/ ./helper-scripts/backup_and_restore.sh backup all
docker compose down --volumes ; docker compose up -d

Make sure your timezone is correct. Use "America/New_York" for example, do not use spaces. Check here for a list.


Click to learn more about getting support. ================================================ FILE: data/web/admin/dashboard.php ================================================ Get('LICENSE_STATUS_CACHE')) { $_SESSION['gal'] = json_decode($license_cache, true); } $js_minifier->add('/web/js/site/dashboard.js'); // vmail df $exec_fields = array('cmd' => 'system', 'task' => 'df', 'dir' => '/var/vmail'); $vmail_df = explode(',', (string)json_decode(docker('post', 'dovecot-mailcow', 'exec', $exec_fields), true)); // containers $containers_info = (array) docker('info'); if ($clamd_status === false) unset($containers_info['clamd-mailcow']); if ($olefy_status === false) unset($containers_info['olefy-mailcow']); ksort($containers_info); $containers = array(); foreach ($containers_info as $container => $container_info) { if (!isset($container_info['State']) || !is_array($container_info['State']) || !isset($container_info['State']['StartedAt'])){ continue; } date_default_timezone_set('UTC'); $StartedAt = date_parse($container_info['State']['StartedAt']); if ($StartedAt['hour'] !== false) { $date = new \DateTime(); $date->setTimestamp(mktime( $StartedAt['hour'], $StartedAt['minute'], $StartedAt['second'], $StartedAt['month'], $StartedAt['day'], $StartedAt['year'])); try { $user_tz = new DateTimeZone(getenv('TZ')); $date->setTimezone($user_tz); $container_info['State']['StartedAtHR'] = $date->format('r'); } catch(Exception $e) { $container_info['State']['StartedAtHR'] = '?'; } } else { $container_info['State']['StartedAtHR'] = '?'; } $containers[$container] = $container_info; } // get mailcow data $hostname = getenv('MAILCOW_HOSTNAME'); $timezone = getenv('TZ'); $template = 'dashboard.twig'; $template_data = [ 'log_lines' => getenv('LOG_LINES'), 'vmail_df' => $vmail_df, 'hostname' => $hostname, 'timezone' => $timezone, 'gal' => @$_SESSION['gal'], 'license_guid' => license('guid'), 'clamd_status' => $clamd_status, 'olefy_status' => $olefy_status, 'containers' => $containers, 'ip_check' => customize('get', 'ip_check'), 'lang_admin' => json_encode($lang['admin']), 'lang_debug' => json_encode($lang['debug']), 'lang_datatables' => json_encode($lang['datatables']), ]; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/footer.inc.php'; ================================================ FILE: data/web/admin/index.php ================================================ @$_SESSION['ldelay'], 'custom_login' => customize('get', 'custom_login'), ]; $js_minifier->add('/web/js/site/index.js'); require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/footer.inc.php'; ================================================ FILE: data/web/admin/mailbox.php ================================================ add('/web/js/site/mailbox.js'); $js_minifier->add('/web/js/presets/sieveMailbox.js'); $js_minifier->add('/web/js/site/pwgen.js'); $role = "admin"; $is_dual = (!empty($_SESSION["dual-login"]["username"])) ? 'true' : 'false'; $allow_admin_email_login = (preg_match("/^([yY][eE][sS]|[yY])+$/", $_ENV["ALLOW_ADMIN_EMAIL_LOGIN"])) ? 'true' : 'false'; // domains $domains = mailbox('get', 'domains'); // mailboxes $mailboxes = []; foreach ($domains as $domain) { foreach (mailbox('get', 'mailboxes', $domain) as $mailbox) { $mailboxes[] = $mailbox; } } $template = 'mailbox.twig'; $template_data = [ 'acl' => $_SESSION['acl'], 'acl_json' => json_encode($_SESSION['acl']), 'role' => $role, 'is_dual' => $is_dual, 'allow_admin_email_login' => $allow_admin_email_login, 'global_filters' => mailbox('get', 'global_filter_details'), 'domains' => $domains, 'mailboxes' => $mailboxes, 'lang_mailbox' => json_encode($lang['mailbox']), 'lang_rl' => json_encode($lang['ratelimit']), 'lang_edit' => json_encode($lang['edit']), 'lang_datatables' => json_encode($lang['datatables']), ]; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/footer.inc.php'; ================================================ FILE: data/web/admin/queue.php ================================================ add('/web/js/site/queue.js'); $_SESSION['return_to'] = $_SERVER['REQUEST_URI']; $role = "admin"; $template = 'queue.twig'; $template_data = [ 'acl' => $_SESSION['acl'], 'acl_json' => json_encode($_SESSION['acl']), 'role' => $role, 'lang_admin' => json_encode($lang['admin']), 'lang_queue' => json_encode($lang['queue']), 'lang_datatables' => json_encode($lang['datatables']) ]; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/footer.inc.php'; ================================================ FILE: data/web/admin/system.php ================================================ "get_friendly_names")); $js_minifier->add('/web/js/site/admin.js'); $js_minifier->add('/web/js/presets/rspamd.js'); $js_minifier->add('/web/js/site/pwgen.js'); // all domains $domains = mailbox('get', 'domains'); $all_domains = array_merge($domains, mailbox('get', 'alias_domains')); // mailboxes $mailboxes = []; foreach ($all_domains as $domain) { foreach (mailbox('get', 'mailboxes', $domain) as $mailbox) { $mailboxes[] = $mailbox; } } $mailboxes = array_filter($mailboxes); // DKIM domains $dkim_domains = []; $dkim_domains_with_keys = []; foreach($domains as $domain) { $dkim_domains[$domain] = ['dkim' => null, 'alias_domains' => []]; if (!empty($dkim = dkim('details', $domain))) { $dkim_domains_with_keys[] = $domain; if ($GLOBALS['SHOW_DKIM_PRIV_KEYS'] !== true) { $dkim['privkey'] = base64_encode('Please set $SHOW_DKIM_PRIV_KEYS to true to show DKIM private keys.'); } $dkim_domains[$domain]['dkim'] = $dkim; } // get alias domains foreach (mailbox('get', 'alias_domains', $domain) as $alias_domain) { $dkim_domains[$domain]['alias_domains'][$alias_domain] = ['dkim' => null]; if (!empty($dkim = dkim('details', $alias_domain))) { $dkim_domains_with_keys[] = $alias_domain; if ($GLOBALS['SHOW_DKIM_PRIV_KEYS'] !== true) { $dkim['privkey'] = base64_encode('Please set $SHOW_DKIM_PRIV_KEYS to true to show DKIM private keys.'); } $dkim_domains[$domain]['alias_domains'][$alias_domain]['dkim'] = $dkim; } } } $dkim_blind_domains = []; foreach(dkim('blind') as $blind) { $dkim_blind_domains[$blind] = ['dkim' => null]; if (!empty($dkim = dkim('details', $blind))) { $dkim_domains_with_keys[] = $blind; if ($GLOBALS['SHOW_DKIM_PRIV_KEYS'] !== true) { $dkim['privkey'] = base64_encode('Please set $SHOW_DKIM_PRIV_KEYS to true to show DKIM private keys.'); } $dkim_blind_domains[$blind]['dkim'] = $dkim; } } // rsettings $rsettings = array_map(function ($rsetting){ $rsetting['details'] = rsettings('details', $rsetting['id']); return $rsetting; }, rsettings('get')); // rspamd regex maps $rspamd_regex_maps = []; foreach ($RSPAMD_MAPS['regex'] as $rspamd_regex_desc => $rspamd_regex_map) { $rspamd_regex_maps[$rspamd_regex_desc] = [ 'map' => $rspamd_regex_map, 'data' => file_get_contents('/rspamd_custom_maps/' . $rspamd_regex_map) ]; } // cors settings $cors_settings = cors('get'); $cors_settings['allowed_origins'] = str_replace(", ", "\n", $cors_settings['allowed_origins']); $cors_settings['allowed_methods'] = explode(", ", $cors_settings['allowed_methods']); $f2b_data = fail2ban('get'); // mbox templates $mbox_templates = mailbox('get', 'mailbox_templates'); $template = 'admin.twig'; $template_data = [ 'tfa_data' => $tfa_data, 'tfa_id' => @$_SESSION['tfa_id'], 'fido2_cid' => @$_SESSION['fido2_cid'], 'fido2_data' => $fido2_data, 'api' => [ 'ro' => admin_api('ro', 'get'), 'rw' => admin_api('rw', 'get'), ], 'dkim_domains' => $dkim_domains, 'dkim_domains_with_keys' => $dkim_domains_with_keys, 'dkim_blind_domains' => $dkim_blind_domains, 'domains' => $domains, 'all_domains' => $all_domains, 'mailboxes' => $mailboxes, 'f2b_data' => $f2b_data, 'f2b_banlist_url' => getBaseUrl() . "/f2b-banlist?id=" . $f2b_data['banlist_id'], 'q_data' => quarantine('settings'), 'qn_data' => quota_notification('get'), 'pw_reset_data' => reset_password('get_notification'), 'rsettings_map' => file_get_contents('http://nginx:8081/settings.php'), 'rsettings' => $rsettings, 'rspamd_regex_maps' => $rspamd_regex_maps, 'logo_specs' => customize('get', 'main_logo_specs'), 'logo_dark_specs' => customize('get', 'main_logo_dark_specs'), 'ip_check' => customize('get', 'ip_check'), 'custom_login' => customize('get', 'custom_login'), 'password_complexity' => password_complexity('get'), 'show_rspamd_global_filters' => @$_SESSION['show_rspamd_global_filters'], 'cors_settings' => $cors_settings, 'is_https' => isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on', 'iam_settings' => $iam_settings, 'mbox_templates' => $mbox_templates, 'lang_admin' => json_encode($lang['admin']), 'lang_datatables' => json_encode($lang['datatables']) ]; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/footer.inc.php'; ================================================ FILE: data/web/api/index.css ================================================ html { box-sizing: border-box; overflow: -moz-scrollbars-vertical; overflow-y: scroll; } *, *:before, *:after { box-sizing: inherit; } body { margin: 0; background: #fafafa; } ================================================ FILE: data/web/api/index.html ================================================ Swagger UI
================================================ FILE: data/web/api/oauth2-redirect.html ================================================ Swagger UI: OAuth2 Redirect ================================================ FILE: data/web/api/openapi.yaml ================================================ openapi: 3.1.0 info: description: >- mailcow is complete e-mailing solution with advanced antispam, antivirus, nice UI and API. In order to use this API you have to create a API key and add your IP address to the whitelist of allowed IPs this can be done by logging into the Mailcow UI using your admin account, then go to Configuration > Access > Edit administrator details > API. There you will find a collapsed API menu. There are two types of API keys - The read only key can only be used for all get endpoints - The read write key can be used for all endpoints title: mailcow API version: "1.0.0" servers: - url: / components: securitySchemes: ApiKeyAuth: type: apiKey in: header name: X-API-Key responses: Unauthorized: description: Unauthorized content: application/json: schema: type: object properties: type: type: string example: error msg: type: string example: authentication failed required: - type - msg security: - ApiKeyAuth: [] paths: /api/v1/add/alias: post: responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - log: - mailbox - add - alias - active: "1" address: alias@domain.tld goto: destination@domain.tld - null msg: - alias_added - alias@domain.tld type: success schema: properties: log: description: contains request object items: {} type: array msg: items: {} type: array type: enum: - success - danger - error type: string type: object description: OK headers: {} tags: - Aliases description: >- You may create your own mailbox alias using this action. It takes a JSON object containing a domain informations. Only one `goto*` option can be used, for ex. if you want learn as spam, then send just `goto_spam = 1` in request body. operationId: Create alias requestBody: content: application/json: schema: example: active: "1" address: alias@domain.tld goto: destination@domain.tld properties: active: description: is alias active or not type: boolean address: description: 'alias address, for catchall use "@domain.tld"' type: string goto: description: "destination address, comma separated" type: string goto_ham: description: learn as ham type: boolean goto_null: description: silently ignore type: boolean goto_spam: description: learn as spam type: boolean sogo_visible: description: toggle visibility as selectable sender in SOGo type: boolean type: object summary: Create alias /api/v1/add/time_limited_alias: post: responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - log: - mailbox - add - time_limited_alias - address: info@domain.tld domain: domain.tld - null msg: - mailbox_modified - info@domain.tld type: success schema: properties: log: description: contains request object items: {} type: array msg: items: {} type: array type: enum: - success - danger - error type: string type: object description: OK headers: {} tags: - Aliases description: >- You may create a time limited alias using this action. It takes a JSON object containing a domain and mailbox informations. Mailcow will generate a random alias. operationId: Create time limited alias requestBody: content: application/json: schema: example: username: info@domain.tld domain: domain.tld properties: username: description: 'the mailbox an alias should be created for' type: string domain: description: "the domain" type: string type: object summary: Create time limited alias /api/v1/add/app-passwd: post: responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - log: - app_passwd - add - active: "1" username: info@domain.tld app_name: wordpress app_passwd: keyleudecticidechothistishownsan31 app_passwd2: keyleudecticidechothistishownsan31 protocols: - imap_access - dav_access - smtp_access - eas_access - pop3_access - sieve_access msg: app_passwd_added type: success schema: properties: log: description: contains request object items: {} type: array msg: items: {} type: array type: enum: - success - danger - error type: string type: object description: OK headers: {} tags: - App Passwords description: >- Using this endpoint you can create a new app password for a specific mailbox. operationId: Create App Password requestBody: content: application/json: schema: example: active: "1" username: info@domain.tld app_name: wordpress app_passwd: keyleudecticidechothistishownsan31 app_passwd2: keyleudecticidechothistishownsan31 protocols: - imap_access - dav_access - smtp_access - eas_access - pop3_access - sieve_access properties: active: description: is alias active or not type: boolean username: description: mailbox for which the app password should be created type: string app_name: description: name of your app password type: string app_passwd: description: your app password type: string app_passwd2: description: your app password type: string type: object summary: Create App Password /api/v1/add/bcc: post: responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - log: - bcc - add - active: "1" bcc_dest: bcc@awesomecow.tld local_dest: mailcow.tld type: sender - null msg: bcc_saved type: success schema: properties: log: description: contains request object items: {} type: array msg: items: {} type: array type: enum: - success - danger - error type: string type: object description: OK headers: {} tags: - Address Rewriting description: >- Using this endpoint you can create a BCC map to forward all mails via a bcc for a given domain. operationId: Create BCC Map requestBody: content: application/json: schema: example: active: "1" bcc_dest: bcc@awesomecow.tld local_dest: mailcow.tld type: sender properties: active: description: 1 for a active user account 0 for a disabled user account type: number bcc_dest: description: the email address where all mails should be send to type: string local_dest: description: the domain which emails should be forwarded type: string type: description: the type of bcc map can be `sender` or `rcpt` enum: [sender, rcpt] type: string type: object summary: Create BCC Map /api/v1/add/dkim: post: responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - log: - dkim - add - dkim_selector: dkim domains: hanspeterlol.de key_size: "2048" msg: - dkim_added - hanspeterlol.de type: success schema: properties: log: description: contains request object items: {} type: array msg: items: {} type: array type: enum: - success - danger - error type: string type: object description: OK headers: {} tags: - DKIM description: Using this endpoint you can generate new DKIM keys. operationId: Generate DKIM Key requestBody: content: application/json: schema: example: dkim_selector: dkim domains: mailcow.tld key_size: "2048" properties: dkim_selector: description: the DKIM selector default dkim type: string domains: description: a list of domains for which a dkim key should be generated type: string key_size: description: the key size (1024, 2048, 3072 or 4096) type: number type: object summary: Generate DKIM Key /api/v1/add/dkim_duplicate: post: responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - log: - dkim - duplicate - from_domain: mailcow.tld to_domain: awesomecow.tld msg: - dkim_duplicated - mailcow.tld - awesomecow.tld type: success schema: properties: log: description: contains request object items: {} type: array msg: items: {} type: array type: enum: - success - danger - error type: string type: object description: OK headers: {} tags: - DKIM description: Using this endpoint you can duplicate the DKIM Key of one domain. operationId: Duplicate DKIM Key requestBody: content: application/json: schema: example: from_domain: mailcow.tld to_domain: awesomecow.tld properties: fron_domain: description: the domain where the dkim key should be copied from type: string to_domain: description: the domain where the dkim key should be copied to type: string type: object summary: Duplicate DKIM Key /api/v1/add/domain: post: responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - log: - ratelimit - edit - domain - object: domain.tld rl_frame: s rl_value: "10" msg: - rl_saved - domain.tld type: success - log: - mailbox - add - domain - active: "1" aliases: "400" restart_sogo: "1" backupmx: "0" defquota: "3072" description: some decsription domain: domain.tld mailboxes: "10" maxquota: "10240" quota: "10240" relay_all_recipients: "0" rl_frame: s rl_value: "10" tags: ["tag1", "tag2"] - null msg: - domain_added - domain.tld type: success schema: type: array items: type: object properties: log: description: contains request object items: {} type: array msg: items: {} type: array type: enum: - success - danger - error type: string description: OK headers: {} tags: - Domains description: >- You may create your own domain using this action. It takes a JSON object containing a domain informations. operationId: Create domain requestBody: content: application/json: schema: example: active: "1" aliases: "400" backupmx: "0" defquota: "3072" description: some decsription domain: domain.tld mailboxes: "10" maxquota: "10240" quota: "10240" relay_all_recipients: "0" rl_frame: s rl_value: "10" restart_sogo: "10" tags: ["tag1", "tag2"] properties: active: description: is domain active or not type: boolean aliases: description: limit count of aliases associated with this domain type: number backupmx: description: relay domain or not type: boolean defquota: description: predefined mailbox quota in `add mailbox` form type: number description: description: Description of domain type: string domain: description: Fully qualified domain name type: string gal: description: >- is domain global address list active or not, it enables shared contacts accross domain in SOGo webmail type: boolean mailboxes: description: limit count of mailboxes associated with this domain type: number maxquota: description: maximum quota per mailbox type: number quota: description: maximum quota for this domain (for all mailboxes in sum) type: number restart_sogo: description: restart SOGo to activate the domain in SOGo type: number relay_all_recipients: description: >- if not, them you have to create "dummy" mailbox for each address to relay type: boolean relay_unknown_only: description: Relay non-existing mailboxes only. Existing mailboxes will be delivered locally. type: boolean rl_frame: enum: - s - m - h - d type: string rl_value: description: rate limit value type: number tags: description: tags for this Domain type: array items: type: string type: object summary: Create domain /api/v1/add/domain-admin: post: responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - log: - domain_admin - add - active: "1" domains: mailcow.tld password: "*" password2: "*" username: testadmin msg: - domain_admin_added - testadmin type: success schema: properties: log: description: contains request object items: {} type: array msg: items: {} type: array type: enum: - success - danger - error type: string type: object description: OK headers: {} tags: - Domain admin description: >- Using this endpoint you can create a new Domain Admin user. This user has full control over a domain, and can create new mailboxes and aliases. operationId: Create Domain Admin user requestBody: content: application/json: schema: example: active: "1" domains: mailcow.tld password: supersecurepw password2: supersecurepw username: testadmin properties: active: description: 1 for a active user account 0 for a disabled user account type: number domains: description: the domains the user should be a admin of type: string password: description: domain admin user password type: string password2: description: domain admin user password type: string username: description: the username for the admin user type: string type: object summary: Create Domain Admin user /api/v1/add/sso/domain-admin: post: responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: token: "591F6D-5C3DD2-7455CD-DAF1C1-AA4FCC" description: OK headers: { } tags: - Single Sign-On description: >- Using this endpoint you can issue a token for Domain Admin user. This token can be used for autologin Domain Admin user by using query_string var sso_token={token}. Token expiration time is 30s operationId: Issue Domain Admin SSO token requestBody: content: application/json: schema: example: username: testadmin properties: username: description: the username for the admin user type: object type: object summary: Issue Domain Admin SSO token /api/v1/edit/da-acl: post: responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - type: success log: - acl - edit - testadmin - username: - testadmin da_acl: - syncjobs - quarantine - login_as - sogo_access - app_passwds - bcc_maps - pushover - filters - ratelimit - spam_policy - extend_sender_acl - unlimited_quota - protocol_access - smtp_ip_access - alias_domains - domain_desc msg: - acl_saved - testadmin schema: properties: log: description: contains request object items: {} type: array msg: items: {} type: array type: enum: - success - danger - error type: string type: object description: OK headers: {} tags: - Domain admin description: >- Using this endpoint you can edit the ACLs of a Domain Admin user. This user has full control over a domain, and can create new mailboxes and aliases. operationId: Edit Domain Admin ACL requestBody: content: application/json: schema: example: items: - testadmin attr: da_acl: - syncjobs - quarantine - login_as - sogo_access - app_passwds - bcc_maps - pushover - filters - ratelimit - spam_policy - extend_sender_acl - unlimited_quota - protocol_access - smtp_ip_access - alias_domains - domain_desc properties: items: description: contains the domain admin username you want to edit type: object attr: properties: da_acl: description: contains the list of acl names that are active for this user type: object type: object summary: Edit Domain Admin ACL /api/v1/edit/domain-admin: post: responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - type: success log: - domain_admin - edit - username: testadmin active: ["0","1"] username_new: testadmin domains: ["domain.tld"] password: "*" password2: "*" msg: - domain_admin_modified - testadmin schema: properties: type: enum: - success - danger - error type: string log: description: contains request object items: {} type: array msg: items: {} type: array type: object description: OK headers: {} tags: - Domain admin description: >- Using this endpoint you can edit a existing Domain Admin user. This user has full control over a domain, and can create new mailboxes and aliases. operationId: Edit Domain Admin user requestBody: content: application/json: schema: example: items: - testadmin attr: active: - '0' - '1' username_new: testadmin domains: ["domain.tld"] password: supersecurepassword password2: supersecurepassword properties: attr: properties: active: description: is the domain admin active or not type: boolean username_new: description: the username of the domain admin, change this to change the username type: string domains: description: a list of all domains managed by this domain admin type: array items: type: string password: description: the new domain admin user password type: string password2: description: the new domain admin user password for confirmation type: string type: object items: description: contains the domain admin username you want to edit type: object summary: Edit Domain Admin user /api/v1/add/domain-policy: post: responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - log: - policy - add - domain - domain: domain.tld object_from: "*@baddomain.tld" object_list: bl msg: - domain_modified - domain.tld type: success schema: properties: log: description: contains request object items: {} type: array msg: items: {} type: array type: enum: - success - danger - error type: string type: object description: OK headers: {} tags: - Domain antispam policies description: >- You may create your own domain policy using this action. It takes a JSON object containing a domain informations. operationId: Create domain policy requestBody: content: application/json: schema: example: domain: domain.tld object_from: "*@baddomain.tld" object_list: bl properties: domain: description: domain name to which policy is associated to type: string object_from: description: exact address or use wildcard to match whole domain type: string object_list: enum: - wl - bl type: string type: object summary: Create domain policy /api/v1/add/fwdhost: post: responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - log: - fwdhost - add - filter_spam: "0" hostname: hosted.mailcow.de msg: - forwarding_host_added - "5.1.76.202, 2a00:f820:417::202" type: success schema: properties: log: description: contains request object items: {} type: array msg: items: {} type: array type: enum: - success - danger - error type: string type: object description: OK headers: {} tags: - Fordwarding Hosts description: >- Add a new Forwarding host to mailcow. You can chose to enable or disable spam filtering of incoming emails by specifing `filter_spam` 0 = inactive, 1 = active. operationId: Add Forward Host requestBody: content: application/json: schema: example: filter_spam: "0" hostname: hosted.mailcow.de properties: filter_spam: description: "1 to enable spam filter, 0 to disable spam filter" type: number hostname: description: contains the hostname you want to add type: string type: object summary: Add Forward Host /api/v1/add/mailbox: post: responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - log: - mailbox - add - mailbox - active: "1" domain: domain.tld local_part: info name: Full name password: "*" password2: "*" quota: "3072" force_pw_update: "1" tls_enforce_in: "1" tls_enforce_out: "1" tags: ["tag1", "tag2"] - null msg: - mailbox_added - info@domain.tld type: success schema: properties: log: description: contains request object items: {} type: array msg: items: {} type: array type: enum: - success - danger - error type: string type: object description: OK headers: {} tags: - Mailboxes description: >- You may create your own mailbox using this action. It takes a JSON object containing a domain informations. operationId: Create mailbox requestBody: content: application/json: schema: example: active: "1" domain: domain.tld local_part: info name: Full name authsource: mailcow password: atedismonsin password2: atedismonsin quota: "3072" force_pw_update: "1" tls_enforce_in: "1" tls_enforce_out: "1" tags: ["tag1", "tag2"] properties: active: description: is mailbox active or not type: boolean domain: description: domain name type: string local_part: description: left part of email address type: string name: description: Full name of the mailbox user type: string authsource: description: Specifies the authentication source for the mailbox. type: string enum: [mailcow, ldap, keycloak, generic-oidc] default: mailcow password2: description: mailbox password for confirmation type: string password: description: mailbox password when using `mailcow` as the authentication source. type: string quota: description: mailbox quota type: number force_pw_update: description: forces the user to update its password on first login type: boolean tls_enforce_in: description: force inbound email tls encryption type: boolean tls_enforce_out: description: force oubound tmail tls encryption type: boolean type: object summary: Create mailbox /api/v1/add/oauth2-client: post: responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - log: - oauth2 - add - client - redirect_uri: "https://mailcow.tld" msg: Added client access type: success schema: properties: log: description: contains request object items: {} type: array msg: items: {} type: array type: enum: - success - danger - error type: string type: object description: OK headers: {} tags: - oAuth Clients description: Using this endpoint you can create a oAuth clients. operationId: Create oAuth Client requestBody: content: application/json: schema: example: redirect_uri: "https://mailcow.tld" properties: redirect_uri: description: the uri where you should be redirected after oAuth type: string type: object summary: Create oAuth Client /api/v1/add/recipient_map: post: responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - log: - recipient_map - add - active: "1" recipient_map_new: target@mailcow.tld recipient_map_old: recipient@mailcow.tld - null msg: - recipient_map_entry_saved - recipient@mailcow.tld type: success schema: properties: log: description: contains request object items: {} type: array msg: items: {} type: array type: enum: - success - danger - error type: string type: object description: OK headers: {} tags: - Address Rewriting description: >- Using this endpoint you can create a recipient map to forward all mails from one email address to another. operationId: Create Recipient Map requestBody: content: application/json: schema: example: active: "1" recipient_map_new: target@mailcow.tld recipient_map_old: recipient@mailcow.tld properties: active: description: 1 for a active user account 0 for a disabled user account type: number recipient_map_new: description: the email address that should receive the forwarded emails type: string recipient_map_old: description: the email address which emails should be forwarded type: string type: object summary: Create Recipient Map /api/v1/add/relayhost: post: responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - log: - relayhost - add - hostname: "mailcow.tld:25" password: supersecurepassword username: testuser msg: - relayhost_added - "" type: success schema: properties: log: description: contains request object items: {} type: array msg: items: {} type: array type: enum: - success - danger - error type: string type: object description: OK headers: {} tags: - Routing description: Using this endpoint you can create Sender-Dependent Transports. operationId: Create Sender-Dependent Transports requestBody: content: application/json: schema: example: hostname: "mailcow.tld:25" password: supersecurepassword username: testuser properties: hostname: description: the hostname of the smtp server with port type: string password: description: the password for the smtp user type: string username: description: the username used to authenticate type: string type: object summary: Create Sender-Dependent Transports /api/v1/add/resource: post: responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - log: - mailbox - add - resource - active: "1" description: test domain: mailcow.tld kind: location multiple_bookings: "0" multiple_bookings_custom: "" multiple_bookings_select: "0" - null msg: - resource_added - mailcow.tld type: success schema: properties: log: description: contains request object items: {} type: array msg: items: {} type: array type: enum: - success - danger - error type: string type: object description: OK headers: {} tags: - Resources description: Using this endpoint you can create Resources. operationId: Create Resources requestBody: content: application/json: schema: example: active: "1" description: test domain: mailcow.tld kind: location multiple_bookings: "0" multiple_bookings_custom: "" multiple_bookings_select: "0" properties: active: description: 1 for a active transport map 0 for a disabled transport map type: number description: description: a description of the resource type: string domain: description: the domain for which the resource should be type: string kind: description: the kind of recouse enum: - location - group - thing type: string multiple_bookings: enum: - "-1" - "1" - custom type: string multiple_bookings_custom: description: always empty type: number multiple_bookings_select: enum: - "-1" - "1" - custom type: string type: object summary: Create Resources /api/v1/add/syncjob: post: responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - log: - mailbox - add - syncjob - active: "1" automap: "1" custom_params: "" delete1: "0" delete2: "0" delete2duplicates: "1" enc1: SSL exclude: (?i)spam|(?i)junk host1: imap.server.tld maxage: "0" maxbytespersecond: "0" mins_interval: "20" password1: supersecret port1: 993 skipcrossduplicates: "0" subfolder2: External subscribeall: "1" timeout1: "600" timeout2: "600" user1: username username: mailbox@domain.tld - null msg: - mailbox_modified - mailbox@domain.tld type: success schema: properties: log: description: contains request object items: {} type: array msg: items: {} type: array type: enum: - success - danger - error type: string type: object description: OK headers: {} tags: - Sync jobs description: >- You can create new sync job using this action. It takes a JSON object containing a domain informations. operationId: Create sync job summary: Create sync job requestBody: content: application/json: schema: example: username: lisa@mailcow.tld host1: mail.mailcow.tld port1: "143" user1: demo@mailcow.tld password1: supersecretpw enc1: TLS mins_interval: "20" subfolder2: "/SyncIntoSubfolder" maxage: "0" maxbytespersecond: "0" timeout1: "600" timeout2: "600" exclude: "(?i)spam|(?i)junk" custom_params: "--dry" delete2duplicates: "1" delete1: "1" delete2: "0" automap: "1" skipcrossduplicates: "0" subscribeall: "0" active: "1" properties: parameters: description: your local mailcow mailbox type: string host1: description: the smtp server where mails should be synced from type: string port1: description: the smtp port of the target mail server type: string user1: description: the username of the mailbox type: string password: description: the password of the mailbox type: string enc1: description: the encryption method used to connect to the mailserver type: string mins_internal: description: the interval in which messages should be syned type: number subfolder2: description: sync into subfolder on destination (empty = do not use subfolder) type: string maxage: description: only sync messages up to this age in days type: number maxbytespersecond: description: max speed transfer limit for the sync type: number timeout1: description: timeout for connection to remote host type: number timeout2: description: timeout for connection to local host type: number exclude: description: exclude objects (regex) type: string custom_params: description: custom parameters type: string delete2duplicates: description: delete duplicates on destination (--delete2duplicates) type: boolean delete1: description: delete from source when completed (--delete1) type: boolean delete2: description: delete messages on destination that are not on source (--delete2) type: boolean automap: description: try to automap folders ("Sent items", "Sent" => "Sent" etc.) (--automap) type: boolean skipcrossduplicates: description: skip duplicate messages across folders (first come, first serve) (--skipcrossduplicates) type: boolean subscribeall: description: subscribe all folders (--subscribeall) type: boolean active: description: enables or disables the sync job type: boolean type: object /api/v1/add/tls-policy-map: post: responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - log: - tls_policy_maps - add - parameters: "" active: "1" dest: mailcow.tld policy: encrypt - null msg: - tls_policy_map_entry_saved - mailcow.tld type: success schema: properties: log: description: contains request object items: {} type: array msg: items: {} type: array type: enum: - success - danger - error type: string type: object description: OK headers: {} tags: - Outgoing TLS Policy Map Overrides description: Using this endpoint you can create a TLS policy map override. operationId: Create TLS Policy Map requestBody: content: application/json: schema: example: parameters: "" active: "1" dest: mailcow.tld policy: encrypt properties: parameters: description: >- custom parameters you find out more about them [here](http://www.postfix.org/postconf.5.html#smtp_tls_policy_maps) type: string active: description: 1 for a active user account 0 for a disabled user account type: number dest: description: the target domain or email address type: string policy: description: the policy enum: - none - may - encrypt - dane - "'dane" - fingerprint - verify - secure type: string type: object summary: Create TLS Policy Map /api/v1/add/transport: post: responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - log: - transport - add - active: "1" destination: example2.org nexthop: "host:25" password: supersecurepw username: testuser msg: - relayhost_added - "" type: success schema: properties: log: description: contains request object items: {} type: array msg: items: {} type: array type: enum: - success - danger - error type: string type: object description: OK headers: {} tags: - Routing description: Using this endpoint you can create Sender-Dependent Transports. operationId: Create Transport Maps requestBody: content: application/json: schema: example: active: "1" destination: example.org nexthop: "host:25" password: supersecurepw username: testuser properties: active: description: 1 for a active transport map 0 for a disabled transport map type: number destination: type: string nexthop: type: string password: description: the password for the smtp user type: string username: description: the username used to authenticate type: string type: object summary: Create Transport Maps /api/v1/delete/alias: post: responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - log: - mailbox - delete - alias - id: - "6" - "9" - null msg: - alias_removed - alias@domain.tld type: success - log: - mailbox - delete - alias - id: - "6" - "9" - null msg: - alias_removed - alias2@domain.tld type: success schema: properties: log: description: contains request object items: {} type: array msg: items: {} type: array type: enum: - success - danger - error type: string type: object description: OK headers: {} tags: - Aliases description: You can delete one or more aliases. operationId: Delete alias requestBody: content: application/json: schema: items: example: "6" type: string type: array summary: Delete alias /api/v1/delete/app-passwd: post: responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - log: - app_passwd - delete - id: - "2" msg: - app_passwd_removed - "2" type: success schema: properties: log: description: contains request object items: {} type: array msg: items: {} type: array type: enum: - success - danger - error type: string type: object description: OK headers: {} tags: - App Passwords description: Using this endpoint you can delete a single app password. operationId: Delete App Password requestBody: content: application/json: schema: example: - "1" properties: items: description: contains list of app passwords you want to delete type: object type: object summary: Delete App Password /api/v1/delete/bcc: post: responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - log: - bcc - delete - id: - "4" - null msg: - bcc_deleted - "4" type: success schema: properties: log: description: contains request object items: {} type: array msg: items: {} type: array type: enum: - success - danger - error type: string type: object description: OK headers: {} tags: - Address Rewriting description: >- Using this endpoint you can delete a BCC map, for this you have to know its ID. You can get the ID using the GET method. operationId: Delete BCC Map requestBody: content: application/json: schema: example: - "3" properties: items: description: contains list of bcc maps you want to delete type: object type: object summary: Delete BCC Map /api/v1/delete/dkim: post: responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - log: - dkim - delete - domains: - mailcow.tld msg: - dkim_removed - mailcow.tld type: success schema: properties: log: description: contains request object items: {} type: array msg: items: {} type: array type: enum: - success - danger - error type: string type: object description: OK headers: {} tags: - DKIM description: Using this endpoint a existing DKIM Key can be deleted operationId: Delete DKIM Key requestBody: content: application/json: schema: items: example: - mailcow.tld type: string type: array summary: Delete DKIM Key /api/v1/delete/domain: post: responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - log: - mailbox - delete - domain - domain: - domain.tld - domain2.tld - null msg: - domain_removed - domain.tld type: success - log: - mailbox - delete - domain - domain: - domain.tld - domain2.tld - null msg: - domain_removed - domain2.tld type: success schema: type: array items: type: object properties: log: description: contains request object items: {} type: array msg: items: {} type: array type: enum: - success - danger - error type: string description: OK headers: {} tags: - Domains description: You can delete one or more domains. operationId: Delete domain requestBody: content: application/json: schema: type: object example: - domain.tld - domain2.tld properties: items: type: array items: type: string summary: Delete domain /api/v1/delete/domain-admin: post: responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - log: - domain_admin - delete - username: - testadmin msg: - domain_admin_removed - testadmin type: success schema: properties: log: description: contains request object items: {} type: array msg: items: {} type: array type: enum: - success - danger - error type: string type: object description: OK headers: {} tags: - Domain admin description: Using this endpoint a existing Domain Admin user can be deleted. operationId: Delete Domain Admin requestBody: content: application/json: schema: example: - testadmin properties: items: description: contains list of usernames of the users you want to delete type: object type: object summary: Delete Domain Admin /api/v1/delete/domain-policy: post: responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - log: - policy - delete - domain - prefid: - "1" - "2" msg: - item_deleted - "1" type: success - log: - policy - delete - domain - prefid: - "1" - "2" msg: - item_deleted - "2" type: success schema: properties: log: description: contains request object items: {} type: array msg: items: {} type: array type: enum: - success - danger - error type: string type: object description: OK headers: {} tags: - Domain antispam policies description: You can delete one o more domain policies. operationId: Delete domain policy requestBody: content: application/json: schema: example: - "1" - "2" properties: items: description: contains list of domain policys you want to delete type: object type: object summary: Delete domain policy /api/v1/delete/fwdhost: post: responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - log: - fwdhost - delete - forwardinghost: - 5.1.76.202 - "2a00:f820:417::202" msg: - forwarding_host_removed - 5.1.76.202 type: success - log: - fwdhost - delete - forwardinghost: - 5.1.76.202 - "2a00:f820:417::202" msg: - forwarding_host_removed - "2a00:f820:417::202" type: success schema: properties: log: description: contains request object items: {} type: array msg: items: {} type: array type: enum: - success - danger - error type: string type: object description: OK headers: {} tags: - Fordwarding Hosts description: >- Using this endpoint you can delete a forwarding host, in order to do so you need to know the IP of the host. operationId: Delete Forward Host requestBody: content: application/json: schema: example: - 5.1.76.202 - "2a00:f820:417::202" properties: ip: description: contains the ip of the fowarding host you want to delete type: string type: object summary: Delete Forward Host /api/v1/delete/mailbox: post: responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - log: - mailbox - delete - mailbox - username: - info@domain.tld - sales@domain.tld - null msg: - mailbox_removed - info@domain.tld type: success - log: - mailbox - delete - mailbox - username: - info@domain.tld - sales@domain.tld - null msg: - mailbox_removed - sales@domain.tld type: success schema: properties: log: description: contains request object items: {} type: array msg: items: {} type: array type: enum: - success - danger - error type: string type: object description: OK headers: {} tags: - Mailboxes description: You can delete one or more mailboxes. operationId: Delete mailbox requestBody: content: application/json: schema: example: - info@domain.tld - sales@domain.tld properties: items: description: contains list of mailboxes you want to delete type: object type: object summary: Delete mailbox /api/v1/delete/mailq: post: responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: msg: Task completed type: success description: OK headers: {} tags: - Queue Manager description: >- Using this API you can delete the current mail queue. This will delete all mails in it. This API uses the command: `postsuper -d` operationId: Delete Queue requestBody: content: application/json: schema: example: action: super_delete properties: action: description: use super_delete to delete the mail queue type: string type: object summary: Delete Queue /api/v1/delete/oauth2-client: post: responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - log: - oauth2 - delete - client - id: - "1" msg: - items_deleted - "1" type: success schema: properties: log: description: contains request object items: {} type: array msg: items: {} type: array type: enum: - success - danger - error type: string type: object description: OK headers: {} tags: - oAuth Clients description: >- Using this endpoint you can delete a oAuth client, for this you have to know its ID. You can get the ID using the GET method. operationId: Delete oAuth Client requestBody: content: application/json: schema: example: - "3" properties: items: description: contains list of oAuth clients you want to delete type: object type: object summary: Delete oAuth Client /api/v1/delete/qitem: post: responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - log: - quarantine - delete - id: - "33" msg: - item_deleted - "33" type: success schema: properties: log: description: contains request object items: {} type: array msg: items: {} type: array type: enum: - success - danger - error type: string type: object description: OK headers: {} tags: - Quarantine description: >- Using this endpoint you can delete a email from quarantine, for this you have to know its ID. You can get the ID using the GET method. operationId: Delete mails in Quarantine requestBody: content: application/json: schema: example: - "33" properties: items: description: contains list of emails you want to delete type: object type: object summary: Delete mails in Quarantine /api/v1/edit/qitem: post: responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: release: value: - log: - quarantine - edit - id: - "33" action: release msg: - item_released - "33" type: success learnham: value: - log: - quarantine - edit - id: - "34" action: learnham msg: - item_learned - "34" type: success schema: properties: log: description: contains request object items: {} type: array msg: items: {} type: array type: enum: - success - danger - error type: string type: object description: OK headers: {} tags: - Quarantine description: >- Using this endpoint you can perform actions on quarantine items. It is possible to release emails from quarantine into to the inbox, or learn them as ham to improve Rspamd filtering. You must provide the quarantine item IDs. You can get the IDs using the GET method. operationId: Edit mails in Quarantine requestBody: content: application/json: schema: example: items: - "33" - "34" attr: action: release properties: items: description: contains list of quarantine item IDs to release or learn as ham type: object attr: description: attributes for the action type: object properties: action: type: string enum: - release - learnham description: "release - return email to inbox; learnham - learn as ham to improve filtering" type: object summary: Edit mails in Quarantine /api/v1/delete/recipient_map: post: responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - log: - recipient_map - delete - id: - "1" - null msg: - recipient_map_entry_deleted - "1" type: success schema: properties: log: description: contains request object items: {} type: array msg: items: {} type: array type: enum: - success - danger - error type: string type: object description: OK headers: {} tags: - Address Rewriting description: >- Using this endpoint you can delete a recipient map, for this you have to know its ID. You can get the ID using the GET method. operationId: Delete Recipient Map requestBody: content: application/json: schema: example: - "1" properties: items: description: contains list of recipient maps you want to delete type: object type: object summary: Delete Recipient Map /api/v1/delete/relayhost: post: responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - log: - relayhost - delete - id: - "1" msg: - relayhost_removed - "1" type: success schema: properties: log: description: contains request object items: {} type: array msg: items: {} type: array type: enum: - success - danger - error type: string type: object description: OK headers: {} tags: - Routing description: >- Using this endpoint you can delete a Sender-Dependent Transport, for this you have to know its ID. You can get the ID using the GET method. operationId: Delete Sender-Dependent Transports requestBody: content: application/json: schema: example: - "1" properties: items: description: >- contains list of Sender-Dependent Transport you want to delete type: object type: object summary: Delete Sender-Dependent Transports /api/v1/delete/resource: post: responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - log: - mailbox - delete - resource - name: - test@mailcow.tld - null msg: - resource_removed - test@mailcow.tld type: success schema: properties: log: description: contains request object items: {} type: array msg: items: {} type: array type: enum: - success - danger - error type: string type: object description: OK headers: {} tags: - Resources description: >- Using this endpoint you can delete a Resources, for this you have to know its ID. You can get the ID using the GET method. operationId: Delete Resources requestBody: content: application/json: schema: example: - test@mailcow.tld properties: items: description: contains list of Resources you want to delete type: object type: object summary: Delete Resources /api/v1/delete/syncjob: post: responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: log: - entity - action - object msg: - message - entity name type: success schema: properties: log: description: contains request object items: {} type: array msg: items: {} type: array type: enum: - success - danger - error type: string type: object description: OK headers: {} tags: - Sync jobs description: You can delete one or more sync jobs. operationId: Delete sync job requestBody: content: application/json: schema: example: - "6" - "9" properties: items: description: contains list of aliases you want to delete type: object type: object summary: Delete sync job /api/v1/delete/tls-policy-map: post: responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - log: - tls_policy_maps - delete - id: - "1" - null msg: - tls_policy_map_entry_deleted - "1" type: success schema: properties: log: description: contains request object items: {} type: array msg: items: {} type: array type: enum: - success - danger - error type: string type: object description: OK headers: {} tags: - Outgoing TLS Policy Map Overrides description: >- Using this endpoint you can delete a TLS Policy Map, for this you have to know its ID. You can get the ID using the GET method. operationId: Delete TLS Policy Map requestBody: content: application/json: schema: example: - "3" properties: items: description: contains list of tls policy maps you want to delete type: object type: object summary: Delete TLS Policy Map /api/v1/delete/transport: post: responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - log: - transport - delete - id: - "1" msg: - relayhost_removed - "1" type: success schema: properties: log: description: contains request object items: {} type: array msg: items: {} type: array type: enum: - success - danger - error type: string type: object description: OK headers: {} tags: - Routing description: >- Using this endpoint you can delete a Transport Maps, for this you have to know its ID. You can get the ID using the GET method. operationId: Delete Transport Maps requestBody: content: application/json: schema: example: - "1" properties: items: description: contains list of transport maps you want to delete type: object type: object summary: Delete Transport Maps "/api/v1/delete/mailbox/tag/{mailbox}": post: parameters: - description: name of mailbox in: path name: mailbox example: info@domain.tld required: true schema: type: string responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - log: - mailbox - delete - tags_mailbox - tags: - tag1 - tag2 mailbox: info@domain.tld - null msg: - mailbox_modified - info@domain.tld type: success schema: properties: log: description: contains request object items: {} type: array msg: items: {} type: array type: enum: - success - danger - error type: string type: object description: OK headers: {} tags: - Mailboxes description: You can delete one or more mailbox tags. operationId: Delete mailbox tags requestBody: content: application/json: schema: example: - tag1 - tag2 properties: items: description: contains list of mailboxes you want to delete type: object type: object summary: Delete mailbox tags "/api/v1/delete/domain/tag/{domain}": post: parameters: - description: name of domain in: path name: domain example: domain.tld required: true schema: type: string responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - log: - mailbox - delete - tags_domain - tags: - tag1 - tag2 domain: domain.tld - null msg: - domain_modified - domain.tld type: success schema: properties: log: description: contains request object items: {} type: array msg: items: {} type: array type: enum: - success - danger - error type: string type: object description: OK headers: {} tags: - Domains description: You can delete one or more domain tags. operationId: Delete domain tags requestBody: content: application/json: schema: example: - tag1 - tag2 properties: items: description: contains list of domains you want to delete type: object type: object summary: Delete domain tags /api/v1/edit/alias: post: responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - log: - mailbox - edit - alias - active: "1" address: alias@domain.tld goto: destination@domain.tld id: - "6" private_comment: private comment public_comment: public comment - null msg: - alias_modified - alias@domain.tld type: success schema: properties: log: description: contains request object items: {} type: array msg: items: {} type: array type: enum: - success - danger - error type: string type: object description: OK headers: {} tags: - Aliases description: >- You can update one or more aliases per request. You can also send just attributes you want to change operationId: Update alias requestBody: content: application/json: schema: example: attr: active: "1" address: alias@domain.tld goto: destination@domain.tld private_comment: private comment public_comment: public comment items: ["6"] properties: attr: properties: active: description: is alias active or not type: boolean address: description: 'alias address, for catchall use "@domain.tld"' type: string goto: description: "destination address, comma separated" type: string goto_ham: description: learn as ham type: boolean goto_null: description: silently ignore type: boolean goto_spam: description: learn as spam type: boolean private_comment: type: string public_comment: type: string sogo_visible: description: toggle visibility as selectable sender in SOGo type: boolean type: object items: description: contains list of aliases you want update type: object type: object summary: Update alias /api/v1/edit/domain: post: responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: schema: type: array items: type: object properties: log: type: array description: contains request object items: {} msg: type: array items: {} type: enum: - success - danger - error type: string description: OK headers: {} tags: - Domains description: >- You can update one or more domains per request. You can also send just attributes you want to change. Example: You can add domain names to items list and in attr object just include `"active": "0"` to deactivate domains. operationId: Update domain requestBody: content: application/json: schema: example: attr: active: "1" aliases: "400" backupmx: "1" defquota: "3072" description: domain description gal: "1" mailboxes: "10" maxquota: "10240" quota: "10240" relay_all_recipients: "0" relayhost: "2" tags: ["tag3", "tag4"] items: domain.tld properties: attr: properties: active: description: is domain active or not type: boolean aliases: description: limit count of aliases associated with this domain type: number backupmx: description: relay domain or not type: boolean defquota: description: predefined mailbox quota in `add mailbox` form type: number description: description: Description of domain type: string gal: description: >- is domain global address list active or not, it enables shared contacts accross domain in SOGo webmail type: boolean mailboxes: description: limit count of mailboxes associated with this domain type: number maxquota: description: maximum quota per mailbox type: number quota: description: maximum quota for this domain (for all mailboxes in sum) type: number relay_all_recipients: description: >- if not, them you have to create "dummy" mailbox for each address to relay type: boolean relay_unknown_only: description: Relay non-existing mailboxes only. Existing mailboxes will be delivered locally. type: boolean relayhost: description: id of relayhost type: number rl_frame: enum: - s - m - h - d type: string rl_value: description: rate limit value type: number tags: description: tags for this Domain type: array items: type: string type: object items: description: contains list of domain names you want update type: array items: type: string type: object summary: Update domain /api/v1/edit/domain/footer: post: responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - log: - mailbox - edit - domain_wide_footer - domains: - mailcow.tld html: "
foo {= foo =}" plain: "- You can update the footer of one or more domains per request. operationId: Update domain wide footer requestBody: content: application/json: schema: example: attr: html: "
foo {= foo =}" plain: "foo {= foo =}" mbox_exclude: - moo@mailcow.tld items: mailcow.tld properties: attr: properties: html: description: Footer text in HTML format type: string plain: description: Footer text in PLAIN text format type: string mbox_exclude: description: Array of mailboxes to exclude from domain wide footer type: object type: object items: description: contains a list of domain names where you want to update the footer type: array items: type: string type: object summary: Update domain wide footer /api/v1/edit/fail2ban: post: responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: "*/*": schema: properties: log: description: contains request object items: {} type: array msg: items: {} type: array type: enum: - success - danger - error type: string type: object description: OK headers: {} tags: - Fail2Ban description: >- Using this endpoint you can edit the Fail2Ban config and black or whitelist new ips. operationId: Edit Fail2Ban requestBody: content: application/json: schema: example: attr: ban_time: "86400" ban_time_increment: "1" blacklist: "10.100.6.5/32,10.100.8.4/32" max_attempts: "5" max_ban_time: "86400" netban_ipv4: "24" netban_ipv6: "64" retry_window: "600" whitelist: mailcow.tld items: none properties: attr: description: array containing the fail2ban settings properties: backlist: description: the backlisted ips or hostnames separated by comma type: string ban_time: description: the time an ip should be banned type: number ban_time_increment: description: if the time of the ban should increase each time type: boolean max_attempts: description: the maximum numbe of wrong logins before a ip is banned type: number max_ban_time: description: the maximum time an ip should be banned type: number netban_ipv4: description: the networks mask to ban for ipv4 type: number netban_ipv6: description: the networks mask to ban for ipv6 type: number retry_window: description: >- the maximum time in which a ip as to login with false credentials to be banned type: number whitelist: description: whitelisted ips or hostnames sepereated by comma type: string type: object items: description: has to be none type: object summary: Edit Fail2Ban /api/v1/edit/mailbox: post: responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - log: - mailbox - edit - mailbox - active: "1" force_pw_update: "0" name: Full name password: "*" password2: "*" quota: "3072" sender_acl: - default - info@domain2.tld - domain3.tld - "*" sogo_access: "1" username: - info@domain.tld tags: ["tag3", "tag4"] - null msg: - mailbox_modified - info@domain.tld type: success schema: properties: log: description: contains request object items: {} type: array msg: items: {} type: array type: enum: - success - danger - error type: string type: object description: OK headers: {} tags: - Mailboxes description: >- You can update one or more mailboxes per request. You can also send just attributes you want to change operationId: Update mailbox requestBody: content: application/json: schema: example: attr: active: "1" force_pw_update: "0" name: Full name authsource: mailcow password: "" password2: "" quota: "3072" sender_acl: - default - info@domain2.tld - domain3.tld - "*" sogo_access: "1" tags: ["tag3", "tag4"] items: - info@domain.tld properties: attr: properties: active: description: is mailbox active or not type: boolean force_pw_update: description: force user to change password on next login type: boolean name: description: Full name of the mailbox user type: string authsource: description: Specifies the authentication source for the mailbox. type: string enum: [mailcow, ldap, keycloak, generic-oidc] password2: description: new mailbox password for confirmation type: string password: description: new mailbox password when using `mailcow` as the authentication source. type: string quota: description: mailbox quota type: number sender_acl: description: list of allowed send from addresses type: object sogo_access: description: is access to SOGo webmail active or not type: boolean type: object items: description: contains list of mailboxes you want update type: object type: object summary: Update mailbox /api/v1/edit/mailbox/custom-attribute: post: responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - log: - mailbox - edit - mailbox_custom_attribute - mailboxes: - moo@mailcow.tld attribute: - role - foo value: - cow - bar - null msg: - mailbox_modified - moo@mailcow.tld type: success schema: properties: log: description: contains request object items: {} type: array msg: items: {} type: array type: enum: - success - danger - error type: string type: object description: OK headers: {} tags: - Mailboxes description: >- You can update custom attributes of one or more mailboxes per request. operationId: Update mailbox custom attributes requestBody: content: application/json: schema: example: attr: attribute: - role - foo value: - cow - bar items: - moo@mailcow.tld properties: attr: properties: attribute: description: Array of attribute keys type: object value: description: Array of attribute values type: object type: object items: description: contains list of mailboxes you want update type: object type: object summary: Update mailbox custom attributes /api/v1/edit/mailq: post: responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: msg: Task completed type: success description: OK headers: {} tags: - Queue Manager description: >- Using this API you can flush the current mail queue. This will try to deliver all mails currently in it. This API uses the command: `postqueue -f` operationId: Flush Queue requestBody: content: application/json: schema: example: action: flush properties: action: description: use flush to flush the mail queue type: string type: object summary: Flush Queue /api/v1/edit/pushover: post: responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - log: - pushover - edit - active: "0" evaluate_x_prio: "0" key: 21e8918e1jksdjcpis712 only_x_prio: "0" sound: "pushover" senders: "" senders_regex: "" text: "" title: Mail token: 9023e2ohcwed27d1idu2 username: - info@domain.tld msg: pushover_settings_edited type: success schema: properties: log: description: contains request object items: {} type: array msg: items: {} type: array type: enum: - success - danger - error type: string type: object description: OK headers: {} tags: - Mailboxes description: >- Using this endpoint it is possible to update the pushover settings for mailboxes operationId: Update Pushover settings requestBody: content: application/json: schema: example: attr: active: "0" evaluate_x_prio: "0" key: 21e8918e1jksdjcpis712 only_x_prio: "0" sound: "pushover" senders: "" senders_regex: "" text: "" title: Mail token: 9023e2ohcwed27d1idu2 items: info@domain.tld properties: attr: properties: active: description: Enables pushover 1 disable pushover 0 type: number evaluate_x_prio: description: Send the Push with High priority type: number key: description: Pushover key type: string only_x_prio: description: Only send push for prio mails type: number sound: description: Set notification sound type: string senders: description: Only send push for emails from these senders type: string senders_regex: description: Regex to match senders for which a push will be send type: string text: description: Custom push noficiation text type: string title: description: Push title type: string token: description: Pushover token type: string type: object items: description: contains list of mailboxes you want to delete type: object type: object summary: Update Pushover settings /api/v1/edit/quarantine_notification: post: responses: "401": $ref: "#/components/responses/Unauthorized" "200": description: OK headers: {} tags: - Mailboxes description: You can update one or more mailboxes per request. operationId: Quarantine Notifications requestBody: content: application/json: schema: example: attr: quarantine_notification: hourly items: anyOf: - mailbox1@domain.tld - mailbox2@domain.tld properties: attr: properties: quarantine_notification: description: recurrence enum: - hourly - daily - weekly - never type: string type: object items: description: >- contains list of mailboxes you want set qurantine notifications type: object type: object summary: Quarantine Notifications /api/v1/edit/syncjob: post: responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: log: - entity - action - object msg: - message - entity name type: success schema: properties: log: description: contains request object items: {} type: array msg: items: {} type: array type: enum: - success - danger - error type: string type: object description: OK headers: {} tags: - Sync jobs description: >- You can update one or more sync jobs per request. You can also send just attributes you want to change. operationId: Update sync job requestBody: content: application/json: schema: example: attr: active: "1" automap: "1" custom_params: "" delete1: "0" delete2: "0" delete2duplicates: "1" enc1: SSL exclude: (?i)spam|(?i)junk host1: imap.server.tld maxage: "0" maxbytespersecond: "0" mins_interval: "20" password1: supersecret port1: "993" skipcrossduplicates: "0" subfolder2: External subscribeall: "1" timeout1: "600" timeout2: "600" user1: username items: "1" properties: attr: properties: active: description: Is sync job active type: boolean automap: description: >- Try to automap folders ("Sent items", "Sent" => "Sent" etc.) type: boolean custom_params: description: Custom parameters passed to imapsync command type: string delete1: description: Delete from source when completed type: boolean delete2: description: Delete messages on destination that are not on source type: boolean delete2duplicates: description: Delete duplicates on destination type: boolean enc1: description: Encryption enum: - TLS - SSL - PLAIN type: string exclude: description: Exclude objects (regex) type: string host1: description: Hostname type: string maxage: description: >- Maximum age of messages in days that will be polled from remote (0 = ignore age) type: number maxbytespersecond: description: Max. bytes per second (0 = unlimited) type: number mins_interval: description: Interval (min) type: number password1: description: Password type: string port1: description: Port type: string skipcrossduplicates: description: >- Skip duplicate messages across folders (first come, first serve) type: boolean subfolder2: description: >- Sync into subfolder on destination (empty = do not use subfolder) type: string subscribeall: description: Subscribe all folders type: boolean timeout1: description: Timeout for connection to remote host type: number timeout2: description: Timeout for connection to local host type: number user1: description: Username type: string type: object items: description: contains list of aliases you want update type: object type: object summary: Update sync job /api/v1/edit/user-acl: post: responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - log: - acl - edit - user - user_acl: - spam_alias - tls_policy - spam_score - spam_policy - delimiter_action - syncjobs - eas_reset - quarantine - sogo_profile_reset - quarantine_attachments - quarantine_notification - app_passwds - pushover username: - info@domain.tld msg: - acl_saved - info@domain.tld type: success schema: properties: log: description: contains request object items: {} type: array msg: items: {} type: array type: enum: - success - danger - error type: string type: object description: OK headers: {} tags: - Mailboxes description: Using this endpoints its possible to update the ACL's for mailboxes operationId: Update mailbox ACL requestBody: content: application/json: schema: example: attr: user_acl: - spam_alias - tls_policy - spam_score - spam_policy - delimiter_action - syncjobs - eas_reset - quarantine - sogo_profile_reset - quarantine_attachments - quarantine_notification - app_passwds - pushover items: info@domain.tld properties: attr: properties: user_acl: description: contains a list of active user acls type: object type: object items: description: contains list of mailboxes you want to delete type: object type: object summary: Update mailbox ACL "/api/v1/get/alias/{id}": get: parameters: - description: id of entry you want to get example: all in: path name: id required: true schema: enum: - all - "1" - "2" - "5" - "10" type: string - description: e.g. api-key-string example: api-key-string in: header name: X-API-Key required: false schema: type: string responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - active: "1" address: alias@domain.tld created: "2019-04-04 19:29:49" domain: domain.tld goto: destination@domain.tld id: 6 in_primary_domain: "" is_catch_all: 0 modified: null private_comment: null public_comment: null - active: "1" address: "@domain.tld" created: "2019-04-27 13:42:39" domain: domain.tld goto: destination@domain.tld id: 10 in_primary_domain: "" is_catch_all: 1 modified: null private_comment: null public_comment: null description: OK headers: {} tags: - Aliases description: You can list mailbox aliases existing in system. operationId: Get aliases summary: Get aliases "/api/v1/get/time_limited_aliases/{mailbox}": get: parameters: - description: mailbox you want to get aliasses from example: domain.tld in: path schema: type: string name: mailbox required: true - description: e.g. api-key-string example: api-key-string in: header name: X-API-Key required: false schema: type: string responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - address: alias@domain.tld goto: destination@domain.tld validity: 1668251246 created: "2021-11-12 12:07:26" modified: null description: OK headers: {} tags: - Aliases description: You can list time limited mailbox aliases existing in system. operationId: Get time limited aliases summary: Get time limited aliases "/api/v1/get/app-passwd/all/{mailbox}": get: parameters: - description: mailbox of entry you want to get example: hello@mailcow.email in: path name: mailbox required: true schema: enum: - hello@mailcow.email type: string - description: e.g. api-key-string example: api-key-string in: header name: X-API-Key required: false schema: type: string responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - active: "1" created: "2019-12-21 16:04:55" domain: mailcow.email id: 2 mailbox: hello@mailcow.email modified: null name: emclient description: OK headers: {} tags: - App Passwords description: >- Using this endpoint you can get all app passwords from a specific mailbox. operationId: Get App Password summary: Get App Password "/api/v1/get/bcc/{id}": get: parameters: - description: id of entry you want to get example: all in: path name: id required: true schema: enum: - all - "1" - "2" - "5" - "10" type: string - description: e.g. api-key-string example: api-key-string in: header name: X-API-Key required: false schema: type: string responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - active: "1" bcc_dest: bcc@awesomecow.tld created: "2019-10-02 21:44:34" domain: mailcow.tld id: 3 local_dest: "@mailcow.tld" modified: null type: sender description: OK headers: {} tags: - Address Rewriting description: Using this endpoint you can get all BCC maps. operationId: Get BCC Map summary: Get BCC Map "/api/v1/get/dkim/{domain}": get: parameters: - description: name of domain in: path name: domain required: true schema: type: string - description: e.g. api-key-string example: api-key-string in: header name: X-API-Key required: false schema: type: string responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: dkim_selector: dkim dkim_txt: >- v=DKIM1;k=rsa;t=s;s=email;p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA21tUSjyasQy/hJmVjPnlRGfzx6TPhYj8mXY9DVOzSAE64Gddw/GnE/GcCR6WXNT23u9q4zPnz1IPoNt5kFOps8vg/iNqrcH++494noaZuYyFPPFnebkfryO4EvEyxC/c66qts+gnOUml+M8uv5WObBJld2gG12jLwFM0263J/N6J8LuUsaXOB2uCIfx8Nf4zjuJ6Ieez2uyHNK5dXjDLfKA4mTr+EEK6W6e34M4KN1liWM6r9Oy5S1FlLrD42VpURxxBZtBiEtaJPEKSQuk6GQz8ihu7W20Yr53tyCdaORu8dhxXVUWVf+GjuuMEdAmQCjYkarXdYCrt56Psw703kwIDAQAB length: "2048" privkey: "" pubkey: >- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA21tUSjyasQy/hJmVjPnlRGfzx6TPhYj8mXY9DVOzSAE64Gddw/GnE/GcCR6WXNT23u9q4zPnz1IPoNt5kFOps8vg/iNqrcH++494noaZuYyFPPFnebkfryO4EvEyxC/c66qts+gnOUml+M8uv5WObBJld2gG12jLwFM0263J/N6J8LuUsaXOB2uCIfx8Nf4zjuJ6Ieez2uyHNK5dXjDLfKA4mTr+EEK6W6e34M4KN1liWM6r9Oy5S1FlLrD42VpURxxBZtBiEtaJPEKSQuk6GQz8ihu7W20Yr53tyCdaORu8dhxXVUWVf+GjuuMEdAmQCjYkarXdYCrt56Psw703kwIDAQAB description: OK headers: {} tags: - DKIM description: >- Using this endpoint you can get the DKIM public key for a specific domain. operationId: Get DKIM Key summary: Get DKIM Key /api/v1/get/domain-admin/all: get: responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - active: "1" created: "2019-10-02 10:29:41" selected_domains: - mailcow.tld tfa_active: "0" unselected_domains: - awesomemailcow.de - mailcowisgreat.de username: testadmin description: OK headers: {} tags: - Domain admin description: "" operationId: Get Domain Admins summary: Get Domain Admins "/api/v1/get/domain/{id}": get: parameters: - description: id of entry you want to get example: all in: path name: id required: true schema: enum: - all - mailcow.tld type: string - description: comma seperated list of tags to filter by example: "tag1,tag2" in: query name: tags required: false schema: type: string - description: e.g. api-key-string example: api-key-string in: header name: X-API-Key required: false schema: type: string responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - active: "1" aliases_in_domain: 0 aliases_left: 400 backupmx: "0" bytes_total: "5076666944" def_new_mailbox_quota: 3221225472 def_quota_for_mbox: 3221225472 description: Some description domain_name: domain.tld gal: "0" max_new_mailbox_quota: 10737418240 max_num_aliases_for_domain: 400 max_num_mboxes_for_domain: 10 max_quota_for_domain: 10737418240 max_quota_for_mbox: 10737418240 mboxes_in_domain: 0 mboxes_left: 10 msgs_total: "172440" quota_used_in_domain: "0" relay_all_recipients: "0" relayhost: "0" rl: false tags: ["tag1", "tag2"] - active: "1" aliases_in_domain: 0 aliases_left: 400 backupmx: "1" bytes_total: "5076666944" def_new_mailbox_quota: 3221225472 def_quota_for_mbox: 3221225472 description: domain description domain_name: domain2.tld gal: "0" max_new_mailbox_quota: 10737418240 max_num_aliases_for_domain: 400 max_num_mboxes_for_domain: 10 max_quota_for_domain: 10737418240 max_quota_for_mbox: 10737418240 mboxes_in_domain: 0 mboxes_left: 10 msgs_total: "172440" quota_used_in_domain: "0" relay_all_recipients: "0" relayhost: "0" rl: false tags: ["tag3", "tag4"] description: OK headers: {} tags: - Domains description: You can list all domains existing in system. operationId: Get domains summary: Get domains /api/v1/get/fail2ban: get: responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: ban_time: 604800 ban_time_increment: 1 blacklist: |- 45.82.153.37/32 92.118.38.52/32 max_attempts: 1 max_ban_time: 604800 netban_ipv4: 32 netban_ipv6: 128 perm_bans: - 45.82.153.37/32 - 92.118.38.52/32 retry_window: 7200 whitelist: 1.1.1.1 description: OK headers: {} tags: - Fail2Ban description: Gets the current Fail2Ban configuration. operationId: Get Fail2Ban Config summary: Get Fail2Ban Config /api/v1/get/fwdhost/all: get: responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - host: 5.1.76.202 keep_spam: "yes" source: hosted.mailcow.de - host: "2a00:f820:417::202" keep_spam: "yes" source: hosted.mailcow.de description: OK headers: {} tags: - Fordwarding Hosts description: You can list all Forwarding Hosts in your mailcow. operationId: Get Forwarding Hosts summary: Get Forwarding Hosts "/api/v1/get/logs/acme/{count}": get: parameters: - description: Number of logs to return in: path name: count required: true schema: type: number - description: e.g. api-key-string example: api-key-string in: header name: X-API-Key required: false schema: type: string responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - message: >- Certificate validation done, neither changed nor due for renewal, sleeping for another day. time: "1569927728" description: OK headers: {} tags: - Logs description: >- This Api endpoint lists all ACME logs from issued Lets Enctypts certificates. Tip: You can limit how many logs you want to get by using `/` at the end of the api url. operationId: Get ACME logs summary: Get ACME logs "/api/v1/get/logs/api/{count}": get: parameters: - description: Number of logs to return in: path name: count required: true schema: type: number - description: e.g. api-key-string example: api-key-string in: header name: X-API-Key required: false schema: type: string responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - data: "" method: GET remote: 1.1.1.1 time: 1569939001 uri: /api/v1/get/logs/api/2 description: OK headers: {} tags: - Logs description: >- This Api endpoint lists all Api logs. Tip: You can limit how many logs you want to get by using `/` at the end of the api url. operationId: Get Api logs summary: Get Api logs "/api/v1/get/logs/autodiscover/{count}": get: parameters: - description: Number of logs to return in: path name: count required: true schema: type: number - description: e.g. api-key-string example: api-key-string in: header name: X-API-Key required: false schema: type: string responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - service: activesync time: 1569684212 ua: >- Microsoft Office/16.0 (Windows NT 6.2; MAPICPL 16.0.11328; Pro) user: awesome@mailcow.de description: OK headers: {} tags: - Logs description: >- This Api endpoint lists all Autodiscover logs. Tip: You can limit how many logs you want to get by using `/` at the end of the api url. operationId: Get Autodiscover logs summary: Get Autodiscover logs "/api/v1/get/logs/dovecot/{count}": get: parameters: - description: Number of logs to return in: path name: count required: true schema: type: number - description: e.g. api-key-string example: api-key-string in: header name: X-API-Key required: false schema: type: string responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - message: >- managesieve-login: Disconnected (no auth attempts in 0 secs): user=<>, rip=172.22.1.3, lip=172.22.1.250 priority: info program: dovecot time: "1569938740" description: OK headers: {} tags: - Logs description: >- This Api endpoint lists all Dovecot logs. Tip: You can limit how many logs you want to get by using `/` at the end of the api url. operationId: Get Dovecot logs summary: Get Dovecot logs "/api/v1/get/logs/netfilter/{count}": get: parameters: - description: Number of logs to return in: path name: count required: true schema: type: number - description: e.g. api-key-string example: api-key-string in: header name: X-API-Key required: false schema: type: string responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - message: "Whitelist was changed, it has 1 entries" priority: info time: 1569754911 - message: Add host/network 1.1.1.1/32 to blacklist priority: crit time: 1569754911 description: OK headers: {} tags: - Logs description: >- This Api endpoint lists all Netfilter logs. Tip: You can limit how many logs you want to get by using `/` at the end of the api url. operationId: Get Netfilter logs summary: Get Netfilter logs "/api/v1/get/logs/postfix/{count}": get: parameters: - description: Number of logs to return in: path name: count required: true schema: type: number - description: e.g. api-key-string example: api-key-string in: header name: X-API-Key required: false schema: type: string responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - message: "EF1711500458: removed" priority: info program: postfix/qmgr time: "1569937433" description: OK headers: {} tags: - Logs description: >- This Api endpoint lists all Postfix logs. Tip: You can limit how many logs you want to get by using `/` at the end of the api url. operationId: Get Postfix logs summary: Get Postfix logs "/api/v1/get/logs/ratelimited/{count}": get: parameters: - description: Number of logs to return in: path name: count required: true schema: type: number - description: e.g. api-key-string example: api-key-string in: header name: X-API-Key required: false schema: type: string responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - from: awesome@mailcow.email header_from: '"Awesome" ' header_subject: Mailcow is amazing ip: 172.22.1.248 message_id: 6a-5d892500-7-240abd80@90879116 qid: E3CF91500458 rcpt: hello@mailcow.email rl_hash: RLsdz3tuabozgd4oacbdh8kc78 rl_info: mailcow(RLsdz3tuabozgd4oacbdh8kc78) rl_name: mailcow time: 1569269003 user: awesome@mailcow.email description: OK headers: {} tags: - Logs description: >- This Api endpoint lists all Ratelimit logs. Tip: You can limit how many logs you want to get by using `/` at the end of the api url. operationId: Get Ratelimit logs summary: Get Ratelimit logs "/api/v1/get/logs/rspamd-history/{count}": get: parameters: - description: Number of logs to return in: path name: count required: true schema: type: number - description: e.g. api-key-string example: api-key-string in: header name: X-API-Key required: false schema: type: string responses: "401": $ref: "#/components/responses/Unauthorized" "200": description: OK headers: {} tags: - Logs description: >- This Api endpoint lists all Rspamd logs. Tip: You can limit how many logs you want to get by using `/` at the end of the api url. operationId: Get Rspamd logs summary: Get Rspamd logs "/api/v1/get/logs/sogo/{count}": get: parameters: - description: Number of logs to return in: path name: count required: true schema: type: number - description: e.g. api-key-string example: api-key-string in: header name: X-API-Key required: false schema: type: string responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - message: >- [109]: mailcowdockerized_watchdog-mailcow_1.mailcowdockerized_mailcow-network "GET /SOGo.index/ HTTP/1.1" 200 2531/0 0.005 - - 0 priority: notice program: sogod time: "1569938874" description: OK headers: {} tags: - Logs description: >- This Api endpoint lists all SOGo logs. Tip: You can limit how many logs you want to get by using `/` at the end of the api url. operationId: Get SOGo logs summary: Get SOGo logs "/api/v1/get/logs/watchdog/{count}": get: parameters: - description: Number of logs to return in: path name: count required: true schema: type: number - description: e.g. api-key-string example: api-key-string in: header name: X-API-Key required: false schema: type: string responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - hpdiff: "0" hpnow: "1" hptotal: "1" lvl: "100" service: Fail2ban time: "1569938958" - hpdiff: "0" hpnow: "5" hptotal: "5" lvl: "100" service: Rspamd time: "1569938956" description: OK headers: {} tags: - Logs description: >- This Api endpoint lists all Watchdog logs. Tip: You can limit how many logs you want to get by using `/` at the end of the api url. operationId: Get Watchdog logs summary: Get Watchdog logs "/api/v1/get/mailbox/{id}": get: parameters: - description: id of entry you want to get example: all in: path name: id required: true schema: enum: - all - user@domain.tld type: string - description: comma seperated list of tags to filter by example: "tag1,tag2" in: query name: tags required: false schema: type: string - description: e.g. api-key-string example: api-key-string in: header name: X-API-Key required: false schema: type: string responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - active: "1" attributes: force_pw_update: "0" mailbox_format: "maildir:" quarantine_notification: never sogo_access: "1" tls_enforce_in: "0" tls_enforce_out: "0" domain: doman3.tld is_relayed: 0 local_part: info max_new_quota: 10737418240 messages: 0 name: Full name percent_class: success percent_in_use: 0 quota: 3221225472 quota_used: 0 rl: false spam_aliases: 0 username: info@doman3.tld tags: ["tag1", "tag2"] description: OK headers: {} tags: - Mailboxes description: You can list all mailboxes existing in system. operationId: Get mailboxes summary: Get mailboxes /api/v1/get/mailq/all: get: responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - arrival_time: 1570091234 message_size: 1848 queue_id: B98C6260CA1 queue_name: incoming recipients: - recipient@awesomecow.tld sender: sender@mailcow.tld description: OK headers: {} tags: - Queue Manager description: Get the current mail queue and everything it contains. operationId: Get Queue summary: Get Queue "/api/v1/get/oauth2-client/{id}": get: parameters: - description: id of entry you want to get example: all in: path name: id required: true schema: enum: - all - "1" - "2" - "5" - "10" type: string - description: e.g. api-key-string example: api-key-string in: header name: X-API-Key required: false schema: type: string responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - client_id: 17c76aaa88c0 client_secret: 73fc668a88147e32a31ff80c grant_types: null id: 1 redirect_uri: "https://mailcow.tld" scope: profile user_id: null description: OK headers: {} tags: - oAuth Clients description: Using this endpoint you can get all oAuth clients. operationId: Get oAuth Clients summary: Get oAuth Clients "/api/v1/get/policy_bl_domain/{domain}": get: parameters: - description: name of domain in: path name: domain required: true schema: type: string - description: e.g. api-key-string example: api-key-string in: header name: X-API-Key required: false schema: type: string responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - object: domain.tld prefid: 2 value: "*@baddomain.tld" description: OK headers: {} tags: - Domain antispam policies description: You can list all blacklist policies per domain. operationId: List blacklist domain policy summary: List blacklist domain policy "/api/v1/get/policy_wl_domain/{domain}": get: parameters: - description: name of domain in: path name: domain required: true schema: type: string - description: e.g. api-key-string example: api-key-string in: header name: X-API-Key required: false schema: type: string responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - object: domain.tld prefid: 1 value: "*@gooddomain.tld" description: OK headers: {} tags: - Domain antispam policies description: You can list all whitelist policies per domain. operationId: List whitelist domain policy summary: List whitelist domain policy /api/v1/get/quarantine/all: get: responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: created: 1572688831 id: 33 notified: 1 qid: 8224615004C1 rcpt: admin@domain.tld score: 15.48 sender: bounces@send.domain.tld subject: mailcow is awesome virus_flag: 0 description: OK headers: {} tags: - Quarantine description: Get all mails that are currently in Quarantine. operationId: Get mails in Quarantine summary: Get mails in Quarantine "/api/v1/get/recipient_map/{id}": get: parameters: - description: id of entry you want to get example: all in: path name: id required: true schema: enum: - all - "1" - "2" - "5" - "10" type: string - description: e.g. api-key-string example: api-key-string in: header name: X-API-Key required: false schema: type: string responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - active: "1" created: "2019-10-02 22:06:29" id: 3 modified: null recipient_map_new: target@mailcow.tld recipient_map_old: recipient@mailcow.tld description: OK headers: {} tags: - Address Rewriting description: Using this endpoint you can get all recipient maps. operationId: Get Recipient Map summary: Get Recipient Map "/api/v1/get/relayhost/{id}": get: parameters: - description: id of entry you want to get example: all in: path name: id required: true schema: enum: - all - "1" - "2" - "5" - "10" type: string - description: e.g. api-key-string example: api-key-string in: header name: X-API-Key required: false schema: type: string responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - active: "1" hostname: "mailcow.tld:25" id: 1 password: supersecurepassword password_short: tes... used_by_domains: "" username: testuser description: OK headers: {} tags: - Routing description: Using this endpoint you can get all Sender-Dependent Transports. operationId: Get Sender-Dependent Transports summary: Get Sender-Dependent Transports /api/v1/get/resource/all: get: responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - active: "1" description: test domain: mailcow.tld kind: location local_part: test multiple_bookings: 0 name: test@mailcow.tld description: OK headers: {} tags: - Resources description: Using this endpoint you can get all Resources. operationId: Get Resources summary: Get Resources "/api/v1/get/rl-mbox/{mailbox}": get: parameters: - description: name of mailbox or all in: path name: mailbox required: true schema: type: string - description: e.g. api-key-string example: api-key-string in: header name: X-API-Key required: false schema: type: string responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - frame: s mailbox: leon@mailcow.tld value: "5" - frame: s mailbox: lisa@mailcow.tld value: "3" description: OK headers: {} tags: - Ratelimits description: >- Using this endpoint you can get the ratelimits for a certain mailbox. You can use all for all mailboxes. operationId: Get mailbox ratelimits summary: Get mailbox ratelimits "/api/v1/get/rl-domain/{domain}": get: parameters: - description: name of domain or all in: path name: domain required: true schema: type: string - description: e.g. api-key-string example: api-key-string in: header name: X-API-Key required: false schema: type: string responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - frame: s domain: domain.tld value: "5" - frame: s mailbox: domain2.tld value: "3" description: OK headers: {} tags: - Ratelimits description: >- Using this endpoint you can get the ratelimits for a certain domains. You can use all for all domain. operationId: Get domain ratelimits summary: Get domain ratelimits /api/v1/edit/rl-mbox/: post: responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - type: success log: - ratelimit - edit - mailbox - object: - info@domain.tld rl_value: "10" rl_frame: h msg: - rl_saved - info@domain.tld schema: properties: log: description: contains request object items: {} type: array msg: items: {} type: array type: enum: - success - danger - error type: string type: object description: OK headers: {} tags: - Ratelimits description: >- Using this endpoint you can edit the ratelimits for a certain mailbox. operationId: Edit mailbox ratelimits requestBody: content: application/json: schema: example: attr: rl_value: "10" rl_frame: "h" items: - info@domain.tld properties: attr: properties: rl_frame: description: contains the frame for the ratelimit h,s,m type: string rl_value: description: contains the rate for the ratelimit 10,20,50,1 type: number type: object items: description: contains list of mailboxes you want to edit the ratelimit of type: object type: object summary: Edit mailbox ratelimits /api/v1/edit/rl-domain/: post: responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - type: success - log: - ratelimit - edit - domain - object: - domain.tld rl_value: "50" rl_frame: "h" msg: - rl_saved - domain.tld schema: properties: log: description: contains request object items: {} type: array msg: items: {} type: array type: enum: - success - danger - error type: string type: object description: OK headers: {} tags: - Ratelimits description: >- Using this endpoint you can edit the ratelimits for a certain domains. operationId: Edit domain ratelimits requestBody: content: application/json: schema: example: attr: rl_value: "10" rl_frame: "h" items: - domain.tld properties: attr: properties: rl_frame: description: contains the frame for the ratelimit h,s,m type: string rl_value: description: contains the rate for the ratelimit 10,20,50,1 type: number type: object items: description: contains list of domains you want to edit the ratelimit of type: object type: object summary: Edit domain ratelimits /api/v1/get/status/containers: get: responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: acme-mailcow: container: acme-mailcow image: "mailcow/acme:1.63" started_at: "2019-12-22T21:00:08.270660275Z" state: running type: info clamd-mailcow: container: clamd-mailcow image: "mailcow/clamd:1.35" started_at: "2019-12-22T21:00:01.622856172Z" state: running type: info dockerapi-mailcow: container: dockerapi-mailcow image: "mailcow/dockerapi:1.36" started_at: "2019-12-22T20:59:59.984797808Z" state: running type: info dovecot-mailcow: container: dovecot-mailcow image: "mailcow/dovecot:1.104" started_at: "2019-12-22T21:00:08.988680259Z" state: running type: info ipv6nat-mailcow: container: ipv6nat-mailcow image: robbertkl/ipv6nat started_at: "2019-12-22T21:06:37.273225445Z" state: running type: info memcached-mailcow: container: memcached-mailcow image: "memcached:alpine" started_at: "2019-12-22T20:59:58.0907785Z" state: running type: info mysql-mailcow: container: mysql-mailcow image: "mariadb:10.3" started_at: "2019-12-22T21:00:02.201937528Z" state: running type: info netfilter-mailcow: container: netfilter-mailcow image: "mailcow/netfilter:1.31" started_at: "2019-12-22T21:00:09.851559297Z" state: running type: info nginx-mailcow: container: nginx-mailcow image: "nginx:mainline-alpine" started_at: "2019-12-22T21:00:12.9843038Z" state: running type: info olefy-mailcow: container: olefy-mailcow image: "mailcow/olefy:1.2" started_at: "2019-12-22T20:59:59.676259274Z" state: running type: info php-fpm-mailcow: container: php-fpm-mailcow image: "mailcow/phpfpm:1.55" started_at: "2019-12-22T21:00:00.955808957Z" state: running type: info postfix-mailcow: container: postfix-mailcow image: "mailcow/postfix:1.44" started_at: "2019-12-22T21:00:07.186717617Z" state: running type: info redis-mailcow: container: redis-mailcow image: "redis:5-alpine" started_at: "2019-12-22T20:59:56.827166834Z" state: running type: info rspamd-mailcow: container: rspamd-mailcow image: "mailcow/rspamd:1.56" started_at: "2019-12-22T21:00:12.456075355Z" state: running type: info sogo-mailcow: container: sogo-mailcow image: "mailcow/sogo:1.65" started_at: "2019-12-22T20:59:58.382274592Z" state: running type: info unbound-mailcow: container: unbound-mailcow image: "mailcow/unbound:1.10" started_at: "2019-12-22T20:59:58.760595825Z" state: running type: info watchdog-mailcow: container: watchdog-mailcow image: "mailcow/watchdog:1.65" started_at: "2019-12-22T20:59:56.028660382Z" state: running type: info description: OK headers: {} tags: - Status description: >- Using this endpoint you can get the status of all containers and when hey where started and a few other details. operationId: Get container status summary: Get container status /api/v1/get/status/vmail: get: responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: disk: /dev/mapper/mail--vg-root total: 41G type: info used: 11G used_percent: 28% description: OK headers: {} tags: - Status description: >- Using this endpoint you can get the status of the vmail and the amount of used storage. operationId: Get vmail status summary: Get vmail status /api/v1/get/status/version: get: responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: version: "2022-04" description: OK headers: {} tags: - Status description: >- Using this endpoint you can get the current running release of this instance. operationId: Get version status summary: Get version status /api/v1/get/syncjobs/all/no_log: get: responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - active: "1" authmd51: 0 authmech1: PLAIN automap: 1 created: "2019-05-22 11:37:25" custom_params: "" delete1: 0 delete2: 0 delete2duplicates: 1 domain2: "" enc1: TLS exclude: (?i)spam|(?i)junk host1: imap.server.tld id: 1 is_running: 0 last_run: "2019-05-22 11:40:02" log: "" maxage: 0 maxbytespersecond: "0" mins_interval: "20" modified: "2019-05-22 11:40:02" port1: 993 regextrans2: "" skipcrossduplicates: 0 subfolder2: External subscribeall: 1 timeout1: 600 timeout2: 600 user1: username user2: mailbox@domain.tld description: OK headers: {} tags: - Sync jobs description: You can list all syn jobs existing in system. operationId: Get sync jobs summary: Get sync jobs "/api/v1/get/tls-policy-map/{id}": get: parameters: - description: id of entry you want to get example: all in: path name: id required: true schema: enum: - all - "1" - "2" - "5" - "10" type: string - description: e.g. api-key-string example: api-key-string in: header name: X-API-Key required: false schema: type: string responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - parameters: "" active: "1" created: "2019-10-03 08:42:12" dest: mailcow.tld id: 1 modified: null policy: encrypt description: OK headers: {} tags: - Outgoing TLS Policy Map Overrides description: Using this endpoint you can get all TLS policy map override maps. operationId: Get TLS Policy Map summary: Get TLS Policy Map "/api/v1/get/transport/{id}": get: parameters: - description: id of entry you want to get example: all in: path name: id required: true schema: enum: - all - "1" - "2" - "5" - "10" type: string - description: e.g. api-key-string example: api-key-string in: header name: X-API-Key required: false schema: type: string responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - active: "1" destination: example.org id: 1 lookup_mx: "0" nexthop: "host:25" password: supersecurepw password_short: sup... username: testuser description: OK headers: {} tags: - Routing description: Using this endpoint you can get all Transport Maps. operationId: Get Transport Maps summary: Get Transport Maps /api/v1/edit/spam-score/: post: responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - type: success log: - mailbox - edit - spam_score - username: - info@domain.tld spam_score: "8,15" msg: - mailbox_modified - info@domain.tld schema: properties: log: description: contains request object items: {} type: array msg: items: {} type: array type: enum: - success - danger - error type: string type: object description: OK headers: {} tags: - Mailboxes description: >- Using this endpoint you can edit the spam filter score for a certain mailbox. operationId: Edit mailbox spam filter score requestBody: content: application/json: schema: example: - items: - info@domain.tld attr: spam_score: "8,15" summary: Edit mailbox spam filter score "/api/v1/get/mailbox/all/{domain}": get: parameters: - description: name of domain in: path name: domain required: true schema: type: string - description: e.g. api-key-string example: api-key-string in: header name: X-API-Key required: false schema: type: string responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - active: "1" attributes: force_pw_update: "0" mailbox_format: "maildir:" quarantine_notification: never sogo_access: "1" tls_enforce_in: "0" tls_enforce_out: "0" custom_attributes: {} domain: domain3.tld is_relayed: 0 local_part: info max_new_quota: 10737418240 messages: 0 name: Full name percent_class: success percent_in_use: 0 quota: 3221225472 quota_used: 0 rl: false spam_aliases: 0 username: info@domain3.tld tags: ["tag1", "tag2"] description: OK headers: {} tags: - Mailboxes description: You can list all mailboxes existing in system for a specific domain. operationId: Get mailboxes of a domain summary: Get mailboxes of a domain /api/v1/edit/cors: post: responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - type: "success" log: ["cors", "edit", {"allowed_origins": ["*", "mail.mailcow.tld"], "allowed_methods": ["POST", "GET", "DELETE", "PUT"]}] msg: "cors_headers_edited" description: OK headers: { } tags: - Cross-Origin Resource Sharing (CORS) description: >- This endpoint allows you to manage Cross-Origin Resource Sharing (CORS) settings for the API. CORS is a security feature implemented by web browsers to prevent unauthorized cross-origin requests. By editing the CORS settings, you can specify which domains and which methods are permitted to access the API resources from outside the mailcow domain. operationId: Edit Cross-Origin Resource Sharing (CORS) settings requestBody: content: application/json: schema: example: attr: allowed_origins: ["*", "mail.mailcow.tld"] allowed_methods: ["POST", "GET", "DELETE", "PUT"] properties: attr: type: object properties: allowed_origins: type: array items: type: string allowed_methods: type: array items: type: string summary: Edit Cross-Origin Resource Sharing (CORS) settings "/api/v1/get/spam-score/{mailbox}": get: parameters: - description: name of mailbox or empty for current user - admin user will retrieve the global spam filter score in: path name: mailbox required: true schema: type: string - description: e.g. api-key-string example: api-key-string in: header name: X-API-Key required: false schema: type: string responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: spam_score: "8,15" description: OK headers: {} tags: - Mailboxes description: >- Using this endpoint you can get the global spam filter score or the spam filter score of a certain mailbox. operationId: Get mailbox or global spam filter score summary: Get mailbox or global spam filter score /api/v1/edit/identity-provider: post: responses: "401": $ref: "#/components/responses/Unauthorized" "200": content: application/json: examples: response: value: - type: "success" log: - "identity_provider" - "edit" - authsource: "keycloak" server_url: "https://auth.mailcow.tld" realm: "mailcow" client_id: "mailcow_client" client_secret: "*" redirect_url: "https://mail.mailcow.tld" redirect_url_extra: ["https://extramail.mailcow.tld"] version: "26.1.3" default_template: "Default" mappers: - "small_mbox" - "medium_mbox" templates: - "small" - "medium" ignore_ssl_error: true mailpassword_flow: true periodic_sync: true import_users: true sync_interval: 30 msg: - "object_modified" - "" description: OK headers: { } tags: - Identity Provider description: >- Configure an external Identity Provider to use as user authentication operationId: Edit external Identity Provider settings requestBody: content: application/json: schema: properties: items: type: array default: ["identity-provider"] attr: type: object properties: authsource: description: Specifies the type of the Identity Provider type: string enum: [ldap, keycloak, generic-oidc] server_url: description: The base URL of your Keycloak server. Required if `authsource` is keycloak. type: string realm: description: The Keycloak realm where the mailcow client is configured. Required if `authsource` is keycloak. type: string client_id: description: The Client ID assigned to mailcow Client in OIDC Provider. Required if `authsource` is keycloak or generic-oidc. type: string client_secret: description: The Client Secret assigned to mailcow Client in OIDC Provider. Required if `authsource` is keycloak or generic-oidc. type: string redirect_url: description: The redirect URL that OIDC Provider will use after authentication. Required if `authsource` is keycloak or generic-oidc. type: string redirect_url_extra: description: Additional redirect URLs that OIDC Provider can use after authentication if valid. type: array version: description: Specifies the Keycloak version. Required if `authsource` is keycloak. type: string default_template: description: (Optional) If no matching Attribute Mapping exists for a User, the default template will be used for creating the mailbox, but not for updating the mailbox. type: string mappers: description: (Optional) Attribute values used to match a mailbox template. Each element corresponds to the respective index in the templates array (i.e., the first element matches the first element of templates, the second matches the second, and so on). type: array templates: description: (Optional) Defines the mailbox templates to be assigned. Each element corresponds to the respective index in the `mappers` array. type: array ignore_ssl_error: description: If enabled, SSL certificate validation is bypassed type: boolean default: false mailpassword_flow: description: If enabled, mailcow will attempt to validate user credentials using the Keycloak Admin REST API instead of relying solely on the Authorization Code Flow. type: boolean default: false periodic_sync: description: If enabled, mailcow periodically performs a full sync of all users from Keycloak or LDAP. type: boolean default: false import_users: description: If enabled, new users are automatically imported from Keycloak or LDAP into mailcow. type: boolean default: false sync_interval: description: Defines the time interval (in minutes) for periodic synchronization and user imports. type: number default: 15 host: description: The address of your LDAP server. You can provide a single hostname or a comma-separated list of hosts for fallback in case the primary server is unreachable. Required if `authsource` is ldap. type: string port: description: The port used to connect to the LDAP server. Required if `authsource` is ldap. type: string use_ssl: description: enable LDAPS connection. If Port is set to 389 it will be overriden to 636. type: boolean default: false use_tls: description: enable TLS connection. TLS is recommended over SSL. SSL Ports cannot be used. type: boolean default: false basedn: description: The Distinguished Name (DN) from which searches will be performed. Required if `authsource` is ldap. type: string username_field: description: The LDAP attribute used to identify users during authentication. Required if `authsource` is ldap. type: string default: mail filter: description: An optional LDAP search filter to refine which users can authenticate. type: string attribute_field: description: Specifies an LDAP attribute that holds a specific value which can be mapped to a mailbox template using the Attribute Mapping section. Required if `authsource` is ldap. type: string binddn: description: The Distinguished Name (DN) of the LDAP user that will be used to authenticate and perform LDAP searches. This account should have sufficient permissions to read the required attributes. Required if `authsource` is ldap. type: string bindpass: description: The password for the Bind DN user. It is required for authentication when connecting to the LDAP server. Required if `authsource` is ldap. type: string authorize_url: description: The OIDC provider's authorization server URL. Required if `authsource` is generic-oidc. type: string token_url: description: The OIDC provider's token server URL. Required if `authsource` is generic-oidc. type: string userinfo_url: description: The OIDC provider's user info server URL. Required if `authsource` is generic-oidc. type: string client_scopes: description: Specifies the OIDC scopes requested during authentication. type: string default: "openid profile email mailcow_template" examples: keycloak: value: items: - "identity-provider" attr: authsource: "keycloak" server_url: "https://auth.mailcow.tld" realm: "mailcow" client_id: "mailcow_client" client_secret: "Xy7GdPqvJ9m3R8sT2LkVZ5W1oNbCaYQf" redirect_url: "https://mail.mailcow.tld" redirect_url_extra: ["https://extramail.mailcow.tld"] version: "26.1.3" default_template: "Default" mappers: ["small_mbox", "medium_mbox"] templates: ["small", "medium"] ignore_ssl_error: true mailpassword_flow: true periodic_sync: true import_users: true sync_interval: 30 ldap: value: items: - "identity-provider" attr: authsource: "ldap" host: "127.0.0.1" port: "389" use_ssl: false use_tls: false ignore_ssl_error: false basedn: "DC=mailcow,DC=local" username_field: "mail" filter: "(memberOf:1.2.840.113556.1.4.1941:=DC=mailcow,DC=local)" attribute_field: "othermailbox" binddn: "CN=LDAP Read Only,CN=Users,DC=mailcow,DC=local" bindpass: "moohoo" default_template: "Default" mappers: ["small_mbox", "medium_mbox"] templates: ["small", "medium"] periodic_sync: true import_users: true sync_interval: 30 generic-oidc: value: items: - "identity-provider" attr: authsource: "generic-oidc" authorize_url: "https://auth.mailcow.tld/application/o/authorize/" token_url: "https://auth.mailcow.tld/application/o/token/" userinfo_url: "https://auth.mailcow.tld/application/o/userinfo/" client_id: "mailcow_client" client_secret: "Xy7GdPqvJ9m3R8sT2LkVZ5W1oNbCaYQf" redirect_url: "https://mail.mailcow.tld" redirect_url_extra: ["https://extramail.mailcow.tld"] client_scopes: "openid profile email mailcow_template" default_template: "Default" mappers: ["small_mbox", "medium_mbox"] templates: ["small", "medium"] ignore_ssl_error: true summary: Edit external Identity Provider tags: - name: Domains description: You can create antispam whitelist and blacklist policies - name: Domain antispam policies description: You can edit the Domain Antispam policies - name: Mailboxes description: You can manage mailboxes - name: Aliases description: You can manage aliases - name: Sync jobs description: Using Syncjobs you can sync your mails with other email servers - name: Fordwarding Hosts description: Forwarding Hosts enable you to send mail using a relay - name: Logs description: Get all mailcow system logs - name: Queue Manager description: Manage the postfix mail queue - name: Quarantine description: Check what emails went to quarantine - name: Fail2Ban description: Manage the Netfilter fail2ban options - name: DKIM description: Manage DKIM keys - name: Domain admin description: Create or udpdate domain admin users - name: Single Sign-On description: Issue tokens for users - name: Address Rewriting description: Create BCC maps or recipient maps - name: Outgoing TLS Policy Map Overrides description: Force global TLS policys - name: oAuth Clients description: Use mailcow as a oAuth server - name: Routing description: Define your own email routes - name: Resources description: Manage ressources - name: App Passwords description: Create mailbox app passwords - name: Status description: Get the status of your cow - name: Ratelimits description: Edit domain ratelimits - name: Cross-Origin Resource Sharing (CORS) description: Manage Cross-Origin Resource Sharing (CORS) settings - name: Identity Provider description: Manage external Identity Provider settings ================================================ FILE: data/web/api/swagger-initializer.js ================================================ window.onload = function() { // Begin Swagger UI call region window.ui = SwaggerUIBundle({ urls: [{url: "/api/openapi.yaml", name: "mailcow API"}], dom_id: '#swagger-ui', deepLinking: true, presets: [ SwaggerUIBundle.presets.apis, SwaggerUIStandalonePreset ], plugins: [ SwaggerUIBundle.plugins.DownloadUrl ], layout: "StandaloneLayout" }); // End Swagger UI call region }; ================================================ FILE: data/web/api/swagger-ui-bundle.js ================================================ /*! For license information please see swagger-ui-bundle.js.LICENSE.txt */ !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.SwaggerUIBundle=t():e.SwaggerUIBundle=t()}(this,(()=>(()=>{var e={17967:(e,t)=>{"use strict";t.N=void 0;var n=/^([^\w]*)(javascript|data|vbscript)/im,r=/&#(\w+)(^\w|;)?/g,o=/&(newline|tab);/gi,s=/[\u0000-\u001F\u007F-\u009F\u2000-\u200D\uFEFF]/gim,i=/^.+(:|:)/gim,a=[".","/"];t.N=function(e){var t,l=(t=e||"",t.replace(r,(function(e,t){return String.fromCharCode(t)}))).replace(o,"").replace(s,"").trim();if(!l)return"about:blank";if(function(e){return a.indexOf(e[0])>-1}(l))return l;var c=l.match(i);if(!c)return l;var u=c[0];return n.test(u)?"about:blank":l}},53795:(e,t,n)=>{"use strict";n.d(t,{Z:()=>P});var r=n(23101),o=n.n(r),s=n(61125),i=n.n(s),a=n(11882),l=n.n(a),c=n(97606),u=n.n(c),p=n(67294),h=n(43393);function f(e){return f="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},f(e)}function d(e,t){for(var n=0;n1&&void 0!==arguments[1]?arguments[1]:{},n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},r=function(e,t){return function(n){if("string"==typeof n)return(0,h.is)(t[n],e[n]);if(Array.isArray(n))return(0,h.is)(x(t,n),x(e,n));throw new TypeError("Invalid key: expected Array or string: "+n)}}(t,n),o=e||Object.keys(function(e){for(var t=1;t1&&void 0!==arguments[1]?arguments[1]:{};return!S(this.updateOnProps,this.props,e,"updateOnProps")||!S(this.updateOnStates,this.state,t,"updateOnStates")}}],r&&d(n.prototype,r),o&&d(n,o),t}(p.Component);var j=n(23930),O=n.n(j),k=n(45697),A=n.n(k);const C=e=>{const t=e.replace(/~1/g,"/").replace(/~0/g,"~");try{return decodeURIComponent(t)}catch{return t}};class P extends _{constructor(){super(...arguments),i()(this,"getModelName",(e=>-1!==l()(e).call(e,"#/definitions/")?C(e.replace(/^.*#\/definitions\//,"")):-1!==l()(e).call(e,"#/components/schemas/")?C(e.replace(/^.*#\/components\/schemas\//,"")):void 0)),i()(this,"getRefSchema",(e=>{let{specSelectors:t}=this.props;return t.findDefinition(e)}))}render(){let{getComponent:e,getConfigs:t,specSelectors:r,schema:s,required:i,name:a,isRef:l,specPath:c,displayName:u,includeReadOnly:h,includeWriteOnly:f}=this.props;const d=e("ObjectModel"),m=e("ArrayModel"),g=e("PrimitiveModel");let y="object",v=s&&s.get("$$ref");if(!a&&v&&(a=this.getModelName(v)),!s&&v&&(s=this.getRefSchema(a)),!s)return p.createElement("span",{className:"model model-title"},p.createElement("span",{className:"model-title__text"},u||a),p.createElement("img",{src:n(2517),height:"20px",width:"20px"}));const b=r.isOAS3()&&s.get("deprecated");switch(l=void 0!==l?l:!!v,y=s&&s.get("type")||y,y){case"object":return p.createElement(d,o()({className:"object"},this.props,{specPath:c,getConfigs:t,schema:s,name:a,deprecated:b,isRef:l,includeReadOnly:h,includeWriteOnly:f}));case"array":return p.createElement(m,o()({className:"array"},this.props,{getConfigs:t,schema:s,name:a,deprecated:b,required:i,includeReadOnly:h,includeWriteOnly:f}));default:return p.createElement(g,o()({},this.props,{getComponent:e,getConfigs:t,schema:s,name:a,deprecated:b,required:i}))}}}i()(P,"propTypes",{schema:u()(O()).isRequired,getComponent:A().func.isRequired,getConfigs:A().func.isRequired,specSelectors:A().object.isRequired,name:A().string,displayName:A().string,isRef:A().bool,required:A().bool,expandDepth:A().number,depth:A().number,specPath:O().list.isRequired,includeReadOnly:A().bool,includeWriteOnly:A().bool})},5623:(e,t,n)=>{"use strict";n.d(t,{Z:()=>h});var r=n(61125),o=n.n(r),s=n(28222),i=n.n(s),a=n(67294),l=n(84564),c=n.n(l),u=n(90242),p=n(27504);class h extends a.Component{constructor(e,t){super(e,t),o()(this,"getDefinitionUrl",(()=>{let{specSelectors:e}=this.props;return new(c())(e.url(),p.Z.location).toString()}));let{getConfigs:n}=e,{validatorUrl:r}=n();this.state={url:this.getDefinitionUrl(),validatorUrl:void 0===r?"https://validator.swagger.io/validator":r}}UNSAFE_componentWillReceiveProps(e){let{getConfigs:t}=e,{validatorUrl:n}=t();this.setState({url:this.getDefinitionUrl(),validatorUrl:void 0===n?"https://validator.swagger.io/validator":n})}render(){let{getConfigs:e}=this.props,{spec:t}=e(),n=(0,u.Nm)(this.state.validatorUrl);return"object"==typeof t&&i()(t).length?null:this.state.url&&(0,u.hW)(this.state.validatorUrl)&&(0,u.hW)(this.state.url)?a.createElement("span",{className:"float-right"},a.createElement("a",{target:"_blank",rel:"noopener noreferrer",href:`${n}/debug?url=${encodeURIComponent(this.state.url)}`},a.createElement(f,{src:`${n}?url=${encodeURIComponent(this.state.url)}`,alt:"Online validator badge"}))):null}}class f extends a.Component{constructor(e){super(e),this.state={loaded:!1,error:!1}}componentDidMount(){const e=new Image;e.onload=()=>{this.setState({loaded:!0})},e.onerror=()=>{this.setState({error:!0})},e.src=this.props.src}UNSAFE_componentWillReceiveProps(e){if(e.src!==this.props.src){const t=new Image;t.onload=()=>{this.setState({loaded:!0})},t.onerror=()=>{this.setState({error:!0})},t.src=e.src}}render(){return this.state.error?a.createElement("img",{alt:"Error"}):this.state.loaded?a.createElement("img",{src:this.props.src,alt:this.props.alt}):null}}},4599:(e,t,n)=>{"use strict";n.d(t,{Z:()=>ye,s:()=>ve});var r=n(67294),o=n(89927);function s(e,t){if(Array.prototype.indexOf)return e.indexOf(t);for(var n=0,r=e.length;n=0;n--)!0===t(e[n])&&e.splice(n,1)}function a(e){throw new Error("Unhandled case for value: '".concat(e,"'"))}var l=function(){function e(e){void 0===e&&(e={}),this.tagName="",this.attrs={},this.innerHTML="",this.whitespaceRegex=/\s+/,this.tagName=e.tagName||"",this.attrs=e.attrs||{},this.innerHTML=e.innerHtml||e.innerHTML||""}return e.prototype.setTagName=function(e){return this.tagName=e,this},e.prototype.getTagName=function(){return this.tagName||""},e.prototype.setAttr=function(e,t){return this.getAttrs()[e]=t,this},e.prototype.getAttr=function(e){return this.getAttrs()[e]},e.prototype.setAttrs=function(e){return Object.assign(this.getAttrs(),e),this},e.prototype.getAttrs=function(){return this.attrs||(this.attrs={})},e.prototype.setClass=function(e){return this.setAttr("class",e)},e.prototype.addClass=function(e){for(var t,n=this.getClass(),r=this.whitespaceRegex,o=n?n.split(r):[],i=e.split(r);t=i.shift();)-1===s(o,t)&&o.push(t);return this.getAttrs().class=o.join(" "),this},e.prototype.removeClass=function(e){for(var t,n=this.getClass(),r=this.whitespaceRegex,o=n?n.split(r):[],i=e.split(r);o.length&&(t=i.shift());){var a=s(o,t);-1!==a&&o.splice(a,1)}return this.getAttrs().class=o.join(" "),this},e.prototype.getClass=function(){return this.getAttrs().class||""},e.prototype.hasClass=function(e){return-1!==(" "+this.getClass()+" ").indexOf(" "+e+" ")},e.prototype.setInnerHTML=function(e){return this.innerHTML=e,this},e.prototype.setInnerHtml=function(e){return this.setInnerHTML(e)},e.prototype.getInnerHTML=function(){return this.innerHTML||""},e.prototype.getInnerHtml=function(){return this.getInnerHTML()},e.prototype.toAnchorString=function(){var e=this.getTagName(),t=this.buildAttrsStr();return["<",e,t=t?" "+t:"",">",this.getInnerHtml(),""].join("")},e.prototype.buildAttrsStr=function(){if(!this.attrs)return"";var e=this.getAttrs(),t=[];for(var n in e)e.hasOwnProperty(n)&&t.push(n+'="'+e[n]+'"');return t.join(" ")},e}();var c=function(){function e(e){void 0===e&&(e={}),this.newWindow=!1,this.truncate={},this.className="",this.newWindow=e.newWindow||!1,this.truncate=e.truncate||{},this.className=e.className||""}return e.prototype.build=function(e){return new l({tagName:"a",attrs:this.createAttrs(e),innerHtml:this.processAnchorText(e.getAnchorText())})},e.prototype.createAttrs=function(e){var t={href:e.getAnchorHref()},n=this.createCssClass(e);return n&&(t.class=n),this.newWindow&&(t.target="_blank",t.rel="noopener noreferrer"),this.truncate&&this.truncate.length&&this.truncate.length=a)return l.host.length==t?(l.host.substr(0,t-o)+n).substr(0,a+r):i(u,a).substr(0,a+r);var p="";if(l.path&&(p+="/"+l.path),l.query&&(p+="?"+l.query),p){if((u+p).length>=a)return(u+p).length==t?(u+p).substr(0,t):(u+i(p,a-u.length)).substr(0,a+r);u+=p}if(l.fragment){var h="#"+l.fragment;if((u+h).length>=a)return(u+h).length==t?(u+h).substr(0,t):(u+i(h,a-u.length)).substr(0,a+r);u+=h}if(l.scheme&&l.host){var f=l.scheme+"://";if((u+f).length0&&(d=u.substr(-1*Math.floor(a/2))),(u.substr(0,Math.ceil(a/2))+n+d).substr(0,a+r)}(e,n):"middle"===r?function(e,t,n){if(e.length<=t)return e;var r,o;null==n?(n="…",r=8,o=3):(r=n.length,o=n.length);var s=t-o,i="";return s>0&&(i=e.substr(-1*Math.floor(s/2))),(e.substr(0,Math.ceil(s/2))+n+i).substr(0,s+r)}(e,n):function(e,t,n){return function(e,t,n){var r;return e.length>t&&(null==n?(n="…",r=3):r=n.length,e=e.substring(0,t-r)+n),e}(e,t,n)}(e,n)},e}(),u=function(){function e(e){this.__jsduckDummyDocProp=null,this.matchedText="",this.offset=0,this.tagBuilder=e.tagBuilder,this.matchedText=e.matchedText,this.offset=e.offset}return e.prototype.getMatchedText=function(){return this.matchedText},e.prototype.setOffset=function(e){this.offset=e},e.prototype.getOffset=function(){return this.offset},e.prototype.getCssClassSuffixes=function(){return[this.getType()]},e.prototype.buildTag=function(){return this.tagBuilder.build(this)},e}(),p=function(e,t){return p=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n])},p(e,t)};function h(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");function n(){this.constructor=e}p(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}var f=function(){return f=Object.assign||function(e){for(var t,n=1,r=arguments.length;n-1},e.isValidUriScheme=function(e){var t=e.match(this.uriSchemeRegex),n=t&&t[0].toLowerCase();return"javascript:"!==n&&"vbscript:"!==n},e.urlMatchDoesNotHaveProtocolOrDot=function(e,t){return!(!e||t&&this.hasFullProtocolRegex.test(t)||-1!==e.indexOf("."))},e.urlMatchDoesNotHaveAtLeastOneWordChar=function(e,t){return!(!e||!t)&&(!this.hasFullProtocolRegex.test(t)&&!this.hasWordCharAfterProtocolRegex.test(e))},e.hasFullProtocolRegex=/^[A-Za-z][-.+A-Za-z0-9]*:\/\//,e.uriSchemeRegex=/^[A-Za-z][-.+A-Za-z0-9]*:/,e.hasWordCharAfterProtocolRegex=new RegExp(":[^\\s]*?["+k+"]"),e.ipRegex=/[0-9][0-9]?[0-9]?\.[0-9][0-9]?[0-9]?\.[0-9][0-9]?[0-9]?\.[0-9][0-9]?[0-9]?(:[0-9]*)?\/?$/,e}(),V=(d=new RegExp("[/?#](?:["+N+"\\-+&@#/%=~_()|'$*\\[\\]{}?!:,.;^✓]*["+N+"\\-+&@#/%=~_()|'$*\\[\\]{}✓])?"),new RegExp(["(?:","(",/(?:[A-Za-z][-.+A-Za-z0-9]{0,63}:(?![A-Za-z][-.+A-Za-z0-9]{0,63}:\/\/)(?!\d+\/?)(?:\/\/)?)/.source,D(2),")","|","(","(//)?",/(?:www\.)/.source,D(6),")","|","(","(//)?",D(10)+"\\.",L.source,"(?![-"+P+"])",")",")","(?::[0-9]+)?","(?:"+d.source+")?"].join(""),"gi")),W=new RegExp("["+N+"]"),J=function(e){function t(t){var n=e.call(this,t)||this;return n.stripPrefix={scheme:!0,www:!0},n.stripTrailingSlash=!0,n.decodePercentEncoding=!0,n.matcherRegex=V,n.wordCharRegExp=W,n.stripPrefix=t.stripPrefix,n.stripTrailingSlash=t.stripTrailingSlash,n.decodePercentEncoding=t.decodePercentEncoding,n}return h(t,e),t.prototype.parseMatches=function(e){for(var t,n=this.matcherRegex,r=this.stripPrefix,o=this.stripTrailingSlash,s=this.decodePercentEncoding,i=this.tagBuilder,a=[],l=function(){var n=t[0],l=t[1],u=t[4],p=t[5],h=t[9],f=t.index,d=p||h,m=e.charAt(f-1);if(!z.isValid(n,l))return"continue";if(f>0&&"@"===m)return"continue";if(f>0&&d&&c.wordCharRegExp.test(m))return"continue";if(/\?$/.test(n)&&(n=n.substr(0,n.length-1)),c.matchHasUnbalancedClosingParen(n))n=n.substr(0,n.length-1);else{var g=c.matchHasInvalidCharAfterTld(n,l);g>-1&&(n=n.substr(0,g))}var y=["http://","https://"].find((function(e){return!!l&&-1!==l.indexOf(e)}));if(y){var v=n.indexOf(y);n=n.substr(v),l=l.substr(v),f+=v}var w=l?"scheme":u?"www":"tld",E=!!l;a.push(new b({tagBuilder:i,matchedText:n,offset:f,urlMatchType:w,url:n,protocolUrlMatch:E,protocolRelativeMatch:!!d,stripPrefix:r,stripTrailingSlash:o,decodePercentEncoding:s}))},c=this;null!==(t=n.exec(e));)l();return a},t.prototype.matchHasUnbalancedClosingParen=function(e){var t,n=e.charAt(e.length-1);if(")"===n)t="(";else if("]"===n)t="[";else{if("}"!==n)return!1;t="{"}for(var r=0,o=0,s=e.length-1;o-1&&s-i<=140){var o=e.slice(i,s),a=new g({tagBuilder:t,matchedText:o,offset:i,serviceName:n,hashtag:o.slice(1)});r.push(a)}}},t}(w),G=["twitter","facebook","instagram","tiktok"],Z=new RegExp("".concat(/(?:(?:(?:(\+)?\d{1,3}[-\040.]?)?\(?\d{3}\)?[-\040.]?\d{3}[-\040.]?\d{4})|(?:(\+)(?:9[976]\d|8[987530]\d|6[987]\d|5[90]\d|42\d|3[875]\d|2[98654321]\d|9[8543210]|8[6421]|6[6543210]|5[87654321]|4[987654310]|3[9643210]|2[70]|7|1)[-\040.]?(?:\d[-\040.]?){6,12}\d+))([,;]+[0-9]+#?)*/.source,"|").concat(/(0([1-9]{1}-?[1-9]\d{3}|[1-9]{2}-?\d{3}|[1-9]{2}\d{1}-?\d{2}|[1-9]{2}\d{2}-?\d{1})-?\d{4}|0[789]0-?\d{4}-?\d{4}|050-?\d{4}-?\d{4})/.source),"g"),Y=function(e){function t(){var t=null!==e&&e.apply(this,arguments)||this;return t.matcherRegex=Z,t}return h(t,e),t.prototype.parseMatches=function(e){for(var t,n=this.matcherRegex,r=this.tagBuilder,o=[];null!==(t=n.exec(e));){var s=t[0],i=s.replace(/[^0-9,;#]/g,""),a=!(!t[1]&&!t[2]),l=0==t.index?"":e.substr(t.index-1,1),c=e.substr(t.index+s.length,1),u=!l.match(/\d/)&&!c.match(/\d/);this.testMatch(t[3])&&this.testMatch(s)&&u&&o.push(new v({tagBuilder:r,matchedText:s,offset:t.index,number:i,plusSign:a}))}return o},t.prototype.testMatch=function(e){return S.test(e)},t}(w),X=new RegExp("@[_".concat(N,"]{1,50}(?![_").concat(N,"])"),"g"),Q=new RegExp("@[_.".concat(N,"]{1,30}(?![_").concat(N,"])"),"g"),ee=new RegExp("@[-_.".concat(N,"]{1,50}(?![-_").concat(N,"])"),"g"),te=new RegExp("@[_.".concat(N,"]{1,23}[_").concat(N,"](?![_").concat(N,"])"),"g"),ne=new RegExp("[^"+N+"]"),re=function(e){function t(t){var n=e.call(this,t)||this;return n.serviceName="twitter",n.matcherRegexes={twitter:X,instagram:Q,soundcloud:ee,tiktok:te},n.nonWordCharRegex=ne,n.serviceName=t.serviceName,n}return h(t,e),t.prototype.parseMatches=function(e){var t,n=this.serviceName,r=this.matcherRegexes[this.serviceName],o=this.nonWordCharRegex,s=this.tagBuilder,i=[];if(!r)return i;for(;null!==(t=r.exec(e));){var a=t.index,l=e.charAt(a-1);if(0===a||o.test(l)){var c=t[0].replace(/\.+$/g,""),u=c.slice(1);i.push(new y({tagBuilder:s,matchedText:c,offset:a,serviceName:n,mention:u}))}}return i},t}(w);function oe(e,t){for(var n,r=t.onOpenTag,o=t.onCloseTag,s=t.onText,i=t.onComment,l=t.onDoctype,c=new se,u=0,p=e.length,h=0,d=0,m=c;u"===e?(m=new se(f(f({},m),{name:J()})),W()):E.test(e)||x.test(e)||":"===e||z()}function w(e){">"===e?z():E.test(e)?h=3:z()}function S(e){_.test(e)||("/"===e?h=12:">"===e?W():"<"===e?V():"="===e||j.test(e)||O.test(e)?z():h=5)}function k(e){_.test(e)?h=6:"/"===e?h=12:"="===e?h=7:">"===e?W():"<"===e?V():j.test(e)&&z()}function A(e){_.test(e)||("/"===e?h=12:"="===e?h=7:">"===e?W():"<"===e?V():j.test(e)?z():h=5)}function C(e){_.test(e)||('"'===e?h=8:"'"===e?h=9:/[>=`]/.test(e)?z():"<"===e?V():h=10)}function P(e){'"'===e&&(h=11)}function N(e){"'"===e&&(h=11)}function I(e){_.test(e)?h=4:">"===e?W():"<"===e&&V()}function T(e){_.test(e)?h=4:"/"===e?h=12:">"===e?W():"<"===e?V():(h=4,u--)}function R(e){">"===e?(m=new se(f(f({},m),{isClosing:!0})),W()):h=4}function M(t){"--"===e.substr(u,2)?(u+=2,m=new se(f(f({},m),{type:"comment"})),h=14):"DOCTYPE"===e.substr(u,7).toUpperCase()?(u+=7,m=new se(f(f({},m),{type:"doctype"})),h=20):z()}function D(e){"-"===e?h=15:">"===e?z():h=16}function F(e){"-"===e?h=18:">"===e?z():h=16}function L(e){"-"===e&&(h=17)}function B(e){h="-"===e?18:16}function $(e){">"===e?W():"!"===e?h=19:"-"===e||(h=16)}function q(e){"-"===e?h=17:">"===e?W():h=16}function U(e){">"===e?W():"<"===e&&V()}function z(){h=0,m=c}function V(){h=1,m=new se({idx:u})}function W(){var t=e.slice(d,m.idx);t&&s(t,d),"comment"===m.type?i(m.idx):"doctype"===m.type?l(m.idx):(m.isOpening&&r(m.name,m.idx),m.isClosing&&o(m.name,m.idx)),z(),d=u+1}function J(){var t=m.idx+(m.isClosing?2:1);return e.slice(t,u).toLowerCase()}d=0&&r++},onText:function(e,n){if(0===r){var s=function(e,t){if(!t.global)throw new Error("`splitRegex` must have the 'g' flag set");for(var n,r=[],o=0;n=t.exec(e);)r.push(e.substring(o,n.index)),r.push(n[0]),o=n.index+n[0].length;return r.push(e.substring(o)),r}(e,/( | |<|<|>|>|"|"|')/gi),i=n;s.forEach((function(e,n){if(n%2==0){var r=t.parseText(e,i);o.push.apply(o,r)}i+=e.length}))}},onCloseTag:function(e){n.indexOf(e)>=0&&(r=Math.max(r-1,0))},onComment:function(e){},onDoctype:function(e){}}),o=this.compactMatches(o),o=this.removeUnwantedMatches(o)},e.prototype.compactMatches=function(e){e.sort((function(e,t){return e.getOffset()-t.getOffset()}));for(var t=0;to?t:t+1;e.splice(i,1);continue}if(e[t+1].getOffset()/g,">"));for(var t=this.parse(e),n=[],r=0,o=0,s=t.length;o/i.test(e)}function ce(){var e=[],t=new ie({stripPrefix:!1,url:!0,email:!0,replaceFn:function(t){switch(t.getType()){case"url":e.push({text:t.matchedText,url:t.getUrl()});break;case"email":e.push({text:t.matchedText,url:"mailto:"+t.getEmail().replace(/^mailto:/i,"")})}return!1}});return{links:e,autolinker:t}}function ue(e){var t,n,r,o,s,i,a,l,c,u,p,h,f,d,m=e.tokens,g=null;for(n=0,r=m.length;n=0;t--)if("link_close"!==(s=o[t]).type){if("htmltag"===s.type&&(d=s.content,/^\s]/i.test(d)&&p>0&&p--,le(s.content)&&p++),!(p>0)&&"text"===s.type&&ae.test(s.content)){if(g||(h=(g=ce()).links,f=g.autolinker),i=s.content,h.length=0,f.link(i),!h.length)continue;for(a=[],u=s.level,l=0;l({useUnsafeMarkdown:!1})};const ye=ge;function ve(e){let{useUnsafeMarkdown:t=!1}=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};const n=t,r=t?[]:["style","class"];return t&&!ve.hasWarnedAboutDeprecation&&(console.warn("useUnsafeMarkdown display configuration parameter is deprecated since >3.26.0 and will be removed in v4.0.0."),ve.hasWarnedAboutDeprecation=!0),fe().sanitize(e,{ADD_ATTR:["target"],FORBID_TAGS:["style","form"],ALLOW_DATA_ATTR:n,FORBID_ATTR:r})}ve.hasWarnedAboutDeprecation=!1},45308:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>h});var r,o=n(86),s=n.n(o),i=n(8712),a=n.n(i),l=n(90242),c=n(27621);const u=n(95102),p={},h=p;s()(r=a()(u).call(u)).call(r,(function(e){if("./index.js"===e)return;let t=u(e);p[(0,l.Zl)(e)]=t.default?t.default:t})),p.SafeRender=c.default},55812:(e,t,n)=>{"use strict";n.r(t),n.d(t,{AUTHORIZE:()=>h,AUTHORIZE_OAUTH2:()=>m,CONFIGURE_AUTH:()=>y,LOGOUT:()=>f,PRE_AUTHORIZE_OAUTH2:()=>d,RESTORE_AUTHORIZATION:()=>v,SHOW_AUTH_POPUP:()=>p,VALIDATE:()=>g,authPopup:()=>M,authorize:()=>w,authorizeAccessCodeWithBasicAuthentication:()=>P,authorizeAccessCodeWithFormParams:()=>C,authorizeApplication:()=>A,authorizeOauth2:()=>j,authorizeOauth2WithPersistOption:()=>O,authorizePassword:()=>k,authorizeRequest:()=>N,authorizeWithPersistOption:()=>E,configureAuth:()=>I,logout:()=>x,logoutWithPersistOption:()=>S,persistAuthorizationIfNeeded:()=>R,preAuthorizeImplicit:()=>_,restoreAuthorization:()=>T,showDefinitions:()=>b});var r=n(35627),o=n.n(r),s=n(76986),i=n.n(s),a=n(84564),l=n.n(a),c=n(27504),u=n(90242);const p="show_popup",h="authorize",f="logout",d="pre_authorize_oauth2",m="authorize_oauth2",g="validate",y="configure_auth",v="restore_authorization";function b(e){return{type:p,payload:e}}function w(e){return{type:h,payload:e}}const E=e=>t=>{let{authActions:n}=t;n.authorize(e),n.persistAuthorizationIfNeeded()};function x(e){return{type:f,payload:e}}const S=e=>t=>{let{authActions:n}=t;n.logout(e),n.persistAuthorizationIfNeeded()},_=e=>t=>{let{authActions:n,errActions:r}=t,{auth:s,token:i,isValid:a}=e,{schema:l,name:u}=s,p=l.get("flow");delete c.Z.swaggerUIRedirectOauth2,"accessCode"===p||a||r.newAuthErr({authId:u,source:"auth",level:"warning",message:"Authorization may be unsafe, passed state was changed in server Passed state wasn't returned from auth server"}),i.error?r.newAuthErr({authId:u,source:"auth",level:"error",message:o()(i)}):n.authorizeOauth2WithPersistOption({auth:s,token:i})};function j(e){return{type:m,payload:e}}const O=e=>t=>{let{authActions:n}=t;n.authorizeOauth2(e),n.persistAuthorizationIfNeeded()},k=e=>t=>{let{authActions:n}=t,{schema:r,name:o,username:s,password:a,passwordType:l,clientId:c,clientSecret:p}=e,h={grant_type:"password",scope:e.scopes.join(" "),username:s,password:a},f={};switch(l){case"request-body":!function(e,t,n){t&&i()(e,{client_id:t});n&&i()(e,{client_secret:n})}(h,c,p);break;case"basic":f.Authorization="Basic "+(0,u.r3)(c+":"+p);break;default:console.warn(`Warning: invalid passwordType ${l} was passed, not including client id and secret`)}return n.authorizeRequest({body:(0,u.GZ)(h),url:r.get("tokenUrl"),name:o,headers:f,query:{},auth:e})};const A=e=>t=>{let{authActions:n}=t,{schema:r,scopes:o,name:s,clientId:i,clientSecret:a}=e,l={Authorization:"Basic "+(0,u.r3)(i+":"+a)},c={grant_type:"client_credentials",scope:o.join(" ")};return n.authorizeRequest({body:(0,u.GZ)(c),name:s,url:r.get("tokenUrl"),auth:e,headers:l})},C=e=>{let{auth:t,redirectUrl:n}=e;return e=>{let{authActions:r}=e,{schema:o,name:s,clientId:i,clientSecret:a,codeVerifier:l}=t,c={grant_type:"authorization_code",code:t.code,client_id:i,client_secret:a,redirect_uri:n,code_verifier:l};return r.authorizeRequest({body:(0,u.GZ)(c),name:s,url:o.get("tokenUrl"),auth:t})}},P=e=>{let{auth:t,redirectUrl:n}=e;return e=>{let{authActions:r}=e,{schema:o,name:s,clientId:i,clientSecret:a,codeVerifier:l}=t,c={Authorization:"Basic "+(0,u.r3)(i+":"+a)},p={grant_type:"authorization_code",code:t.code,client_id:i,redirect_uri:n,code_verifier:l};return r.authorizeRequest({body:(0,u.GZ)(p),name:s,url:o.get("tokenUrl"),auth:t,headers:c})}},N=e=>t=>{let n,{fn:r,getConfigs:s,authActions:a,errActions:c,oas3Selectors:u,specSelectors:p,authSelectors:h}=t,{body:f,query:d={},headers:m={},name:g,url:y,auth:v}=e,{additionalQueryStringParams:b}=h.getConfigs()||{};if(p.isOAS3()){let e=u.serverEffectiveValue(u.selectedServer());n=l()(y,e,!0)}else n=l()(y,p.url(),!0);"object"==typeof b&&(n.query=i()({},n.query,b));const w=n.toString();let E=i()({Accept:"application/json, text/plain, */*","Content-Type":"application/x-www-form-urlencoded","X-Requested-With":"XMLHttpRequest"},m);r.fetch({url:w,method:"post",headers:E,query:d,body:f,requestInterceptor:s().requestInterceptor,responseInterceptor:s().responseInterceptor}).then((function(e){let t=JSON.parse(e.data),n=t&&(t.error||""),r=t&&(t.parseError||"");e.ok?n||r?c.newAuthErr({authId:g,level:"error",source:"auth",message:o()(t)}):a.authorizeOauth2WithPersistOption({auth:v,token:t}):c.newAuthErr({authId:g,level:"error",source:"auth",message:e.statusText})})).catch((e=>{let t=new Error(e).message;if(e.response&&e.response.data){const n=e.response.data;try{const e="string"==typeof n?JSON.parse(n):n;e.error&&(t+=`, error: ${e.error}`),e.error_description&&(t+=`, description: ${e.error_description}`)}catch(e){}}c.newAuthErr({authId:g,level:"error",source:"auth",message:t})}))};function I(e){return{type:y,payload:e}}function T(e){return{type:v,payload:e}}const R=()=>e=>{let{authSelectors:t,getConfigs:n}=e;if(!n().persistAuthorization)return;const r=t.authorized().toJS();localStorage.setItem("authorized",o()(r))},M=(e,t)=>()=>{c.Z.swaggerUIRedirectOauth2=t,c.Z.open(e)}},53779:(e,t,n)=>{"use strict";n.r(t),n.d(t,{loaded:()=>r});const r=(e,t)=>n=>{const{getConfigs:r,authActions:o}=t,s=r();if(e(n),s.persistAuthorization){const e=localStorage.getItem("authorized");e&&o.restoreAuthorization({authorized:JSON.parse(e)})}}},93705:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>p,preauthorizeApiKey:()=>f,preauthorizeBasic:()=>h});var r=n(11189),o=n.n(r),s=n(43962),i=n(55812),a=n(60035),l=n(60489),c=n(53779),u=n(22849);function p(){return{afterLoad(e){this.rootInjects=this.rootInjects||{},this.rootInjects.initOAuth=e.authActions.configureAuth,this.rootInjects.preauthorizeApiKey=o()(f).call(f,null,e),this.rootInjects.preauthorizeBasic=o()(h).call(h,null,e)},statePlugins:{auth:{reducers:s.default,actions:i,selectors:a,wrapActions:{authorize:u.authorize,logout:u.logout}},configs:{wrapActions:{loaded:c.loaded}},spec:{wrapActions:{execute:l.execute}}}}}function h(e,t,n,r){const{authActions:{authorize:o},specSelectors:{specJson:s,isOAS3:i}}=e,a=i()?["components","securitySchemes"]:["securityDefinitions"],l=s().getIn([...a,t]);return l?o({[t]:{value:{username:n,password:r},schema:l.toJS()}}):null}function f(e,t,n){const{authActions:{authorize:r},specSelectors:{specJson:o,isOAS3:s}}=e,i=s()?["components","securitySchemes"]:["securityDefinitions"],a=o().getIn([...i,t]);return a?r({[t]:{value:n,schema:a.toJS()}}):null}},43962:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>u});var r=n(86),o=n.n(r),s=n(76986),i=n.n(s),a=n(43393),l=n(90242),c=n(55812);const u={[c.SHOW_AUTH_POPUP]:(e,t)=>{let{payload:n}=t;return e.set("showDefinitions",n)},[c.AUTHORIZE]:(e,t)=>{var n;let{payload:r}=t,s=(0,a.fromJS)(r),i=e.get("authorized")||(0,a.Map)();return o()(n=s.entrySeq()).call(n,(t=>{let[n,r]=t;if(!(0,l.Wl)(r.getIn))return e.set("authorized",i);let o=r.getIn(["schema","type"]);if("apiKey"===o||"http"===o)i=i.set(n,r);else if("basic"===o){let e=r.getIn(["value","username"]),t=r.getIn(["value","password"]);i=i.setIn([n,"value"],{username:e,header:"Basic "+(0,l.r3)(e+":"+t)}),i=i.setIn([n,"schema"],r.get("schema"))}})),e.set("authorized",i)},[c.AUTHORIZE_OAUTH2]:(e,t)=>{let n,{payload:r}=t,{auth:o,token:s}=r;o.token=i()({},s),n=(0,a.fromJS)(o);let l=e.get("authorized")||(0,a.Map)();return l=l.set(n.get("name"),n),e.set("authorized",l)},[c.LOGOUT]:(e,t)=>{let{payload:n}=t,r=e.get("authorized").withMutations((e=>{o()(n).call(n,(t=>{e.delete(t)}))}));return e.set("authorized",r)},[c.CONFIGURE_AUTH]:(e,t)=>{let{payload:n}=t;return e.set("configs",n)},[c.RESTORE_AUTHORIZATION]:(e,t)=>{let{payload:n}=t;return e.set("authorized",(0,a.fromJS)(n.authorized))}}},60035:(e,t,n)=>{"use strict";n.r(t),n.d(t,{authorized:()=>x,definitionsForRequirements:()=>E,definitionsToAuthorize:()=>b,getConfigs:()=>_,getDefinitionsByNames:()=>w,isAuthorized:()=>S,shownDefinitions:()=>v});var r=n(86),o=n.n(r),s=n(51679),i=n.n(s),a=n(14418),l=n.n(a),c=n(11882),u=n.n(c),p=n(97606),h=n.n(p),f=n(28222),d=n.n(f),m=n(20573),g=n(43393);const y=e=>e,v=(0,m.P1)(y,(e=>e.get("showDefinitions"))),b=(0,m.P1)(y,(()=>e=>{var t;let{specSelectors:n}=e,r=n.securityDefinitions()||(0,g.Map)({}),s=(0,g.List)();return o()(t=r.entrySeq()).call(t,(e=>{let[t,n]=e,r=(0,g.Map)();r=r.set(t,n),s=s.push(r)})),s})),w=(e,t)=>e=>{var n;let{specSelectors:r}=e;console.warn("WARNING: getDefinitionsByNames is deprecated and will be removed in the next major version.");let s=r.securityDefinitions(),i=(0,g.List)();return o()(n=t.valueSeq()).call(n,(e=>{var t;let n=(0,g.Map)();o()(t=e.entrySeq()).call(t,(e=>{let t,[r,i]=e,a=s.get(r);var l;"oauth2"===a.get("type")&&i.size&&(t=a.get("scopes"),o()(l=t.keySeq()).call(l,(e=>{i.contains(e)||(t=t.delete(e))})),a=a.set("allowedScopes",t));n=n.set(r,a)})),i=i.push(n)})),i},E=function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:(0,g.List)();return e=>{let{authSelectors:n}=e;const r=n.definitionsToAuthorize()||(0,g.List)();let s=(0,g.List)();return o()(r).call(r,(e=>{let n=i()(t).call(t,(t=>t.get(e.keySeq().first())));n&&(o()(e).call(e,((t,r)=>{if("oauth2"===t.get("type")){const i=n.get(r);let a=t.get("scopes");var s;if(g.List.isList(i)&&g.Map.isMap(a))o()(s=a.keySeq()).call(s,(e=>{i.contains(e)||(a=a.delete(e))})),e=e.set(r,t.set("scopes",a))}})),s=s.push(e))})),s}},x=(0,m.P1)(y,(e=>e.get("authorized")||(0,g.Map)())),S=(e,t)=>e=>{var n;let{authSelectors:r}=e,o=r.authorized();return g.List.isList(t)?!!l()(n=t.toJS()).call(n,(e=>{var t,n;return-1===u()(t=h()(n=d()(e)).call(n,(e=>!!o.get(e)))).call(t,!1)})).length:null},_=(0,m.P1)(y,(e=>e.get("configs")))},60489:(e,t,n)=>{"use strict";n.r(t),n.d(t,{execute:()=>r});const r=(e,t)=>{let{authSelectors:n,specSelectors:r}=t;return t=>{let{path:o,method:s,operation:i,extras:a}=t,l={authorized:n.authorized()&&n.authorized().toJS(),definitions:r.securityDefinitions()&&r.securityDefinitions().toJS(),specSecurity:r.security()&&r.security().toJS()};return e({path:o,method:s,operation:i,securities:l,...a})}}},22849:(e,t,n)=>{"use strict";n.r(t),n.d(t,{authorize:()=>c,logout:()=>u});var r=n(3665),o=n.n(r),s=n(58309),i=n.n(s),a=n(86),l=n.n(a);const c=(e,t)=>n=>{e(n);if(t.getConfigs().persistAuthorization)try{const[{schema:e,value:t}]=o()(n),r="apiKey"===e.get("type"),s="cookie"===e.get("in");r&&s&&(document.cookie=`${e.get("name")}=${t}; SameSite=None; Secure`)}catch(e){console.error("Error persisting cookie based apiKey in document.cookie.",e)}},u=(e,t)=>n=>{const r=t.getConfigs(),o=t.authSelectors.authorized();try{r.persistAuthorization&&i()(n)&&l()(n).call(n,(e=>{const t=o.get(e,{}),n="apiKey"===t.getIn(["schema","type"]),r="cookie"===t.getIn(["schema","in"]);if(n&&r){const e=t.getIn(["schema","name"]);document.cookie=`${e}=; Max-Age=-99999999`}}))}catch(e){console.error("Error deleting cookie based apiKey from document.cookie.",e)}e(n)}},70714:(e,t,n)=>{"use strict";n.r(t),n.d(t,{TOGGLE_CONFIGS:()=>o,UPDATE_CONFIGS:()=>r,loaded:()=>a,toggle:()=>i,update:()=>s});const r="configs_update",o="configs_toggle";function s(e,t){return{type:r,payload:{[e]:t}}}function i(e){return{type:o,payload:e}}const a=()=>()=>{}},92256:(e,t,n)=>{"use strict";n.r(t),n.d(t,{parseYamlConfig:()=>o});var r=n(1272);const o=(e,t)=>{try{return r.ZP.load(e)}catch(e){return t&&t.errActions.newThrownErr(new Error(e)),{}}}},46709:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>c});var r=n(92256),o=n(70714),s=n(22698),i=n(69018),a=n(37743);const l={getLocalConfig:()=>(0,r.parseYamlConfig)('---\nurl: "https://petstore.swagger.io/v2/swagger.json"\ndom_id: "#swagger-ui"\nvalidatorUrl: "https://validator.swagger.io/validator"\n')};function c(){return{statePlugins:{spec:{actions:s,selectors:l},configs:{reducers:a.default,actions:o,selectors:i}}}}},37743:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>s});var r=n(43393),o=n(70714);const s={[o.UPDATE_CONFIGS]:(e,t)=>e.merge((0,r.fromJS)(t.payload)),[o.TOGGLE_CONFIGS]:(e,t)=>{const n=t.payload,r=e.get(n);return e.set(n,!r)}}},69018:(e,t,n)=>{"use strict";n.r(t),n.d(t,{get:()=>s});var r=n(58309),o=n.n(r);const s=(e,t)=>e.getIn(o()(t)?t:[t])},22698:(e,t,n)=>{"use strict";n.r(t),n.d(t,{downloadConfig:()=>o,getConfigByUrl:()=>s});var r=n(92256);const o=e=>t=>{const{fn:{fetch:n}}=t;return n(e)},s=(e,t)=>n=>{let{specActions:o}=n;if(e)return o.downloadConfig(e).then(s,s);function s(n){n instanceof Error||n.status>=400?(o.updateLoadingStatus("failedConfig"),o.updateLoadingStatus("failedConfig"),o.updateUrl(""),console.error(n.statusText+" "+e.url),t(null)):t((0,r.parseYamlConfig)(n.text))}}},31970:(e,t,n)=>{"use strict";n.r(t),n.d(t,{setHash:()=>r});const r=e=>e?history.pushState(null,null,`#${e}`):window.location.hash=""},34980:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>i});var r=n(41599),o=n(60877),s=n(34584);function i(){return[r.default,{statePlugins:{configs:{wrapActions:{loaded:(e,t)=>function(){e(...arguments);const n=decodeURIComponent(window.location.hash);t.layoutActions.parseDeepLinkHash(n)}}}},wrapComponents:{operation:o.default,OperationTag:s.default}}]}},41599:(e,t,n)=>{"use strict";n.r(t),n.d(t,{clearScrollTo:()=>_,default:()=>j,parseDeepLinkHash:()=>E,readyToScroll:()=>x,scrollTo:()=>w,scrollToElement:()=>S,show:()=>b});var r=n(58309),o=n.n(r),s=n(24278),i=n.n(s),a=n(97606),l=n.n(a),c=n(11882),u=n.n(c),p=n(31970),h=n(45172),f=n.n(h),d=n(90242),m=n(43393),g=n.n(m);const y="layout_scroll_to",v="layout_clear_scroll",b=(e,t)=>{let{getConfigs:n,layoutSelectors:r}=t;return function(){for(var t=arguments.length,s=new Array(t),i=0;i({type:y,payload:o()(e)?e:[e]}),E=e=>t=>{let{layoutActions:n,layoutSelectors:r,getConfigs:o}=t;if(o().deepLinking&&e){var s;let t=i()(e).call(e,1);"!"===t[0]&&(t=i()(t).call(t,1)),"/"===t[0]&&(t=i()(t).call(t,1));const o=l()(s=t.split("/")).call(s,(e=>e||"")),a=r.isShownKeyFromUrlHashArray(o),[c,p="",h=""]=a;if("operations"===c){const e=r.isShownKeyFromUrlHashArray([p]);u()(p).call(p,"_")>-1&&(console.warn("Warning: escaping deep link whitespace with `_` will be unsupported in v4.0, use `%20` instead."),n.show(l()(e).call(e,(e=>e.replace(/_/g," "))),!0)),n.show(e,!0)}(u()(p).call(p,"_")>-1||u()(h).call(h,"_")>-1)&&(console.warn("Warning: escaping deep link whitespace with `_` will be unsupported in v4.0, use `%20` instead."),n.show(l()(a).call(a,(e=>e.replace(/_/g," "))),!0)),n.show(a,!0),n.scrollTo(a)}},x=(e,t)=>n=>{const r=n.layoutSelectors.getScrollToKey();g().is(r,(0,m.fromJS)(e))&&(n.layoutActions.scrollToElement(t),n.layoutActions.clearScrollTo())},S=(e,t)=>n=>{try{t=t||n.fn.getScrollParent(e),f().createScroller(t).to(e)}catch(e){console.error(e)}},_=()=>({type:v});const j={fn:{getScrollParent:function(e,t){const n=document.documentElement;let r=getComputedStyle(e);const o="absolute"===r.position,s=t?/(auto|scroll|hidden)/:/(auto|scroll)/;if("fixed"===r.position)return n;for(let t=e;t=t.parentElement;)if(r=getComputedStyle(t),(!o||"static"!==r.position)&&s.test(r.overflow+r.overflowY+r.overflowX))return t;return n}},statePlugins:{layout:{actions:{scrollToElement:S,scrollTo:w,clearScrollTo:_,readyToScroll:x,parseDeepLinkHash:E},selectors:{getScrollToKey:e=>e.get("scrollToKey"),isShownKeyFromUrlHashArray(e,t){const[n,r]=t;return r?["operations",n,r]:n?["operations-tag",n]:[]},urlHashArrayFromIsShownKey(e,t){let[n,r,o]=t;return"operations"==n?[r,o]:"operations-tag"==n?[r]:[]}},reducers:{[y]:(e,t)=>e.set("scrollToKey",g().fromJS(t.payload)),[v]:e=>e.delete("scrollToKey")},wrapActions:{show:b}}}}},34584:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>i});var r=n(61125),o=n.n(r),s=n(67294);const i=(e,t)=>class extends s.Component{constructor(){super(...arguments),o()(this,"onLoad",(e=>{const{tag:n}=this.props,r=["operations-tag",n];t.layoutActions.readyToScroll(r,e)}))}render(){return s.createElement("span",{ref:this.onLoad},s.createElement(e,this.props))}}},60877:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>i});var r=n(61125),o=n.n(r),s=n(67294);n(23930);const i=(e,t)=>class extends s.Component{constructor(){super(...arguments),o()(this,"onLoad",(e=>{const{operation:n}=this.props,{tag:r,operationId:o}=n.toObject();let{isShownKey:s}=n.toObject();s=s||["operations",r,o],t.layoutActions.readyToScroll(s,e)}))}render(){return s.createElement("span",{ref:this.onLoad},s.createElement(e,this.props))}}},48011:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>d});var r=n(76986),o=n.n(r),s=n(63460),i=n.n(s),a=n(11882),l=n.n(a),c=n(35627),u=n.n(c),p=n(20573),h=n(43393),f=n(27504);function d(e){let{fn:t}=e;return{statePlugins:{spec:{actions:{download:e=>n=>{let{errActions:r,specSelectors:s,specActions:a,getConfigs:l}=n,{fetch:c}=t;const u=l();function p(t){if(t instanceof Error||t.status>=400)return a.updateLoadingStatus("failed"),r.newThrownErr(o()(new Error((t.message||t.statusText)+" "+e),{source:"fetch"})),void(!t.status&&t instanceof Error&&function(){try{let t;if("URL"in f.Z?t=new(i())(e):(t=document.createElement("a"),t.href=e),"https:"!==t.protocol&&"https:"===f.Z.location.protocol){const e=o()(new Error(`Possible mixed-content issue? The page was loaded over https:// but a ${t.protocol}// URL was specified. Check that you are not attempting to load mixed content.`),{source:"fetch"});return void r.newThrownErr(e)}if(t.origin!==f.Z.location.origin){const e=o()(new Error(`Possible cross-origin (CORS) issue? The URL origin (${t.origin}) does not match the page (${f.Z.location.origin}). Check the server returns the correct 'Access-Control-Allow-*' headers.`),{source:"fetch"});r.newThrownErr(e)}}catch(e){return}}());a.updateLoadingStatus("success"),a.updateSpec(t.text),s.url()!==e&&a.updateUrl(e)}e=e||s.url(),a.updateLoadingStatus("loading"),r.clear({source:"fetch"}),c({url:e,loadSpec:!0,requestInterceptor:u.requestInterceptor||(e=>e),responseInterceptor:u.responseInterceptor||(e=>e),credentials:"same-origin",headers:{Accept:"application/json,*/*"}}).then(p,p)},updateLoadingStatus:e=>{let t=[null,"loading","failed","success","failedConfig"];return-1===l()(t).call(t,e)&&console.error(`Error: ${e} is not one of ${u()(t)}`),{type:"spec_update_loading_status",payload:e}}},reducers:{spec_update_loading_status:(e,t)=>"string"==typeof t.payload?e.set("loadingStatus",t.payload):e},selectors:{loadingStatus:(0,p.P1)((e=>e||(0,h.Map)()),(e=>e.get("loadingStatus")||null))}}}}}},34966:(e,t,n)=>{"use strict";n.r(t),n.d(t,{CLEAR:()=>c,CLEAR_BY:()=>u,NEW_AUTH_ERR:()=>l,NEW_SPEC_ERR:()=>i,NEW_SPEC_ERR_BATCH:()=>a,NEW_THROWN_ERR:()=>o,NEW_THROWN_ERR_BATCH:()=>s,clear:()=>g,clearBy:()=>y,newAuthErr:()=>m,newSpecErr:()=>f,newSpecErrBatch:()=>d,newThrownErr:()=>p,newThrownErrBatch:()=>h});var r=n(7710);const o="err_new_thrown_err",s="err_new_thrown_err_batch",i="err_new_spec_err",a="err_new_spec_err_batch",l="err_new_auth_err",c="err_clear",u="err_clear_by";function p(e){return{type:o,payload:(0,r.serializeError)(e)}}function h(e){return{type:s,payload:e}}function f(e){return{type:i,payload:e}}function d(e){return{type:a,payload:e}}function m(e){return{type:l,payload:e}}function g(){return{type:c,payload:arguments.length>0&&void 0!==arguments[0]?arguments[0]:{}}}function y(){return{type:u,payload:arguments.length>0&&void 0!==arguments[0]?arguments[0]:()=>!0}}},56982:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>u});var r=n(14418),o=n.n(r),s=n(97606),i=n.n(s),a=n(54061),l=n.n(a);const c=[n(2392),n(21835)];function u(e){var t;let n={jsSpec:{}},r=l()(c,((e,t)=>{try{let r=t.transform(e,n);return o()(r).call(r,(e=>!!e))}catch(t){return console.error("Transformer error:",t),e}}),e);return i()(t=o()(r).call(r,(e=>!!e))).call(t,(e=>(!e.get("line")&&e.get("path"),e)))}},2392:(e,t,n)=>{"use strict";n.r(t),n.d(t,{transform:()=>p});var r=n(97606),o=n.n(r),s=n(11882),i=n.n(s),a=n(24278),l=n.n(a),c=n(24282),u=n.n(c);function p(e){return o()(e).call(e,(e=>{var t;let n="is not of a type(s)",r=i()(t=e.get("message")).call(t,n);if(r>-1){var o,s;let t=l()(o=e.get("message")).call(o,r+19).split(",");return e.set("message",l()(s=e.get("message")).call(s,0,r)+function(e){return u()(e).call(e,((e,t,n,r)=>n===r.length-1&&r.length>1?e+"or "+t:r[n+1]&&r.length>2?e+t+", ":r[n+1]?e+t+" ":e+t),"should be a")}(t))}return e}))}},21835:(e,t,n)=>{"use strict";n.r(t),n.d(t,{transform:()=>r});n(97606),n(11882),n(27361),n(43393);function r(e,t){let{jsSpec:n}=t;return e}},77793:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>i});var r=n(93527),o=n(34966),s=n(87667);function i(e){return{statePlugins:{err:{reducers:(0,r.default)(e),actions:o,selectors:s}}}}},93527:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>y});var r=n(76986),o=n.n(r),s=n(97606),i=n.n(s),a=n(39022),l=n.n(a),c=n(14418),u=n.n(c),p=n(2250),h=n.n(p),f=n(34966),d=n(43393),m=n(56982);let g={line:0,level:"error",message:"Unknown error"};function y(){return{[f.NEW_THROWN_ERR]:(e,t)=>{let{payload:n}=t,r=o()(g,n,{type:"thrown"});return e.update("errors",(e=>(e||(0,d.List)()).push((0,d.fromJS)(r)))).update("errors",(e=>(0,m.default)(e)))},[f.NEW_THROWN_ERR_BATCH]:(e,t)=>{let{payload:n}=t;return n=i()(n).call(n,(e=>(0,d.fromJS)(o()(g,e,{type:"thrown"})))),e.update("errors",(e=>{var t;return l()(t=e||(0,d.List)()).call(t,(0,d.fromJS)(n))})).update("errors",(e=>(0,m.default)(e)))},[f.NEW_SPEC_ERR]:(e,t)=>{let{payload:n}=t,r=(0,d.fromJS)(n);return r=r.set("type","spec"),e.update("errors",(e=>(e||(0,d.List)()).push((0,d.fromJS)(r)).sortBy((e=>e.get("line"))))).update("errors",(e=>(0,m.default)(e)))},[f.NEW_SPEC_ERR_BATCH]:(e,t)=>{let{payload:n}=t;return n=i()(n).call(n,(e=>(0,d.fromJS)(o()(g,e,{type:"spec"})))),e.update("errors",(e=>{var t;return l()(t=e||(0,d.List)()).call(t,(0,d.fromJS)(n))})).update("errors",(e=>(0,m.default)(e)))},[f.NEW_AUTH_ERR]:(e,t)=>{let{payload:n}=t,r=(0,d.fromJS)(o()({},n));return r=r.set("type","auth"),e.update("errors",(e=>(e||(0,d.List)()).push((0,d.fromJS)(r)))).update("errors",(e=>(0,m.default)(e)))},[f.CLEAR]:(e,t)=>{var n;let{payload:r}=t;if(!r||!e.get("errors"))return e;let o=u()(n=e.get("errors")).call(n,(e=>{var t;return h()(t=e.keySeq()).call(t,(t=>{const n=e.get(t),o=r[t];return!o||n!==o}))}));return e.merge({errors:o})},[f.CLEAR_BY]:(e,t)=>{var n;let{payload:r}=t;if(!r||"function"!=typeof r)return e;let o=u()(n=e.get("errors")).call(n,(e=>r(e)));return e.merge({errors:o})}}}},87667:(e,t,n)=>{"use strict";n.r(t),n.d(t,{allErrors:()=>s,lastError:()=>i});var r=n(43393),o=n(20573);const s=(0,o.P1)((e=>e),(e=>e.get("errors",(0,r.List)()))),i=(0,o.P1)(s,(e=>e.last()))},49978:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(4309);function o(){return{fn:{opsFilter:r.default}}}},4309:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>a});var r=n(14418),o=n.n(r),s=n(11882),i=n.n(s);function a(e,t){return o()(e).call(e,((e,n)=>-1!==i()(n).call(n,t)))}},47349:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>l});var r=n(67294),o=n(94184),s=n.n(o),i=n(12603);const a=e=>{let{expanded:t,children:n,onChange:o}=e;const a=(0,i.useComponent)("ChevronRightIcon"),l=(0,r.useCallback)((e=>{o(e,!t)}),[t,o]);return r.createElement("button",{type:"button",className:"json-schema-2020-12-accordion",onClick:l},r.createElement("div",{className:"json-schema-2020-12-accordion__children"},n),r.createElement("span",{className:s()("json-schema-2020-12-accordion__icon",{"json-schema-2020-12-accordion__icon--expanded":t,"json-schema-2020-12-accordion__icon--collapsed":!t})},r.createElement(a,null)))};a.defaultProps={expanded:!1};const l=a},36867:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(67294);const o=e=>{let{expanded:t,onClick:n}=e;const o=(0,r.useCallback)((e=>{n(e,!t)}),[t,n]);return r.createElement("button",{type:"button",className:"json-schema-2020-12-expand-deep-button",onClick:o},t?"Collapse all":"Expand all")}},22675:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>p});var r=n(97606),o=n.n(r),s=n(67294),i=n(94184),a=n.n(i),l=(n(16648),n(12603)),c=n(69006);const u=(0,s.forwardRef)(((e,t)=>{let{schema:n,name:r,dependentRequired:i,onExpand:u}=e;const p=(0,l.useFn)(),h=(0,l.useIsExpanded)(),f=(0,l.useIsExpandedDeeply)(),[d,m]=(0,s.useState)(h||f),[g,y]=(0,s.useState)(f),[v,b]=(0,l.useLevel)(),w=(0,l.useIsEmbedded)(),E=p.isExpandable(n)||i.length>0,x=(0,l.useIsCircular)(n),S=(0,l.useRenderedSchemas)(n),_=p.stringifyConstraints(n),j=(0,l.useComponent)("Accordion"),O=(0,l.useComponent)("Keyword$schema"),k=(0,l.useComponent)("Keyword$vocabulary"),A=(0,l.useComponent)("Keyword$id"),C=(0,l.useComponent)("Keyword$anchor"),P=(0,l.useComponent)("Keyword$dynamicAnchor"),N=(0,l.useComponent)("Keyword$ref"),I=(0,l.useComponent)("Keyword$dynamicRef"),T=(0,l.useComponent)("Keyword$defs"),R=(0,l.useComponent)("Keyword$comment"),M=(0,l.useComponent)("KeywordAllOf"),D=(0,l.useComponent)("KeywordAnyOf"),F=(0,l.useComponent)("KeywordOneOf"),L=(0,l.useComponent)("KeywordNot"),B=(0,l.useComponent)("KeywordIf"),$=(0,l.useComponent)("KeywordThen"),q=(0,l.useComponent)("KeywordElse"),U=(0,l.useComponent)("KeywordDependentSchemas"),z=(0,l.useComponent)("KeywordPrefixItems"),V=(0,l.useComponent)("KeywordItems"),W=(0,l.useComponent)("KeywordContains"),J=(0,l.useComponent)("KeywordProperties"),K=(0,l.useComponent)("KeywordPatternProperties"),H=(0,l.useComponent)("KeywordAdditionalProperties"),G=(0,l.useComponent)("KeywordPropertyNames"),Z=(0,l.useComponent)("KeywordUnevaluatedItems"),Y=(0,l.useComponent)("KeywordUnevaluatedProperties"),X=(0,l.useComponent)("KeywordType"),Q=(0,l.useComponent)("KeywordEnum"),ee=(0,l.useComponent)("KeywordConst"),te=(0,l.useComponent)("KeywordConstraint"),ne=(0,l.useComponent)("KeywordDependentRequired"),re=(0,l.useComponent)("KeywordContentSchema"),oe=(0,l.useComponent)("KeywordTitle"),se=(0,l.useComponent)("KeywordDescription"),ie=(0,l.useComponent)("KeywordDefault"),ae=(0,l.useComponent)("KeywordDeprecated"),le=(0,l.useComponent)("KeywordReadOnly"),ce=(0,l.useComponent)("KeywordWriteOnly"),ue=(0,l.useComponent)("ExpandDeepButton");(0,s.useEffect)((()=>{y(f)}),[f]),(0,s.useEffect)((()=>{y(g)}),[g]);const pe=(0,s.useCallback)(((e,t)=>{m(t),!t&&y(!1),u(e,t,!1)}),[u]),he=(0,s.useCallback)(((e,t)=>{m(t),y(t),u(e,t,!0)}),[u]);return s.createElement(c.JSONSchemaLevelContext.Provider,{value:b},s.createElement(c.JSONSchemaDeepExpansionContext.Provider,{value:g},s.createElement(c.JSONSchemaCyclesContext.Provider,{value:S},s.createElement("article",{ref:t,"data-json-schema-level":v,className:a()("json-schema-2020-12",{"json-schema-2020-12--embedded":w,"json-schema-2020-12--circular":x})},s.createElement("div",{className:"json-schema-2020-12-head"},E&&!x?s.createElement(s.Fragment,null,s.createElement(j,{expanded:d,onChange:pe},s.createElement(oe,{title:r,schema:n})),s.createElement(ue,{expanded:d,onClick:he})):s.createElement(oe,{title:r,schema:n}),s.createElement(ae,{schema:n}),s.createElement(le,{schema:n}),s.createElement(ce,{schema:n}),s.createElement(X,{schema:n,isCircular:x}),_.length>0&&o()(_).call(_,(e=>s.createElement(te,{key:`${e.scope}-${e.value}`,constraint:e})))),s.createElement("div",{className:a()("json-schema-2020-12-body",{"json-schema-2020-12-body--collapsed":!d})},d&&s.createElement(s.Fragment,null,s.createElement(se,{schema:n}),!x&&E&&s.createElement(s.Fragment,null,s.createElement(J,{schema:n}),s.createElement(K,{schema:n}),s.createElement(H,{schema:n}),s.createElement(Y,{schema:n}),s.createElement(G,{schema:n}),s.createElement(M,{schema:n}),s.createElement(D,{schema:n}),s.createElement(F,{schema:n}),s.createElement(L,{schema:n}),s.createElement(B,{schema:n}),s.createElement($,{schema:n}),s.createElement(q,{schema:n}),s.createElement(U,{schema:n}),s.createElement(z,{schema:n}),s.createElement(V,{schema:n}),s.createElement(Z,{schema:n}),s.createElement(W,{schema:n}),s.createElement(re,{schema:n})),s.createElement(Q,{schema:n}),s.createElement(ee,{schema:n}),s.createElement(ne,{schema:n,dependentRequired:i}),s.createElement(ie,{schema:n}),s.createElement(O,{schema:n}),s.createElement(k,{schema:n}),s.createElement(A,{schema:n}),s.createElement(C,{schema:n}),s.createElement(P,{schema:n}),s.createElement(N,{schema:n}),!x&&E&&s.createElement(T,{schema:n}),s.createElement(I,{schema:n}),s.createElement(R,{schema:n})))))))}));u.defaultProps={name:"",dependentRequired:[],onExpand:()=>{}};const p=u},12260:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(67294);const o=()=>r.createElement("svg",{xmlns:"http://www.w3.org/2000/svg",width:"24",height:"24",viewBox:"0 0 24 24"},r.createElement("path",{d:"M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"}))},64922:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(67294);n(16648);const o=e=>{let{schema:t}=e;return null!=t&&t.$anchor?r.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--$anchor"},r.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary"},"$anchor"),r.createElement("span",{className:"json-schema-2020-12-keyword__value json-schema-2020-12-keyword__value--secondary"},t.$anchor)):null}},4685:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(67294);n(16648);const o=e=>{let{schema:t}=e;return null!=t&&t.$comment?r.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--$comment"},r.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary"},"$comment"),r.createElement("span",{className:"json-schema-2020-12-keyword__value json-schema-2020-12-keyword__value--secondary"},t.$comment)):null}},36418:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>d});var r=n(28222),o=n.n(r),s=n(97606),i=n.n(s),a=n(2018),l=n.n(a),c=n(67294),u=n(94184),p=n.n(u),h=(n(16648),n(12603)),f=n(69006);const d=e=>{var t;let{schema:n}=e;const r=(null==n?void 0:n.$defs)||{},s=(0,h.useIsExpandedDeeply)(),[a,u]=(0,c.useState)(s),[d,m]=(0,c.useState)(!1),g=(0,h.useComponent)("Accordion"),y=(0,h.useComponent)("ExpandDeepButton"),v=(0,h.useComponent)("JSONSchema"),b=(0,c.useCallback)((()=>{u((e=>!e))}),[]),w=(0,c.useCallback)(((e,t)=>{u(t),m(t)}),[]);return 0===o()(r).length?null:c.createElement(f.JSONSchemaDeepExpansionContext.Provider,{value:d},c.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--$defs"},c.createElement(g,{expanded:a,onChange:b},c.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary"},"$defs")),c.createElement(y,{expanded:a,onClick:w}),c.createElement("strong",{className:"json-schema-2020-12__attribute json-schema-2020-12__attribute--primary"},"object"),c.createElement("ul",{className:p()("json-schema-2020-12-keyword__children",{"json-schema-2020-12-keyword__children--collapsed":!a})},a&&c.createElement(c.Fragment,null,i()(t=l()(r)).call(t,(e=>{let[t,n]=e;return c.createElement("li",{key:t,className:"json-schema-2020-12-property"},c.createElement(v,{name:t,schema:n}))}))))))}},51338:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(67294);n(16648);const o=e=>{let{schema:t}=e;return null!=t&&t.$dynamicAnchor?r.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--$dynamicAnchor"},r.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary"},"$dynamicAnchor"),r.createElement("span",{className:"json-schema-2020-12-keyword__value json-schema-2020-12-keyword__value--secondary"},t.$dynamicAnchor)):null}},27655:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(67294);n(16648);const o=e=>{let{schema:t}=e;return null!=t&&t.$dynamicRef?r.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--$dynamicRef"},r.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary"},"$dynamicRef"),r.createElement("span",{className:"json-schema-2020-12-keyword__value json-schema-2020-12-keyword__value--secondary"},t.$dynamicRef)):null}},93460:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(67294);n(16648);const o=e=>{let{schema:t}=e;return null!=t&&t.$id?r.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--$id"},r.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary"},"$id"),r.createElement("span",{className:"json-schema-2020-12-keyword__value json-schema-2020-12-keyword__value--secondary"},t.$id)):null}},72348:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(67294);n(16648);const o=e=>{let{schema:t}=e;return null!=t&&t.$ref?r.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--$ref"},r.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary"},"$ref"),r.createElement("span",{className:"json-schema-2020-12-keyword__value json-schema-2020-12-keyword__value--secondary"},t.$ref)):null}},69359:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(67294);n(16648);const o=e=>{let{schema:t}=e;return null!=t&&t.$schema?r.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--$schema"},r.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary"},"$schema"),r.createElement("span",{className:"json-schema-2020-12-keyword__value json-schema-2020-12-keyword__value--secondary"},t.$schema)):null}},7568:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>p});var r=n(97606),o=n.n(r),s=n(2018),i=n.n(s),a=n(67294),l=n(94184),c=n.n(l),u=(n(16648),n(12603));const p=e=>{var t;let{schema:n}=e;const r=(0,u.useIsExpandedDeeply)(),[s,l]=(0,a.useState)(r),p=(0,u.useComponent)("Accordion"),h=(0,a.useCallback)((()=>{l((e=>!e))}),[]);return null!=n&&n.$vocabulary?"object"!=typeof n.$vocabulary?null:a.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--$vocabulary"},a.createElement(p,{expanded:s,onChange:h},a.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary"},"$vocabulary")),a.createElement("strong",{className:"json-schema-2020-12__attribute json-schema-2020-12__attribute--primary"},"object"),a.createElement("ul",null,s&&o()(t=i()(n.$vocabulary)).call(t,(e=>{let[t,n]=e;return a.createElement("li",{key:t,className:c()("json-schema-2020-12-$vocabulary-uri",{"json-schema-2020-12-$vocabulary-uri--disabled":!n})},a.createElement("span",{className:"json-schema-2020-12-keyword__value json-schema-2020-12-keyword__value--secondary"},t))})))):null}},65253:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>s});var r=n(67294),o=(n(16648),n(12603));const s=e=>{let{schema:t}=e;const n=(0,o.useFn)(),{additionalProperties:s}=t,i=(0,o.useComponent)("JSONSchema");if(!n.hasKeyword(t,"additionalProperties"))return null;const a=r.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"},"Additional properties");return r.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--additionalProperties"},!0===s?r.createElement(r.Fragment,null,a,r.createElement("span",{className:"json-schema-2020-12__attribute json-schema-2020-12__attribute--primary"},"allowed")):!1===s?r.createElement(r.Fragment,null,a,r.createElement("span",{className:"json-schema-2020-12__attribute json-schema-2020-12__attribute--primary"},"forbidden")):r.createElement(i,{name:a,schema:s}))}},46457:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>h});var r=n(58309),o=n.n(r),s=n(97606),i=n.n(s),a=n(67294),l=n(94184),c=n.n(l),u=(n(16648),n(12603)),p=n(69006);const h=e=>{let{schema:t}=e;const n=(null==t?void 0:t.allOf)||[],r=(0,u.useFn)(),s=(0,u.useIsExpandedDeeply)(),[l,h]=(0,a.useState)(s),[f,d]=(0,a.useState)(!1),m=(0,u.useComponent)("Accordion"),g=(0,u.useComponent)("ExpandDeepButton"),y=(0,u.useComponent)("JSONSchema"),v=(0,u.useComponent)("KeywordType"),b=(0,a.useCallback)((()=>{h((e=>!e))}),[]),w=(0,a.useCallback)(((e,t)=>{h(t),d(t)}),[]);return o()(n)&&0!==n.length?a.createElement(p.JSONSchemaDeepExpansionContext.Provider,{value:f},a.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--allOf"},a.createElement(m,{expanded:l,onChange:b},a.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"},"All of")),a.createElement(g,{expanded:l,onClick:w}),a.createElement(v,{schema:{allOf:n}}),a.createElement("ul",{className:c()("json-schema-2020-12-keyword__children",{"json-schema-2020-12-keyword__children--collapsed":!l})},l&&a.createElement(a.Fragment,null,i()(n).call(n,((e,t)=>a.createElement("li",{key:`#${t}`,className:"json-schema-2020-12-property"},a.createElement(y,{name:`#${t} ${r.getTitle(e)}`,schema:e})))))))):null}},8776:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>h});var r=n(58309),o=n.n(r),s=n(97606),i=n.n(s),a=n(67294),l=n(94184),c=n.n(l),u=(n(16648),n(12603)),p=n(69006);const h=e=>{let{schema:t}=e;const n=(null==t?void 0:t.anyOf)||[],r=(0,u.useFn)(),s=(0,u.useIsExpandedDeeply)(),[l,h]=(0,a.useState)(s),[f,d]=(0,a.useState)(!1),m=(0,u.useComponent)("Accordion"),g=(0,u.useComponent)("ExpandDeepButton"),y=(0,u.useComponent)("JSONSchema"),v=(0,u.useComponent)("KeywordType"),b=(0,a.useCallback)((()=>{h((e=>!e))}),[]),w=(0,a.useCallback)(((e,t)=>{h(t),d(t)}),[]);return o()(n)&&0!==n.length?a.createElement(p.JSONSchemaDeepExpansionContext.Provider,{value:f},a.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--anyOf"},a.createElement(m,{expanded:l,onChange:b},a.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"},"Any of")),a.createElement(g,{expanded:l,onClick:w}),a.createElement(v,{schema:{anyOf:n}}),a.createElement("ul",{className:c()("json-schema-2020-12-keyword__children",{"json-schema-2020-12-keyword__children--collapsed":!l})},l&&a.createElement(a.Fragment,null,i()(n).call(n,((e,t)=>a.createElement("li",{key:`#${t}`,className:"json-schema-2020-12-property"},a.createElement(y,{name:`#${t} ${r.getTitle(e)}`,schema:e})))))))):null}},27308:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>s});var r=n(67294),o=(n(16648),n(12603));const s=e=>{let{schema:t}=e;const n=(0,o.useFn)();return n.hasKeyword(t,"const")?r.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--const"},r.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"},"Const"),r.createElement("span",{className:"json-schema-2020-12-keyword__value json-schema-2020-12-keyword__value--const"},n.stringify(t.const))):null}},69956:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>s});var r=n(67294);const o=e=>{let{constraint:t}=e;return r.createElement("span",{className:`json-schema-2020-12__constraint json-schema-2020-12__constraint--${t.scope}`},t.value)},s=r.memo(o)},38993:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>s});var r=n(67294),o=(n(16648),n(12603));const s=e=>{let{schema:t}=e;const n=(0,o.useFn)(),s=(0,o.useComponent)("JSONSchema");if(!n.hasKeyword(t,"contains"))return null;const i=r.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"},"Contains");return r.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--contains"},r.createElement(s,{name:i,schema:t.contains}))}},3484:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>s});var r=n(67294),o=(n(16648),n(12603));const s=e=>{let{schema:t}=e;const n=(0,o.useFn)(),s=(0,o.useComponent)("JSONSchema");if(!n.hasKeyword(t,"contentSchema"))return null;const i=r.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"},"Content schema");return r.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--contentSchema"},r.createElement(s,{name:i,schema:t.contentSchema}))}},55148:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>s});var r=n(67294),o=(n(16648),n(12603));const s=e=>{let{schema:t}=e;const n=(0,o.useFn)();return n.hasKeyword(t,"default")?r.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--default"},r.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"},"Default"),r.createElement("span",{className:"json-schema-2020-12-keyword__value json-schema-2020-12-keyword__value--const"},n.stringify(t.default))):null}},24539:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>i});var r=n(97606),o=n.n(r),s=n(67294);n(16648);const i=e=>{let{dependentRequired:t}=e;return 0===t.length?null:s.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--dependentRequired"},s.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"},"Required when defined"),s.createElement("ul",null,o()(t).call(t,(e=>s.createElement("li",{key:e},s.createElement("span",{className:"json-schema-2020-12-keyword__value json-schema-2020-12-keyword__value--warning"},e))))))}},26076:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>d});var r=n(28222),o=n.n(r),s=n(97606),i=n.n(s),a=n(2018),l=n.n(a),c=n(67294),u=n(94184),p=n.n(u),h=(n(16648),n(12603)),f=n(69006);const d=e=>{var t;let{schema:n}=e;const r=(null==n?void 0:n.dependentSchemas)||[],s=(0,h.useIsExpandedDeeply)(),[a,u]=(0,c.useState)(s),[d,m]=(0,c.useState)(!1),g=(0,h.useComponent)("Accordion"),y=(0,h.useComponent)("ExpandDeepButton"),v=(0,h.useComponent)("JSONSchema"),b=(0,c.useCallback)((()=>{u((e=>!e))}),[]),w=(0,c.useCallback)(((e,t)=>{u(t),m(t)}),[]);return"object"!=typeof r||0===o()(r).length?null:c.createElement(f.JSONSchemaDeepExpansionContext.Provider,{value:d},c.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--dependentSchemas"},c.createElement(g,{expanded:a,onChange:b},c.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"},"Dependent schemas")),c.createElement(y,{expanded:a,onClick:w}),c.createElement("strong",{className:"json-schema-2020-12__attribute json-schema-2020-12__attribute--primary"},"object"),c.createElement("ul",{className:p()("json-schema-2020-12-keyword__children",{"json-schema-2020-12-keyword__children--collapsed":!a})},a&&c.createElement(c.Fragment,null,i()(t=l()(r)).call(t,(e=>{let[t,n]=e;return c.createElement("li",{key:t,className:"json-schema-2020-12-property"},c.createElement(v,{name:t,schema:n}))}))))))}},26661:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(67294);n(16648);const o=e=>{let{schema:t}=e;return!0!==(null==t?void 0:t.deprecated)?null:r.createElement("span",{className:"json-schema-2020-12__attribute json-schema-2020-12__attribute--warning"},"deprecated")}},79446:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(67294);n(16648);const o=e=>{let{schema:t}=e;return null!=t&&t.description?r.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--description"},r.createElement("div",{className:"json-schema-2020-12-core-keyword__value json-schema-2020-12-core-keyword__value--secondary"},t.description)):null}},67207:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>s});var r=n(67294),o=(n(16648),n(12603));const s=e=>{let{schema:t}=e;const n=(0,o.useFn)(),s=(0,o.useComponent)("JSONSchema");if(!n.hasKeyword(t,"else"))return null;const i=r.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"},"Else");return r.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--if"},r.createElement(s,{name:i,schema:t.else}))}},91805:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>c});var r=n(58309),o=n.n(r),s=n(97606),i=n.n(s),a=n(67294),l=(n(16648),n(12603));const c=e=>{var t;let{schema:n}=e;const r=(0,l.useFn)();return o()(null==n?void 0:n.enum)?a.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--enum"},a.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"},"Allowed values"),a.createElement("ul",null,i()(t=n.enum).call(t,(e=>{const t=r.stringify(e);return a.createElement("li",{key:t},a.createElement("span",{className:"json-schema-2020-12-keyword__value json-schema-2020-12-keyword__value--const"},t))})))):null}},40487:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>s});var r=n(67294),o=(n(16648),n(12603));const s=e=>{let{schema:t}=e;const n=(0,o.useFn)(),s=(0,o.useComponent)("JSONSchema");if(!n.hasKeyword(t,"if"))return null;const i=r.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"},"If");return r.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--if"},r.createElement(s,{name:i,schema:t.if}))}},89206:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>s});var r=n(67294),o=(n(16648),n(12603));const s=e=>{let{schema:t}=e;const n=(0,o.useFn)(),s=(0,o.useComponent)("JSONSchema");if(!n.hasKeyword(t,"items"))return null;const i=r.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"},"Items");return r.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--items"},r.createElement(s,{name:i,schema:t.items}))}},65174:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>s});var r=n(67294),o=(n(16648),n(12603));const s=e=>{let{schema:t}=e;const n=(0,o.useFn)(),s=(0,o.useComponent)("JSONSchema");if(!n.hasKeyword(t,"not"))return null;const i=r.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"},"Not");return r.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--not"},r.createElement(s,{name:i,schema:t.not}))}},13834:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>h});var r=n(58309),o=n.n(r),s=n(97606),i=n.n(s),a=n(67294),l=n(94184),c=n.n(l),u=(n(16648),n(12603)),p=n(69006);const h=e=>{let{schema:t}=e;const n=(null==t?void 0:t.oneOf)||[],r=(0,u.useFn)(),s=(0,u.useIsExpandedDeeply)(),[l,h]=(0,a.useState)(s),[f,d]=(0,a.useState)(!1),m=(0,u.useComponent)("Accordion"),g=(0,u.useComponent)("ExpandDeepButton"),y=(0,u.useComponent)("JSONSchema"),v=(0,u.useComponent)("KeywordType"),b=(0,a.useCallback)((()=>{h((e=>!e))}),[]),w=(0,a.useCallback)(((e,t)=>{h(t),d(t)}),[]);return o()(n)&&0!==n.length?a.createElement(p.JSONSchemaDeepExpansionContext.Provider,{value:f},a.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--oneOf"},a.createElement(m,{expanded:l,onChange:b},a.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"},"One of")),a.createElement(g,{expanded:l,onClick:w}),a.createElement(v,{schema:{oneOf:n}}),a.createElement("ul",{className:c()("json-schema-2020-12-keyword__children",{"json-schema-2020-12-keyword__children--collapsed":!l})},l&&a.createElement(a.Fragment,null,i()(n).call(n,((e,t)=>a.createElement("li",{key:`#${t}`,className:"json-schema-2020-12-property"},a.createElement(y,{name:`#${t} ${r.getTitle(e)}`,schema:e})))))))):null}},36746:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>p});var r=n(28222),o=n.n(r),s=n(97606),i=n.n(s),a=n(2018),l=n.n(a),c=n(67294),u=(n(16648),n(12603));const p=e=>{var t;let{schema:n}=e;const r=(null==n?void 0:n.patternProperties)||{},s=(0,u.useComponent)("JSONSchema");return 0===o()(r).length?null:c.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--patternProperties"},c.createElement("ul",null,i()(t=l()(r)).call(t,(e=>{let[t,n]=e;return c.createElement("li",{key:t,className:"json-schema-2020-12-property"},c.createElement(s,{name:t,schema:n}))}))))}},93971:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>h});var r=n(58309),o=n.n(r),s=n(97606),i=n.n(s),a=n(67294),l=n(94184),c=n.n(l),u=(n(16648),n(12603)),p=n(69006);const h=e=>{let{schema:t}=e;const n=(null==t?void 0:t.prefixItems)||[],r=(0,u.useFn)(),s=(0,u.useIsExpandedDeeply)(),[l,h]=(0,a.useState)(s),[f,d]=(0,a.useState)(!1),m=(0,u.useComponent)("Accordion"),g=(0,u.useComponent)("ExpandDeepButton"),y=(0,u.useComponent)("JSONSchema"),v=(0,u.useComponent)("KeywordType"),b=(0,a.useCallback)((()=>{h((e=>!e))}),[]),w=(0,a.useCallback)(((e,t)=>{h(t),d(t)}),[]);return o()(n)&&0!==n.length?a.createElement(p.JSONSchemaDeepExpansionContext.Provider,{value:f},a.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--prefixItems"},a.createElement(m,{expanded:l,onChange:b},a.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"},"Prefix items")),a.createElement(g,{expanded:l,onClick:w}),a.createElement(v,{schema:{prefixItems:n}}),a.createElement("ul",{className:c()("json-schema-2020-12-keyword__children",{"json-schema-2020-12-keyword__children--collapsed":!l})},l&&a.createElement(a.Fragment,null,i()(n).call(n,((e,t)=>a.createElement("li",{key:`#${t}`,className:"json-schema-2020-12-property"},a.createElement(y,{name:`#${t} ${r.getTitle(e)}`,schema:e})))))))):null}},25472:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>y});var r=n(58309),o=n.n(r),s=n(28222),i=n.n(s),a=n(97606),l=n.n(a),c=n(2018),u=n.n(c),p=n(58118),h=n.n(p),f=n(67294),d=n(94184),m=n.n(d),g=(n(16648),n(12603));const y=e=>{var t;let{schema:n}=e;const r=(0,g.useFn)(),s=(null==n?void 0:n.properties)||{},a=o()(null==n?void 0:n.required)?n.required:[],c=(0,g.useComponent)("JSONSchema");return 0===i()(s).length?null:f.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--properties"},f.createElement("ul",null,l()(t=u()(s)).call(t,(e=>{let[t,o]=e;const s=h()(a).call(a,t),i=r.getDependentRequired(t,n);return f.createElement("li",{key:t,className:m()("json-schema-2020-12-property",{"json-schema-2020-12-property--required":s})},f.createElement(c,{name:t,schema:o,dependentRequired:i}))}))))}},42338:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>s});var r=n(67294),o=(n(16648),n(12603));const s=e=>{let{schema:t}=e;const n=(0,o.useFn)(),{propertyNames:s}=t,i=(0,o.useComponent)("JSONSchema"),a=r.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"},"Property names");return n.hasKeyword(t,"propertyNames")?r.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--propertyNames"},r.createElement(i,{name:a,schema:s})):null}},16456:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(67294);n(16648);const o=e=>{let{schema:t}=e;return!0!==(null==t?void 0:t.readOnly)?null:r.createElement("span",{className:"json-schema-2020-12__attribute json-schema-2020-12__attribute--muted"},"read-only")}},67401:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>s});var r=n(67294),o=(n(16648),n(12603));const s=e=>{let{schema:t}=e;const n=(0,o.useFn)(),s=(0,o.useComponent)("JSONSchema");if(!n.hasKeyword(t,"then"))return null;const i=r.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"},"Then");return r.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--then"},r.createElement(s,{name:i,schema:t.then}))}},78137:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>i});var r=n(67294),o=(n(16648),n(12603));const s=e=>{let{title:t,schema:n}=e;const s=(0,o.useFn)();return t||s.getTitle(n)?r.createElement("div",{className:"json-schema-2020-12__title"},t||s.getTitle(n)):null};s.defaultProps={title:""};const i=s},22285:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>i});var r=n(67294),o=(n(16648),n(12603));const s=e=>{let{schema:t,isCircular:n}=e;const s=(0,o.useFn)().getType(t),i=n?" [circular]":"";return r.createElement("strong",{className:"json-schema-2020-12__attribute json-schema-2020-12__attribute--primary"},`${s}${i}`)};s.defaultProps={isCircular:!1};const i=s},85828:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>s});var r=n(67294),o=(n(16648),n(12603));const s=e=>{let{schema:t}=e;const n=(0,o.useFn)(),{unevaluatedItems:s}=t,i=(0,o.useComponent)("JSONSchema");if(!n.hasKeyword(t,"unevaluatedItems"))return null;const a=r.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"},"Unevaluated items");return r.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--unevaluatedItems"},r.createElement(i,{name:a,schema:s}))}},6907:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>s});var r=n(67294),o=(n(16648),n(12603));const s=e=>{let{schema:t}=e;const n=(0,o.useFn)(),{unevaluatedProperties:s}=t,i=(0,o.useComponent)("JSONSchema");if(!n.hasKeyword(t,"unevaluatedProperties"))return null;const a=r.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"},"Unevaluated properties");return r.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--unevaluatedProperties"},r.createElement(i,{name:a,schema:s}))}},15789:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(67294);n(16648);const o=e=>{let{schema:t}=e;return!0!==(null==t?void 0:t.writeOnly)?null:r.createElement("span",{className:"json-schema-2020-12__attribute json-schema-2020-12__attribute--muted"},"write-only")}},69006:(e,t,n)=>{"use strict";n.r(t),n.d(t,{JSONSchemaContext:()=>i,JSONSchemaCyclesContext:()=>c,JSONSchemaDeepExpansionContext:()=>l,JSONSchemaLevelContext:()=>a});var r=n(82737),o=n.n(r),s=n(67294);const i=(0,s.createContext)(null);i.displayName="JSONSchemaContext";const a=(0,s.createContext)(0);a.displayName="JSONSchemaLevelContext";const l=(0,s.createContext)(!1);l.displayName="JSONSchemaDeepExpansionContext";const c=(0,s.createContext)(new(o()))},33499:(e,t,n)=>{"use strict";n.r(t),n.d(t,{getDependentRequired:()=>F,getTitle:()=>C,getType:()=>P,hasKeyword:()=>I,isBooleanJSONSchema:()=>N,isExpandable:()=>T,stringify:()=>R,stringifyConstraints:()=>D,upperFirst:()=>A});var r=n(24278),o=n.n(r),s=n(19030),i=n.n(s),a=n(58309),l=n.n(a),c=n(97606),u=n.n(c),p=n(58118),h=n.n(p),f=n(91086),d=n.n(f),m=n(14418),g=n.n(m),y=n(35627),v=n.n(y),b=n(25110),w=n.n(b),E=n(24282),x=n.n(E),S=n(2018),_=n.n(S),j=n(82737),O=n.n(j),k=n(12603);const A=e=>"string"==typeof e?`${e.charAt(0).toUpperCase()}${o()(e).call(e,1)}`:e,C=e=>{const t=(0,k.useFn)();return null!=e&&e.title?t.upperFirst(e.title):null!=e&&e.$anchor?t.upperFirst(e.$anchor):null!=e&&e.$id?e.$id:""},P=function(e){var t,n;let r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:new(i());const o=(0,k.useFn)();if(null==e)return"any";if(o.isBooleanJSONSchema(e))return e?"any":"never";if("object"!=typeof e)return"any";if(r.has(e))return"any";r.add(e);const{type:s,prefixItems:a,items:c}=e,p=()=>{if(l()(a)){const e=u()(a).call(a,(e=>P(e,r))),t=c?P(c,r):"any";return`array<[${e.join(", ")}], ${t}>`}if(c){return`array<${P(c,r)}>`}return"array"};if(e.not&&"any"===P(e.not))return"never";const f=l()(s)?u()(s).call(s,(e=>"array"===e?p():e)).join(" | "):"array"===s?p():h()(t=["null","boolean","object","array","number","string"]).call(t,s)?s:(()=>{var t,n;if(Object.hasOwn(e,"prefixItems")||Object.hasOwn(e,"items")||Object.hasOwn(e,"contains"))return p();if(Object.hasOwn(e,"properties")||Object.hasOwn(e,"additionalProperties")||Object.hasOwn(e,"patternProperties"))return"object";if(h()(t=["int32","int64"]).call(t,e.format))return"integer";if(h()(n=["float","double"]).call(n,e.format))return"number";if(Object.hasOwn(e,"minimum")||Object.hasOwn(e,"maximum")||Object.hasOwn(e,"exclusiveMinimum")||Object.hasOwn(e,"exclusiveMaximum")||Object.hasOwn(e,"multipleOf"))return"number | integer";if(Object.hasOwn(e,"pattern")||Object.hasOwn(e,"format")||Object.hasOwn(e,"minLength")||Object.hasOwn(e,"maxLength"))return"string";if(void 0!==e.const){if(null===e.const)return"null";if("boolean"==typeof e.const)return"boolean";if("number"==typeof e.const)return d()(e.const)?"integer":"number";if("string"==typeof e.const)return"string";if(l()(e.const))return"array";if("object"==typeof e.const)return"object"}return null})(),m=(t,n)=>{if(l()(e[t])){var o;return`(${u()(o=e[t]).call(o,(e=>P(e,r))).join(n)})`}return null},y=m("oneOf"," | "),v=m("anyOf"," | "),b=m("allOf"," & "),w=g()(n=[f,y,v,b]).call(n,Boolean).join(" | ");return r.delete(e),w||"any"},N=e=>"boolean"==typeof e,I=(e,t)=>null!==e&&"object"==typeof e&&Object.hasOwn(e,t),T=e=>{const t=(0,k.useFn)();return(null==e?void 0:e.$schema)||(null==e?void 0:e.$vocabulary)||(null==e?void 0:e.$id)||(null==e?void 0:e.$anchor)||(null==e?void 0:e.$dynamicAnchor)||(null==e?void 0:e.$ref)||(null==e?void 0:e.$dynamicRef)||(null==e?void 0:e.$defs)||(null==e?void 0:e.$comment)||(null==e?void 0:e.allOf)||(null==e?void 0:e.anyOf)||(null==e?void 0:e.oneOf)||t.hasKeyword(e,"not")||t.hasKeyword(e,"if")||t.hasKeyword(e,"then")||t.hasKeyword(e,"else")||(null==e?void 0:e.dependentSchemas)||(null==e?void 0:e.prefixItems)||t.hasKeyword(e,"items")||t.hasKeyword(e,"contains")||(null==e?void 0:e.properties)||(null==e?void 0:e.patternProperties)||t.hasKeyword(e,"additionalProperties")||t.hasKeyword(e,"propertyNames")||t.hasKeyword(e,"unevaluatedItems")||t.hasKeyword(e,"unevaluatedProperties")||(null==e?void 0:e.description)||(null==e?void 0:e.enum)||t.hasKeyword(e,"const")||t.hasKeyword(e,"contentSchema")||t.hasKeyword(e,"default")},R=e=>{var t;return null===e||h()(t=["number","bigint","boolean"]).call(t,typeof e)?String(e):l()(e)?`[${u()(e).call(e,R).join(", ")}]`:v()(e)},M=(e,t,n)=>{const r="number"==typeof t,o="number"==typeof n;return r&&o?t===n?`${t} ${e}`:`[${t}, ${n}] ${e}`:r?`>= ${t} ${e}`:o?`<= ${n} ${e}`:null},D=e=>{const t=[],n=(e=>{if("number"!=typeof(null==e?void 0:e.multipleOf))return null;if(e.multipleOf<=0)return null;if(1===e.multipleOf)return null;const{multipleOf:t}=e;if(d()(t))return`multiple of ${t}`;const n=10**t.toString().split(".")[1].length;return`multiple of ${t*n}/${n}`})(e);null!==n&&t.push({scope:"number",value:n});const r=(e=>{const t=null==e?void 0:e.minimum,n=null==e?void 0:e.maximum,r=null==e?void 0:e.exclusiveMinimum,o=null==e?void 0:e.exclusiveMaximum,s="number"==typeof t,i="number"==typeof n,a="number"==typeof r&&to;if(s&&i)return`${a?"(":"["}${a?r:t}, ${l?o:n}${l?")":"]"}`;if(s)return`${a?">":"≥"} ${a?r:t}`;if(i)return`${l?"<":"≤"} ${l?o:n}`;return null})(e);null!==r&&t.push({scope:"number",value:r}),null!=e&&e.format&&t.push({scope:"string",value:e.format});const o=M("characters",null==e?void 0:e.minLength,null==e?void 0:e.maxLength);null!==o&&t.push({scope:"string",value:o}),null!=e&&e.pattern&&t.push({scope:"string",value:`matches ${null==e?void 0:e.pattern}`}),null!=e&&e.contentMediaType&&t.push({scope:"string",value:`media type: ${e.contentMediaType}`}),null!=e&&e.contentEncoding&&t.push({scope:"string",value:`encoding: ${e.contentEncoding}`});const s=M(null!=e&&e.hasUniqueItems?"unique items":"items",null==e?void 0:e.minItems,null==e?void 0:e.maxItems);null!==s&&t.push({scope:"array",value:s});const i=M("contained items",null==e?void 0:e.minContains,null==e?void 0:e.maxContains);null!==i&&t.push({scope:"array",value:i});const a=M("properties",null==e?void 0:e.minProperties,null==e?void 0:e.maxProperties);return null!==a&&t.push({scope:"object",value:a}),t},F=(e,t)=>{var n;return null!=t&&t.dependentRequired?w()(x()(n=_()(t.dependentRequired)).call(n,((t,n)=>{let[r,o]=n;return l()(o)&&h()(o).call(o,e)?(t.add(r),t):t}),new(O()))):[]}},65077:(e,t,n)=>{"use strict";n.r(t),n.d(t,{withJSONSchemaContext:()=>H});var r=n(67294),o=n(22675),s=n(69359),i=n(7568),a=n(93460),l=n(64922),c=n(51338),u=n(72348),p=n(27655),h=n(36418),f=n(4685),d=n(46457),m=n(8776),g=n(13834),y=n(65174),v=n(40487),b=n(67401),w=n(67207),E=n(26076),x=n(93971),S=n(89206),_=n(38993),j=n(25472),O=n(36746),k=n(65253),A=n(42338),C=n(85828),P=n(6907),N=n(22285),I=n(91805),T=n(27308),R=n(69956),M=n(24539),D=n(3484),F=n(78137),L=n(79446),B=n(55148),$=n(26661),q=n(16456),U=n(15789),z=n(47349),V=n(36867),W=n(12260),J=n(69006),K=n(33499);const H=function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};const n={components:{JSONSchema:o.default,Keyword$schema:s.default,Keyword$vocabulary:i.default,Keyword$id:a.default,Keyword$anchor:l.default,Keyword$dynamicAnchor:c.default,Keyword$ref:u.default,Keyword$dynamicRef:p.default,Keyword$defs:h.default,Keyword$comment:f.default,KeywordAllOf:d.default,KeywordAnyOf:m.default,KeywordOneOf:g.default,KeywordNot:y.default,KeywordIf:v.default,KeywordThen:b.default,KeywordElse:w.default,KeywordDependentSchemas:E.default,KeywordPrefixItems:x.default,KeywordItems:S.default,KeywordContains:_.default,KeywordProperties:j.default,KeywordPatternProperties:O.default,KeywordAdditionalProperties:k.default,KeywordPropertyNames:A.default,KeywordUnevaluatedItems:C.default,KeywordUnevaluatedProperties:P.default,KeywordType:N.default,KeywordEnum:I.default,KeywordConst:T.default,KeywordConstraint:R.default,KeywordDependentRequired:M.default,KeywordContentSchema:D.default,KeywordTitle:F.default,KeywordDescription:L.default,KeywordDefault:B.default,KeywordDeprecated:$.default,KeywordReadOnly:q.default,KeywordWriteOnly:U.default,Accordion:z.default,ExpandDeepButton:V.default,ChevronRightIcon:W.default,...t.components},config:{default$schema:"https://json-schema.org/draft/2020-12/schema",defaultExpandedLevels:0,...t.config},fn:{upperFirst:K.upperFirst,getTitle:K.getTitle,getType:K.getType,isBooleanJSONSchema:K.isBooleanJSONSchema,hasKeyword:K.hasKeyword,isExpandable:K.isExpandable,stringify:K.stringify,stringifyConstraints:K.stringifyConstraints,getDependentRequired:K.getDependentRequired,...t.fn}},H=t=>r.createElement(J.JSONSchemaContext.Provider,{value:n},r.createElement(e,t));return H.contexts={JSONSchemaContext:J.JSONSchemaContext},H.displayName=e.displayName,H}},12603:(e,t,n)=>{"use strict";n.r(t),n.d(t,{useComponent:()=>l,useConfig:()=>a,useFn:()=>c,useIsCircular:()=>m,useIsEmbedded:()=>p,useIsExpanded:()=>h,useIsExpandedDeeply:()=>f,useLevel:()=>u,useRenderedSchemas:()=>d});var r=n(82737),o=n.n(r),s=n(67294),i=n(69006);const a=()=>{const{config:e}=(0,s.useContext)(i.JSONSchemaContext);return e},l=e=>{const{components:t}=(0,s.useContext)(i.JSONSchemaContext);return t[e]||null},c=function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:void 0;const{fn:t}=(0,s.useContext)(i.JSONSchemaContext);return void 0!==e?t[e]:t},u=()=>{const e=(0,s.useContext)(i.JSONSchemaLevelContext);return[e,e+1]},p=()=>{const[e]=u();return e>0},h=()=>{const[e]=u(),{defaultExpandedLevels:t}=a();return t-e>0},f=()=>(0,s.useContext)(i.JSONSchemaDeepExpansionContext),d=function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:void 0;if(void 0===e)return(0,s.useContext)(i.JSONSchemaCyclesContext);const t=(0,s.useContext)(i.JSONSchemaCyclesContext);return new(o())([...t,e])},m=e=>d().has(e)},97139:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>Z});var r=n(22675),o=n(69359),s=n(7568),i=n(93460),a=n(64922),l=n(51338),c=n(72348),u=n(27655),p=n(36418),h=n(4685),f=n(46457),d=n(8776),m=n(13834),g=n(65174),y=n(40487),v=n(67401),b=n(67207),w=n(26076),E=n(93971),x=n(89206),S=n(38993),_=n(25472),j=n(36746),O=n(65253),k=n(42338),A=n(85828),C=n(6907),P=n(22285),N=n(91805),I=n(27308),T=n(69956),R=n(24539),M=n(3484),D=n(78137),F=n(79446),L=n(55148),B=n(26661),$=n(16456),q=n(15789),U=n(47349),z=n(36867),V=n(12260),W=n(33499),J=n(78591),K=n(69006),H=n(12603),G=n(65077);const Z=()=>({components:{JSONSchema202012:r.default,JSONSchema202012Keyword$schema:o.default,JSONSchema202012Keyword$vocabulary:s.default,JSONSchema202012Keyword$id:i.default,JSONSchema202012Keyword$anchor:a.default,JSONSchema202012Keyword$dynamicAnchor:l.default,JSONSchema202012Keyword$ref:c.default,JSONSchema202012Keyword$dynamicRef:u.default,JSONSchema202012Keyword$defs:p.default,JSONSchema202012Keyword$comment:h.default,JSONSchema202012KeywordAllOf:f.default,JSONSchema202012KeywordAnyOf:d.default,JSONSchema202012KeywordOneOf:m.default,JSONSchema202012KeywordNot:g.default,JSONSchema202012KeywordIf:y.default,JSONSchema202012KeywordThen:v.default,JSONSchema202012KeywordElse:b.default,JSONSchema202012KeywordDependentSchemas:w.default,JSONSchema202012KeywordPrefixItems:E.default,JSONSchema202012KeywordItems:x.default,JSONSchema202012KeywordContains:S.default,JSONSchema202012KeywordProperties:_.default,JSONSchema202012KeywordPatternProperties:j.default,JSONSchema202012KeywordAdditionalProperties:O.default,JSONSchema202012KeywordPropertyNames:k.default,JSONSchema202012KeywordUnevaluatedItems:A.default,JSONSchema202012KeywordUnevaluatedProperties:C.default,JSONSchema202012KeywordType:P.default,JSONSchema202012KeywordEnum:N.default,JSONSchema202012KeywordConst:I.default,JSONSchema202012KeywordConstraint:T.default,JSONSchema202012KeywordDependentRequired:R.default,JSONSchema202012KeywordContentSchema:M.default,JSONSchema202012KeywordTitle:D.default,JSONSchema202012KeywordDescription:F.default,JSONSchema202012KeywordDefault:L.default,JSONSchema202012KeywordDeprecated:B.default,JSONSchema202012KeywordReadOnly:$.default,JSONSchema202012KeywordWriteOnly:q.default,JSONSchema202012Accordion:U.default,JSONSchema202012ExpandDeepButton:z.default,JSONSchema202012ChevronRightIcon:V.default,withJSONSchema202012Context:G.withJSONSchemaContext,JSONSchema202012DeepExpansionContext:()=>K.JSONSchemaDeepExpansionContext},fn:{upperFirst:W.upperFirst,jsonSchema202012:{isExpandable:W.isExpandable,hasKeyword:W.hasKeyword,useFn:H.useFn,useConfig:H.useConfig,useComponent:H.useComponent,useIsExpandedDeeply:H.useIsExpandedDeeply,sampleFromSchema:J.sampleFromSchema,sampleFromSchemaGeneric:J.sampleFromSchemaGeneric,sampleEncoderAPI:J.encoderAPI,sampleFormatAPI:J.formatAPI,sampleMediaTypeAPI:J.mediaTypeAPI,createXMLExample:J.createXMLExample,memoizedSampleFromSchema:J.memoizedSampleFromSchema,memoizedCreateXMLExample:J.memoizedCreateXMLExample}}})},16648:(e,t,n)=>{"use strict";n.r(t),n.d(t,{booleanSchema:()=>i,objectSchema:()=>s,schema:()=>a});var r=n(45697),o=n.n(r);const s=o().object,i=o().bool,a=o().oneOfType([s,i])},9507:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>s});const r=new(n(70674).default),o=(e,t)=>"function"==typeof t?r.register(e,t):null===t?r.unregister(e):r.get(e);o.getDefaults=()=>r.defaults;const s=o},22906:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});const r=new(n(14215).default),o=(e,t)=>"function"==typeof t?r.register(e,t):null===t?r.unregister(e):r.get(e)},90537:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>s});const r=new(n(43782).default),o=(e,t)=>{if("function"==typeof t)return r.register(e,t);if(null===t)return r.unregister(e);const n=e.split(";").at(0),o=`${n.split("/").at(0)}/*`;return r.get(e)||r.get(n)||r.get(o)};o.getDefaults=()=>r.defaults;const s=o},70674:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>w});var r=n(61125),o=n.n(r),s=n(47667),i=n.n(s),a=n(28886),l=n.n(a),c=n(14215),u=n(41433),p=n(58509),h=n(44366),f=n(65037),d=n(5709),m=n(54180),g=n(91967);function y(e,t,n){!function(e,t){if(t.has(e))throw new TypeError("Cannot initialize the same private elements twice on an object")}(e,t),t.set(e,n)}var v=new(l());class b extends c.default{constructor(){super(...arguments),y(this,v,{writable:!0,value:{"7bit":u.default,"8bit":p.default,binary:h.default,"quoted-printable":f.default,base16:d.default,base32:m.default,base64:g.default}}),o()(this,"data",{...i()(this,v)})}get defaults(){return{...i()(this,v)}}}const w=b},43782:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>v});var r=n(61125),o=n.n(r),s=n(47667),i=n.n(s),a=n(28886),l=n.n(a),c=n(14215),u=n(65378),p=n(46724),h=n(54342),f=n(92974),d=n(2672);function m(e,t,n){!function(e,t){if(t.has(e))throw new TypeError("Cannot initialize the same private elements twice on an object")}(e,t),t.set(e,n)}var g=new(l());class y extends c.default{constructor(){super(...arguments),m(this,g,{writable:!0,value:{...u.default,...p.default,...h.default,...f.default,...d.default}}),o()(this,"data",{...i()(this,g)})}get defaults(){return{...i()(this,g)}}}const v=y},14215:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>s});var r=n(61125),o=n.n(r);const s=class{constructor(){o()(this,"data",{})}register(e,t){this.data[e]=t}unregister(e){void 0===e?this.data={}:delete this.data[e]}get(e){return this.data[e]}}},84539:(e,t,n)=>{"use strict";n.r(t),n.d(t,{ALL_TYPES:()=>o,SCALAR_TYPES:()=>r});const r=["number","integer","string","boolean","null"],o=["array","object",...r]},13783:(e,t,n)=>{"use strict";n.r(t),n.d(t,{extractExample:()=>a,hasExample:()=>i});var r=n(58309),o=n.n(r),s=n(23084);const i=e=>{if(!(0,s.isJSONSchemaObject)(e))return!1;const{examples:t,example:n,default:r}=e;return!!(o()(t)&&t.length>=1)||(void 0!==r||void 0!==n)},a=e=>{if(!(0,s.isJSONSchemaObject)(e))return null;const{examples:t,example:n,default:r}=e;return o()(t)&&t.length>=1?t.at(0):void 0!==r?r:void 0!==n?n:void 0}},37078:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>v});var r=n(58309),o=n.n(r),s=n(39022),i=n.n(s),a=n(25110),l=n.n(a),c=n(82737),u=n.n(c),p=n(28222),h=n.n(p),f=n(14418),d=n.n(f),m=n(90242),g=n(23084);const y=function(e,t){let n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};if((0,g.isBooleanJSONSchema)(e)&&!0===e)return!0;if((0,g.isBooleanJSONSchema)(e)&&!1===e)return!1;if((0,g.isBooleanJSONSchema)(t)&&!0===t)return!0;if((0,g.isBooleanJSONSchema)(t)&&!1===t)return!1;if(!(0,g.isJSONSchema)(e))return t;if(!(0,g.isJSONSchema)(t))return e;const r={...t,...e};if(t.type&&e.type&&o()(t.type)&&"string"==typeof t.type){var s;const n=i()(s=(0,m.AF)(t.type)).call(s,e.type);r.type=l()(new(u())(n))}if(o()(t.required)&&o()(e.required)&&(r.required=[...new(u())([...e.required,...t.required])]),t.properties&&e.properties){const o=new(u())([...h()(t.properties),...h()(e.properties)]);r.properties={};for(const s of o){const o=t.properties[s]||{},i=e.properties[s]||{};var a;if(o.readOnly&&!n.includeReadOnly||o.writeOnly&&!n.includeWriteOnly)r.required=d()(a=r.required||[]).call(a,(e=>e!==s));else r.properties[s]=y(i,o,n)}}return(0,g.isJSONSchema)(t.items)&&(0,g.isJSONSchema)(e.items)&&(r.items=y(e.items,t.items,n)),(0,g.isJSONSchema)(t.contains)&&(0,g.isJSONSchema)(e.contains)&&(r.contains=y(e.contains,t.contains,n)),(0,g.isJSONSchema)(t.contentSchema)&&(0,g.isJSONSchema)(e.contentSchema)&&(r.contentSchema=y(e.contentSchema,t.contentSchema,n)),r},v=y},23084:(e,t,n)=>{"use strict";n.r(t),n.d(t,{isBooleanJSONSchema:()=>s,isJSONSchema:()=>a,isJSONSchemaObject:()=>i});var r=n(68630),o=n.n(r);const s=e=>"boolean"==typeof e,i=e=>o()(e),a=e=>s(e)||i(e)},35202:(e,t,n)=>{"use strict";n.r(t),n.d(t,{bytes:()=>a,integer:()=>h,number:()=>p,pick:()=>c,randexp:()=>l,string:()=>u});var r=n(92282),o=n.n(r),s=n(14419),i=n.n(s);const a=e=>o()(e),l=e=>{try{return new(i())(e).gen()}catch{return"string"}},c=e=>e.at(0),u=()=>"string",p=()=>0,h=()=>0},96276:(e,t,n)=>{"use strict";n.r(t),n.d(t,{foldType:()=>_,getType:()=>O,inferType:()=>j});var r=n(58309),o=n.n(r),s=n(91086),i=n.n(s),a=n(58118),l=n.n(a),c=n(19030),u=n.n(c),p=n(28222),h=n.n(p),f=n(97606),d=n.n(f),m=n(14418),g=n.n(m),y=n(84539),v=n(23084),b=n(35202),w=n(13783);const E={array:["items","prefixItems","contains","maxContains","minContains","maxItems","minItems","uniqueItems","unevaluatedItems"],object:["properties","additionalProperties","patternProperties","propertyNames","minProperties","maxProperties","required","dependentSchemas","dependentRequired","unevaluatedProperties"],string:["pattern","format","minLength","maxLength","contentEncoding","contentMediaType","contentSchema"],integer:["minimum","maximum","exclusiveMinimum","exclusiveMaximum","multipleOf"]};E.number=E.integer;const x="string",S=e=>void 0===e?null:null===e?"null":o()(e)?"array":i()(e)?"integer":typeof e,_=e=>{if(o()(e)&&e.length>=1){if(l()(e).call(e,"array"))return"array";if(l()(e).call(e,"object"))return"object";{const t=(0,b.pick)(e);if(l()(y.ALL_TYPES).call(y.ALL_TYPES,t))return t}}return l()(y.ALL_TYPES).call(y.ALL_TYPES,e)?e:null},j=function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:new(u());if(!(0,v.isJSONSchemaObject)(e))return x;if(t.has(e))return x;t.add(e);let{type:n,const:r}=e;if(n=_(n),"string"!=typeof n){const t=h()(E);e:for(let r=0;r{if(o()(e[n])){var r;const o=d()(r=e[n]).call(r,(e=>j(e,t)));return _(o)}return null},i=r("allOf"),a=r("anyOf"),l=r("oneOf"),c=e.not?j(e.not,t):null;var s;if(i||a||l||c)n=_(g()(s=[i,a,l,c]).call(s,Boolean))}if("string"!=typeof n&&(0,w.hasExample)(e)){const t=(0,w.extractExample)(e),r=S(t);n="string"==typeof r?r:n}return t.delete(e),n||x},O=e=>j(e)},99346:(e,t,n)=>{"use strict";n.r(t),n.d(t,{fromJSONBooleanSchema:()=>o,typeCast:()=>s});var r=n(23084);const o=e=>!1===e?{not:{}}:{},s=e=>(0,r.isBooleanJSONSchema)(e)?o(e):(0,r.isJSONSchemaObject)(e)?e:{}},41433:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(48764).Buffer;const o=e=>r.from(e).toString("ascii")},58509:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(48764).Buffer;const o=e=>r.from(e).toString("utf8")},5709:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(48764).Buffer;const o=e=>r.from(e).toString("hex")},54180:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(48764).Buffer;const o=e=>{const t=r.from(e).toString("utf8"),n="ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";let o=0,s="",i=0,a=0;for(let e=0;e=5;)s+=n.charAt(i>>>a-5&31),a-=5;a>0&&(s+=n.charAt(i<<5-a&31),o=(8-8*t.length%5)%5);for(let e=0;e{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(48764).Buffer;const o=e=>r.from(e).toString("base64")},44366:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(48764).Buffer;const o=e=>r.from(e).toString("binary")},65037:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>s});var r=n(24278),o=n.n(r);const s=e=>{let t="";for(let s=0;s=33&&i<=60||i>=62&&i<=126||9===i||32===i)t+=e.charAt(s);else if(13===i||10===i)t+="\r\n";else if(i>126){const r=unescape(encodeURIComponent(e.charAt(s)));for(let e=0;e{"use strict";n.r(t),n.d(t,{default:()=>r});const r=()=>(new Date).toISOString()},81456:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>r});const r=()=>(new Date).toISOString().substring(0,10)},560:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>r});const r=()=>.1},64299:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>r});const r=()=>"P3D"},3981:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>r});const r=()=>"user@example.com"},51890:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>r});const r=()=>.1},69375:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>r});const r=()=>"example.com"},94518:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>r});const r=()=>"실례@example.com"},70273:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>r});const r=()=>"실례.com"},57864:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>r});const r=()=>2**30>>>0},21726:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>r});const r=()=>2**53-1},28793:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>r});const r=()=>"198.51.100.42"},98269:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>r});const r=()=>"2001:0db8:5b96:0000:0000:426f:8e17:642a"},45693:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>r});const r=()=>"path/실례.html"},13080:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>r});const r=()=>"https://실례.com/"},37856:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>r});const r=()=>"/a/b/c"},2672:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>i});var r=n(57740),o=n.n(r),s=n(35202);const i={"application/json":()=>'{"key":"value"}',"application/ld+json":()=>'{"name": "John Doe"}',"application/x-httpd-php":()=>"Hello World!

'; ?>","application/rtf":()=>o()`{\rtf1\adeflang1025\ansi\ansicpg1252\uc1`,"application/x-sh":()=>'echo "Hello World!"',"application/xhtml+xml":()=>"

content

","application/*":()=>(0,s.bytes)(25).toString("binary")}},54342:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(35202);const o={"audio/*":()=>(0,r.bytes)(25).toString("binary")}},46724:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(35202);const o={"image/*":()=>(0,r.bytes)(25).toString("binary")}},65378:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>r});const r={"text/plain":()=>"string","text/css":()=>".selector { border: 1px solid red }","text/csv":()=>"value1,value2,value3","text/html":()=>"

content

","text/calendar":()=>"BEGIN:VCALENDAR","text/javascript":()=>"console.dir('Hello world!');","text/xml":()=>'John Doe',"text/*":()=>"string"}},92974:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(35202);const o={"video/*":()=>(0,r.bytes)(25).toString("binary")}},93393:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>r});const r=()=>"********"},4335:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>r});const r=()=>"^[a-z]+$"},80375:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>r});const r=()=>"1/0"},65243:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>r});const r=()=>(new Date).toISOString().substring(11)},94692:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>r});const r=()=>"path/index.html"},83829:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>r});const r=()=>"https://example.com/dictionary/{term:1}/{term}"},52978:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>r});const r=()=>"https://example.com/"},38859:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>r});const r=()=>"3fa85f64-5717-4562-b3fc-2c963f66afa6"},78591:(e,t,n)=>{"use strict";n.r(t),n.d(t,{createXMLExample:()=>r.createXMLExample,encoderAPI:()=>o.default,formatAPI:()=>s.default,mediaTypeAPI:()=>i.default,memoizedCreateXMLExample:()=>r.memoizedCreateXMLExample,memoizedSampleFromSchema:()=>r.memoizedSampleFromSchema,sampleFromSchema:()=>r.sampleFromSchema,sampleFromSchemaGeneric:()=>r.sampleFromSchemaGeneric});var r=n(94277),o=n(9507),s=n(22906),i=n(90537)},94277:(e,t,n)=>{"use strict";n.r(t),n.d(t,{createXMLExample:()=>M,memoizedCreateXMLExample:()=>L,memoizedSampleFromSchema:()=>B,sampleFromSchema:()=>D,sampleFromSchemaGeneric:()=>R});var r=n(58309),o=n.n(r),s=n(91086),i=n.n(s),a=n(86),l=n.n(a),c=n(51679),u=n.n(c),p=n(58118),h=n.n(p),f=n(39022),d=n.n(f),m=n(97606),g=n.n(m),y=n(35627),v=n.n(y),b=n(53479),w=n.n(b),E=n(41609),x=n.n(E),S=n(68630),_=n.n(S),j=n(90242),O=n(60314),k=n(63273),A=n(96276),C=n(99346),P=n(13783),N=n(35202),I=n(37078),T=n(23084);const R=function(e){var t;let n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:void 0,s=arguments.length>3&&void 0!==arguments[3]&&arguments[3];"function"==typeof(null===(t=e)||void 0===t?void 0:t.toJS)&&(e=e.toJS()),e=(0,C.typeCast)(e);let a=void 0!==r||(0,P.hasExample)(e);const c=!a&&o()(e.oneOf)&&e.oneOf.length>0,p=!a&&o()(e.anyOf)&&e.anyOf.length>0;if(!a&&(c||p)){const t=(0,C.typeCast)(c?(0,N.pick)(e.oneOf):(0,N.pick)(e.anyOf));!(e=(0,I.default)(e,t,n)).xml&&t.xml&&(e.xml=t.xml),(0,P.hasExample)(e)&&(0,P.hasExample)(t)&&(a=!0)}const f={};let{xml:m,properties:y,additionalProperties:v,items:b,contains:w}=e||{},E=(0,A.getType)(e),{includeReadOnly:S,includeWriteOnly:O}=n;m=m||{};let M,{name:D,prefix:F,namespace:L}=m,B={};if(Object.hasOwn(e,"type")||(e.type=E),s&&(D=D||"notagname",M=(F?`${F}:`:"")+D,L)){f[F?`xmlns:${F}`:"xmlns"]=L}s&&(B[M]=[]);const $=(0,j.mz)(y);let q,U=0;const z=()=>i()(e.maxProperties)&&e.maxProperties>0&&U>=e.maxProperties,V=t=>!(i()(e.maxProperties)&&e.maxProperties>0)||!z()&&(!(t=>{var n;return!o()(e.required)||0===e.required.length||!h()(n=e.required).call(n,t)})(t)||e.maxProperties-U-(()=>{if(!o()(e.required)||0===e.required.length)return 0;let t=0;var n,r;return s?l()(n=e.required).call(n,(e=>t+=void 0===B[e]?0:1)):l()(r=e.required).call(r,(e=>{var n;t+=void 0===(null===(n=B[M])||void 0===n?void 0:u()(n).call(n,(t=>void 0!==t[e])))?0:1})),e.required.length-t})()>0);if(q=s?function(t){let r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:void 0;if(e&&$[t]){if($[t].xml=$[t].xml||{},$[t].xml.attribute){const e=o()($[t].enum)?(0,N.pick)($[t].enum):void 0;if((0,P.hasExample)($[t]))f[$[t].xml.name||t]=(0,P.extractExample)($[t]);else if(void 0!==e)f[$[t].xml.name||t]=e;else{const e=(0,C.typeCast)($[t]),n=(0,A.getType)(e),r=$[t].xml.name||t;f[r]=k.default[n](e)}return}$[t].xml.name=$[t].xml.name||t}else $[t]||!1===v||($[t]={xml:{name:t}});let i=R($[t],n,r,s);var a;V(t)&&(U++,o()(i)?B[M]=d()(a=B[M]).call(a,i):B[M].push(i))}:(t,r)=>{var o;if(V(t)){if(_()(null===(o=e.discriminator)||void 0===o?void 0:o.mapping)&&e.discriminator.propertyName===t&&"string"==typeof e.$$ref){for(const n in e.discriminator.mapping)if(-1!==e.$$ref.search(e.discriminator.mapping[n])){B[t]=n;break}}else B[t]=R($[t],n,r,s);U++}},a){let t;if(t=void 0!==r?r:(0,P.extractExample)(e),!s){if("number"==typeof t&&"string"===E)return`${t}`;if("string"!=typeof t||"string"===E)return t;try{return JSON.parse(t)}catch{return t}}if("array"===E){if(!o()(t)){if("string"==typeof t)return t;t=[t]}let r=[];return(0,T.isJSONSchemaObject)(b)&&(b.xml=b.xml||m||{},b.xml.name=b.xml.name||m.name,r=g()(t).call(t,(e=>R(b,n,e,s)))),(0,T.isJSONSchemaObject)(w)&&(w.xml=w.xml||m||{},w.xml.name=w.xml.name||m.name,r=[R(w,n,void 0,s),...r]),r=k.default.array(e,{sample:r}),m.wrapped?(B[M]=r,x()(f)||B[M].push({_attr:f})):B=r,B}if("object"===E){if("string"==typeof t)return t;for(const e in t){var W,J,K,H;Object.hasOwn(t,e)&&(null!==(W=$[e])&&void 0!==W&&W.readOnly&&!S||null!==(J=$[e])&&void 0!==J&&J.writeOnly&&!O||(null!==(K=$[e])&&void 0!==K&&null!==(H=K.xml)&&void 0!==H&&H.attribute?f[$[e].xml.name||e]=t[e]:q(e,t[e])))}return x()(f)||B[M].push({_attr:f}),B}return B[M]=x()(f)?t:[{_attr:f},t],B}if("array"===E){let t=[];var G,Z;if((0,T.isJSONSchemaObject)(w))if(s&&(w.xml=w.xml||e.xml||{},w.xml.name=w.xml.name||m.name),o()(w.anyOf))t.push(...g()(G=w.anyOf).call(G,(e=>R((0,I.default)(e,w,n),n,void 0,s))));else if(o()(w.oneOf)){var Y;t.push(...g()(Y=w.oneOf).call(Y,(e=>R((0,I.default)(e,w,n),n,void 0,s))))}else{if(!(!s||s&&m.wrapped))return R(w,n,void 0,s);t.push(R(w,n,void 0,s))}if((0,T.isJSONSchemaObject)(b))if(s&&(b.xml=b.xml||e.xml||{},b.xml.name=b.xml.name||m.name),o()(b.anyOf))t.push(...g()(Z=b.anyOf).call(Z,(e=>R((0,I.default)(e,b,n),n,void 0,s))));else if(o()(b.oneOf)){var X;t.push(...g()(X=b.oneOf).call(X,(e=>R((0,I.default)(e,b,n),n,void 0,s))))}else{if(!(!s||s&&m.wrapped))return R(b,n,void 0,s);t.push(R(b,n,void 0,s))}return t=k.default.array(e,{sample:t}),s&&m.wrapped?(B[M]=t,x()(f)||B[M].push({_attr:f}),B):t}if("object"===E){for(let e in $){var Q,ee,te;Object.hasOwn($,e)&&(null!==(Q=$[e])&&void 0!==Q&&Q.deprecated||null!==(ee=$[e])&&void 0!==ee&&ee.readOnly&&!S||null!==(te=$[e])&&void 0!==te&&te.writeOnly&&!O||q(e))}if(s&&f&&B[M].push({_attr:f}),z())return B;if((0,T.isBooleanJSONSchema)(v))s?B[M].push({additionalProp:"Anything can be here"}):B.additionalProp1={},U++;else if((0,T.isJSONSchemaObject)(v)){var ne,re;const t=v,r=R(t,n,void 0,s);if(s&&"string"==typeof(null==t||null===(ne=t.xml)||void 0===ne?void 0:ne.name)&&"notagname"!==(null==t||null===(re=t.xml)||void 0===re?void 0:re.name))B[M].push(r);else{const t=i()(e.minProperties)&&e.minProperties>0&&U{const r=R(e,t,n,!0);if(r)return"string"==typeof r?r:w()(r,{declaration:!0,indent:"\t"})},D=(e,t,n)=>R(e,t,n,!1),F=(e,t,n)=>[e,v()(t),v()(n)],L=(0,O.Z)(M,F),B=(0,O.Z)(D,F)},83982:(e,t,n)=>{"use strict";n.r(t),n.d(t,{applyArrayConstraints:()=>p,default:()=>h});var r=n(91086),o=n.n(r),s=n(24278),i=n.n(s),a=n(25110),l=n.n(a),c=n(82737),u=n.n(c);const p=function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};const{minItems:n,maxItems:r,uniqueItems:s}=t,{contains:a,minContains:c,maxContains:p}=t;let h=[...e];if(null!=a&&"object"==typeof a){if(o()(c)&&c>1){const e=h.at(0);for(let t=1;t0&&(h=i()(e).call(e,0,r)),o()(n)&&n>0)for(let e=0;h.length{let{sample:n}=t;return p(n,e)}},34108:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>r});const r=e=>"boolean"!=typeof e.default||e.default},63273:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>p});var r=n(83982),o=n(46852),s=n(74522),i=n(83455),a=n(58864),l=n(34108),c=n(90853);const u={array:r.default,object:o.default,string:s.default,number:i.default,integer:a.default,boolean:l.default,null:c.default},p=new Proxy(u,{get:(e,t)=>"string"==typeof t&&Object.hasOwn(e,t)?e[t]:()=>`Unknown Type: ${t}`})},58864:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>a});var r=n(35202),o=n(22906),s=n(57864),i=n(21726);const a=e=>{const{format:t}=e;return"string"==typeof t?(e=>{const{format:t}=e,n=(0,o.default)(t);if("function"==typeof n)return n(e);switch(t){case"int32":return(0,s.default)();case"int64":return(0,i.default)()}return(0,r.integer)()})(e):(0,r.integer)()}},90853:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>r});const r=()=>null},83455:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>p});var r=n(91086),o=n.n(r),s=n(44081),i=n.n(s),a=n(35202),l=n(22906),c=n(51890),u=n(560);const p=e=>{const{format:t}=e;let n;return n="string"==typeof t?(e=>{const{format:t}=e,n=(0,l.default)(t);if("function"==typeof n)return n(e);switch(t){case"float":return(0,c.default)();case"double":return(0,u.default)()}return(0,a.number)()})(e):(0,a.number)(),function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};const{minimum:n,maximum:r,exclusiveMinimum:s,exclusiveMaximum:a}=t,{multipleOf:l}=t,c=o()(e)?1:i();let u="number"==typeof n?n:null,p="number"==typeof r?r:null,h=e;if("number"==typeof s&&(u=null!==u?Math.max(u,s+c):s+c),"number"==typeof a&&(p=null!==p?Math.min(p,a-c):a-c),h=u>p&&e||u||p||h,"number"==typeof l&&l>0){const e=h%l;h=0===e?h:h+l-e}return h}(n,e)}},46852:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>r});const r=()=>{throw new Error("Not implemented")}},74522:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>L});var r=n(91086),o=n.n(r),s=n(24278),i=n.n(s),a=n(58309),l=n.n(a),c=n(35627),u=n.n(c),p=n(6557),h=n.n(p),f=n(35202),d=n(23084),m=n(3981),g=n(94518),y=n(69375),v=n(70273),b=n(28793),w=n(98269),E=n(52978),x=n(94692),S=n(13080),_=n(45693),j=n(38859),O=n(83829),k=n(37856),A=n(80375),C=n(74045),P=n(81456),N=n(65243),I=n(64299),T=n(93393),R=n(4335),M=n(22906),D=n(9507),F=n(90537);const L=function(e){let{sample:t}=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};const{contentEncoding:n,contentMediaType:r,contentSchema:s}=e,{pattern:a,format:c}=e,p=(0,D.default)(n)||h();let L;if("string"==typeof a)L=(0,f.randexp)(a);else if("string"==typeof c)L=(e=>{const{format:t}=e,n=(0,M.default)(t);if("function"==typeof n)return n(e);switch(t){case"email":return(0,m.default)();case"idn-email":return(0,g.default)();case"hostname":return(0,y.default)();case"idn-hostname":return(0,v.default)();case"ipv4":return(0,b.default)();case"ipv6":return(0,w.default)();case"uri":return(0,E.default)();case"uri-reference":return(0,x.default)();case"iri":return(0,S.default)();case"iri-reference":return(0,_.default)();case"uuid":return(0,j.default)();case"uri-template":return(0,O.default)();case"json-pointer":return(0,k.default)();case"relative-json-pointer":return(0,A.default)();case"date-time":return(0,C.default)();case"date":return(0,P.default)();case"time":return(0,N.default)();case"duration":return(0,I.default)();case"password":return(0,T.default)();case"regex":return(0,R.default)()}return(0,f.string)()})(e);else if((0,d.isJSONSchema)(s)&&"string"==typeof r&&void 0!==t)L=l()(t)||"object"==typeof t?u()(t):String(t);else if("string"==typeof r){const t=(0,F.default)(r);"function"==typeof t&&(L=t(e))}else L=(0,f.string)();return p(function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};const{maxLength:n,minLength:r}=t;let s=e;if(o()(n)&&n>0&&(s=i()(s).call(s,0,n)),o()(r)&&r>0){let e=0;for(;s.length{"use strict";n.r(t),n.d(t,{SHOW:()=>a,UPDATE_FILTER:()=>s,UPDATE_LAYOUT:()=>o,UPDATE_MODE:()=>i,changeMode:()=>p,show:()=>u,updateFilter:()=>c,updateLayout:()=>l});var r=n(90242);const o="layout_update_layout",s="layout_update_filter",i="layout_update_mode",a="layout_show";function l(e){return{type:o,payload:e}}function c(e){return{type:s,payload:e}}function u(e){let t=!(arguments.length>1&&void 0!==arguments[1])||arguments[1];return e=(0,r.AF)(e),{type:a,payload:{thing:e,shown:t}}}function p(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";return e=(0,r.AF)(e),{type:i,payload:{thing:e,mode:t}}}},26821:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>a});var r=n(5672),o=n(25474),s=n(4400),i=n(28989);function a(){return{statePlugins:{layout:{reducers:r.default,actions:o,selectors:s},spec:{wrapSelectors:i}}}}},5672:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>a});var r=n(39022),o=n.n(r),s=n(43393),i=n(25474);const a={[i.UPDATE_LAYOUT]:(e,t)=>e.set("layout",t.payload),[i.UPDATE_FILTER]:(e,t)=>e.set("filter",t.payload),[i.SHOW]:(e,t)=>{const n=t.payload.shown,r=(0,s.fromJS)(t.payload.thing);return e.update("shown",(0,s.fromJS)({}),(e=>e.set(r,n)))},[i.UPDATE_MODE]:(e,t)=>{var n;let r=t.payload.thing,s=t.payload.mode;return e.setIn(o()(n=["modes"]).call(n,r),(s||"")+"")}}},4400:(e,t,n)=>{"use strict";n.r(t),n.d(t,{current:()=>i,currentFilter:()=>a,isShown:()=>l,showSummary:()=>u,whatMode:()=>c});var r=n(20573),o=n(90242),s=n(43393);const i=e=>e.get("layout"),a=e=>e.get("filter"),l=(e,t,n)=>(t=(0,o.AF)(t),e.get("shown",(0,s.fromJS)({})).get((0,s.fromJS)(t),n)),c=function(e,t){let n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"";return t=(0,o.AF)(t),e.getIn(["modes",...t],n)},u=(0,r.P1)((e=>e),(e=>!l(e,"editor")))},28989:(e,t,n)=>{"use strict";n.r(t),n.d(t,{taggedOperations:()=>s});var r=n(24278),o=n.n(r);const s=(e,t)=>function(n){for(var r=arguments.length,s=new Array(r>1?r-1:0),i=1;i=0&&(a=o()(a).call(a,0,h)),a}},9150:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>s});var r=n(11189),o=n.n(r);function s(e){let{configs:t}=e;const n={debug:0,info:1,log:2,warn:3,error:4},r=e=>n[e]||-1;let{logLevel:s}=t,i=r(s);function a(e){for(var t=arguments.length,n=new Array(t>1?t-1:0),o=1;o=i&&console[e](...n)}return a.warn=o()(a).call(a,null,"warn"),a.error=o()(a).call(a,null,"error"),a.info=o()(a).call(a,null,"info"),a.debug=o()(a).call(a,null,"debug"),{rootInjects:{log:a}}}},67002:(e,t,n)=>{"use strict";n.r(t),n.d(t,{CLEAR_REQUEST_BODY_VALIDATE_ERROR:()=>h,CLEAR_REQUEST_BODY_VALUE:()=>f,SET_REQUEST_BODY_VALIDATE_ERROR:()=>p,UPDATE_ACTIVE_EXAMPLES_MEMBER:()=>a,UPDATE_REQUEST_BODY_INCLUSION:()=>i,UPDATE_REQUEST_BODY_VALUE:()=>o,UPDATE_REQUEST_BODY_VALUE_RETAIN_FLAG:()=>s,UPDATE_REQUEST_CONTENT_TYPE:()=>l,UPDATE_RESPONSE_CONTENT_TYPE:()=>c,UPDATE_SELECTED_SERVER:()=>r,UPDATE_SERVER_VARIABLE_VALUE:()=>u,clearRequestBodyValidateError:()=>S,clearRequestBodyValue:()=>j,initRequestBodyValidateError:()=>_,setActiveExamplesMember:()=>v,setRequestBodyInclusion:()=>y,setRequestBodyValidateError:()=>x,setRequestBodyValue:()=>m,setRequestContentType:()=>b,setResponseContentType:()=>w,setRetainRequestBodyValueFlag:()=>g,setSelectedServer:()=>d,setServerVariableValue:()=>E});const r="oas3_set_servers",o="oas3_set_request_body_value",s="oas3_set_request_body_retain_flag",i="oas3_set_request_body_inclusion",a="oas3_set_active_examples_member",l="oas3_set_request_content_type",c="oas3_set_response_content_type",u="oas3_set_server_variable_value",p="oas3_set_request_body_validate_error",h="oas3_clear_request_body_validate_error",f="oas3_clear_request_body_value";function d(e,t){return{type:r,payload:{selectedServerUrl:e,namespace:t}}}function m(e){let{value:t,pathMethod:n}=e;return{type:o,payload:{value:t,pathMethod:n}}}const g=e=>{let{value:t,pathMethod:n}=e;return{type:s,payload:{value:t,pathMethod:n}}};function y(e){let{value:t,pathMethod:n,name:r}=e;return{type:i,payload:{value:t,pathMethod:n,name:r}}}function v(e){let{name:t,pathMethod:n,contextType:r,contextName:o}=e;return{type:a,payload:{name:t,pathMethod:n,contextType:r,contextName:o}}}function b(e){let{value:t,pathMethod:n}=e;return{type:l,payload:{value:t,pathMethod:n}}}function w(e){let{value:t,path:n,method:r}=e;return{type:c,payload:{value:t,path:n,method:r}}}function E(e){let{server:t,namespace:n,key:r,val:o}=e;return{type:u,payload:{server:t,namespace:n,key:r,val:o}}}const x=e=>{let{path:t,method:n,validationErrors:r}=e;return{type:p,payload:{path:t,method:n,validationErrors:r}}},S=e=>{let{path:t,method:n}=e;return{type:h,payload:{path:t,method:n}}},_=e=>{let{pathMethod:t}=e;return{type:h,payload:{path:t[0],method:t[1]}}},j=e=>{let{pathMethod:t}=e;return{type:f,payload:{pathMethod:t}}}},73723:(e,t,n)=>{"use strict";n.r(t),n.d(t,{definitionsToAuthorize:()=>p});var r=n(86),o=n.n(r),s=n(14418),i=n.n(s),a=n(24282),l=n.n(a),c=n(20573),u=n(43393);const p=(h=(0,c.P1)((e=>e),(e=>{let{specSelectors:t}=e;return t.securityDefinitions()}),((e,t)=>{var n;let r=(0,u.List)();return t?(o()(n=t.entrySeq()).call(n,(e=>{let[t,n]=e;const s=n.get("type");var a;if("oauth2"===s&&o()(a=n.get("flows").entrySeq()).call(a,(e=>{let[o,s]=e,a=(0,u.fromJS)({flow:o,authorizationUrl:s.get("authorizationUrl"),tokenUrl:s.get("tokenUrl"),scopes:s.get("scopes"),type:n.get("type"),description:n.get("description")});r=r.push(new u.Map({[t]:i()(a).call(a,(e=>void 0!==e))}))})),"http"!==s&&"apiKey"!==s||(r=r.push(new u.Map({[t]:n}))),"openIdConnect"===s&&n.get("openIdConnectData")){let e=n.get("openIdConnectData"),s=e.get("grant_types_supported")||["authorization_code","implicit"];o()(s).call(s,(o=>{var s;let a=e.get("scopes_supported")&&l()(s=e.get("scopes_supported")).call(s,((e,t)=>e.set(t,"")),new u.Map),c=(0,u.fromJS)({flow:o,authorizationUrl:e.get("authorization_endpoint"),tokenUrl:e.get("token_endpoint"),scopes:a,type:"oauth2",openIdConnectUrl:n.get("openIdConnectUrl")});r=r.push(new u.Map({[t]:i()(c).call(c,(e=>void 0!==e))}))}))}})),r):r})),(e,t)=>function(){for(var n=arguments.length,r=new Array(n),o=0;o{"use strict";n.r(t),n.d(t,{default:()=>l});var r=n(28222),o=n.n(r),s=n(97606),i=n.n(s),a=n(67294);n(23930);const l=e=>{let{callbacks:t,specPath:n,specSelectors:r,getComponent:s}=e;const l=r.callbacksOperations({callbacks:t,specPath:n}),c=o()(l),u=s("OperationContainer",!0);return 0===c.length?a.createElement("span",null,"No callbacks"):a.createElement("div",null,i()(c).call(c,(e=>{var t;return a.createElement("div",{key:`${e}`},a.createElement("h2",null,e),i()(t=l[e]).call(t,(t=>a.createElement(u,{key:`${e}-${t.path}-${t.method}`,op:t.operation,tag:"callbacks",method:t.method,path:t.path,specPath:t.specPath,allowTryItOut:!1}))))})))}},86775:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>h});var r=n(61125),o=n.n(r),s=n(76986),i=n.n(s),a=n(14418),l=n.n(a),c=n(97606),u=n.n(c),p=n(67294);class h extends p.Component{constructor(e,t){super(e,t),o()(this,"onChange",(e=>{let{onChange:t}=this.props,{value:n,name:r}=e.target,o=i()({},this.state.value);r?o[r]=n:o=n,this.setState({value:o},(()=>t(this.state)))}));let{name:n,schema:r}=this.props,s=this.getValue();this.state={name:n,schema:r,value:s}}getValue(){let{name:e,authorized:t}=this.props;return t&&t.getIn([e,"value"])}render(){var e;let{schema:t,getComponent:n,errSelectors:r,name:o}=this.props;const s=n("Input"),i=n("Row"),a=n("Col"),c=n("authError"),h=n("Markdown",!0),f=n("JumpToPath",!0),d=(t.get("scheme")||"").toLowerCase();let m=this.getValue(),g=l()(e=r.allErrors()).call(e,(e=>e.get("authId")===o));if("basic"===d){var y;let e=m?m.get("username"):null;return p.createElement("div",null,p.createElement("h4",null,p.createElement("code",null,o||t.get("name")),"  (http, Basic)",p.createElement(f,{path:["securityDefinitions",o]})),e&&p.createElement("h6",null,"Authorized"),p.createElement(i,null,p.createElement(h,{source:t.get("description")})),p.createElement(i,null,p.createElement("label",null,"Username:"),e?p.createElement("code",null," ",e," "):p.createElement(a,null,p.createElement(s,{type:"text",required:"required",name:"username","aria-label":"auth-basic-username",onChange:this.onChange,autoFocus:!0}))),p.createElement(i,null,p.createElement("label",null,"Password:"),e?p.createElement("code",null," ****** "):p.createElement(a,null,p.createElement(s,{autoComplete:"new-password",name:"password",type:"password","aria-label":"auth-basic-password",onChange:this.onChange}))),u()(y=g.valueSeq()).call(y,((e,t)=>p.createElement(c,{error:e,key:t}))))}var v;return"bearer"===d?p.createElement("div",null,p.createElement("h4",null,p.createElement("code",null,o||t.get("name")),"  (http, Bearer)",p.createElement(f,{path:["securityDefinitions",o]})),m&&p.createElement("h6",null,"Authorized"),p.createElement(i,null,p.createElement(h,{source:t.get("description")})),p.createElement(i,null,p.createElement("label",null,"Value:"),m?p.createElement("code",null," ****** "):p.createElement(a,null,p.createElement(s,{type:"text","aria-label":"auth-bearer-value",onChange:this.onChange,autoFocus:!0}))),u()(v=g.valueSeq()).call(v,((e,t)=>p.createElement(c,{error:e,key:t})))):p.createElement("div",null,p.createElement("em",null,p.createElement("b",null,o)," HTTP authentication: unsupported scheme ",`'${d}'`))}}},76467:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>p});var r=n(33427),o=n(42458),s=n(15757),i=n(56617),a=n(9928),l=n(45327),c=n(86775),u=n(96796);const p={Callbacks:r.default,HttpAuth:c.default,RequestBody:o.default,Servers:i.default,ServersContainer:a.default,RequestBodyEditor:l.default,OperationServers:u.default,operationLink:s.default}},15757:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>c});var r=n(35627),o=n.n(r),s=n(97606),i=n.n(s),a=n(67294);n(23930);class l extends a.Component{render(){const{link:e,name:t,getComponent:n}=this.props,r=n("Markdown",!0);let s=e.get("operationId")||e.get("operationRef"),l=e.get("parameters")&&e.get("parameters").toJS(),c=e.get("description");return a.createElement("div",{className:"operation-link"},a.createElement("div",{className:"description"},a.createElement("b",null,a.createElement("code",null,t)),c?a.createElement(r,{source:c}):null),a.createElement("pre",null,"Operation `",s,"`",a.createElement("br",null),a.createElement("br",null),"Parameters ",function(e,t){var n;if("string"!=typeof t)return"";return i()(n=t.split("\n")).call(n,((t,n)=>n>0?Array(e+1).join(" ")+t:t)).join("\n")}(0,o()(l,null,2))||"{}",a.createElement("br",null)))}}const c=l},96796:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>i});var r=n(61125),o=n.n(r),s=n(67294);n(23930);class i extends s.Component{constructor(){super(...arguments),o()(this,"setSelectedServer",(e=>{const{path:t,method:n}=this.props;return this.forceUpdate(),this.props.setSelectedServer(e,`${t}:${n}`)})),o()(this,"setServerVariableValue",(e=>{const{path:t,method:n}=this.props;return this.forceUpdate(),this.props.setServerVariableValue({...e,namespace:`${t}:${n}`})})),o()(this,"getSelectedServer",(()=>{const{path:e,method:t}=this.props;return this.props.getSelectedServer(`${e}:${t}`)})),o()(this,"getServerVariable",((e,t)=>{const{path:n,method:r}=this.props;return this.props.getServerVariable({namespace:`${n}:${r}`,server:e},t)})),o()(this,"getEffectiveServerValue",(e=>{const{path:t,method:n}=this.props;return this.props.getEffectiveServerValue({server:e,namespace:`${t}:${n}`})}))}render(){const{operationServers:e,pathServers:t,getComponent:n}=this.props;if(!e&&!t)return null;const r=n("Servers"),o=e||t,i=e?"operation":"path";return s.createElement("div",{className:"opblock-section operation-servers"},s.createElement("div",{className:"opblock-section-header"},s.createElement("div",{className:"tab-header"},s.createElement("h4",{className:"opblock-title"},"Servers"))),s.createElement("div",{className:"opblock-description-wrapper"},s.createElement("h4",{className:"message"},"These ",i,"-level options override the global server options."),s.createElement(r,{servers:o,currentServer:this.getSelectedServer(),setSelectedServer:this.setSelectedServer,setServerVariableValue:this.setServerVariableValue,getServerVariable:this.getServerVariable,getEffectiveServerValue:this.getEffectiveServerValue})))}}},45327:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>u});var r=n(61125),o=n.n(r),s=n(67294),i=n(94184),a=n.n(i),l=n(90242);const c=Function.prototype;class u extends s.PureComponent{constructor(e,t){super(e,t),o()(this,"applyDefaultValue",(e=>{const{onChange:t,defaultValue:n}=e||this.props;return this.setState({value:n}),t(n)})),o()(this,"onChange",(e=>{this.props.onChange((0,l.Pz)(e))})),o()(this,"onDomChange",(e=>{const t=e.target.value;this.setState({value:t},(()=>this.onChange(t)))})),this.state={value:(0,l.Pz)(e.value)||e.defaultValue},e.onChange(e.value)}UNSAFE_componentWillReceiveProps(e){this.props.value!==e.value&&e.value!==this.state.value&&this.setState({value:(0,l.Pz)(e.value)}),!e.value&&e.defaultValue&&this.state.value&&this.applyDefaultValue(e)}render(){let{getComponent:e,errors:t}=this.props,{value:n}=this.state,r=t.size>0;const o=e("TextArea");return s.createElement("div",{className:"body-param"},s.createElement(o,{className:a()("body-param__text",{invalid:r}),title:t.size?t.join(", "):"",value:n,onChange:this.onDomChange}))}}o()(u,"defaultProps",{onChange:c,userHasEditedBody:!1})},42458:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>g,getDefaultRequestBodyValue:()=>m});var r=n(97606),o=n.n(r),s=n(11882),i=n.n(s),a=n(58118),l=n.n(a),c=n(58309),u=n.n(c),p=n(67294),h=(n(23930),n(43393)),f=n(90242),d=n(2518);const m=(e,t,n,r)=>{const o=e.getIn(["content",t]),s=o.get("schema").toJS(),i=void 0!==o.get("examples"),a=o.get("example"),l=i?o.getIn(["examples",n,"value"]):a,c=r.getSampleSchema(s,t,{includeWriteOnly:!0},l);return(0,f.Pz)(c)},g=e=>{let{userHasEditedBody:t,requestBody:n,requestBodyValue:r,requestBodyInclusionSetting:s,requestBodyErrors:a,getComponent:c,getConfigs:g,specSelectors:y,fn:v,contentType:b,isExecute:w,specPath:E,onChange:x,onChangeIncludeEmpty:S,activeExamplesKey:_,updateActiveExamplesKey:j,setRetainRequestBodyValueFlag:O}=e;const k=e=>{x(e.target.files[0])},A=e=>{let t={key:e,shouldDispatchInit:!1,defaultValue:!0};return"no value"===s.get(e,"no value")&&(t.shouldDispatchInit=!0),t},C=c("Markdown",!0),P=c("modelExample"),N=c("RequestBodyEditor"),I=c("highlightCode"),T=c("ExamplesSelectValueRetainer"),R=c("Example"),M=c("ParameterIncludeEmpty"),{showCommonExtensions:D}=g(),F=n&&n.get("description")||null,L=n&&n.get("content")||new h.OrderedMap;b=b||L.keySeq().first()||"";const B=L.get(b,(0,h.OrderedMap)()),$=B.get("schema",(0,h.OrderedMap)()),q=B.get("examples",null),U=null==q?void 0:o()(q).call(q,((e,t)=>{var r;const o=null===(r=e)||void 0===r?void 0:r.get("value",null);return o&&(e=e.set("value",m(n,b,t,v),o)),e}));if(a=h.List.isList(a)?a:(0,h.List)(),!B.size)return null;const z="object"===B.getIn(["schema","type"]),V="binary"===B.getIn(["schema","format"]),W="base64"===B.getIn(["schema","format"]);if("application/octet-stream"===b||0===i()(b).call(b,"image/")||0===i()(b).call(b,"audio/")||0===i()(b).call(b,"video/")||V||W){const e=c("Input");return w?p.createElement(e,{type:"file",onChange:k}):p.createElement("i",null,"Example values are not available for ",p.createElement("code",null,b)," media types.")}if(z&&("application/x-www-form-urlencoded"===b||0===i()(b).call(b,"multipart/"))&&$.get("properties",(0,h.OrderedMap)()).size>0){var J;const e=c("JsonSchemaForm"),t=c("ParameterExt"),n=$.get("properties",(0,h.OrderedMap)());return r=h.Map.isMap(r)?r:(0,h.OrderedMap)(),p.createElement("div",{className:"table-container"},F&&p.createElement(C,{source:F}),p.createElement("table",null,p.createElement("tbody",null,h.Map.isMap(n)&&o()(J=n.entrySeq()).call(J,(n=>{var i,d;let[m,g]=n;if(g.get("readOnly"))return;let y=D?(0,f.po)(g):null;const b=l()(i=$.get("required",(0,h.List)())).call(i,m),E=g.get("type"),_=g.get("format"),j=g.get("description"),O=r.getIn([m,"value"]),k=r.getIn([m,"errors"])||a,P=s.get(m)||!1,N=g.has("default")||g.has("example")||g.hasIn(["items","example"])||g.hasIn(["items","default"]),I=g.has("enum")&&(1===g.get("enum").size||b),T=N||I;let R="";"array"!==E||T||(R=[]),("object"===E||T)&&(R=v.getSampleSchema(g,!1,{includeWriteOnly:!0})),"string"!=typeof R&&"object"===E&&(R=(0,f.Pz)(R)),"string"==typeof R&&"array"===E&&(R=JSON.parse(R));const F="string"===E&&("binary"===_||"base64"===_);return p.createElement("tr",{key:m,className:"parameters","data-property-name":m},p.createElement("td",{className:"parameters-col_name"},p.createElement("div",{className:b?"parameter__name required":"parameter__name"},m,b?p.createElement("span",null," *"):null),p.createElement("div",{className:"parameter__type"},E,_&&p.createElement("span",{className:"prop-format"},"($",_,")"),D&&y.size?o()(d=y.entrySeq()).call(d,(e=>{let[n,r]=e;return p.createElement(t,{key:`${n}-${r}`,xKey:n,xVal:r})})):null),p.createElement("div",{className:"parameter__deprecated"},g.get("deprecated")?"deprecated":null)),p.createElement("td",{className:"parameters-col_description"},p.createElement(C,{source:j}),w?p.createElement("div",null,p.createElement(e,{fn:v,dispatchInitialValue:!F,schema:g,description:m,getComponent:c,value:void 0===O?R:O,required:b,errors:k,onChange:e=>{x(e,[m])}}),b?null:p.createElement(M,{onChange:e=>S(m,e),isIncluded:P,isIncludedOptions:A(m),isDisabled:u()(O)?0!==O.length:!(0,f.O2)(O)})):null))})))))}const K=m(n,b,_,v);let H=null;return(0,d.O)(K)&&(H="json"),p.createElement("div",null,F&&p.createElement(C,{source:F}),U?p.createElement(T,{userHasEditedBody:t,examples:U,currentKey:_,currentUserInputValue:r,onSelect:e=>{j(e)},updateValue:x,defaultToFirstExample:!0,getComponent:c,setRetainRequestBodyValueFlag:O}):null,w?p.createElement("div",null,p.createElement(N,{value:r,errors:a,defaultValue:K,onChange:x,getComponent:c})):p.createElement(P,{getComponent:c,getConfigs:g,specSelectors:y,expandDepth:1,isExecute:w,schema:B.get("schema"),specPath:E.push("content",b),example:p.createElement(I,{className:"body-param__example",getConfigs:g,language:H,value:(0,f.Pz)(r)||K}),includeWriteOnly:!0}),U?p.createElement(R,{example:U.get(_),getComponent:c,getConfigs:g}):null)}},9928:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(67294);class o extends r.Component{render(){const{specSelectors:e,oas3Selectors:t,oas3Actions:n,getComponent:o}=this.props,s=e.servers(),i=o("Servers");return s&&s.size?r.createElement("div",null,r.createElement("span",{className:"servers-title"},"Servers"),r.createElement(i,{servers:s,currentServer:t.selectedServer(),setSelectedServer:n.setSelectedServer,setServerVariableValue:n.setServerVariableValue,getServerVariable:t.serverVariableValue,getEffectiveServerValue:t.serverEffectiveValue})):null}}},56617:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>p});var r=n(61125),o=n.n(r),s=n(51679),i=n.n(s),a=n(97606),l=n.n(a),c=n(67294),u=n(43393);n(23930);class p extends c.Component{constructor(){super(...arguments),o()(this,"onServerChange",(e=>{this.setServer(e.target.value)})),o()(this,"onServerVariableValueChange",(e=>{let{setServerVariableValue:t,currentServer:n}=this.props,r=e.target.getAttribute("data-variable"),o=e.target.value;"function"==typeof t&&t({server:n,key:r,val:o})})),o()(this,"setServer",(e=>{let{setSelectedServer:t}=this.props;t(e)}))}componentDidMount(){var e;let{servers:t,currentServer:n}=this.props;n||this.setServer(null===(e=t.first())||void 0===e?void 0:e.get("url"))}UNSAFE_componentWillReceiveProps(e){let{servers:t,setServerVariableValue:n,getServerVariable:r}=e;if(this.props.currentServer!==e.currentServer||this.props.servers!==e.servers){var o;let s=i()(t).call(t,(t=>t.get("url")===e.currentServer)),a=i()(o=this.props.servers).call(o,(e=>e.get("url")===this.props.currentServer))||(0,u.OrderedMap)();if(!s)return this.setServer(t.first().get("url"));let c=a.get("variables")||(0,u.OrderedMap)(),p=(i()(c).call(c,(e=>e.get("default")))||(0,u.OrderedMap)()).get("default"),h=s.get("variables")||(0,u.OrderedMap)(),f=(i()(h).call(h,(e=>e.get("default")))||(0,u.OrderedMap)()).get("default");l()(h).call(h,((t,o)=>{r(e.currentServer,o)&&p===f||n({server:e.currentServer,key:o,val:t.get("default")||""})}))}}render(){var e,t;let{servers:n,currentServer:r,getServerVariable:o,getEffectiveServerValue:s}=this.props,a=(i()(n).call(n,(e=>e.get("url")===r))||(0,u.OrderedMap)()).get("variables")||(0,u.OrderedMap)(),p=0!==a.size;return c.createElement("div",{className:"servers"},c.createElement("label",{htmlFor:"servers"},c.createElement("select",{onChange:this.onServerChange,value:r},l()(e=n.valueSeq()).call(e,(e=>c.createElement("option",{value:e.get("url"),key:e.get("url")},e.get("url"),e.get("description")&&` - ${e.get("description")}`))).toArray())),p?c.createElement("div",null,c.createElement("div",{className:"computed-url"},"Computed URL:",c.createElement("code",null,s(r))),c.createElement("h4",null,"Server variables"),c.createElement("table",null,c.createElement("tbody",null,l()(t=a.entrySeq()).call(t,(e=>{var t;let[n,s]=e;return c.createElement("tr",{key:n},c.createElement("td",null,n),c.createElement("td",null,s.get("enum")?c.createElement("select",{"data-variable":n,onChange:this.onServerVariableValueChange},l()(t=s.get("enum")).call(t,(e=>c.createElement("option",{selected:e===o(r,n),key:e,value:e},e)))):c.createElement("input",{type:"text",value:o(r,n)||"",onChange:this.onServerVariableValueChange,"data-variable":n})))}))))):null)}}},7779:(e,t,n)=>{"use strict";n.r(t),n.d(t,{OAS30ComponentWrapFactory:()=>c,OAS3ComponentWrapFactory:()=>l,isOAS30:()=>i,isSwagger2:()=>a});var r=n(23101),o=n.n(r),s=n(67294);function i(e){const t=e.get("openapi");return"string"==typeof t&&/^3\.0\.([0123])(?:-rc[012])?$/.test(t)}function a(e){const t=e.get("swagger");return"string"==typeof t&&"2.0"===t}function l(e){return(t,n)=>r=>{var i;return"function"==typeof(null===(i=n.specSelectors)||void 0===i?void 0:i.isOAS3)?n.specSelectors.isOAS3()?s.createElement(e,o()({},r,n,{Ori:t})):s.createElement(t,r):(console.warn("OAS3 wrapper: couldn't get spec"),null)}}function c(e){return(t,n)=>r=>{var i;return"function"==typeof(null===(i=n.specSelectors)||void 0===i?void 0:i.isOAS30)?n.specSelectors.isOAS30()?s.createElement(e,o()({},r,n,{Ori:t})):s.createElement(t,r):(console.warn("OAS30 wrapper: couldn't get spec"),null)}}},97451:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>p});var r=n(92044),o=n(73723),s=n(91741),i=n(76467),a=n(37761),l=n(67002),c=n(5065),u=n(62109);function p(){return{components:i.default,wrapComponents:a.default,statePlugins:{spec:{wrapSelectors:r,selectors:s},auth:{wrapSelectors:o},oas3:{actions:l,reducers:u.default,selectors:c}}}}},62109:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>p});var r=n(8712),o=n.n(r),s=n(86),i=n.n(s),a=n(24282),l=n.n(a),c=n(43393),u=n(67002);const p={[u.UPDATE_SELECTED_SERVER]:(e,t)=>{let{payload:{selectedServerUrl:n,namespace:r}}=t;const o=r?[r,"selectedServer"]:["selectedServer"];return e.setIn(o,n)},[u.UPDATE_REQUEST_BODY_VALUE]:(e,t)=>{let{payload:{value:n,pathMethod:r}}=t,[s,a]=r;if(!c.Map.isMap(n))return e.setIn(["requestData",s,a,"bodyValue"],n);let l,u=e.getIn(["requestData",s,a,"bodyValue"])||(0,c.Map)();c.Map.isMap(u)||(u=(0,c.Map)());const[...p]=o()(n).call(n);return i()(p).call(p,(e=>{let t=n.getIn([e]);u.has(e)&&c.Map.isMap(t)||(l=u.setIn([e,"value"],t))})),e.setIn(["requestData",s,a,"bodyValue"],l)},[u.UPDATE_REQUEST_BODY_VALUE_RETAIN_FLAG]:(e,t)=>{let{payload:{value:n,pathMethod:r}}=t,[o,s]=r;return e.setIn(["requestData",o,s,"retainBodyValue"],n)},[u.UPDATE_REQUEST_BODY_INCLUSION]:(e,t)=>{let{payload:{value:n,pathMethod:r,name:o}}=t,[s,i]=r;return e.setIn(["requestData",s,i,"bodyInclusion",o],n)},[u.UPDATE_ACTIVE_EXAMPLES_MEMBER]:(e,t)=>{let{payload:{name:n,pathMethod:r,contextType:o,contextName:s}}=t,[i,a]=r;return e.setIn(["examples",i,a,o,s,"activeExample"],n)},[u.UPDATE_REQUEST_CONTENT_TYPE]:(e,t)=>{let{payload:{value:n,pathMethod:r}}=t,[o,s]=r;return e.setIn(["requestData",o,s,"requestContentType"],n)},[u.UPDATE_RESPONSE_CONTENT_TYPE]:(e,t)=>{let{payload:{value:n,path:r,method:o}}=t;return e.setIn(["requestData",r,o,"responseContentType"],n)},[u.UPDATE_SERVER_VARIABLE_VALUE]:(e,t)=>{let{payload:{server:n,namespace:r,key:o,val:s}}=t;const i=r?[r,"serverVariableValues",n,o]:["serverVariableValues",n,o];return e.setIn(i,s)},[u.SET_REQUEST_BODY_VALIDATE_ERROR]:(e,t)=>{let{payload:{path:n,method:r,validationErrors:o}}=t,s=[];if(s.push("Required field is not provided"),o.missingBodyValue)return e.setIn(["requestData",n,r,"errors"],(0,c.fromJS)(s));if(o.missingRequiredKeys&&o.missingRequiredKeys.length>0){const{missingRequiredKeys:t}=o;return e.updateIn(["requestData",n,r,"bodyValue"],(0,c.fromJS)({}),(e=>l()(t).call(t,((e,t)=>e.setIn([t,"errors"],(0,c.fromJS)(s))),e)))}return console.warn("unexpected result: SET_REQUEST_BODY_VALIDATE_ERROR"),e},[u.CLEAR_REQUEST_BODY_VALIDATE_ERROR]:(e,t)=>{let{payload:{path:n,method:r}}=t;const s=e.getIn(["requestData",n,r,"bodyValue"]);if(!c.Map.isMap(s))return e.setIn(["requestData",n,r,"errors"],(0,c.fromJS)([]));const[...i]=o()(s).call(s);return i?e.updateIn(["requestData",n,r,"bodyValue"],(0,c.fromJS)({}),(e=>l()(i).call(i,((e,t)=>e.setIn([t,"errors"],(0,c.fromJS)([]))),e))):e},[u.CLEAR_REQUEST_BODY_VALUE]:(e,t)=>{let{payload:{pathMethod:n}}=t,[r,o]=n;const s=e.getIn(["requestData",r,o,"bodyValue"]);return s?c.Map.isMap(s)?e.setIn(["requestData",r,o,"bodyValue"],(0,c.Map)()):e.setIn(["requestData",r,o,"bodyValue"],""):e}}},5065:(e,t,n)=>{"use strict";n.r(t),n.d(t,{activeExamplesMember:()=>S,hasUserEditedBody:()=>w,requestBodyErrors:()=>x,requestBodyInclusionSetting:()=>E,requestBodyValue:()=>y,requestContentType:()=>_,responseContentType:()=>j,selectDefaultRequestBodyValue:()=>b,selectedServer:()=>g,serverEffectiveValue:()=>A,serverVariableValue:()=>O,serverVariables:()=>k,shouldRetainRequestBodyValue:()=>v,validOperationMethods:()=>I,validateBeforeExecute:()=>C,validateShallowRequired:()=>N});var r=n(97606),o=n.n(r),s=n(86),i=n.n(s),a=n(28222),l=n.n(a),c=n(11882),u=n.n(c),p=n(43393),h=n(20573),f=n(42458),d=n(90242);const m=e=>function(t){for(var n=arguments.length,r=new Array(n>1?n-1:0),o=1;o{if(n.getSystem().specSelectors.isOAS3()){const o=e(t,...r);return"function"==typeof o?o(n):o}return null}};const g=m(((e,t)=>{const n=t?[t,"selectedServer"]:["selectedServer"];return e.getIn(n)||""})),y=m(((e,t,n)=>e.getIn(["requestData",t,n,"bodyValue"])||null)),v=m(((e,t,n)=>e.getIn(["requestData",t,n,"retainBodyValue"])||!1)),b=(e,t,n)=>e=>{const{oas3Selectors:r,specSelectors:o,fn:s}=e.getSystem();if(o.isOAS3()){const e=r.requestContentType(t,n);if(e)return(0,f.getDefaultRequestBodyValue)(o.specResolvedSubtree(["paths",t,n,"requestBody"]),e,r.activeExamplesMember(t,n,"requestBody","requestBody"),s)}return null},w=m(((e,t,n)=>e=>{const{oas3Selectors:r,specSelectors:o,fn:s}=e;let i=!1;const a=r.requestContentType(t,n);let l=r.requestBodyValue(t,n);const c=o.specResolvedSubtree(["paths",t,n,"requestBody"]);if(!c)return!1;if(p.Map.isMap(l)&&(l=(0,d.Pz)(l.mapEntries((e=>p.Map.isMap(e[1])?[e[0],e[1].get("value")]:e)).toJS())),p.List.isList(l)&&(l=(0,d.Pz)(l)),a){const e=(0,f.getDefaultRequestBodyValue)(c,a,r.activeExamplesMember(t,n,"requestBody","requestBody"),s);i=!!l&&l!==e}return i})),E=m(((e,t,n)=>e.getIn(["requestData",t,n,"bodyInclusion"])||(0,p.Map)())),x=m(((e,t,n)=>e.getIn(["requestData",t,n,"errors"])||null)),S=m(((e,t,n,r,o)=>e.getIn(["examples",t,n,r,o,"activeExample"])||null)),_=m(((e,t,n)=>e.getIn(["requestData",t,n,"requestContentType"])||null)),j=m(((e,t,n)=>e.getIn(["requestData",t,n,"responseContentType"])||null)),O=m(((e,t,n)=>{let r;if("string"!=typeof t){const{server:e,namespace:o}=t;r=o?[o,"serverVariableValues",e,n]:["serverVariableValues",e,n]}else{r=["serverVariableValues",t,n]}return e.getIn(r)||null})),k=m(((e,t)=>{let n;if("string"!=typeof t){const{server:e,namespace:r}=t;n=r?[r,"serverVariableValues",e]:["serverVariableValues",e]}else{n=["serverVariableValues",t]}return e.getIn(n)||(0,p.OrderedMap)()})),A=m(((e,t)=>{var n,r;if("string"!=typeof t){const{server:o,namespace:s}=t;r=o,n=s?e.getIn([s,"serverVariableValues",r]):e.getIn(["serverVariableValues",r])}else r=t,n=e.getIn(["serverVariableValues",r]);n=n||(0,p.OrderedMap)();let s=r;return o()(n).call(n,((e,t)=>{s=s.replace(new RegExp(`{${t}}`,"g"),e)})),s})),C=(P=(e,t)=>((e,t)=>(t=t||[],!!e.getIn(["requestData",...t,"bodyValue"])))(e,t),function(){for(var e=arguments.length,t=new Array(e),n=0;n{const n=e.getSystem().specSelectors.specJson();let r=[...t][1]||[];return!n.getIn(["paths",...r,"requestBody","required"])||P(...t)}});var P;const N=(e,t)=>{var n;let{oas3RequiredRequestBodyContentType:r,oas3RequestContentType:o,oas3RequestBodyValue:s}=t,a=[];if(!p.Map.isMap(s))return a;let c=[];return i()(n=l()(r.requestContentType)).call(n,(e=>{if(e===o){let t=r.requestContentType[e];i()(t).call(t,(e=>{u()(c).call(c,e)<0&&c.push(e)}))}})),i()(c).call(c,(e=>{s.getIn([e,"value"])||a.push(e)})),a},I=(0,h.P1)((()=>["get","put","post","delete","options","head","patch","trace"]))},91741:(e,t,n)=>{"use strict";n.r(t),n.d(t,{callbacksOperations:()=>E,isOAS3:()=>v,isOAS30:()=>y,isSwagger2:()=>g,servers:()=>w});var r=n(97606),o=n.n(r),s=n(24282),i=n.n(s),a=n(14418),l=n.n(a),c=n(58118),u=n.n(c),p=n(39022),h=n.n(p),f=n(43393),d=n(7779);const m=(0,f.Map)(),g=()=>e=>{const t=e.getSystem().specSelectors.specJson();return(0,d.isSwagger2)(t)},y=()=>e=>{const t=e.getSystem().specSelectors.specJson();return(0,d.isOAS30)(t)},v=()=>e=>e.getSystem().specSelectors.isOAS30();function b(e){return function(t){for(var n=arguments.length,r=new Array(n>1?n-1:0),o=1;o{if(n.specSelectors.isOAS3()){const o=e(t,...r);return"function"==typeof o?o(n):o}return null}}}const w=b((()=>e=>e.specSelectors.specJson().get("servers",m))),E=b(((e,t)=>{let{callbacks:n,specPath:r}=t;return e=>{var t;const s=e.specSelectors.validOperationMethods();return f.Map.isMap(n)?o()(t=i()(n).call(n,((e,t,n)=>f.Map.isMap(t)?i()(t).call(t,((e,t,i)=>{var a,c;if(!f.Map.isMap(t))return e;const p=o()(a=l()(c=t.entrySeq()).call(c,(e=>{let[t]=e;return u()(s).call(s,t)}))).call(a,(e=>{let[t,o]=e;return{operation:(0,f.Map)({operation:o}),method:t,path:i,callbackName:n,specPath:h()(r).call(r,[n,i,t])}}));return h()(e).call(e,p)}),(0,f.List)()):e),(0,f.List)()).groupBy((e=>e.callbackName))).call(t,(e=>e.toArray())).toObject():{}}}))},92044:(e,t,n)=>{"use strict";n.r(t),n.d(t,{basePath:()=>d,consumes:()=>m,definitions:()=>c,hasHost:()=>u,host:()=>f,produces:()=>g,schemes:()=>y,securityDefinitions:()=>p,validOperationMethods:()=>h});var r=n(20573),o=n(33881),s=n(43393);const i=(0,s.Map)();function a(e){return(t,n)=>function(){if(n.getSystem().specSelectors.isOAS3()){const t=e(...arguments);return"function"==typeof t?t(n):t}return t(...arguments)}}const l=a((0,r.P1)((()=>null))),c=a((()=>e=>{const t=e.getSystem().specSelectors.specJson().getIn(["components","schemas"]);return s.Map.isMap(t)?t:i})),u=a((()=>e=>e.getSystem().specSelectors.specJson().hasIn(["servers",0]))),p=a((0,r.P1)(o.specJsonWithResolvedSubtrees,(e=>e.getIn(["components","securitySchemes"])||null))),h=(e,t)=>function(n){if(t.specSelectors.isOAS3())return t.oas3Selectors.validOperationMethods();for(var r=arguments.length,o=new Array(r>1?r-1:0),s=1;s{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(67294);const o=(0,n(7779).OAS3ComponentWrapFactory)((e=>{let{Ori:t,...n}=e;const{schema:o,getComponent:s,errSelectors:i,authorized:a,onAuthChange:l,name:c}=n,u=s("HttpAuth");return"http"===o.get("type")?r.createElement(u,{key:c,schema:o,name:c,errSelectors:i,authorized:a,getComponent:s,onChange:l}):r.createElement(t,n)}))},37761:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>c});var r=n(22460),o=n(70356),s=n(69487),i=n(50058),a=n(53499),l=n(90287);const c={Markdown:r.default,AuthItem:o.default,JsonSchema_string:l.default,VersionStamp:s.default,model:a.default,onlineValidatorBadge:i.default}},90287:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(67294);const o=(0,n(7779).OAS3ComponentWrapFactory)((e=>{let{Ori:t,...n}=e;const{schema:o,getComponent:s,errors:i,onChange:a}=n,l=o&&o.get?o.get("format"):null,c=o&&o.get?o.get("type"):null,u=s("Input");return c&&"string"===c&&l&&("binary"===l||"base64"===l)?r.createElement(u,{type:"file",className:i.length?"invalid":"",title:i.length?i:"",onChange:e=>{a(e.target.files[0])},disabled:t.isDisabled}):r.createElement(t,n)}))},22460:(e,t,n)=>{"use strict";n.r(t),n.d(t,{Markdown:()=>h,default:()=>f});var r=n(81607),o=n.n(r),s=n(67294),i=n(94184),a=n.n(i),l=n(89927),c=n(7779),u=n(4599);const p=new l._("commonmark");p.block.ruler.enable(["table"]),p.set({linkTarget:"_blank"});const h=e=>{let{source:t,className:n="",getConfigs:r}=e;if("string"!=typeof t)return null;if(t){const{useUnsafeMarkdown:e}=r(),i=p.render(t),l=(0,u.s)(i,{useUnsafeMarkdown:e});let c;return"string"==typeof l&&(c=o()(l).call(l)),s.createElement("div",{dangerouslySetInnerHTML:{__html:c},className:a()(n,"renderedMarkdown")})}return null};h.defaultProps={getConfigs:()=>({useUnsafeMarkdown:!1})};const f=(0,c.OAS3ComponentWrapFactory)(h)},53499:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>c});var r=n(23101),o=n.n(r),s=n(67294),i=n(7779),a=n(53795);class l extends s.Component{render(){let{getConfigs:e,schema:t}=this.props,n=["model-box"],r=null;return!0===t.get("deprecated")&&(n.push("deprecated"),r=s.createElement("span",{className:"model-deprecated-warning"},"Deprecated:")),s.createElement("div",{className:n.join(" ")},r,s.createElement(a.Z,o()({},this.props,{getConfigs:e,depth:1,expandDepth:this.props.expandDepth||0})))}}const c=(0,i.OAS3ComponentWrapFactory)(l)},50058:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>s});var r=n(7779),o=n(5623);const s=(0,r.OAS3ComponentWrapFactory)(o.Z)},69487:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(67294);const o=(0,n(7779).OAS30ComponentWrapFactory)((e=>{const{Ori:t}=e;return r.createElement("span",null,r.createElement(t,e),r.createElement("small",{className:"version-stamp"},r.createElement("pre",{className:"version"},"OAS 3.0")))}))},92372:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>a});var r=n(76986),o=n.n(r),s=n(25800),i=n(84380);const a=function(e){let{fn:t,getSystem:n}=e;if(t.jsonSchema202012){const e=(0,s.makeIsExpandable)(t.jsonSchema202012.isExpandable,n);o()(this.fn.jsonSchema202012,{isExpandable:e,getProperties:s.getProperties})}if("function"==typeof t.sampleFromSchema&&t.jsonSchema202012){const e=(0,i.wrapOAS31Fn)({sampleFromSchema:t.jsonSchema202012.sampleFromSchema,sampleFromSchemaGeneric:t.jsonSchema202012.sampleFromSchemaGeneric,createXMLExample:t.jsonSchema202012.createXMLExample,memoizedSampleFromSchema:t.jsonSchema202012.memoizedSampleFromSchema,memoizedCreateXMLExample:t.jsonSchema202012.memoizedCreateXMLExample},n());o()(this.fn,e)}}},89503:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>s});var r=n(67294),o=n(90242);const s=e=>{let{getComponent:t,specSelectors:n}=e;const s=n.selectContactNameField(),i=n.selectContactUrl(),a=n.selectContactEmailField(),l=t("Link");return r.createElement("div",{className:"info__contact"},i&&r.createElement("div",null,r.createElement(l,{href:(0,o.Nm)(i),target:"_blank"},s," - Website")),a&&r.createElement(l,{href:(0,o.Nm)(`mailto:${a}`)},i?`Send email to ${s}`:`Contact ${s}`))}},16133:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>s});var r=n(67294),o=n(90242);const s=e=>{let{getComponent:t,specSelectors:n}=e;const s=n.version(),i=n.url(),a=n.basePath(),l=n.host(),c=n.selectInfoSummaryField(),u=n.selectInfoDescriptionField(),p=n.selectInfoTitleField(),h=n.selectInfoTermsOfServiceUrl(),f=n.selectExternalDocsUrl(),d=n.selectExternalDocsDescriptionField(),m=n.contact(),g=n.license(),y=t("Markdown",!0),v=t("Link"),b=t("VersionStamp"),w=t("InfoUrl"),E=t("InfoBasePath"),x=t("License",!0),S=t("Contact",!0),_=t("JsonSchemaDialect",!0);return r.createElement("div",{className:"info"},r.createElement("hgroup",{className:"main"},r.createElement("h2",{className:"title"},p,s&&r.createElement(b,{version:s})),(l||a)&&r.createElement(E,{host:l,basePath:a}),i&&r.createElement(w,{getComponent:t,url:i})),c&&r.createElement("p",{className:"info__summary"},c),r.createElement("div",{className:"info__description description"},r.createElement(y,{source:u})),h&&r.createElement("div",{className:"info__tos"},r.createElement(v,{target:"_blank",href:(0,o.Nm)(h)},"Terms of service")),m.size>0&&r.createElement(S,null),g.size>0&&r.createElement(x,null),f&&r.createElement(v,{className:"info__extdocs",target:"_blank",href:(0,o.Nm)(f)},d||f),r.createElement(_,null))}},92562:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>s});var r=n(67294),o=n(90242);const s=e=>{let{getComponent:t,specSelectors:n}=e;const s=n.selectJsonSchemaDialectField(),i=n.selectJsonSchemaDialectDefault(),a=t("Link");return r.createElement(r.Fragment,null,s&&s===i&&r.createElement("p",{className:"info__jsonschemadialect"},"JSON Schema dialect:"," ",r.createElement(a,{target:"_blank",href:(0,o.Nm)(s)},s)),s&&s!==i&&r.createElement("div",{className:"error-wrapper"},r.createElement("div",{className:"no-margin"},r.createElement("div",{className:"errors"},r.createElement("div",{className:"errors-wrapper"},r.createElement("h4",{className:"center"},"Warning"),r.createElement("p",{className:"message"},r.createElement("strong",null,"OpenAPI.jsonSchemaDialect")," field contains a value different from the default value of"," ",r.createElement(a,{target:"_blank",href:i},i),". Values different from the default one are currently not supported. Please either omit the field or provide it with the default value."))))))}},51876:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>s});var r=n(67294),o=n(90242);const s=e=>{let{getComponent:t,specSelectors:n}=e;const s=n.selectLicenseNameField(),i=n.selectLicenseUrl(),a=t("Link");return r.createElement("div",{className:"info__license"},i?r.createElement("div",{className:"info__license__url"},r.createElement(a,{target:"_blank",href:(0,o.Nm)(i)},s)):r.createElement("span",null,s))}},92718:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>l});var r=n(58118),o=n.n(r),s=n(67294);n(23930);const i=e=>"string"==typeof e&&o()(e).call(e,"#/components/schemas/")?(e=>{const t=e.replace(/~1/g,"/").replace(/~0/g,"~");try{return decodeURIComponent(t)}catch{return t}})(e.replace(/^.*#\/components\/schemas\//,"")):null,a=(0,s.forwardRef)(((e,t)=>{let{schema:n,getComponent:r,onToggle:o}=e;const a=r("JSONSchema202012"),l=i(n.get("$$ref")),c=(0,s.useCallback)(((e,t)=>{o(l,t)}),[l,o]);return s.createElement(a,{name:l,schema:n.toJS(),ref:t,onExpand:c})}));a.defaultProps={name:"",displayName:"",isRef:!1,required:!1,expandDepth:0,depth:1,includeReadOnly:!1,includeWriteOnly:!1,onToggle:()=>{}};const l=a},20263:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>h});var r=n(28222),o=n.n(r),s=n(97606),i=n.n(s),a=n(2018),l=n.n(a),c=n(67294),u=n(94184),p=n.n(u);const h=e=>{var t;let{specActions:n,specSelectors:r,layoutSelectors:s,layoutActions:a,getComponent:u,getConfigs:h}=e;const f=r.selectSchemas(),d=o()(f).length>0,m=["components","schemas"],{docExpansion:g,defaultModelsExpandDepth:y}=h(),v=y>0&&"none"!==g,b=s.isShown(m,v),w=u("Collapse"),E=u("JSONSchema202012");(0,c.useEffect)((()=>{const e=b&&y>1,t=null!=r.specResolvedSubtree(m);e&&!t&&n.requestResolvedSubtree(m)}),[b,y]);const x=(0,c.useCallback)((()=>{a.show(m,!b)}),[b]),S=(0,c.useCallback)((e=>{null!==e&&a.readyToScroll(m,e)}),[]),_=e=>t=>{null!==t&&a.readyToScroll([...m,e],t)},j=e=>(t,o)=>{if(o){const t=[...m,e];null!=r.specResolvedSubtree(t)||n.requestResolvedSubtree([...m,e])}};return!d||y<0?null:c.createElement("section",{className:p()("models",{"is-open":b}),ref:S},c.createElement("h4",null,c.createElement("button",{"aria-expanded":b,className:"models-control",onClick:x},c.createElement("span",null,"Schemas"),c.createElement("svg",{width:"20",height:"20","aria-hidden":"true",focusable:"false"},c.createElement("use",{xlinkHref:b?"#large-arrow-up":"#large-arrow-down"})))),c.createElement(w,{isOpened:b},i()(t=l()(f)).call(t,(e=>{let[t,n]=e;return c.createElement(E,{key:t,ref:_(t),schema:n,name:t,onExpand:j(t)})}))))}},33429:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(67294);const o=e=>{let{bypass:t,isSwagger2:n,isOAS3:o,isOAS31:s,alsoShow:i,children:a}=e;return t?r.createElement("div",null,a):n&&(o||s)?r.createElement("div",{className:"version-pragma"},i,r.createElement("div",{className:"version-pragma__message version-pragma__message--ambiguous"},r.createElement("div",null,r.createElement("h3",null,"Unable to render this definition"),r.createElement("p",null,r.createElement("code",null,"swagger")," and ",r.createElement("code",null,"openapi")," fields cannot be present in the same Swagger or OpenAPI definition. Please remove one of the fields."),r.createElement("p",null,"Supported version fields are ",r.createElement("code",null,'swagger: "2.0"')," and those that match ",r.createElement("code",null,"openapi: 3.x.y")," (for example,"," ",r.createElement("code",null,"openapi: 3.1.0"),").")))):n||o||s?r.createElement("div",null,a):r.createElement("div",{className:"version-pragma"},i,r.createElement("div",{className:"version-pragma__message version-pragma__message--missing"},r.createElement("div",null,r.createElement("h3",null,"Unable to render this definition"),r.createElement("p",null,"The provided definition does not specify a valid version field."),r.createElement("p",null,"Please indicate a valid Swagger or OpenAPI version field. Supported version fields are ",r.createElement("code",null,'swagger: "2.0"')," and those that match ",r.createElement("code",null,"openapi: 3.x.y")," (for example,"," ",r.createElement("code",null,"openapi: 3.1.0"),")."))))}},39508:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>l});var r=n(28222),o=n.n(r),s=n(97606),i=n.n(s),a=n(67294);const l=e=>{let{specSelectors:t,getComponent:n}=e;const r=t.selectWebhooksOperations(),s=o()(r),l=n("OperationContainer",!0);return 0===s.length?null:a.createElement("div",{className:"webhooks"},a.createElement("h2",null,"Webhooks"),i()(s).call(s,(e=>{var t;return a.createElement("div",{key:`${e}-webhook`},i()(t=r[e]).call(t,(t=>a.createElement(l,{key:`${e}-${t.method}-webhook`,op:t.operation,tag:"webhooks",method:t.method,path:e,specPath:t.specPath,allowTryItOut:!1}))))})))}},84380:(e,t,n)=>{"use strict";n.r(t),n.d(t,{createOnlyOAS31ComponentWrapper:()=>g,createOnlyOAS31Selector:()=>f,createOnlyOAS31SelectorWrapper:()=>d,createSystemSelector:()=>m,isOAS31:()=>h,wrapOAS31Fn:()=>y});var r=n(23101),o=n.n(r),s=n(82865),i=n.n(s),a=n(97606),l=n.n(a),c=n(2018),u=n.n(c),p=n(67294);const h=e=>{const t=e.get("openapi");return"string"==typeof t&&/^3\.1\.(?:[1-9]\d*|0)$/.test(t)},f=e=>function(t){for(var n=arguments.length,r=new Array(n>1?n-1:0),o=1;o{if(n.getSystem().specSelectors.isOAS31()){const o=e(t,...r);return"function"==typeof o?o(n):o}return null}},d=e=>(t,n)=>function(r){for(var o=arguments.length,s=new Array(o>1?o-1:0),i=1;ifunction(t){for(var n=arguments.length,r=new Array(n>1?n-1:0),o=1;o{const o=e(t,n,...r);return"function"==typeof o?o(n):o}},g=e=>(t,n)=>r=>n.specSelectors.isOAS31()?p.createElement(e,o()({},r,{originalComponent:t,getSystem:n.getSystem})):p.createElement(t,r),y=(e,t)=>{var n;const{fn:r,specSelectors:o}=t;return i()(l()(n=u()(e)).call(n,(e=>{let[t,n]=e;const s=r[t];return[t,function(){return o.isOAS31()?n(...arguments):"function"==typeof s?s(...arguments):void 0}]})))}},29806:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>P});var r=n(39508),o=n(51876),s=n(89503),i=n(16133),a=n(92562),l=n(33429),c=n(92718),u=n(20263),p=n(6608),h=n(77423),f=n(284),d=n(17042),m=n(22914),g=n(41434),y=n(1122),v=n(84380),b=n(9305),w=n(32884),E=n(64280),x=n(59450),S=n(36617),_=n(19525),j=n(25324),O=n(80809),k=n(14951),A=n(77536),C=n(92372);const P=e=>{let{fn:t}=e;const n=t.createSystemSelector||v.createSystemSelector,P=t.createOnlyOAS31Selector||v.createOnlyOAS31Selector;return{afterLoad:C.default,fn:{isOAS31:v.isOAS31,createSystemSelector:v.createSystemSelector,createOnlyOAS31Selector:v.createOnlyOAS31Selector},components:{Webhooks:r.default,JsonSchemaDialect:a.default,OAS31Info:i.default,OAS31License:o.default,OAS31Contact:s.default,OAS31VersionPragmaFilter:l.default,OAS31Model:c.default,OAS31Models:u.default,JSONSchema202012KeywordExample:x.default,JSONSchema202012KeywordXml:S.default,JSONSchema202012KeywordDiscriminator:_.default,JSONSchema202012KeywordExternalDocs:j.default},wrapComponents:{InfoContainer:f.default,License:p.default,Contact:h.default,VersionPragmaFilter:g.default,VersionStamp:y.default,Model:d.default,Models:m.default,JSONSchema202012KeywordDescription:O.default,JSONSchema202012KeywordDefault:k.default,JSONSchema202012KeywordProperties:A.default},statePlugins:{spec:{selectors:{isOAS31:n(b.isOAS31),license:b.license,selectLicenseNameField:b.selectLicenseNameField,selectLicenseUrlField:b.selectLicenseUrlField,selectLicenseIdentifierField:P(b.selectLicenseIdentifierField),selectLicenseUrl:n(b.selectLicenseUrl),contact:b.contact,selectContactNameField:b.selectContactNameField,selectContactEmailField:b.selectContactEmailField,selectContactUrlField:b.selectContactUrlField,selectContactUrl:n(b.selectContactUrl),selectInfoTitleField:b.selectInfoTitleField,selectInfoSummaryField:P(b.selectInfoSummaryField),selectInfoDescriptionField:b.selectInfoDescriptionField,selectInfoTermsOfServiceField:b.selectInfoTermsOfServiceField,selectInfoTermsOfServiceUrl:n(b.selectInfoTermsOfServiceUrl),selectExternalDocsDescriptionField:b.selectExternalDocsDescriptionField,selectExternalDocsUrlField:b.selectExternalDocsUrlField,selectExternalDocsUrl:n(b.selectExternalDocsUrl),webhooks:P(b.webhooks),selectWebhooksOperations:P(n(b.selectWebhooksOperations)),selectJsonSchemaDialectField:b.selectJsonSchemaDialectField,selectJsonSchemaDialectDefault:b.selectJsonSchemaDialectDefault,selectSchemas:n(b.selectSchemas)},wrapSelectors:{isOAS3:w.isOAS3,selectLicenseUrl:w.selectLicenseUrl}},oas31:{selectors:{selectLicenseUrl:P(n(E.selectLicenseUrl))}}}}}},45989:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(67294);const o=e=>{let{schema:t,getSystem:n}=e;if(null==t||!t.description)return null;const{getComponent:o}=n(),s=o("Markdown");return r.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--description"},r.createElement("div",{className:"json-schema-2020-12-core-keyword__value json-schema-2020-12-core-keyword__value--secondary"},r.createElement(s,{source:t.description})))}},19525:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>c});var r=n(28222),o=n.n(r),s=n(67294),i=n(94184),a=n.n(i),l=n(7749);const c=e=>{let{schema:t,getSystem:n}=e;const r=(null==t?void 0:t.discriminator)||{},{fn:i,getComponent:c}=n(),{useIsExpandedDeeply:u,useComponent:p}=i.jsonSchema202012,h=u(),f=!!r.mapping,[d,m]=(0,s.useState)(h),[g,y]=(0,s.useState)(!1),v=p("Accordion"),b=p("ExpandDeepButton"),w=c("JSONSchema202012DeepExpansionContext")(),E=(0,s.useCallback)((()=>{m((e=>!e))}),[]),x=(0,s.useCallback)(((e,t)=>{m(t),y(t)}),[]);return 0===o()(r).length?null:s.createElement(w.Provider,{value:g},s.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--discriminator"},f?s.createElement(s.Fragment,null,s.createElement(v,{expanded:d,onChange:E},s.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary"},"Discriminator")),s.createElement(b,{expanded:d,onClick:x})):s.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary"},"Discriminator"),r.propertyName&&s.createElement("span",{className:"json-schema-2020-12__attribute json-schema-2020-12__attribute--muted"},r.propertyName),s.createElement("strong",{className:"json-schema-2020-12__attribute json-schema-2020-12__attribute--primary"},"object"),s.createElement("ul",{className:a()("json-schema-2020-12-keyword__children",{"json-schema-2020-12-keyword__children--collapsed":!d})},d&&s.createElement("li",{className:"json-schema-2020-12-property"},s.createElement(l.default,{discriminator:r})))))}},7749:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>p});var r=n(28222),o=n.n(r),s=n(97606),i=n.n(s),a=n(2018),l=n.n(a),c=n(67294);const u=e=>{var t;let{discriminator:n}=e;const r=(null==n?void 0:n.mapping)||{};return 0===o()(r).length?null:i()(t=l()(r)).call(t,(e=>{let[t,n]=e;return c.createElement("div",{key:`${t}-${n}`,className:"json-schema-2020-12-keyword"},c.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary"},t),c.createElement("span",{className:"json-schema-2020-12-keyword__value json-schema-2020-12-keyword__value--secondary"},n))}))};u.defaultProps={mapping:void 0};const p=u},59450:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(67294);const o=e=>{let{schema:t,getSystem:n}=e;const{fn:o}=n(),{hasKeyword:s,stringify:i}=o.jsonSchema202012.useFn();return s(t,"example")?r.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--example"},r.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary"},"Example"),r.createElement("span",{className:"json-schema-2020-12-keyword__value json-schema-2020-12-keyword__value--const"},i(t.example))):null}},25324:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>c});var r=n(28222),o=n.n(r),s=n(67294),i=n(94184),a=n.n(i),l=n(90242);const c=e=>{let{schema:t,getSystem:n}=e;const r=(null==t?void 0:t.externalDocs)||{},{fn:i,getComponent:c}=n(),{useIsExpandedDeeply:u,useComponent:p}=i.jsonSchema202012,h=u(),f=!(!r.description&&!r.url),[d,m]=(0,s.useState)(h),[g,y]=(0,s.useState)(!1),v=p("Accordion"),b=p("ExpandDeepButton"),w=c("JSONSchema202012KeywordDescription"),E=c("Link"),x=c("JSONSchema202012DeepExpansionContext")(),S=(0,s.useCallback)((()=>{m((e=>!e))}),[]),_=(0,s.useCallback)(((e,t)=>{m(t),y(t)}),[]);return 0===o()(r).length?null:s.createElement(x.Provider,{value:g},s.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--externalDocs"},f?s.createElement(s.Fragment,null,s.createElement(v,{expanded:d,onChange:S},s.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary"},"External documentation")),s.createElement(b,{expanded:d,onClick:_})):s.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary"},"External documentation"),s.createElement("strong",{className:"json-schema-2020-12__attribute json-schema-2020-12__attribute--primary"},"object"),s.createElement("ul",{className:a()("json-schema-2020-12-keyword__children",{"json-schema-2020-12-keyword__children--collapsed":!d})},d&&s.createElement(s.Fragment,null,r.description&&s.createElement("li",{className:"json-schema-2020-12-property"},s.createElement(w,{schema:r,getSystem:n})),r.url&&s.createElement("li",{className:"json-schema-2020-12-property"},s.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword"},s.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary"},"url"),s.createElement("span",{className:"json-schema-2020-12-keyword__value json-schema-2020-12-keyword__value--secondary"},s.createElement(E,{target:"_blank",href:(0,l.Nm)(r.url)},r.url))))))))}},9023:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>g});var r=n(58309),o=n.n(r),s=n(28222),i=n.n(s),a=n(97606),l=n.n(a),c=n(2018),u=n.n(c),p=n(58118),h=n.n(p),f=n(67294),d=n(94184),m=n.n(d);const g=e=>{var t;let{schema:n,getSystem:r}=e;const{fn:s}=r(),{useComponent:a}=s.jsonSchema202012,{getDependentRequired:c,getProperties:p}=s.jsonSchema202012.useFn(),d=s.jsonSchema202012.useConfig(),g=o()(null==n?void 0:n.required)?n.required:[],y=a("JSONSchema"),v=p(n,d);return 0===i()(v).length?null:f.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--properties"},f.createElement("ul",null,l()(t=u()(v)).call(t,(e=>{let[t,r]=e;const o=h()(g).call(g,t),s=c(t,n);return f.createElement("li",{key:t,className:m()("json-schema-2020-12-property",{"json-schema-2020-12-property--required":o})},f.createElement(y,{name:t,schema:r,dependentRequired:s}))}))))}},36617:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>l});var r=n(28222),o=n.n(r),s=n(67294),i=n(94184),a=n.n(i);const l=e=>{let{schema:t,getSystem:n}=e;const r=(null==t?void 0:t.xml)||{},{fn:i,getComponent:l}=n(),{useIsExpandedDeeply:c,useComponent:u}=i.jsonSchema202012,p=c(),h=!!(r.name||r.namespace||r.prefix),[f,d]=(0,s.useState)(p),[m,g]=(0,s.useState)(!1),y=u("Accordion"),v=u("ExpandDeepButton"),b=l("JSONSchema202012DeepExpansionContext")(),w=(0,s.useCallback)((()=>{d((e=>!e))}),[]),E=(0,s.useCallback)(((e,t)=>{d(t),g(t)}),[]);return 0===o()(r).length?null:s.createElement(b.Provider,{value:m},s.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword--xml"},h?s.createElement(s.Fragment,null,s.createElement(y,{expanded:f,onChange:w},s.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary"},"XML")),s.createElement(v,{expanded:f,onClick:E})):s.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary"},"XML"),!0===r.attribute&&s.createElement("span",{className:"json-schema-2020-12__attribute json-schema-2020-12__attribute--muted"},"attribute"),!0===r.wrapped&&s.createElement("span",{className:"json-schema-2020-12__attribute json-schema-2020-12__attribute--muted"},"wrapped"),s.createElement("strong",{className:"json-schema-2020-12__attribute json-schema-2020-12__attribute--primary"},"object"),s.createElement("ul",{className:a()("json-schema-2020-12-keyword__children",{"json-schema-2020-12-keyword__children--collapsed":!f})},f&&s.createElement(s.Fragment,null,r.name&&s.createElement("li",{className:"json-schema-2020-12-property"},s.createElement("div",{className:"json-schema-2020-12-keyword json-schema-2020-12-keyword"},s.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary"},"name"),s.createElement("span",{className:"json-schema-2020-12-keyword__value json-schema-2020-12-keyword__value--secondary"},r.name))),r.namespace&&s.createElement("li",{className:"json-schema-2020-12-property"},s.createElement("div",{className:"json-schema-2020-12-keyword"},s.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary"},"namespace"),s.createElement("span",{className:"json-schema-2020-12-keyword__value json-schema-2020-12-keyword__value--secondary"},r.namespace))),r.prefix&&s.createElement("li",{className:"json-schema-2020-12-property"},s.createElement("div",{className:"json-schema-2020-12-keyword"},s.createElement("span",{className:"json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary"},"prefix"),s.createElement("span",{className:"json-schema-2020-12-keyword__value json-schema-2020-12-keyword__value--secondary"},r.prefix)))))))}},25800:(e,t,n)=>{"use strict";n.r(t),n.d(t,{getProperties:()=>u,makeIsExpandable:()=>c});var r=n(2018),o=n.n(r),s=n(14418),i=n.n(s),a=n(82865),l=n.n(a);const c=(e,t)=>{const{fn:n}=t();if("function"!=typeof e)return null;const{hasKeyword:r}=n.jsonSchema202012;return t=>e(t)||r(t,"example")||(null==t?void 0:t.xml)||(null==t?void 0:t.discriminator)||(null==t?void 0:t.externalDocs)},u=(e,t)=>{let{includeReadOnly:n,includeWriteOnly:r}=t;if(null==e||!e.properties)return{};const s=o()(e.properties),a=i()(s).call(s,(e=>{let[,t]=e;const o=!0===(null==t?void 0:t.readOnly),s=!0===(null==t?void 0:t.writeOnly);return(!o||n)&&(!s||r)}));return l()(a)}},14951:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(67294);const o=(0,n(84380).createOnlyOAS31ComponentWrapper)((e=>{let{schema:t,getSystem:n,originalComponent:o}=e;const{getComponent:s}=n(),i=s("JSONSchema202012KeywordDiscriminator"),a=s("JSONSchema202012KeywordXml"),l=s("JSONSchema202012KeywordExample"),c=s("JSONSchema202012KeywordExternalDocs");return r.createElement(r.Fragment,null,r.createElement(o,{schema:t}),r.createElement(i,{schema:t,getSystem:n}),r.createElement(a,{schema:t,getSystem:n}),r.createElement(c,{schema:t,getSystem:n}),r.createElement(l,{schema:t,getSystem:n}))}))},80809:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(45989);const o=(0,n(84380).createOnlyOAS31ComponentWrapper)(r.default)},77536:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(9023);const o=(0,n(84380).createOnlyOAS31ComponentWrapper)(r.default)},64280:(e,t,n)=>{"use strict";n.r(t),n.d(t,{selectLicenseUrl:()=>s});var r=n(20573),o=n(63543);const s=(0,r.P1)(((e,t)=>t.specSelectors.url()),((e,t)=>t.oas3Selectors.selectedServer()),((e,t)=>t.specSelectors.selectLicenseUrlField()),((e,t)=>t.specSelectors.selectLicenseIdentifierField()),((e,t,n,r)=>n?(0,o.mn)(n,e,{selectedServer:t}):r?`https://spdx.org/licenses/${r}.html`:void 0))},9305:(e,t,n)=>{"use strict";n.r(t),n.d(t,{contact:()=>A,isOAS31:()=>w,license:()=>S,selectContactEmailField:()=>P,selectContactNameField:()=>C,selectContactUrl:()=>I,selectContactUrlField:()=>N,selectExternalDocsDescriptionField:()=>L,selectExternalDocsUrl:()=>$,selectExternalDocsUrlField:()=>B,selectInfoDescriptionField:()=>M,selectInfoSummaryField:()=>R,selectInfoTermsOfServiceField:()=>D,selectInfoTermsOfServiceUrl:()=>F,selectInfoTitleField:()=>T,selectJsonSchemaDialectDefault:()=>U,selectJsonSchemaDialectField:()=>q,selectLicenseIdentifierField:()=>k,selectLicenseNameField:()=>_,selectLicenseUrl:()=>O,selectLicenseUrlField:()=>j,selectSchemas:()=>z,selectWebhooksOperations:()=>x,webhooks:()=>E});var r=n(97606),o=n.n(r),s=n(24282),i=n.n(s),a=n(14418),l=n.n(a),c=n(58118),u=n.n(c),p=n(39022),h=n.n(p),f=n(2018),d=n.n(f),m=n(43393),g=n(20573),y=n(63543),v=n(84380);const b=(0,m.Map)(),w=(0,g.P1)(((e,t)=>t.specSelectors.specJson()),v.isOAS31),E=()=>e=>e.specSelectors.specJson().get("webhooks",b),x=(0,g.P1)(((e,t)=>t.specSelectors.webhooks()),((e,t)=>t.specSelectors.validOperationMethods()),((e,t)=>t.specSelectors.specResolvedSubtree(["webhooks"])),((e,t)=>{var n;return m.Map.isMap(e)?o()(n=i()(e).call(e,((e,n,r)=>{var s,i;if(!m.Map.isMap(n))return e;const a=o()(s=l()(i=n.entrySeq()).call(i,(e=>{let[n]=e;return u()(t).call(t,n)}))).call(s,(e=>{let[t,n]=e;return{operation:(0,m.Map)({operation:n}),method:t,path:r,specPath:(0,m.List)(["webhooks",r,t])}}));return h()(e).call(e,a)}),(0,m.List)()).groupBy((e=>e.path))).call(n,(e=>e.toArray())).toObject():{}})),S=()=>e=>e.specSelectors.info().get("license",b),_=()=>e=>e.specSelectors.license().get("name","License"),j=()=>e=>e.specSelectors.license().get("url"),O=(0,g.P1)(((e,t)=>t.specSelectors.url()),((e,t)=>t.oas3Selectors.selectedServer()),((e,t)=>t.specSelectors.selectLicenseUrlField()),((e,t,n)=>{if(n)return(0,y.mn)(n,e,{selectedServer:t})})),k=()=>e=>e.specSelectors.license().get("identifier"),A=()=>e=>e.specSelectors.info().get("contact",b),C=()=>e=>e.specSelectors.contact().get("name","the developer"),P=()=>e=>e.specSelectors.contact().get("email"),N=()=>e=>e.specSelectors.contact().get("url"),I=(0,g.P1)(((e,t)=>t.specSelectors.url()),((e,t)=>t.oas3Selectors.selectedServer()),((e,t)=>t.specSelectors.selectContactUrlField()),((e,t,n)=>{if(n)return(0,y.mn)(n,e,{selectedServer:t})})),T=()=>e=>e.specSelectors.info().get("title"),R=()=>e=>e.specSelectors.info().get("summary"),M=()=>e=>e.specSelectors.info().get("description"),D=()=>e=>e.specSelectors.info().get("termsOfService"),F=(0,g.P1)(((e,t)=>t.specSelectors.url()),((e,t)=>t.oas3Selectors.selectedServer()),((e,t)=>t.specSelectors.selectInfoTermsOfServiceField()),((e,t,n)=>{if(n)return(0,y.mn)(n,e,{selectedServer:t})})),L=()=>e=>e.specSelectors.externalDocs().get("description"),B=()=>e=>e.specSelectors.externalDocs().get("url"),$=(0,g.P1)(((e,t)=>t.specSelectors.url()),((e,t)=>t.oas3Selectors.selectedServer()),((e,t)=>t.specSelectors.selectExternalDocsUrlField()),((e,t,n)=>{if(n)return(0,y.mn)(n,e,{selectedServer:t})})),q=()=>e=>e.specSelectors.specJson().get("jsonSchemaDialect"),U=()=>"https://spec.openapis.org/oas/3.1/dialect/base",z=(0,g.P1)(((e,t)=>t.specSelectors.definitions()),((e,t)=>t.specSelectors.specResolvedSubtree(["components","schemas"])),((e,t)=>{var n;return m.Map.isMap(e)?m.Map.isMap(t)?i()(n=d()(e.toJS())).call(n,((e,n)=>{let[r,o]=n;const s=t.get(r);return e[r]=(null==s?void 0:s.toJS())||o,e}),{}):e.toJS():{}}))},32884:(e,t,n)=>{"use strict";n.r(t),n.d(t,{isOAS3:()=>o,selectLicenseUrl:()=>s});var r=n(84380);const o=(e,t)=>function(n){const r=t.specSelectors.isOAS31();for(var o=arguments.length,s=new Array(o>1?o-1:0),i=1;i(e,t)=>t.oas31Selectors.selectLicenseUrl()))},77423:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(67294);const o=(0,n(84380).createOnlyOAS31ComponentWrapper)((e=>{let{getSystem:t}=e;const n=t().getComponent("OAS31Contact",!0);return r.createElement(n,null)}))},284:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(67294);const o=(0,n(84380).createOnlyOAS31ComponentWrapper)((e=>{let{getSystem:t}=e;const n=t().getComponent("OAS31Info",!0);return r.createElement(n,null)}))},6608:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(67294);const o=(0,n(84380).createOnlyOAS31ComponentWrapper)((e=>{let{getSystem:t}=e;const n=t().getComponent("OAS31License",!0);return r.createElement(n,null)}))},17042:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>i});var r=n(67294),o=n(84380),s=n(25800);const i=(0,o.createOnlyOAS31ComponentWrapper)((e=>{let{getSystem:t,...n}=e;const o=t(),{getComponent:i,fn:a,getConfigs:l}=o,c=l(),u=i("OAS31Model"),p=i("JSONSchema202012"),h=i("JSONSchema202012Keyword$schema"),f=i("JSONSchema202012Keyword$vocabulary"),d=i("JSONSchema202012Keyword$id"),m=i("JSONSchema202012Keyword$anchor"),g=i("JSONSchema202012Keyword$dynamicAnchor"),y=i("JSONSchema202012Keyword$ref"),v=i("JSONSchema202012Keyword$dynamicRef"),b=i("JSONSchema202012Keyword$defs"),w=i("JSONSchema202012Keyword$comment"),E=i("JSONSchema202012KeywordAllOf"),x=i("JSONSchema202012KeywordAnyOf"),S=i("JSONSchema202012KeywordOneOf"),_=i("JSONSchema202012KeywordNot"),j=i("JSONSchema202012KeywordIf"),O=i("JSONSchema202012KeywordThen"),k=i("JSONSchema202012KeywordElse"),A=i("JSONSchema202012KeywordDependentSchemas"),C=i("JSONSchema202012KeywordPrefixItems"),P=i("JSONSchema202012KeywordItems"),N=i("JSONSchema202012KeywordContains"),I=i("JSONSchema202012KeywordProperties"),T=i("JSONSchema202012KeywordPatternProperties"),R=i("JSONSchema202012KeywordAdditionalProperties"),M=i("JSONSchema202012KeywordPropertyNames"),D=i("JSONSchema202012KeywordUnevaluatedItems"),F=i("JSONSchema202012KeywordUnevaluatedProperties"),L=i("JSONSchema202012KeywordType"),B=i("JSONSchema202012KeywordEnum"),$=i("JSONSchema202012KeywordConst"),q=i("JSONSchema202012KeywordConstraint"),U=i("JSONSchema202012KeywordDependentRequired"),z=i("JSONSchema202012KeywordContentSchema"),V=i("JSONSchema202012KeywordTitle"),W=i("JSONSchema202012KeywordDescription"),J=i("JSONSchema202012KeywordDefault"),K=i("JSONSchema202012KeywordDeprecated"),H=i("JSONSchema202012KeywordReadOnly"),G=i("JSONSchema202012KeywordWriteOnly"),Z=i("JSONSchema202012Accordion"),Y=i("JSONSchema202012ExpandDeepButton"),X=i("JSONSchema202012ChevronRightIcon"),Q=i("withJSONSchema202012Context")(u,{config:{default$schema:"https://spec.openapis.org/oas/3.1/dialect/base",defaultExpandedLevels:c.defaultModelExpandDepth,includeReadOnly:Boolean(n.includeReadOnly),includeWriteOnly:Boolean(n.includeWriteOnly)},components:{JSONSchema:p,Keyword$schema:h,Keyword$vocabulary:f,Keyword$id:d,Keyword$anchor:m,Keyword$dynamicAnchor:g,Keyword$ref:y,Keyword$dynamicRef:v,Keyword$defs:b,Keyword$comment:w,KeywordAllOf:E,KeywordAnyOf:x,KeywordOneOf:S,KeywordNot:_,KeywordIf:j,KeywordThen:O,KeywordElse:k,KeywordDependentSchemas:A,KeywordPrefixItems:C,KeywordItems:P,KeywordContains:N,KeywordProperties:I,KeywordPatternProperties:T,KeywordAdditionalProperties:R,KeywordPropertyNames:M,KeywordUnevaluatedItems:D,KeywordUnevaluatedProperties:F,KeywordType:L,KeywordEnum:B,KeywordConst:$,KeywordConstraint:q,KeywordDependentRequired:U,KeywordContentSchema:z,KeywordTitle:V,KeywordDescription:W,KeywordDefault:J,KeywordDeprecated:K,KeywordReadOnly:H,KeywordWriteOnly:G,Accordion:Z,ExpandDeepButton:Y,ChevronRightIcon:X},fn:{upperFirst:a.upperFirst,isExpandable:(0,s.makeIsExpandable)(a.jsonSchema202012.isExpandable,t),getProperties:s.getProperties}});return r.createElement(Q,n)}))},22914:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>s});var r=n(67294);const o=(0,n(84380).createOnlyOAS31ComponentWrapper)((e=>{let{getSystem:t}=e;const{getComponent:n,fn:s,getConfigs:i}=t(),a=i();if(o.ModelsWithJSONSchemaContext)return r.createElement(o.ModelsWithJSONSchemaContext,null);const l=n("OAS31Models",!0),c=n("JSONSchema202012"),u=n("JSONSchema202012Keyword$schema"),p=n("JSONSchema202012Keyword$vocabulary"),h=n("JSONSchema202012Keyword$id"),f=n("JSONSchema202012Keyword$anchor"),d=n("JSONSchema202012Keyword$dynamicAnchor"),m=n("JSONSchema202012Keyword$ref"),g=n("JSONSchema202012Keyword$dynamicRef"),y=n("JSONSchema202012Keyword$defs"),v=n("JSONSchema202012Keyword$comment"),b=n("JSONSchema202012KeywordAllOf"),w=n("JSONSchema202012KeywordAnyOf"),E=n("JSONSchema202012KeywordOneOf"),x=n("JSONSchema202012KeywordNot"),S=n("JSONSchema202012KeywordIf"),_=n("JSONSchema202012KeywordThen"),j=n("JSONSchema202012KeywordElse"),O=n("JSONSchema202012KeywordDependentSchemas"),k=n("JSONSchema202012KeywordPrefixItems"),A=n("JSONSchema202012KeywordItems"),C=n("JSONSchema202012KeywordContains"),P=n("JSONSchema202012KeywordProperties"),N=n("JSONSchema202012KeywordPatternProperties"),I=n("JSONSchema202012KeywordAdditionalProperties"),T=n("JSONSchema202012KeywordPropertyNames"),R=n("JSONSchema202012KeywordUnevaluatedItems"),M=n("JSONSchema202012KeywordUnevaluatedProperties"),D=n("JSONSchema202012KeywordType"),F=n("JSONSchema202012KeywordEnum"),L=n("JSONSchema202012KeywordConst"),B=n("JSONSchema202012KeywordConstraint"),$=n("JSONSchema202012KeywordDependentRequired"),q=n("JSONSchema202012KeywordContentSchema"),U=n("JSONSchema202012KeywordTitle"),z=n("JSONSchema202012KeywordDescription"),V=n("JSONSchema202012KeywordDefault"),W=n("JSONSchema202012KeywordDeprecated"),J=n("JSONSchema202012KeywordReadOnly"),K=n("JSONSchema202012KeywordWriteOnly"),H=n("JSONSchema202012Accordion"),G=n("JSONSchema202012ExpandDeepButton"),Z=n("JSONSchema202012ChevronRightIcon"),Y=n("withJSONSchema202012Context");return o.ModelsWithJSONSchemaContext=Y(l,{config:{default$schema:"https://spec.openapis.org/oas/3.1/dialect/base",defaultExpandedLevels:a.defaultModelsExpandDepth-1,includeReadOnly:!0,includeWriteOnly:!0},components:{JSONSchema:c,Keyword$schema:u,Keyword$vocabulary:p,Keyword$id:h,Keyword$anchor:f,Keyword$dynamicAnchor:d,Keyword$ref:m,Keyword$dynamicRef:g,Keyword$defs:y,Keyword$comment:v,KeywordAllOf:b,KeywordAnyOf:w,KeywordOneOf:E,KeywordNot:x,KeywordIf:S,KeywordThen:_,KeywordElse:j,KeywordDependentSchemas:O,KeywordPrefixItems:k,KeywordItems:A,KeywordContains:C,KeywordProperties:P,KeywordPatternProperties:N,KeywordAdditionalProperties:I,KeywordPropertyNames:T,KeywordUnevaluatedItems:R,KeywordUnevaluatedProperties:M,KeywordType:D,KeywordEnum:F,KeywordConst:L,KeywordConstraint:B,KeywordDependentRequired:$,KeywordContentSchema:q,KeywordTitle:U,KeywordDescription:z,KeywordDefault:V,KeywordDeprecated:W,KeywordReadOnly:J,KeywordWriteOnly:K,Accordion:H,ExpandDeepButton:G,ChevronRightIcon:Z},fn:{upperFirst:s.upperFirst,isExpandable:s.jsonSchema202012.isExpandable,getProperties:s.jsonSchema202012.getProperties}}),r.createElement(o.ModelsWithJSONSchemaContext,null)}));o.ModelsWithJSONSchemaContext=null;const s=o},41434:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>i});var r=n(23101),o=n.n(r),s=n(67294);const i=(e,t)=>e=>{const n=t.specSelectors.isOAS31(),r=t.getComponent("OAS31VersionPragmaFilter");return s.createElement(r,o()({isOAS31:n},e))}},1122:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(67294);const o=(0,n(84380).createOnlyOAS31ComponentWrapper)((e=>{let{originalComponent:t,...n}=e;return r.createElement("span",null,r.createElement(t,n),r.createElement("small",{className:"version-stamp"},r.createElement("pre",{className:"version"},"OAS 3.1")))}))},28560:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>i});var r=n(87198),o=n.n(r);let s=!1;function i(){return{statePlugins:{spec:{wrapActions:{updateSpec:e=>function(){return s=!0,e(...arguments)},updateJsonSpec:(e,t)=>function(){const n=t.getConfigs().onComplete;return s&&"function"==typeof n&&(o()(n,0),s=!1),e(...arguments)}}}}}}},92135:(e,t,n)=>{"use strict";n.r(t),n.d(t,{requestSnippetGenerator_curl_bash:()=>j,requestSnippetGenerator_curl_cmd:()=>O,requestSnippetGenerator_curl_powershell:()=>_});var r=n(11882),o=n.n(r),s=n(81607),i=n.n(s),a=n(35627),l=n.n(a),c=n(97606),u=n.n(c),p=n(12196),h=n.n(p),f=n(74386),d=n.n(f),m=n(58118),g=n.n(m),y=n(27504),v=n(43393);const b=e=>{var t;const n="_**[]";return o()(e).call(e,n)<0?e:i()(t=e.split(n)[0]).call(t)},w=e=>"-d "===e||/^[_\/-]/g.test(e)?e:"'"+e.replace(/'/g,"'\\''")+"'",E=e=>"-d "===(e=e.replace(/\^/g,"^^").replace(/\\"/g,'\\\\"').replace(/"/g,'""').replace(/\n/g,"^\n"))?e.replace(/-d /g,"-d ^\n"):/^[_\/-]/g.test(e)?e:'"'+e+'"',x=e=>"-d "===e?e:/\n/.test(e)?'@"\n'+e.replace(/"/g,'\\"').replace(/`/g,"``").replace(/\$/,"`$")+'\n"@':/^[_\/-]/g.test(e)?e:"'"+e.replace(/"/g,'""').replace(/'/g,"''")+"'";const S=function(e,t,n){let r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:"",o=!1,s="";const i=function(){for(var e=arguments.length,n=new Array(e),r=0;rs+=` ${n}`,p=function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:1;return s+=h()(" ").call(" ",e)};let f=e.get("headers");if(s+="curl"+r,e.has("curlOptions")&&i(...e.get("curlOptions")),i("-X",e.get("method")),c(),p(),a(`${e.get("url")}`),f&&f.size)for(let t of d()(m=e.get("headers")).call(m)){var m;c(),p();let[e,n]=t;a("-H",`${e}: ${n}`),o=o||/^content-type$/i.test(e)&&/^multipart\/form-data$/i.test(n)}const w=e.get("body");var E;if(w)if(o&&g()(E=["POST","PUT","PATCH"]).call(E,e.get("method")))for(let[e,t]of w.entrySeq()){let n=b(e);c(),p(),a("-F"),t instanceof y.Z.File?i(`${n}=@${t.name}${t.type?`;type=${t.type}`:""}`):i(`${n}=${t}`)}else if(w instanceof y.Z.File)c(),p(),a(`--data-binary '@${w.name}'`);else{c(),p(),a("-d ");let t=w;v.Map.isMap(t)?a(function(e){let t=[];for(let[n,r]of e.get("body").entrySeq()){let e=b(n);r instanceof y.Z.File?t.push(` "${e}": {\n "name": "${r.name}"${r.type?`,\n "type": "${r.type}"`:""}\n }`):t.push(` "${e}": ${l()(r,null,2).replace(/(\r\n|\r|\n)/g,"\n ")}`)}return`{\n${t.join(",\n")}\n}`}(e)):("string"!=typeof t&&(t=l()(t)),a(t))}else w||"POST"!==e.get("method")||(c(),p(),a("-d ''"));return s},_=e=>S(e,x,"`\n",".exe"),j=e=>S(e,w,"\\\n"),O=e=>S(e,E,"^\n")},86575:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>i});var r=n(92135),o=n(4669),s=n(84206);const i=()=>({components:{RequestSnippets:s.default},fn:r,statePlugins:{requestSnippets:{selectors:o}}})},84206:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>w});var r=n(14418),o=n.n(r),s=n(25110),i=n.n(s),a=n(86),l=n.n(a),c=n(97606),u=n.n(c),p=n(67294),h=n(27361),f=n.n(h),d=n(23560),m=n.n(d),g=n(74855),y=n(33424);const v={cursor:"pointer",lineHeight:1,display:"inline-flex",backgroundColor:"rgb(250, 250, 250)",paddingBottom:"0",paddingTop:"0",border:"1px solid rgb(51, 51, 51)",borderRadius:"4px 4px 0 0",boxShadow:"none",borderBottom:"none"},b={cursor:"pointer",lineHeight:1,display:"inline-flex",backgroundColor:"rgb(51, 51, 51)",boxShadow:"none",border:"1px solid rgb(51, 51, 51)",paddingBottom:"0",paddingTop:"0",borderRadius:"4px 4px 0 0",marginTop:"-5px",marginRight:"-5px",marginLeft:"-5px",zIndex:"9999",borderBottom:"none"},w=e=>{var t,n;let{request:r,requestSnippetsSelectors:s,getConfigs:a}=e;const c=m()(a)?a():null,h=!1!==f()(c,"syntaxHighlight")&&f()(c,"syntaxHighlight.activated",!0),d=(0,p.useRef)(null),[w,E]=(0,p.useState)(null===(t=s.getSnippetGenerators())||void 0===t?void 0:t.keySeq().first()),[x,S]=(0,p.useState)(null==s?void 0:s.getDefaultExpanded());(0,p.useEffect)((()=>{}),[]),(0,p.useEffect)((()=>{var e;const t=o()(e=i()(d.current.childNodes)).call(e,(e=>{var t;return!!e.nodeType&&(null===(t=e.classList)||void 0===t?void 0:t.contains("curl-command"))}));return l()(t).call(t,(e=>e.addEventListener("mousewheel",C,{passive:!1}))),()=>{l()(t).call(t,(e=>e.removeEventListener("mousewheel",C)))}}),[r]);const _=s.getSnippetGenerators(),j=_.get(w),O=j.get("fn")(r),k=()=>{S(!x)},A=e=>e===w?b:v,C=e=>{const{target:t,deltaY:n}=e,{scrollHeight:r,offsetHeight:o,scrollTop:s}=t;r>o&&(0===s&&n<0||o+s>=r&&n>0)&&e.preventDefault()},P=h?p.createElement(y.d3,{language:j.get("syntax"),className:"curl microlight",style:(0,y.C2)(f()(c,"syntaxHighlight.theme"))},O):p.createElement("textarea",{readOnly:!0,className:"curl",value:O});return p.createElement("div",{className:"request-snippets",ref:d},p.createElement("div",{style:{width:"100%",display:"flex",justifyContent:"flex-start",alignItems:"center",marginBottom:"15px"}},p.createElement("h4",{onClick:()=>k(),style:{cursor:"pointer"}},"Snippets"),p.createElement("button",{onClick:()=>k(),style:{border:"none",background:"none"},title:x?"Collapse operation":"Expand operation"},p.createElement("svg",{className:"arrow",width:"10",height:"10"},p.createElement("use",{href:x?"#large-arrow-down":"#large-arrow",xlinkHref:x?"#large-arrow-down":"#large-arrow"})))),x&&p.createElement("div",{className:"curl-command"},p.createElement("div",{style:{paddingLeft:"15px",paddingRight:"10px",width:"100%",display:"flex"}},u()(n=_.entrySeq()).call(n,(e=>{let[t,n]=e;return p.createElement("div",{style:A(t),className:"btn",key:t,onClick:()=>(e=>{w!==e&&E(e)})(t)},p.createElement("h4",{style:t===w?{color:"white"}:{}},n.get("title")))}))),p.createElement("div",{className:"copy-to-clipboard"},p.createElement(g.CopyToClipboard,{text:O},p.createElement("button",null))),p.createElement("div",null,P)))}},4669:(e,t,n)=>{"use strict";n.r(t),n.d(t,{getActiveLanguage:()=>d,getDefaultExpanded:()=>m,getGenerators:()=>h,getSnippetGenerators:()=>f});var r=n(14418),o=n.n(r),s=n(58118),i=n.n(s),a=n(97606),l=n.n(a),c=n(20573),u=n(43393);const p=e=>e||(0,u.Map)(),h=(0,c.P1)(p,(e=>{const t=e.get("languages"),n=e.get("generators",(0,u.Map)());return!t||t.isEmpty()?n:o()(n).call(n,((e,n)=>i()(t).call(t,n)))})),f=e=>t=>{var n,r;let{fn:s}=t;return o()(n=l()(r=h(e)).call(r,((e,t)=>{const n=(e=>s[`requestSnippetGenerator_${e}`])(t);return"function"!=typeof n?null:e.set("fn",n)}))).call(n,(e=>e))},d=(0,c.P1)(p,(e=>e.get("activeLanguage"))),m=(0,c.P1)(p,(e=>e.get("defaultExpanded")))},36195:(e,t,n)=>{"use strict";n.r(t),n.d(t,{ErrorBoundary:()=>i,default:()=>a});var r=n(67294),o=n(56189),s=n(29403);class i extends r.Component{static getDerivedStateFromError(e){return{hasError:!0,error:e}}constructor(){super(...arguments),this.state={hasError:!1,error:null}}componentDidCatch(e,t){this.props.fn.componentDidCatch(e,t)}render(){const{getComponent:e,targetName:t,children:n}=this.props;if(this.state.hasError){const n=e("Fallback");return r.createElement(n,{name:t})}return n}}i.defaultProps={targetName:"this component",getComponent:()=>s.default,fn:{componentDidCatch:o.componentDidCatch},children:null};const a=i},29403:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(67294);const o=e=>{let{name:t}=e;return r.createElement("div",{className:"fallback"},"😱 ",r.createElement("i",null,"Could not render ","t"===t?"this component":t,", see the console."))}},56189:(e,t,n)=>{"use strict";n.r(t),n.d(t,{componentDidCatch:()=>i,withErrorBoundary:()=>a});var r=n(23101),o=n.n(r),s=n(67294);const i=console.error,a=e=>t=>{const{getComponent:n,fn:r}=e(),i=n("ErrorBoundary"),a=r.getDisplayName(t);class l extends s.Component{render(){return s.createElement(i,{targetName:a,getComponent:n,fn:r},s.createElement(t,o()({},this.props,this.context)))}}var c;return l.displayName=`WithErrorBoundary(${a})`,(c=t).prototype&&c.prototype.isReactComponent&&(l.prototype.mapStateToProps=t.prototype.mapStateToProps),l}},27621:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>u});var r=n(47475),o=n.n(r),s=n(7287),i=n.n(s),a=n(36195),l=n(29403),c=n(56189);const u=function(){let{componentList:e=[],fullOverride:t=!1}=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return n=>{var r;let{getSystem:s}=n;const u=t?e:["App","BaseLayout","VersionPragmaFilter","InfoContainer","ServersContainer","SchemesContainer","AuthorizeBtnContainer","FilterContainer","Operations","OperationContainer","parameters","responses","OperationServers","Models","ModelWrapper",...e],p=i()(u,o()(r=Array(u.length)).call(r,((e,t)=>{let{fn:n}=t;return n.withErrorBoundary(e)})));return{fn:{componentDidCatch:c.componentDidCatch,withErrorBoundary:(0,c.withErrorBoundary)(s)},components:{ErrorBoundary:a.default,Fallback:l.default},wrapComponents:p}}}},72846:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>p});var r=n(24282),o=n.n(r),s=n(35627),i=n.n(s),a=n(59704),l=n.n(a);const c=[{when:/json/,shouldStringifyTypes:["string"]}],u=["object"],p=e=>(t,n,r,s)=>{const{fn:a}=e(),p=a.memoizedSampleFromSchema(t,n,s),h=typeof p,f=o()(c).call(c,((e,t)=>t.when.test(r)?[...e,...t.shouldStringifyTypes]:e),u);return l()(f,(e=>e===h))?i()(p,null,2):p}},16132:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>r});const r=e=>function(t){var n,r;let o=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"",s=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},i=arguments.length>3&&void 0!==arguments[3]?arguments[3]:void 0;const{fn:a}=e();return"function"==typeof(null===(n=t)||void 0===n?void 0:n.toJS)&&(t=t.toJS()),"function"==typeof(null===(r=i)||void 0===r?void 0:r.toJS)&&(i=i.toJS()),/xml/.test(o)?a.getXmlSampleSchema(t,s,i):/(yaml|yml)/.test(o)?a.getYamlSampleSchema(t,s,o,i):a.getJsonSampleSchema(t,s,o,i)}},81169:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>r});const r=e=>(t,n,r)=>{const{fn:o}=e();if(t&&!t.xml&&(t.xml={}),t&&!t.xml.name){if(!t.$$ref&&(t.type||t.items||t.properties||t.additionalProperties))return'\n\x3c!-- XML example cannot be generated; root element name is undefined --\x3e';if(t.$$ref){let e=t.$$ref.match(/\S*\/(\S+)$/);t.xml.name=e[1]}}return o.memoizedCreateXMLExample(t,n,r)}},79431:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>i});var r=n(24278),o=n.n(r),s=n(1272);const i=e=>(t,n,r,i)=>{const{fn:a}=e(),l=a.getJsonSampleSchema(t,n,r,i);let c;try{c=s.ZP.dump(s.ZP.load(l),{lineWidth:-1},{schema:s.A8}),"\n"===c[c.length-1]&&(c=o()(c).call(c,0,c.length-1))}catch(e){return console.error(e),"error: could not generate yaml example"}return c.replace(/\t/g," ")}},29812:(e,t,n)=>{"use strict";n.r(t),n.d(t,{createXMLExample:()=>q,inferSchema:()=>$,memoizedCreateXMLExample:()=>V,memoizedSampleFromSchema:()=>W,sampleFromSchema:()=>U,sampleFromSchemaGeneric:()=>B});var r=n(11882),o=n.n(r),s=n(86),i=n.n(s),a=n(58309),l=n.n(a),c=n(58118),u=n.n(c),p=n(92039),h=n.n(p),f=n(24278),d=n.n(f),m=n(51679),g=n.n(m),y=n(39022),v=n.n(y),b=n(97606),w=n.n(b),E=n(35627),x=n.n(E),S=n(53479),_=n.n(S),j=n(14419),O=n.n(j),k=n(41609),A=n.n(k),C=n(90242),P=n(60314);const N={string:e=>e.pattern?(e=>{try{return new(O())(e).gen()}catch(e){return"string"}})(e.pattern):"string",string_email:()=>"user@example.com","string_date-time":()=>(new Date).toISOString(),string_date:()=>(new Date).toISOString().substring(0,10),string_uuid:()=>"3fa85f64-5717-4562-b3fc-2c963f66afa6",string_hostname:()=>"example.com",string_ipv4:()=>"198.51.100.42",string_ipv6:()=>"2001:0db8:5b96:0000:0000:426f:8e17:642a",number:()=>0,number_float:()=>0,integer:()=>0,boolean:e=>"boolean"!=typeof e.default||e.default},I=e=>{e=(0,C.mz)(e);let{type:t,format:n}=e,r=N[`${t}_${n}`]||N[t];return(0,C.Wl)(r)?r(e):"Unknown Type: "+e.type},T=e=>(0,C.XV)(e,"$$ref",(e=>"string"==typeof e&&o()(e).call(e,"#")>-1)),R=["maxProperties","minProperties"],M=["minItems","maxItems"],D=["minimum","maximum","exclusiveMinimum","exclusiveMaximum"],F=["minLength","maxLength"],L=function(e,t){var n;let r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};var s;(i()(n=["example","default","enum","xml","type",...R,...M,...D,...F]).call(n,(n=>(n=>{void 0===t[n]&&void 0!==e[n]&&(t[n]=e[n])})(n))),void 0!==e.required&&l()(e.required))&&(void 0!==t.required&&t.required.length||(t.required=[]),i()(s=e.required).call(s,(e=>{var n;u()(n=t.required).call(n,e)||t.required.push(e)})));if(e.properties){t.properties||(t.properties={});let n=(0,C.mz)(e.properties);for(let s in n){var a;if(Object.prototype.hasOwnProperty.call(n,s))if(!n[s]||!n[s].deprecated)if(!n[s]||!n[s].readOnly||r.includeReadOnly)if(!n[s]||!n[s].writeOnly||r.includeWriteOnly)if(!t.properties[s])t.properties[s]=n[s],!e.required&&l()(e.required)&&-1!==o()(a=e.required).call(a,s)&&(t.required?t.required.push(s):t.required=[s])}}return e.items&&(t.items||(t.items={}),t.items=L(e.items,t.items,r)),t},B=function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:void 0,r=arguments.length>3&&void 0!==arguments[3]&&arguments[3];e&&(0,C.Wl)(e.toJS)&&(e=e.toJS());let s=void 0!==n||e&&void 0!==e.example||e&&void 0!==e.default;const a=!s&&e&&e.oneOf&&e.oneOf.length>0,c=!s&&e&&e.anyOf&&e.anyOf.length>0;if(!s&&(a||c)){const n=(0,C.mz)(a?e.oneOf[0]:e.anyOf[0]);if(L(n,e,t),!e.xml&&n.xml&&(e.xml=n.xml),void 0!==e.example&&void 0!==n.example)s=!0;else if(n.properties){e.properties||(e.properties={});let r=(0,C.mz)(n.properties);for(let s in r){var p;if(Object.prototype.hasOwnProperty.call(r,s))if(!r[s]||!r[s].deprecated)if(!r[s]||!r[s].readOnly||t.includeReadOnly)if(!r[s]||!r[s].writeOnly||t.includeWriteOnly)if(!e.properties[s])e.properties[s]=r[s],!n.required&&l()(n.required)&&-1!==o()(p=n.required).call(p,s)&&(e.required?e.required.push(s):e.required=[s])}}}const f={};let{xml:m,type:y,example:b,properties:E,additionalProperties:x,items:S}=e||{},{includeReadOnly:_,includeWriteOnly:j}=t;m=m||{};let O,{name:k,prefix:P,namespace:N}=m,F={};if(r&&(k=k||"notagname",O=(P?P+":":"")+k,N)){f[P?"xmlns:"+P:"xmlns"]=N}r&&(F[O]=[]);const $=t=>h()(t).call(t,(t=>Object.prototype.hasOwnProperty.call(e,t)));e&&!y&&(E||x||$(R)?y="object":S||$(M)?y="array":$(D)?(y="number",e.type="number"):s||e.enum||(y="string",e.type="string"));const q=t=>{var n,r,o,s,i;null!==(null===(n=e)||void 0===n?void 0:n.maxItems)&&void 0!==(null===(r=e)||void 0===r?void 0:r.maxItems)&&(t=d()(t).call(t,0,null===(i=e)||void 0===i?void 0:i.maxItems));if(null!==(null===(o=e)||void 0===o?void 0:o.minItems)&&void 0!==(null===(s=e)||void 0===s?void 0:s.minItems)){let n=0;for(;t.length<(null===(a=e)||void 0===a?void 0:a.minItems);){var a;t.push(t[n++%t.length])}}return t},U=(0,C.mz)(E);let z,V=0;const W=()=>e&&null!==e.maxProperties&&void 0!==e.maxProperties&&V>=e.maxProperties,J=t=>!e||null===e.maxProperties||void 0===e.maxProperties||!W()&&(!(t=>{var n;return!(e&&e.required&&e.required.length&&u()(n=e.required).call(n,t))})(t)||e.maxProperties-V-(()=>{if(!e||!e.required)return 0;let t=0;var n,o;return r?i()(n=e.required).call(n,(e=>t+=void 0===F[e]?0:1)):i()(o=e.required).call(o,(e=>{var n;return t+=void 0===(null===(n=F[O])||void 0===n?void 0:g()(n).call(n,(t=>void 0!==t[e])))?0:1})),e.required.length-t})()>0);if(z=r?function(n){let o=arguments.length>1&&void 0!==arguments[1]?arguments[1]:void 0;if(e&&U[n]){if(U[n].xml=U[n].xml||{},U[n].xml.attribute){const e=l()(U[n].enum)?U[n].enum[0]:void 0,t=U[n].example,r=U[n].default;return void(f[U[n].xml.name||n]=void 0!==t?t:void 0!==r?r:void 0!==e?e:I(U[n]))}U[n].xml.name=U[n].xml.name||n}else U[n]||!1===x||(U[n]={xml:{name:n}});let s=B(e&&U[n]||void 0,t,o,r);var i;J(n)&&(V++,l()(s)?F[O]=v()(i=F[O]).call(i,s):F[O].push(s))}:(n,o)=>{if(J(n)){if(Object.prototype.hasOwnProperty.call(e,"discriminator")&&e.discriminator&&Object.prototype.hasOwnProperty.call(e.discriminator,"mapping")&&e.discriminator.mapping&&Object.prototype.hasOwnProperty.call(e,"$$ref")&&e.$$ref&&e.discriminator.propertyName===n){for(let t in e.discriminator.mapping)if(-1!==e.$$ref.search(e.discriminator.mapping[t])){F[n]=t;break}}else F[n]=B(U[n],t,o,r);V++}},s){let o;if(o=T(void 0!==n?n:void 0!==b?b:e.default),!r){if("number"==typeof o&&"string"===y)return`${o}`;if("string"!=typeof o||"string"===y)return o;try{return JSON.parse(o)}catch(e){return o}}if(e||(y=l()(o)?"array":typeof o),"array"===y){if(!l()(o)){if("string"==typeof o)return o;o=[o]}const n=e?e.items:void 0;n&&(n.xml=n.xml||m||{},n.xml.name=n.xml.name||m.name);let s=w()(o).call(o,(e=>B(n,t,e,r)));return s=q(s),m.wrapped?(F[O]=s,A()(f)||F[O].push({_attr:f})):F=s,F}if("object"===y){if("string"==typeof o)return o;for(let t in o)Object.prototype.hasOwnProperty.call(o,t)&&(e&&U[t]&&U[t].readOnly&&!_||e&&U[t]&&U[t].writeOnly&&!j||(e&&U[t]&&U[t].xml&&U[t].xml.attribute?f[U[t].xml.name||t]=o[t]:z(t,o[t])));return A()(f)||F[O].push({_attr:f}),F}return F[O]=A()(f)?o:[{_attr:f},o],F}if("object"===y){for(let e in U)Object.prototype.hasOwnProperty.call(U,e)&&(U[e]&&U[e].deprecated||U[e]&&U[e].readOnly&&!_||U[e]&&U[e].writeOnly&&!j||z(e));if(r&&f&&F[O].push({_attr:f}),W())return F;if(!0===x)r?F[O].push({additionalProp:"Anything can be here"}):F.additionalProp1={},V++;else if(x){const n=(0,C.mz)(x),o=B(n,t,void 0,r);if(r&&n.xml&&n.xml.name&&"notagname"!==n.xml.name)F[O].push(o);else{const t=null!==e.minProperties&&void 0!==e.minProperties&&VB(L(S,e,t),t,void 0,r)));else if(l()(S.oneOf)){var G;n=w()(G=S.oneOf).call(G,(e=>B(L(S,e,t),t,void 0,r)))}else{if(!(!r||r&&m.wrapped))return B(S,t,void 0,r);n=[B(S,t,void 0,r)]}return n=q(n),r&&m.wrapped?(F[O]=n,A()(f)||F[O].push({_attr:f}),F):n}let Z;if(e&&l()(e.enum))Z=(0,C.AF)(e.enum)[0];else{if(!e)return;if(Z=I(e),"number"==typeof Z){let t=e.minimum;null!=t&&(e.exclusiveMinimum&&t++,Z=t);let n=e.maximum;null!=n&&(e.exclusiveMaximum&&n--,Z=n)}if("string"==typeof Z&&(null!==e.maxLength&&void 0!==e.maxLength&&(Z=d()(Z).call(Z,0,e.maxLength)),null!==e.minLength&&void 0!==e.minLength)){let t=0;for(;Z.length(e.schema&&(e=e.schema),e.properties&&(e.type="object"),e),q=(e,t,n)=>{const r=B(e,t,n,!0);if(r)return"string"==typeof r?r:_()(r,{declaration:!0,indent:"\t"})},U=(e,t,n)=>B(e,t,n,!1),z=(e,t,n)=>[e,x()(t),x()(n)],V=(0,P.Z)(q,z),W=(0,P.Z)(U,z)},8883:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>l});var r=n(29812),o=n(72846),s=n(79431),i=n(81169),a=n(16132);const l=e=>{let{getSystem:t}=e;return{fn:{inferSchema:r.inferSchema,sampleFromSchema:r.sampleFromSchema,sampleFromSchemaGeneric:r.sampleFromSchemaGeneric,createXMLExample:r.createXMLExample,memoizedSampleFromSchema:r.memoizedSampleFromSchema,memoizedCreateXMLExample:r.memoizedCreateXMLExample,getJsonSampleSchema:(0,o.default)(t),getYamlSampleSchema:(0,s.default)(t),getXmlSampleSchema:(0,i.default)(t),getSampleSchema:(0,a.default)(t)}}}},51228:(e,t,n)=>{"use strict";n.r(t),n.d(t,{CLEAR_REQUEST:()=>ee,CLEAR_RESPONSE:()=>Q,CLEAR_VALIDATE_PARAMS:()=>te,LOG_REQUEST:()=>X,SET_MUTATED_REQUEST:()=>Y,SET_REQUEST:()=>Z,SET_RESPONSE:()=>G,SET_SCHEME:()=>se,UPDATE_EMPTY_PARAM_INCLUSION:()=>K,UPDATE_JSON:()=>W,UPDATE_OPERATION_META_VALUE:()=>ne,UPDATE_PARAM:()=>J,UPDATE_RESOLVED:()=>re,UPDATE_RESOLVED_SUBTREE:()=>oe,UPDATE_SPEC:()=>z,UPDATE_URL:()=>V,VALIDATE_PARAMS:()=>H,changeConsumesValue:()=>_e,changeParam:()=>ye,changeParamByIdentity:()=>ve,changeProducesValue:()=>je,clearRequest:()=>Te,clearResponse:()=>Ie,clearValidateParams:()=>Se,execute:()=>Ne,executeRequest:()=>Pe,invalidateResolvedSubtreeCache:()=>we,logRequest:()=>Ce,parseToJson:()=>pe,requestResolvedSubtree:()=>ge,resolveSpec:()=>fe,setMutatedRequest:()=>Ae,setRequest:()=>ke,setResponse:()=>Oe,setScheme:()=>Re,updateEmptyParamInclusion:()=>xe,updateJsonSpec:()=>ue,updateResolved:()=>le,updateResolvedSubtree:()=>be,updateSpec:()=>ae,updateUrl:()=>ce,validateParams:()=>Ee});var r=n(58309),o=n.n(r),s=n(97606),i=n.n(s),a=n(96718),l=n.n(a),c=n(24282),u=n.n(c),p=n(2250),h=n.n(p),f=n(6226),d=n.n(f),m=n(14418),g=n.n(m),y=n(3665),v=n.n(y),b=n(11882),w=n.n(b),E=n(86),x=n.n(E),S=n(28222),_=n.n(S),j=n(76986),O=n.n(j),k=n(70586),A=n.n(k),C=n(1272),P=n(43393),N=n(84564),I=n.n(N),T=n(7710),R=n(47037),M=n.n(R),D=n(23279),F=n.n(D),L=n(36968),B=n.n(L),$=n(72700),q=n.n($),U=n(90242);const z="spec_update_spec",V="spec_update_url",W="spec_update_json",J="spec_update_param",K="spec_update_empty_param_inclusion",H="spec_validate_param",G="spec_set_response",Z="spec_set_request",Y="spec_set_mutated_request",X="spec_log_request",Q="spec_clear_response",ee="spec_clear_request",te="spec_clear_validate_param",ne="spec_update_operation_meta_value",re="spec_update_resolved",oe="spec_update_resolved_subtree",se="set_scheme",ie=e=>M()(e)?e:"";function ae(e){const t=ie(e).replace(/\t/g," ");if("string"==typeof e)return{type:z,payload:t}}function le(e){return{type:re,payload:e}}function ce(e){return{type:V,payload:e}}function ue(e){return{type:W,payload:e}}const pe=e=>t=>{let{specActions:n,specSelectors:r,errActions:o}=t,{specStr:s}=r,i=null;try{e=e||s(),o.clear({source:"parser"}),i=C.ZP.load(e,{schema:C.A8})}catch(e){return console.error(e),o.newSpecErr({source:"parser",level:"error",message:e.reason,line:e.mark&&e.mark.line?e.mark.line+1:void 0})}return i&&"object"==typeof i?n.updateJsonSpec(i):{}};let he=!1;const fe=(e,t)=>n=>{let{specActions:r,specSelectors:s,errActions:a,fn:{fetch:c,resolve:u,AST:p={}},getConfigs:h}=n;he||(console.warn("specActions.resolveSpec is deprecated since v3.10.0 and will be removed in v4.0.0; use requestResolvedSubtree instead!"),he=!0);const{modelPropertyMacro:f,parameterMacro:d,requestInterceptor:m,responseInterceptor:g}=h();void 0===e&&(e=s.specJson()),void 0===t&&(t=s.url());let y=p.getLineNumberForPath?p.getLineNumberForPath:()=>{},v=s.specStr();return u({fetch:c,spec:e,baseDoc:t,modelPropertyMacro:f,parameterMacro:d,requestInterceptor:m,responseInterceptor:g}).then((e=>{let{spec:t,errors:n}=e;if(a.clear({type:"thrown"}),o()(n)&&n.length>0){let e=i()(n).call(n,(e=>(console.error(e),e.line=e.fullPath?y(v,e.fullPath):null,e.path=e.fullPath?e.fullPath.join("."):null,e.level="error",e.type="thrown",e.source="resolver",l()(e,"message",{enumerable:!0,value:e.message}),e)));a.newThrownErrBatch(e)}return r.updateResolved(t)}))};let de=[];const me=F()((async()=>{const e=de.system;if(!e)return void console.error("debResolveSubtrees: don't have a system to operate on, aborting.");const{errActions:t,errSelectors:n,fn:{resolveSubtree:r,fetch:s,AST:a={}},specSelectors:c,specActions:p}=e;if(!r)return void console.error("Error: Swagger-Client did not provide a `resolveSubtree` method, doing nothing.");let f=a.getLineNumberForPath?a.getLineNumberForPath:()=>{};const m=c.specStr(),{modelPropertyMacro:y,parameterMacro:b,requestInterceptor:w,responseInterceptor:E}=e.getConfigs();try{var x=await u()(de).call(de,(async(e,a)=>{let{resultMap:u,specWithCurrentSubtrees:p}=await e;const{errors:x,spec:S}=await r(p,a,{baseDoc:c.url(),modelPropertyMacro:y,parameterMacro:b,requestInterceptor:w,responseInterceptor:E});if(n.allErrors().size&&t.clearBy((e=>{var t;return"thrown"!==e.get("type")||"resolver"!==e.get("source")||!h()(t=e.get("fullPath")).call(t,((e,t)=>e===a[t]||void 0===a[t]))})),o()(x)&&x.length>0){let e=i()(x).call(x,(e=>(e.line=e.fullPath?f(m,e.fullPath):null,e.path=e.fullPath?e.fullPath.join("."):null,e.level="error",e.type="thrown",e.source="resolver",l()(e,"message",{enumerable:!0,value:e.message}),e)));t.newThrownErrBatch(e)}var _,j;S&&c.isOAS3()&&"components"===a[0]&&"securitySchemes"===a[1]&&await d().all(i()(_=g()(j=v()(S)).call(j,(e=>"openIdConnect"===e.type))).call(_,(async e=>{const t={url:e.openIdConnectUrl,requestInterceptor:w,responseInterceptor:E};try{const n=await s(t);n instanceof Error||n.status>=400?console.error(n.statusText+" "+t.url):e.openIdConnectData=JSON.parse(n.text)}catch(e){console.error(e)}})));return B()(u,a,S),p=q()(a,S,p),{resultMap:u,specWithCurrentSubtrees:p}}),d().resolve({resultMap:(c.specResolvedSubtree([])||(0,P.Map)()).toJS(),specWithCurrentSubtrees:c.specJS()}));delete de.system,de=[]}catch(e){console.error(e)}p.updateResolvedSubtree([],x.resultMap)}),35),ge=e=>t=>{var n;w()(n=i()(de).call(de,(e=>e.join("@@")))).call(n,e.join("@@"))>-1||(de.push(e),de.system=t,me())};function ye(e,t,n,r,o){return{type:J,payload:{path:e,value:r,paramName:t,paramIn:n,isXml:o}}}function ve(e,t,n,r){return{type:J,payload:{path:e,param:t,value:n,isXml:r}}}const be=(e,t)=>({type:oe,payload:{path:e,value:t}}),we=()=>({type:oe,payload:{path:[],value:(0,P.Map)()}}),Ee=(e,t)=>({type:H,payload:{pathMethod:e,isOAS3:t}}),xe=(e,t,n,r)=>({type:K,payload:{pathMethod:e,paramName:t,paramIn:n,includeEmptyValue:r}});function Se(e){return{type:te,payload:{pathMethod:e}}}function _e(e,t){return{type:ne,payload:{path:e,value:t,key:"consumes_value"}}}function je(e,t){return{type:ne,payload:{path:e,value:t,key:"produces_value"}}}const Oe=(e,t,n)=>({payload:{path:e,method:t,res:n},type:G}),ke=(e,t,n)=>({payload:{path:e,method:t,req:n},type:Z}),Ae=(e,t,n)=>({payload:{path:e,method:t,req:n},type:Y}),Ce=e=>({payload:e,type:X}),Pe=e=>t=>{let{fn:n,specActions:r,specSelectors:s,getConfigs:a,oas3Selectors:l}=t,{pathName:c,method:u,operation:p}=e,{requestInterceptor:h,responseInterceptor:f}=a(),d=p.toJS();var m,y;p&&p.get("parameters")&&x()(m=g()(y=p.get("parameters")).call(y,(e=>e&&!0===e.get("allowEmptyValue")))).call(m,(t=>{if(s.parameterInclusionSettingFor([c,u],t.get("name"),t.get("in"))){e.parameters=e.parameters||{};const n=(0,U.cz)(t,e.parameters);(!n||n&&0===n.size)&&(e.parameters[t.get("name")]="")}}));if(e.contextUrl=I()(s.url()).toString(),d&&d.operationId?e.operationId=d.operationId:d&&c&&u&&(e.operationId=n.opId(d,c,u)),s.isOAS3()){const t=`${c}:${u}`;e.server=l.selectedServer(t)||l.selectedServer();const n=l.serverVariables({server:e.server,namespace:t}).toJS(),r=l.serverVariables({server:e.server}).toJS();e.serverVariables=_()(n).length?n:r,e.requestContentType=l.requestContentType(c,u),e.responseContentType=l.responseContentType(c,u)||"*/*";const s=l.requestBodyValue(c,u),a=l.requestBodyInclusionSetting(c,u);var v;if(s&&s.toJS)e.requestBody=g()(v=i()(s).call(s,(e=>P.Map.isMap(e)?e.get("value"):e))).call(v,((e,t)=>(o()(e)?0!==e.length:!(0,U.O2)(e))||a.get(t))).toJS();else e.requestBody=s}let b=O()({},e);b=n.buildRequest(b),r.setRequest(e.pathName,e.method,b);e.requestInterceptor=async t=>{let n=await h.apply(void 0,[t]),o=O()({},n);return r.setMutatedRequest(e.pathName,e.method,o),n},e.responseInterceptor=f;const w=A()();return n.execute(e).then((t=>{t.duration=A()()-w,r.setResponse(e.pathName,e.method,t)})).catch((t=>{"Failed to fetch"===t.message&&(t.name="",t.message='**Failed to fetch.** \n**Possible Reasons:** \n - CORS \n - Network Failure \n - URL scheme must be "http" or "https" for CORS request.'),r.setResponse(e.pathName,e.method,{error:!0,err:(0,T.serializeError)(t)})}))},Ne=function(){let{path:e,method:t,...n}=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return r=>{let{fn:{fetch:o},specSelectors:s,specActions:i}=r,a=s.specJsonWithResolvedSubtrees().toJS(),l=s.operationScheme(e,t),{requestContentType:c,responseContentType:u}=s.contentTypeValues([e,t]).toJS(),p=/xml/i.test(c),h=s.parameterValues([e,t],p).toJS();return i.executeRequest({...n,fetch:o,spec:a,pathName:e,method:t,parameters:h,requestContentType:c,scheme:l,responseContentType:u})}};function Ie(e,t){return{type:Q,payload:{path:e,method:t}}}function Te(e,t){return{type:ee,payload:{path:e,method:t}}}function Re(e,t,n){return{type:se,payload:{scheme:e,path:t,method:n}}}},37038:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>a});var r=n(20032),o=n(51228),s=n(33881),i=n(77508);function a(){return{statePlugins:{spec:{wrapActions:i,reducers:r.default,actions:o,selectors:s}}}}},20032:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>d});var r=n(24282),o=n.n(r),s=n(97606),i=n.n(s),a=n(76986),l=n.n(a),c=n(43393),u=n(90242),p=n(27504),h=n(33881),f=n(51228);const d={[f.UPDATE_SPEC]:(e,t)=>"string"==typeof t.payload?e.set("spec",t.payload):e,[f.UPDATE_URL]:(e,t)=>e.set("url",t.payload+""),[f.UPDATE_JSON]:(e,t)=>e.set("json",(0,u.oG)(t.payload)),[f.UPDATE_RESOLVED]:(e,t)=>e.setIn(["resolved"],(0,u.oG)(t.payload)),[f.UPDATE_RESOLVED_SUBTREE]:(e,t)=>{const{value:n,path:r}=t.payload;return e.setIn(["resolvedSubtrees",...r],(0,u.oG)(n))},[f.UPDATE_PARAM]:(e,t)=>{let{payload:n}=t,{path:r,paramName:o,paramIn:s,param:i,value:a,isXml:l}=n,c=i?(0,u.V9)(i):`${s}.${o}`;const p=l?"value_xml":"value";return e.setIn(["meta","paths",...r,"parameters",c,p],a)},[f.UPDATE_EMPTY_PARAM_INCLUSION]:(e,t)=>{let{payload:n}=t,{pathMethod:r,paramName:o,paramIn:s,includeEmptyValue:i}=n;if(!o||!s)return console.warn("Warning: UPDATE_EMPTY_PARAM_INCLUSION could not generate a paramKey."),e;const a=`${s}.${o}`;return e.setIn(["meta","paths",...r,"parameter_inclusions",a],i)},[f.VALIDATE_PARAMS]:(e,t)=>{let{payload:{pathMethod:n,isOAS3:r}}=t;const s=(0,h.specJsonWithResolvedSubtrees)(e).getIn(["paths",...n]),i=(0,h.parameterValues)(e,n).toJS();return e.updateIn(["meta","paths",...n,"parameters"],(0,c.fromJS)({}),(t=>{var a;return o()(a=s.get("parameters",(0,c.List)())).call(a,((t,o)=>{const s=(0,u.cz)(o,i),a=(0,h.parameterInclusionSettingFor)(e,n,o.get("name"),o.get("in")),l=(0,u.Ik)(o,s,{bypassRequiredCheck:a,isOAS3:r});return t.setIn([(0,u.V9)(o),"errors"],(0,c.fromJS)(l))}),t)}))},[f.CLEAR_VALIDATE_PARAMS]:(e,t)=>{let{payload:{pathMethod:n}}=t;return e.updateIn(["meta","paths",...n,"parameters"],(0,c.fromJS)([]),(e=>i()(e).call(e,(e=>e.set("errors",(0,c.fromJS)([]))))))},[f.SET_RESPONSE]:(e,t)=>{let n,{payload:{res:r,path:o,method:s}}=t;n=r.error?l()({error:!0,name:r.err.name,message:r.err.message,statusCode:r.err.statusCode},r.err.response):r,n.headers=n.headers||{};let i=e.setIn(["responses",o,s],(0,u.oG)(n));return p.Z.Blob&&r.data instanceof p.Z.Blob&&(i=i.setIn(["responses",o,s,"text"],r.data)),i},[f.SET_REQUEST]:(e,t)=>{let{payload:{req:n,path:r,method:o}}=t;return e.setIn(["requests",r,o],(0,u.oG)(n))},[f.SET_MUTATED_REQUEST]:(e,t)=>{let{payload:{req:n,path:r,method:o}}=t;return e.setIn(["mutatedRequests",r,o],(0,u.oG)(n))},[f.UPDATE_OPERATION_META_VALUE]:(e,t)=>{let{payload:{path:n,value:r,key:o}}=t,s=["paths",...n],i=["meta","paths",...n];return e.getIn(["json",...s])||e.getIn(["resolved",...s])||e.getIn(["resolvedSubtrees",...s])?e.setIn([...i,o],(0,c.fromJS)(r)):e},[f.CLEAR_RESPONSE]:(e,t)=>{let{payload:{path:n,method:r}}=t;return e.deleteIn(["responses",n,r])},[f.CLEAR_REQUEST]:(e,t)=>{let{payload:{path:n,method:r}}=t;return e.deleteIn(["requests",n,r])},[f.SET_SCHEME]:(e,t)=>{let{payload:{scheme:n,path:r,method:o}}=t;return r&&o?e.setIn(["scheme",r,o],n):r||o?void 0:e.setIn(["scheme","_defaultScheme"],n)}}},33881:(e,t,n)=>{"use strict";n.r(t),n.d(t,{allowTryItOutFor:()=>fe,basePath:()=>Q,canExecuteScheme:()=>Ae,consumes:()=>K,consumesOptionsFor:()=>Oe,contentTypeValues:()=>Se,currentProducesFor:()=>_e,definitions:()=>X,externalDocs:()=>q,findDefinition:()=>Y,getOAS3RequiredRequestBodyContentType:()=>Ne,getParameter:()=>ve,hasHost:()=>be,host:()=>ee,info:()=>$,isMediaTypeSchemaPropertiesEqual:()=>Ie,isOAS3:()=>B,lastError:()=>A,mutatedRequestFor:()=>he,mutatedRequests:()=>ce,operationScheme:()=>ke,operationWithMeta:()=>ye,operations:()=>J,operationsWithRootInherited:()=>ne,operationsWithTags:()=>se,parameterInclusionSettingFor:()=>me,parameterValues:()=>we,parameterWithMeta:()=>ge,parameterWithMetaByIdentity:()=>de,parametersIncludeIn:()=>Ee,parametersIncludeType:()=>xe,paths:()=>V,produces:()=>H,producesOptionsFor:()=>je,requestFor:()=>pe,requests:()=>le,responseFor:()=>ue,responses:()=>ae,schemes:()=>te,security:()=>G,securityDefinitions:()=>Z,semver:()=>z,spec:()=>L,specJS:()=>T,specJson:()=>I,specJsonWithResolvedSubtrees:()=>F,specResolved:()=>R,specResolvedSubtree:()=>M,specSource:()=>N,specStr:()=>P,tagDetails:()=>oe,taggedOperations:()=>ie,tags:()=>re,url:()=>C,validOperationMethods:()=>W,validateBeforeExecute:()=>Pe,validationErrors:()=>Ce,version:()=>U});var r=n(24278),o=n.n(r),s=n(86),i=n.n(s),a=n(11882),l=n.n(a),c=n(97606),u=n.n(c),p=n(14418),h=n.n(p),f=n(51679),d=n.n(f),m=n(24282),g=n.n(m),y=n(2578),v=n.n(y),b=n(92039),w=n.n(b),E=n(58309),x=n.n(E),S=n(20573),_=n(90242),j=n(43393);const O=["get","put","post","delete","options","head","patch","trace"],k=e=>e||(0,j.Map)(),A=(0,S.P1)(k,(e=>e.get("lastError"))),C=(0,S.P1)(k,(e=>e.get("url"))),P=(0,S.P1)(k,(e=>e.get("spec")||"")),N=(0,S.P1)(k,(e=>e.get("specSource")||"not-editor")),I=(0,S.P1)(k,(e=>e.get("json",(0,j.Map)()))),T=(0,S.P1)(I,(e=>e.toJS())),R=(0,S.P1)(k,(e=>e.get("resolved",(0,j.Map)()))),M=(e,t)=>e.getIn(["resolvedSubtrees",...t],void 0),D=(e,t)=>j.Map.isMap(e)&&j.Map.isMap(t)?t.get("$$ref")?t:(0,j.OrderedMap)().mergeWith(D,e,t):t,F=(0,S.P1)(k,(e=>(0,j.OrderedMap)().mergeWith(D,e.get("json"),e.get("resolvedSubtrees")))),L=e=>I(e),B=(0,S.P1)(L,(()=>!1)),$=(0,S.P1)(L,(e=>Te(e&&e.get("info")))),q=(0,S.P1)(L,(e=>Te(e&&e.get("externalDocs")))),U=(0,S.P1)($,(e=>e&&e.get("version"))),z=(0,S.P1)(U,(e=>{var t;return o()(t=/v?([0-9]*)\.([0-9]*)\.([0-9]*)/i.exec(e)).call(t,1)})),V=(0,S.P1)(F,(e=>e.get("paths"))),W=(0,S.P1)((()=>["get","put","post","delete","options","head","patch"])),J=(0,S.P1)(V,(e=>{if(!e||e.size<1)return(0,j.List)();let t=(0,j.List)();return e&&i()(e)?(i()(e).call(e,((e,n)=>{if(!e||!i()(e))return{};i()(e).call(e,((e,r)=>{l()(O).call(O,r)<0||(t=t.push((0,j.fromJS)({path:n,method:r,operation:e,id:`${r}-${n}`})))}))})),t):(0,j.List)()})),K=(0,S.P1)(L,(e=>(0,j.Set)(e.get("consumes")))),H=(0,S.P1)(L,(e=>(0,j.Set)(e.get("produces")))),G=(0,S.P1)(L,(e=>e.get("security",(0,j.List)()))),Z=(0,S.P1)(L,(e=>e.get("securityDefinitions"))),Y=(e,t)=>{const n=e.getIn(["resolvedSubtrees","definitions",t],null),r=e.getIn(["json","definitions",t],null);return n||r||null},X=(0,S.P1)(L,(e=>{const t=e.get("definitions");return j.Map.isMap(t)?t:(0,j.Map)()})),Q=(0,S.P1)(L,(e=>e.get("basePath"))),ee=(0,S.P1)(L,(e=>e.get("host"))),te=(0,S.P1)(L,(e=>e.get("schemes",(0,j.Map)()))),ne=(0,S.P1)(J,K,H,((e,t,n)=>u()(e).call(e,(e=>e.update("operation",(e=>{if(e){if(!j.Map.isMap(e))return;return e.withMutations((e=>(e.get("consumes")||e.update("consumes",(e=>(0,j.Set)(e).merge(t))),e.get("produces")||e.update("produces",(e=>(0,j.Set)(e).merge(n))),e)))}return(0,j.Map)()})))))),re=(0,S.P1)(L,(e=>{const t=e.get("tags",(0,j.List)());return j.List.isList(t)?h()(t).call(t,(e=>j.Map.isMap(e))):(0,j.List)()})),oe=(e,t)=>{var n;let r=re(e)||(0,j.List)();return d()(n=h()(r).call(r,j.Map.isMap)).call(n,(e=>e.get("name")===t),(0,j.Map)())},se=(0,S.P1)(ne,re,((e,t)=>g()(e).call(e,((e,t)=>{let n=(0,j.Set)(t.getIn(["operation","tags"]));return n.count()<1?e.update("default",(0,j.List)(),(e=>e.push(t))):g()(n).call(n,((e,n)=>e.update(n,(0,j.List)(),(e=>e.push(t)))),e)}),g()(t).call(t,((e,t)=>e.set(t.get("name"),(0,j.List)())),(0,j.OrderedMap)())))),ie=e=>t=>{var n;let{getConfigs:r}=t,{tagsSorter:o,operationsSorter:s}=r();return u()(n=se(e).sortBy(((e,t)=>t),((e,t)=>{let n="function"==typeof o?o:_.wh.tagsSorter[o];return n?n(e,t):null}))).call(n,((t,n)=>{let r="function"==typeof s?s:_.wh.operationsSorter[s],o=r?v()(t).call(t,r):t;return(0,j.Map)({tagDetails:oe(e,n),operations:o})}))},ae=(0,S.P1)(k,(e=>e.get("responses",(0,j.Map)()))),le=(0,S.P1)(k,(e=>e.get("requests",(0,j.Map)()))),ce=(0,S.P1)(k,(e=>e.get("mutatedRequests",(0,j.Map)()))),ue=(e,t,n)=>ae(e).getIn([t,n],null),pe=(e,t,n)=>le(e).getIn([t,n],null),he=(e,t,n)=>ce(e).getIn([t,n],null),fe=()=>!0,de=(e,t,n)=>{const r=F(e).getIn(["paths",...t,"parameters"],(0,j.OrderedMap)()),o=e.getIn(["meta","paths",...t,"parameters"],(0,j.OrderedMap)()),s=u()(r).call(r,(e=>{const t=o.get(`${n.get("in")}.${n.get("name")}`),r=o.get(`${n.get("in")}.${n.get("name")}.hash-${n.hashCode()}`);return(0,j.OrderedMap)().merge(e,t,r)}));return d()(s).call(s,(e=>e.get("in")===n.get("in")&&e.get("name")===n.get("name")),(0,j.OrderedMap)())},me=(e,t,n,r)=>{const o=`${r}.${n}`;return e.getIn(["meta","paths",...t,"parameter_inclusions",o],!1)},ge=(e,t,n,r)=>{const o=F(e).getIn(["paths",...t,"parameters"],(0,j.OrderedMap)()),s=d()(o).call(o,(e=>e.get("in")===r&&e.get("name")===n),(0,j.OrderedMap)());return de(e,t,s)},ye=(e,t,n)=>{var r;const o=F(e).getIn(["paths",t,n],(0,j.OrderedMap)()),s=e.getIn(["meta","paths",t,n],(0,j.OrderedMap)()),i=u()(r=o.get("parameters",(0,j.List)())).call(r,(r=>de(e,[t,n],r)));return(0,j.OrderedMap)().merge(o,s).set("parameters",i)};function ve(e,t,n,r){t=t||[];let o=e.getIn(["meta","paths",...t,"parameters"],(0,j.fromJS)([]));return d()(o).call(o,(e=>j.Map.isMap(e)&&e.get("name")===n&&e.get("in")===r))||(0,j.Map)()}const be=(0,S.P1)(L,(e=>{const t=e.get("host");return"string"==typeof t&&t.length>0&&"/"!==t[0]}));function we(e,t,n){t=t||[];let r=ye(e,...t).get("parameters",(0,j.List)());return g()(r).call(r,((e,t)=>{let r=n&&"body"===t.get("in")?t.get("value_xml"):t.get("value");return e.set((0,_.V9)(t,{allowHashes:!1}),r)}),(0,j.fromJS)({}))}function Ee(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";if(j.List.isList(e))return w()(e).call(e,(e=>j.Map.isMap(e)&&e.get("in")===t))}function xe(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";if(j.List.isList(e))return w()(e).call(e,(e=>j.Map.isMap(e)&&e.get("type")===t))}function Se(e,t){t=t||[];let n=F(e).getIn(["paths",...t],(0,j.fromJS)({})),r=e.getIn(["meta","paths",...t],(0,j.fromJS)({})),o=_e(e,t);const s=n.get("parameters")||new j.List,i=r.get("consumes_value")?r.get("consumes_value"):xe(s,"file")?"multipart/form-data":xe(s,"formData")?"application/x-www-form-urlencoded":void 0;return(0,j.fromJS)({requestContentType:i,responseContentType:o})}function _e(e,t){t=t||[];const n=F(e).getIn(["paths",...t],null);if(null===n)return;const r=e.getIn(["meta","paths",...t,"produces_value"],null),o=n.getIn(["produces",0],null);return r||o||"application/json"}function je(e,t){t=t||[];const n=F(e),r=n.getIn(["paths",...t],null);if(null===r)return;const[o]=t,s=r.get("produces",null),i=n.getIn(["paths",o,"produces"],null),a=n.getIn(["produces"],null);return s||i||a}function Oe(e,t){t=t||[];const n=F(e),r=n.getIn(["paths",...t],null);if(null===r)return;const[o]=t,s=r.get("consumes",null),i=n.getIn(["paths",o,"consumes"],null),a=n.getIn(["consumes"],null);return s||i||a}const ke=(e,t,n)=>{let r=e.get("url").match(/^([a-z][a-z0-9+\-.]*):/),o=x()(r)?r[1]:null;return e.getIn(["scheme",t,n])||e.getIn(["scheme","_defaultScheme"])||o||""},Ae=(e,t,n)=>{var r;return l()(r=["http","https"]).call(r,ke(e,t,n))>-1},Ce=(e,t)=>{t=t||[];let n=e.getIn(["meta","paths",...t,"parameters"],(0,j.fromJS)([]));const r=[];return i()(n).call(n,(e=>{let t=e.get("errors");t&&t.count()&&i()(t).call(t,(e=>r.push(e)))})),r},Pe=(e,t)=>0===Ce(e,t).length,Ne=(e,t)=>{var n;let r={requestBody:!1,requestContentType:{}},o=e.getIn(["resolvedSubtrees","paths",...t,"requestBody"],(0,j.fromJS)([]));return o.size<1||(o.getIn(["required"])&&(r.requestBody=o.getIn(["required"])),i()(n=o.getIn(["content"]).entrySeq()).call(n,(e=>{const t=e[0];if(e[1].getIn(["schema","required"])){const n=e[1].getIn(["schema","required"]).toJS();r.requestContentType[t]=n}}))),r},Ie=(e,t,n,r)=>{if((n||r)&&n===r)return!0;let o=e.getIn(["resolvedSubtrees","paths",...t,"requestBody","content"],(0,j.fromJS)([]));if(o.size<2||!n||!r)return!1;let s=o.getIn([n,"schema","properties"],(0,j.fromJS)([])),i=o.getIn([r,"schema","properties"],(0,j.fromJS)([]));return!!s.equals(i)};function Te(e){return j.Map.isMap(e)?e:new j.Map}},77508:(e,t,n)=>{"use strict";n.r(t),n.d(t,{executeRequest:()=>p,updateJsonSpec:()=>u,updateSpec:()=>c,validateParams:()=>h});var r=n(28222),o=n.n(r),s=n(86),i=n.n(s),a=n(27361),l=n.n(a);const c=(e,t)=>{let{specActions:n}=t;return function(){e(...arguments),n.parseToJson(...arguments)}},u=(e,t)=>{let{specActions:n}=t;return function(){for(var t=arguments.length,r=new Array(t),s=0;s{l()(c,[e]).$ref&&n.requestResolvedSubtree(["paths",e])})),n.requestResolvedSubtree(["components","securitySchemes"])}},p=(e,t)=>{let{specActions:n}=t;return t=>(n.logRequest(t),e(t))},h=(e,t)=>{let{specSelectors:n}=t;return t=>e(t,n.isOAS3())}},34852:(e,t,n)=>{"use strict";n.r(t),n.d(t,{loaded:()=>r});const r=(e,t)=>function(){e(...arguments);const n=t.getConfigs().withCredentials;void 0!==n&&(t.fn.fetch.withCredentials="string"==typeof n?"true"===n:!!n)}},79934:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>BE});var r={};n.r(r),n.d(r,{JsonPatchError:()=>j,_areEquals:()=>M,applyOperation:()=>P,applyPatch:()=>N,applyReducer:()=>I,deepClone:()=>O,getValueByPointer:()=>C,validate:()=>R,validator:()=>T});var o={};n.r(o),n.d(o,{compare:()=>z,generate:()=>q,observe:()=>$,unobserve:()=>B});var s={};n.r(s),n.d(s,{hasElementSourceMap:()=>Cs,includesClasses:()=>Ns,includesSymbols:()=>Ps,isAnnotationElement:()=>_s,isArrayElement:()=>ws,isBooleanElement:()=>vs,isCommentElement:()=>js,isElement:()=>ds,isLinkElement:()=>xs,isMemberElement:()=>Es,isNullElement:()=>ys,isNumberElement:()=>gs,isObjectElement:()=>bs,isParseResultElement:()=>Os,isPrimitiveElement:()=>As,isRefElement:()=>Ss,isSourceMapElement:()=>ks,isStringElement:()=>ms});var i={};n.r(i),n.d(i,{isJSONReferenceElement:()=>cc,isJSONSchemaElement:()=>lc,isLinkDescriptionElement:()=>pc,isMediaElement:()=>uc});var a={};n.r(a),n.d(a,{isOpenApi3_0LikeElement:()=>$c,isOpenApiExtension:()=>Kc,isParameterLikeElement:()=>qc,isReferenceLikeElement:()=>Uc,isRequestBodyLikeElement:()=>zc,isResponseLikeElement:()=>Vc,isServerLikeElement:()=>Wc,isTagLikeElement:()=>Jc});var l={};n.r(l),n.d(l,{isBooleanJsonSchemaElement:()=>ap,isCallbackElement:()=>Lu,isComponentsElement:()=>Bu,isContactElement:()=>$u,isExampleElement:()=>qu,isExternalDocumentationElement:()=>Uu,isHeaderElement:()=>zu,isInfoElement:()=>Vu,isLicenseElement:()=>Wu,isLinkElement:()=>Ju,isLinkElementExternal:()=>Ku,isMediaTypeElement:()=>pp,isOpenApi3_0Element:()=>Gu,isOpenapiElement:()=>Hu,isOperationElement:()=>Zu,isParameterElement:()=>Yu,isPathItemElement:()=>Xu,isPathItemElementExternal:()=>Qu,isPathsElement:()=>ep,isReferenceElement:()=>tp,isReferenceElementExternal:()=>np,isRequestBodyElement:()=>rp,isResponseElement:()=>op,isResponsesElement:()=>sp,isSchemaElement:()=>ip,isSecurityRequirementElement:()=>lp,isServerElement:()=>cp,isServerVariableElement:()=>up});var c={};n.r(c),n.d(c,{isBooleanJsonSchemaElement:()=>Kg,isCallbackElement:()=>Sg,isComponentsElement:()=>_g,isContactElement:()=>jg,isExampleElement:()=>Og,isExternalDocumentationElement:()=>kg,isHeaderElement:()=>Ag,isInfoElement:()=>Cg,isJsonSchemaDialectElement:()=>Pg,isLicenseElement:()=>Ng,isLinkElement:()=>Ig,isLinkElementExternal:()=>Tg,isMediaTypeElement:()=>Yg,isOpenApi3_1Element:()=>Mg,isOpenapiElement:()=>Rg,isOperationElement:()=>Dg,isParameterElement:()=>Fg,isPathItemElement:()=>Lg,isPathItemElementExternal:()=>Bg,isPathsElement:()=>$g,isReferenceElement:()=>qg,isReferenceElementExternal:()=>Ug,isRequestBodyElement:()=>zg,isResponseElement:()=>Vg,isResponsesElement:()=>Wg,isSchemaElement:()=>Jg,isSecurityRequirementElement:()=>Hg,isServerElement:()=>Gg,isServerVariableElement:()=>Zg});var u={};n.r(u),n.d(u,{cookie:()=>EE,header:()=>wE,path:()=>yE,query:()=>vE});var p,h=n(58826),f=n.n(h),d=(p=function(e,t){return p=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n])},p(e,t)},function(e,t){function n(){this.constructor=e}p(e,t),e.prototype=null===t?Object.create(t):(n.prototype=t.prototype,new n)}),m=Object.prototype.hasOwnProperty;function g(e,t){return m.call(e,t)}function y(e){if(Array.isArray(e)){for(var t=new Array(e.length),n=0;n=48&&t<=57))return!1;n++}return!0}function w(e){return-1===e.indexOf("/")&&-1===e.indexOf("~")?e:e.replace(/~/g,"~0").replace(/\//g,"~1")}function E(e){return e.replace(/~1/g,"/").replace(/~0/g,"~")}function x(e){if(void 0===e)return!0;if(e)if(Array.isArray(e)){for(var t=0,n=e.length;t0&&"constructor"==a[c-1]))throw new TypeError("JSON-Patch: modifying `__proto__` or `constructor/prototype` prop is banned for security reasons, if this was on purpose, please set `banPrototypeModifications` flag false and pass it to this function. More info in fast-json-patch README");if(n&&void 0===p&&(void 0===l[h]?p=a.slice(0,c).join("/"):c==u-1&&(p=t.path),void 0!==p&&f(t,0,e,p)),c++,Array.isArray(l)){if("-"===h)h=l.length;else{if(n&&!b(h))throw new j("Expected an unsigned base-10 integer value, making the new referenced value the array element with the zero-based index","OPERATION_PATH_ILLEGAL_ARRAY_INDEX",s,t,e);b(h)&&(h=~~h)}if(c>=u){if(n&&"add"===t.op&&h>l.length)throw new j("The specified index MUST NOT be greater than the number of elements in the array","OPERATION_VALUE_OUT_OF_BOUNDS",s,t,e);if(!1===(i=A[t.op].call(t,l,h,e)).test)throw new j("Test operation failed","TEST_OPERATION_FAILED",s,t,e);return i}}else if(c>=u){if(!1===(i=k[t.op].call(t,l,h,e)).test)throw new j("Test operation failed","TEST_OPERATION_FAILED",s,t,e);return i}if(l=l[h],n&&c0)throw new j('Operation `path` property must start with "/"',"OPERATION_PATH_INVALID",t,e,n);if(("move"===e.op||"copy"===e.op)&&"string"!=typeof e.from)throw new j("Operation `from` property is not present (applicable in `move` and `copy` operations)","OPERATION_FROM_REQUIRED",t,e,n);if(("add"===e.op||"replace"===e.op||"test"===e.op)&&void 0===e.value)throw new j("Operation `value` property is not present (applicable in `add`, `replace` and `test` operations)","OPERATION_VALUE_REQUIRED",t,e,n);if(("add"===e.op||"replace"===e.op||"test"===e.op)&&x(e.value))throw new j("Operation `value` property is not present (applicable in `add`, `replace` and `test` operations)","OPERATION_VALUE_CANNOT_CONTAIN_UNDEFINED",t,e,n);if(n)if("add"==e.op){var o=e.path.split("/").length,s=r.split("/").length;if(o!==s+1&&o!==s)throw new j("Cannot perform an `add` operation at the desired path","OPERATION_PATH_CANNOT_ADD",t,e,n)}else if("replace"===e.op||"remove"===e.op||"_get"===e.op){if(e.path!==r)throw new j("Cannot perform the operation at a path that does not exist","OPERATION_PATH_UNRESOLVABLE",t,e,n)}else if("move"===e.op||"copy"===e.op){var i=R([{op:"_get",path:e.from,value:void 0}],n);if(i&&"OPERATION_PATH_UNRESOLVABLE"===i.name)throw new j("Cannot perform the operation from a path that does not exist","OPERATION_FROM_UNRESOLVABLE",t,e,n)}}function R(e,t,n){try{if(!Array.isArray(e))throw new j("Patch sequence must be an array","SEQUENCE_NOT_AN_ARRAY");if(t)N(v(t),v(e),n||!0);else{n=n||T;for(var r=0;r0&&(e.patches=[],e.callback&&e.callback(r)),r}function U(e,t,n,r,o){if(t!==e){"function"==typeof t.toJSON&&(t=t.toJSON());for(var s=y(t),i=y(e),a=!1,l=i.length-1;l>=0;l--){var c=e[p=i[l]];if(!g(t,p)||void 0===t[p]&&void 0!==c&&!1===Array.isArray(t))Array.isArray(e)===Array.isArray(t)?(o&&n.push({op:"test",path:r+"/"+w(p),value:v(c)}),n.push({op:"remove",path:r+"/"+w(p)}),a=!0):(o&&n.push({op:"test",path:r,value:e}),n.push({op:"replace",path:r,value:t}),!0);else{var u=t[p];"object"==typeof c&&null!=c&&"object"==typeof u&&null!=u&&Array.isArray(c)===Array.isArray(u)?U(c,u,n,r+"/"+w(p),o):c!==u&&(!0,o&&n.push({op:"test",path:r+"/"+w(p),value:v(c)}),n.push({op:"replace",path:r+"/"+w(p),value:v(u)}))}}if(a||s.length!=i.length)for(l=0;lvoid 0!==t&&e?e[t]:e),e)},applyPatch:function(e,t,n){if(n=n||{},"merge"===(t=f()(f()({},t),{},{path:t.path&&K(t.path)})).op){const n=ae(e,t.path);Object.assign(n,t.value),N(e,[H(t.path,n)])}else if("mergeDeep"===t.op){const n=ae(e,t.path),r=W()(n,t.value);e=N(e,[H(t.path,r)]).newDocument}else if("add"===t.op&&""===t.path&&te(t.value)){N(e,Object.keys(t.value).reduce(((e,n)=>(e.push({op:"add",path:`/${K(n)}`,value:t.value[n]}),e)),[]))}else if("replace"===t.op&&""===t.path){let{value:r}=t;n.allowMetaPatches&&t.meta&&se(t)&&(Array.isArray(t.value)||te(t.value))&&(r=f()(f()({},r),t.meta)),e=r}else if(N(e,[t]),n.allowMetaPatches&&t.meta&&se(t)&&(Array.isArray(t.value)||te(t.value))){const n=ae(e,t.path),r=f()(f()({},n),t.meta);N(e,[H(t.path,r)])}return e},parentPathMatch:function(e,t){if(!Array.isArray(t))return!1;for(let n=0,r=t.length;n(e+"").replace(/~/g,"~0").replace(/\//g,"~1"))).join("/")}`:e}function H(e,t,n){return{op:"replace",path:e,value:t,meta:n}}function G(e,t,n){return ee(Q(e.filter(se).map((e=>t(e.value,n,e.path)))||[]))}function Z(e,t,n){return n=n||[],Array.isArray(e)?e.map(((e,r)=>Z(e,t,n.concat(r)))):te(e)?Object.keys(e).map((r=>Z(e[r],t,n.concat(r)))):t(e,n[n.length-1],n)}function Y(e,t,n){let r=[];if((n=n||[]).length>0){const o=t(e,n[n.length-1],n);o&&(r=r.concat(o))}if(Array.isArray(e)){const o=e.map(((e,r)=>Y(e,t,n.concat(r))));o&&(r=r.concat(o))}else if(te(e)){const o=Object.keys(e).map((r=>Y(e[r],t,n.concat(r))));o&&(r=r.concat(o))}return r=Q(r),r}function X(e){return Array.isArray(e)?e:[e]}function Q(e){return[].concat(...e.map((e=>Array.isArray(e)?Q(e):e)))}function ee(e){return e.filter((e=>void 0!==e))}function te(e){return e&&"object"==typeof e}function ne(e){return e&&"function"==typeof e}function re(e){if(ie(e)){const{op:t}=e;return"add"===t||"remove"===t||"replace"===t}return!1}function oe(e){return re(e)||ie(e)&&"mutation"===e.type}function se(e){return oe(e)&&("add"===e.op||"replace"===e.op||"merge"===e.op||"mergeDeep"===e.op)}function ie(e){return e&&"object"==typeof e}function ae(e,t){try{return C(e,t)}catch(e){return console.error(e),{}}}n(31905);var le=n(1272),ce=n(8575);function ue(e,t){function n(){Error.captureStackTrace?Error.captureStackTrace(this,this.constructor):this.stack=(new Error).stack;for(var e=arguments.length,n=new Array(e),r=0;r-1&&-1===de.indexOf(n)||me.indexOf(r)>-1||ge.some((e=>r.indexOf(e)>-1))}function ve(e,t){const[n,r]=e.split("#"),o=ce.resolve(n||"",t||"");return r?`${o}#${r}`:o}const be="application/json, application/yaml",we=/^([a-z]+:\/\/|\/\/)/i,Ee=ue("JSONRefError",(function(e,t,n){this.originalError=n,Object.assign(this,t||{})})),xe={},Se=new WeakMap,_e=[e=>"paths"===e[0]&&"responses"===e[3]&&"examples"===e[5],e=>"paths"===e[0]&&"responses"===e[3]&&"content"===e[5]&&"example"===e[7],e=>"paths"===e[0]&&"responses"===e[3]&&"content"===e[5]&&"examples"===e[7]&&"value"===e[9],e=>"paths"===e[0]&&"requestBody"===e[3]&&"content"===e[4]&&"example"===e[6],e=>"paths"===e[0]&&"requestBody"===e[3]&&"content"===e[4]&&"examples"===e[6]&&"value"===e[8],e=>"paths"===e[0]&&"parameters"===e[2]&&"example"===e[4],e=>"paths"===e[0]&&"parameters"===e[3]&&"example"===e[5],e=>"paths"===e[0]&&"parameters"===e[2]&&"examples"===e[4]&&"value"===e[6],e=>"paths"===e[0]&&"parameters"===e[3]&&"examples"===e[5]&&"value"===e[7],e=>"paths"===e[0]&&"parameters"===e[2]&&"content"===e[4]&&"example"===e[6],e=>"paths"===e[0]&&"parameters"===e[2]&&"content"===e[4]&&"examples"===e[6]&&"value"===e[8],e=>"paths"===e[0]&&"parameters"===e[3]&&"content"===e[4]&&"example"===e[7],e=>"paths"===e[0]&&"parameters"===e[3]&&"content"===e[5]&&"examples"===e[7]&&"value"===e[9]],je={key:"$ref",plugin:(e,t,n,r)=>{const o=r.getInstance(),s=n.slice(0,-1);if(ye(s)||(e=>_e.some((t=>t(e))))(s))return;const{baseDoc:i}=r.getContext(n);if("string"!=typeof e)return new Ee("$ref: must be a string (JSON-Ref)",{$ref:e,baseDoc:i,fullPath:n});const a=Pe(e),l=a[0],c=a[1]||"";let u,p,h;try{u=i||l?Ae(l,i):null}catch(t){return Ce(t,{pointer:c,$ref:e,basePath:u,fullPath:n})}if(function(e,t,n,r){let o=Se.get(r);o||(o={},Se.set(r,o));const s=function(e){if(0===e.length)return"";return`/${e.map(De).join("/")}`}(n),i=`${t||""}#${e}`,a=s.replace(/allOf\/\d+\/?/g,""),l=r.contextTree.get([]).baseDoc;if(t===l&&Le(a,e))return!0;let c="";const u=n.some((e=>(c=`${c}/${De(e)}`,o[c]&&o[c].some((e=>Le(e,i)||Le(i,e))))));if(u)return!0;return void(o[a]=(o[a]||[]).concat(i))}(c,u,s,r)&&!o.useCircularStructures){const t=ve(e,u);return e===t?null:J.replace(n,t)}if(null==u?(h=Re(c),p=r.get(h),void 0===p&&(p=new Ee(`Could not resolve reference: ${e}`,{pointer:c,$ref:e,baseDoc:i,fullPath:n}))):(p=Ne(u,c),p=null!=p.__value?p.__value:p.catch((t=>{throw Ce(t,{pointer:c,$ref:e,baseDoc:i,fullPath:n})}))),p instanceof Error)return[J.remove(n),p];const f=ve(e,u),d=J.replace(s,p,{$$ref:f});if(u&&u!==i)return[d,J.context(s,{baseDoc:u})];try{if(!function(e,t){const n=[e];return t.path.reduce(((e,t)=>(n.push(e[t]),e[t])),e),r(t.value);function r(e){return J.isObject(e)&&(n.indexOf(e)>=0||Object.keys(e).some((t=>r(e[t]))))}}(r.state,d)||o.useCircularStructures)return d}catch(e){return null}}},Oe=Object.assign(je,{docCache:xe,absoluteify:Ae,clearCache:function(e){void 0!==e?delete xe[e]:Object.keys(xe).forEach((e=>{delete xe[e]}))},JSONRefError:Ee,wrapError:Ce,getDoc:Ie,split:Pe,extractFromDoc:Ne,fetchJSON:function(e){return fetch(e,{headers:{Accept:be},loadSpec:!0}).then((e=>e.text())).then((e=>le.ZP.load(e)))},extract:Te,jsonPointerToArray:Re,unescapeJsonPointerToken:Me}),ke=Oe;function Ae(e,t){if(!we.test(e)){if(!t)throw new Ee(`Tried to resolve a relative URL, without having a basePath. path: '${e}' basePath: '${t}'`);return ce.resolve(t,e)}return e}function Ce(e,t){let n;return n=e&&e.response&&e.response.body?`${e.response.body.code} ${e.response.body.message}`:e.message,new Ee(`Could not resolve reference: ${n}`,t,e)}function Pe(e){return(e+"").split("#")}function Ne(e,t){const n=xe[e];if(n&&!J.isPromise(n))try{const e=Te(t,n);return Object.assign(Promise.resolve(e),{__value:e})}catch(e){return Promise.reject(e)}return Ie(e).then((e=>Te(t,e)))}function Ie(e){const t=xe[e];return t?J.isPromise(t)?t:Promise.resolve(t):(xe[e]=Oe.fetchJSON(e).then((t=>(xe[e]=t,t))),xe[e])}function Te(e,t){const n=Re(e);if(n.length<1)return t;const r=J.getIn(t,n);if(void 0===r)throw new Ee(`Could not resolve pointer: ${e} does not exist in document`,{pointer:e});return r}function Re(e){if("string"!=typeof e)throw new TypeError("Expected a string, got a "+typeof e);return"/"===e[0]&&(e=e.substr(1)),""===e?[]:e.split("/").map(Me)}function Me(e){if("string"!=typeof e)return e;return new URLSearchParams(`=${e.replace(/~1/g,"/").replace(/~0/g,"~")}`).get("")}function De(e){return new URLSearchParams([["",e.replace(/~/g,"~0").replace(/\//g,"~1")]]).toString().slice(1)}const Fe=e=>!e||"/"===e||"#"===e;function Le(e,t){if(Fe(t))return!0;const n=e.charAt(t.length),r=t.slice(-1);return 0===e.indexOf(t)&&(!n||"/"===n||"#"===n)&&"#"!==r}const Be={key:"allOf",plugin:(e,t,n,r,o)=>{if(o.meta&&o.meta.$$ref)return;const s=n.slice(0,-1);if(ye(s))return;if(!Array.isArray(e)){const e=new TypeError("allOf must be an array");return e.fullPath=n,e}let i=!1,a=o.value;if(s.forEach((e=>{a&&(a=a[e])})),a=f()({},a),0===Object.keys(a).length)return;delete a.allOf;const l=[];return l.push(r.replace(s,{})),e.forEach(((e,t)=>{if(!r.isObject(e)){if(i)return null;i=!0;const e=new TypeError("Elements in allOf must be objects");return e.fullPath=n,l.push(e)}l.push(r.mergeDeep(s,e));const o=function(e,t){let{specmap:n,getBaseUrlForNodePath:r=(e=>n.getContext([...t,...e]).baseDoc),targetKeys:o=["$ref","$$ref"]}=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};const s=[];return he()(e).forEach((function(){if(o.includes(this.key)&&"string"==typeof this.node){const e=this.path,o=t.concat(this.path),i=ve(this.node,r(e));s.push(n.replace(o,i))}})),s}(e,n.slice(0,-1),{getBaseUrlForNodePath:e=>r.getContext([...n,t,...e]).baseDoc,specmap:r});l.push(...o)})),a.example&&l.push(r.remove([].concat(s,"example"))),l.push(r.mergeDeep(s,a)),a.$$ref||l.push(r.remove([].concat(s,"$$ref"))),l}},$e={key:"parameters",plugin:(e,t,n,r)=>{if(Array.isArray(e)&&e.length){const t=Object.assign([],e),o=n.slice(0,-1),s=f()({},J.getIn(r.spec,o));for(let o=0;o{const o=f()({},e);for(const t in e)try{o[t].default=r.modelPropertyMacro(o[t])}catch(e){const t=new Error(e);return t.fullPath=n,t}return J.replace(n,o)}};class Ue{constructor(e){this.root=ze(e||{})}set(e,t){const n=this.getParent(e,!0);if(!n)return void Ve(this.root,t,null);const r=e[e.length-1],{children:o}=n;o[r]?Ve(o[r],t,n):o[r]=ze(t,n)}get(e){if((e=e||[]).length<1)return this.root.value;let t,n,r=this.root;for(let o=0;o{if(!e)return e;const{children:r}=e;return!r[n]&&t&&(r[n]=ze(null,e)),r[n]}),this.root)}}function ze(e,t){return Ve({children:{}},e,t)}function Ve(e,t,n){return e.value=t||{},e.protoValue=n?f()(f()({},n.protoValue),e.value):e.value,Object.keys(e.children).forEach((t=>{const n=e.children[t];e.children[t]=Ve(n,n.value,e)})),e}const We=()=>{};class Je{static getPluginName(e){return e.pluginName}static getPatchesOfType(e,t){return e.filter(t)}constructor(e){Object.assign(this,{spec:"",debugLevel:"info",plugins:[],pluginHistory:{},errors:[],mutations:[],promisedPatches:[],state:{},patches:[],context:{},contextTree:new Ue,showDebug:!1,allPatches:[],pluginProp:"specMap",libMethods:Object.assign(Object.create(this),J,{getInstance:()=>this}),allowMetaPatches:!1},e),this.get=this._get.bind(this),this.getContext=this._getContext.bind(this),this.hasRun=this._hasRun.bind(this),this.wrappedPlugins=this.plugins.map(this.wrapPlugin.bind(this)).filter(J.isFunction),this.patches.push(J.add([],this.spec)),this.patches.push(J.context([],this.context)),this.updatePatches(this.patches)}debug(e){if(this.debugLevel===e){for(var t=arguments.length,n=new Array(t>1?t-1:0),r=1;r1?t-1:0),r=1;r!Array.isArray(e)||e.every(((e,n)=>e===t[n]));return function*(r,o){const s={};for(const e of r.filter(J.isAdditiveMutation))yield*i(e.value,e.path,e);function*i(r,a,l){if(J.isObject(r)){const c=a.length-1,u=a[c],p=a.indexOf("properties"),h="properties"===u&&c===p,f=o.allowMetaPatches&&s[r.$$ref];for(const c of Object.keys(r)){const u=r[c],p=a.concat(c),d=J.isObject(u),m=r.$$ref;if(f||d&&(o.allowMetaPatches&&m&&(s[m]=!0),yield*i(u,p,l)),!h&&c===e.key){const r=t(n,a);n&&!r||(yield e.plugin(u,c,p,o,l))}}}else e.key===a[a.length-1]&&(yield e.plugin(r,e.key,a,o))}}}(e)),Object.assign(r.bind(o),{pluginName:e.name||t,isGenerator:J.isGenerator(r)})}nextPlugin(){return this.wrappedPlugins.find((e=>this.getMutationsForPlugin(e).length>0))}nextPromisedPatch(){if(this.promisedPatches.length>0)return Promise.race(this.promisedPatches.map((e=>e.value)))}getPluginHistory(e){const t=this.constructor.getPluginName(e);return this.pluginHistory[t]||[]}getPluginRunCount(e){return this.getPluginHistory(e).length}getPluginHistoryTip(e){const t=this.getPluginHistory(e);return t&&t[t.length-1]||{}}getPluginMutationIndex(e){const t=this.getPluginHistoryTip(e).mutationIndex;return"number"!=typeof t?-1:t}updatePluginHistory(e,t){const n=this.constructor.getPluginName(e);this.pluginHistory[n]=this.pluginHistory[n]||[],this.pluginHistory[n].push(t)}updatePatches(e){J.normalizeArray(e).forEach((e=>{if(e instanceof Error)this.errors.push(e);else try{if(!J.isObject(e))return void this.debug("updatePatches","Got a non-object patch",e);if(this.showDebug&&this.allPatches.push(e),J.isPromise(e.value))return this.promisedPatches.push(e),void this.promisedPatchThen(e);if(J.isContextPatch(e))return void this.setContext(e.path,e.value);J.isMutation(e)&&this.updateMutations(e)}catch(e){console.error(e),this.errors.push(e)}}))}updateMutations(e){"object"==typeof e.value&&!Array.isArray(e.value)&&this.allowMetaPatches&&(e.value=f()({},e.value));const t=J.applyPatch(this.state,e,{allowMetaPatches:this.allowMetaPatches});t&&(this.mutations.push(e),this.state=t)}removePromisedPatch(e){const t=this.promisedPatches.indexOf(e);t<0?this.debug("Tried to remove a promisedPatch that isn't there!"):this.promisedPatches.splice(t,1)}promisedPatchThen(e){return e.value=e.value.then((t=>{const n=f()(f()({},e),{},{value:t});this.removePromisedPatch(e),this.updatePatches(n)})).catch((t=>{this.removePromisedPatch(e),this.updatePatches(t)})),e.value}getMutations(e,t){return e=e||0,"number"!=typeof t&&(t=this.mutations.length),this.mutations.slice(e,t)}getCurrentMutations(){return this.getMutationsForPlugin(this.getCurrentPlugin())}getMutationsForPlugin(e){const t=this.getPluginMutationIndex(e);return this.getMutations(t+1)}getCurrentPlugin(){return this.currentPlugin}getLib(){return this.libMethods}_get(e){return J.getIn(this.state,e)}_getContext(e){return this.contextTree.get(e)}setContext(e,t){return this.contextTree.set(e,t)}_hasRun(e){return this.getPluginRunCount(this.getCurrentPlugin())>(e||0)}dispatch(){const e=this,t=this.nextPlugin();if(!t){const e=this.nextPromisedPatch();if(e)return e.then((()=>this.dispatch())).catch((()=>this.dispatch()));const t={spec:this.state,errors:this.errors};return this.showDebug&&(t.patches=this.allPatches),Promise.resolve(t)}if(e.pluginCount=e.pluginCount||{},e.pluginCount[t]=(e.pluginCount[t]||0)+1,e.pluginCount[t]>100)return Promise.resolve({spec:e.state,errors:e.errors.concat(new Error("We've reached a hard limit of 100 plugin runs"))});if(t!==this.currentPlugin&&this.promisedPatches.length){const e=this.promisedPatches.map((e=>e.value));return Promise.all(e.map((e=>e.then(We,We)))).then((()=>this.dispatch()))}return function(){e.currentPlugin=t;const r=e.getCurrentMutations(),o=e.mutations.length-1;try{if(t.isGenerator)for(const o of t(r,e.getLib()))n(o);else{n(t(r,e.getLib()))}}catch(e){console.error(e),n([Object.assign(Object.create(e),{plugin:t})])}finally{e.updatePluginHistory(t,{mutationIndex:o})}return e.dispatch()}();function n(n){n&&(n=J.fullyNormalizeArray(n),e.updatePatches(n,t))}}}const Ke={refs:ke,allOf:Be,parameters:$e,properties:qe};var He=n(32454);function Ge(e){const{spec:t}=e,{paths:n}=t,r={};if(!n||t.$$normalized)return e;for(const e in n){const o=n[e];if(null==o||!["object","function"].includes(typeof o))continue;const s=o.parameters;for(const n in o){const i=o[n];if(null==i||!["object","function"].includes(typeof i))continue;const a=(0,He.Z)(i,e,n);if(a){r[a]?r[a].push(i):r[a]=[i];const e=r[a];if(e.length>1)e.forEach(((e,t)=>{e.__originalOperationId=e.__originalOperationId||e.operationId,e.operationId=`${a}${t+1}`}));else if(void 0!==i.operationId){const t=e[0];t.__originalOperationId=t.__originalOperationId||i.operationId,t.operationId=a}}if("parameters"!==n){const e=[],n={};for(const r in t)"produces"!==r&&"consumes"!==r&&"security"!==r||(n[r]=t[r],e.push(n));if(s&&(n.parameters=s,e.push(n)),e.length)for(const t of e)for(const e in t)if(i[e]){if("parameters"===e)for(const n of t[e]){i[e].some((e=>e.name&&e.name===n.name||e.$ref&&e.$ref===n.$ref||e.$$ref&&e.$$ref===n.$$ref||e===n))||i[e].push(n)}}else i[e]=t[e]}}}return t.$$normalized=!0,e}function Ze(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};const{requestInterceptor:n,responseInterceptor:r}=t,o=e.withCredentials?"include":"same-origin";return t=>e({url:t,loadSpec:!0,requestInterceptor:n,responseInterceptor:r,headers:{Accept:be},credentials:o}).then((e=>e.body))}var Ye=n(80129),Xe=n.n(Ye);const Qe="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:window,{FormData:et,Blob:tt,File:nt}=Qe,rt=e=>":/?#[]@!$&'()*+,;=".indexOf(e)>-1,ot=e=>/^[a-z0-9\-._~]+$/i.test(e);function st(e){let{escape:t}=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=arguments.length>2?arguments[2]:void 0;return"number"==typeof e&&(e=e.toString()),"string"==typeof e&&e.length&&t?n?JSON.parse(e):[...e].map((e=>{if(ot(e))return e;if(rt(e)&&"unsafe"===t)return e;const n=new TextEncoder;return Array.from(n.encode(e)).map((e=>`0${e.toString(16).toUpperCase()}`.slice(-2))).map((e=>`%${e}`)).join("")})).join(""):e}function it(e){const{value:t}=e;return Array.isArray(t)?function(e){let{key:t,value:n,style:r,explode:o,escape:s}=e;const i=e=>st(e,{escape:s});if("simple"===r)return n.map((e=>i(e))).join(",");if("label"===r)return`.${n.map((e=>i(e))).join(".")}`;if("matrix"===r)return n.map((e=>i(e))).reduce(((e,n)=>!e||o?`${e||""};${t}=${n}`:`${e},${n}`),"");if("form"===r){const e=o?`&${t}=`:",";return n.map((e=>i(e))).join(e)}if("spaceDelimited"===r){const e=o?`${t}=`:"";return n.map((e=>i(e))).join(` ${e}`)}if("pipeDelimited"===r){const e=o?`${t}=`:"";return n.map((e=>i(e))).join(`|${e}`)}return}(e):"object"==typeof t?function(e){let{key:t,value:n,style:r,explode:o,escape:s}=e;const i=e=>st(e,{escape:s}),a=Object.keys(n);if("simple"===r)return a.reduce(((e,t)=>{const r=i(n[t]);return`${e?`${e},`:""}${t}${o?"=":","}${r}`}),"");if("label"===r)return a.reduce(((e,t)=>{const r=i(n[t]);return`${e?`${e}.`:"."}${t}${o?"=":"."}${r}`}),"");if("matrix"===r&&o)return a.reduce(((e,t)=>`${e?`${e};`:";"}${t}=${i(n[t])}`),"");if("matrix"===r)return a.reduce(((e,r)=>{const o=i(n[r]);return`${e?`${e},`:`;${t}=`}${r},${o}`}),"");if("form"===r)return a.reduce(((e,t)=>{const r=i(n[t]);return`${e?`${e}${o?"&":","}`:""}${t}${o?"=":","}${r}`}),"");return}(e):function(e){let{key:t,value:n,style:r,escape:o}=e;const s=e=>st(e,{escape:o});if("simple"===r)return s(n);if("label"===r)return`.${s(n)}`;if("matrix"===r)return`;${t}=${s(n)}`;if("form"===r)return s(n);if("deepObject"===r)return s(n,{},!0);return}(e)}const at=(e,t)=>{t.body=e},lt={serializeRes:pt,mergeInQueryOrForm:wt};async function ct(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};"object"==typeof e&&(t=e,e=t.url),t.headers=t.headers||{},lt.mergeInQueryOrForm(t),t.headers&&Object.keys(t.headers).forEach((e=>{const n=t.headers[e];"string"==typeof n&&(t.headers[e]=n.replace(/\n+/g," "))})),t.requestInterceptor&&(t=await t.requestInterceptor(t)||t);const n=t.headers["content-type"]||t.headers["Content-Type"];let r;/multipart\/form-data/i.test(n)&&t.body instanceof et&&(delete t.headers["content-type"],delete t.headers["Content-Type"]);try{r=await(t.userFetch||fetch)(t.url,t),r=await lt.serializeRes(r,e,t),t.responseInterceptor&&(r=await t.responseInterceptor(r)||r)}catch(e){if(!r)throw e;const t=new Error(r.statusText||`response status is ${r.status}`);throw t.status=r.status,t.statusCode=r.status,t.responseError=e,t}if(!r.ok){const e=new Error(r.statusText||`response status is ${r.status}`);throw e.status=r.status,e.statusCode=r.status,e.response=r,e}return r}const ut=function(){return/(json|xml|yaml|text)\b/.test(arguments.length>0&&void 0!==arguments[0]?arguments[0]:"")};function pt(e,t){let{loadSpec:n=!1}=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};const r={ok:e.ok,url:e.url||t,status:e.status,statusText:e.statusText,headers:ht(e.headers)},o=r.headers["content-type"],s=n||ut(o);return(s?e.text:e.blob||e.buffer).call(e).then((e=>{if(r.text=e,r.data=e,s)try{const t=function(e,t){return t&&(0===t.indexOf("application/json")||t.indexOf("+json")>0)?JSON.parse(e):le.ZP.load(e)}(e,o);r.body=t,r.obj=t}catch(e){r.parseError=e}return r}))}function ht(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return"function"!=typeof e.entries?{}:Array.from(e.entries()).reduce(((e,t)=>{let[n,r]=t;return e[n]=function(e){return e.includes(", ")?e.split(", "):e}(r),e}),{})}function ft(e,t){return t||"undefined"==typeof navigator||(t=navigator),t&&"ReactNative"===t.product?!(!e||"object"!=typeof e||"string"!=typeof e.uri):void 0!==nt&&e instanceof nt||(void 0!==tt&&e instanceof tt||(!!ArrayBuffer.isView(e)||null!==e&&"object"==typeof e&&"function"==typeof e.pipe))}function dt(e,t){return Array.isArray(e)&&e.some((e=>ft(e,t)))}const mt={form:",",spaceDelimited:"%20",pipeDelimited:"|"},gt={csv:",",ssv:"%20",tsv:"%09",pipes:"|"};function yt(e,t){let n=arguments.length>2&&void 0!==arguments[2]&&arguments[2];const{collectionFormat:r,allowEmptyValue:o,serializationOption:s,encoding:i}=t,a="object"!=typeof t||Array.isArray(t)?t:t.value,l=n?e=>e.toString():e=>encodeURIComponent(e),c=l(e);if(void 0===a&&o)return[[c,""]];if(ft(a)||dt(a))return[[c,a]];if(s)return vt(e,a,n,s);if(i){if([typeof i.style,typeof i.explode,typeof i.allowReserved].some((e=>"undefined"!==e))){const{style:t,explode:r,allowReserved:o}=i;return vt(e,a,n,{style:t,explode:r,allowReserved:o})}if(i.contentType){if("application/json"===i.contentType){return[[c,l("string"==typeof a?a:JSON.stringify(a))]]}return[[c,l(a.toString())]]}return"object"!=typeof a?[[c,l(a)]]:Array.isArray(a)&&a.every((e=>"object"!=typeof e))?[[c,a.map(l).join(",")]]:[[c,l(JSON.stringify(a))]]}return"object"!=typeof a?[[c,l(a)]]:Array.isArray(a)?"multi"===r?[[c,a.map(l)]]:[[c,a.map(l).join(gt[r||"csv"])]]:[[c,""]]}function vt(e,t,n,r){const o=r.style||"form",s=void 0===r.explode?"form"===o:r.explode,i=!n&&(r&&r.allowReserved?"unsafe":"reserved"),a=e=>st(e,{escape:i}),l=n?e=>e:e=>st(e,{escape:i});return"object"!=typeof t?[[l(e),a(t)]]:Array.isArray(t)?s?[[l(e),t.map(a)]]:[[l(e),t.map(a).join(mt[o])]]:"deepObject"===o?Object.keys(t).map((n=>[l(`${e}[${n}]`),a(t[n])])):s?Object.keys(t).map((e=>[l(e),a(t[e])])):[[l(e),Object.keys(t).map((e=>[`${l(e)},${a(t[e])}`])).join(",")]]}function bt(e){const t=Object.keys(e).reduce(((t,n)=>{for(const[r,o]of yt(n,e[n]))t[r]=o;return t}),{});return Xe().stringify(t,{encode:!1,indices:!1})||""}function wt(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};const{url:t="",query:n,form:r}=e;if(r){const t=Object.keys(r).some((e=>{const{value:t}=r[e];return ft(t)||dt(t)})),n=e.headers["content-type"]||e.headers["Content-Type"];if(t||/multipart\/form-data/i.test(n)){const t=(o=e.form,Object.entries(o).reduce(((e,t)=>{let[n,r]=t;for(const[t,o]of yt(n,r,!0))if(Array.isArray(o))for(const n of o)if(ArrayBuffer.isView(n)){const r=new tt([n]);e.append(t,r)}else e.append(t,n);else if(ArrayBuffer.isView(o)){const n=new tt([o]);e.append(t,n)}else e.append(t,o);return e}),new et));at(t,e)}else e.body=bt(r);delete e.form}var o;if(n){const[r,o]=t.split("?");let s="";if(o){const e=Xe().parse(o);Object.keys(n).forEach((t=>delete e[t])),s=Xe().stringify(e,{encode:!0})}const i=function(){for(var e=arguments.length,t=new Array(e),n=0;ne)).join("&");return r?`?${r}`:""}(s,bt(n));e.url=r+i,delete e.query}return e}const Et=e=>{const{baseDoc:t,url:n}=e;return t||n||""},xt=e=>{const{fetch:t,http:n}=e;return t||n||ct};async function St(e){const{spec:t,mode:n,allowMetaPatches:r=!0,pathDiscriminator:o,modelPropertyMacro:s,parameterMacro:i,requestInterceptor:a,responseInterceptor:l,skipNormalization:c,useCircularStructures:u}=e,p=Et(e),h=xt(e);return function(e){p&&(Ke.refs.docCache[p]=e);Ke.refs.fetchJSON=Ze(h,{requestInterceptor:a,responseInterceptor:l});const t=[Ke.refs];"function"==typeof i&&t.push(Ke.parameters);"function"==typeof s&&t.push(Ke.properties);"strict"!==n&&t.push(Ke.allOf);return(f={spec:e,context:{baseDoc:p},plugins:t,allowMetaPatches:r,pathDiscriminator:o,parameterMacro:i,modelPropertyMacro:s,useCircularStructures:u},new Je(f).dispatch()).then(c?async e=>e:Ge);var f}(t)}const _t={name:"generic",match:()=>!0,normalize(e){let{spec:t}=e;const{spec:n}=Ge({spec:t});return n},resolve:async e=>St(e)};const jt=e=>{try{const{openapi:t}=e;return"string"==typeof t&&/^3\.0\.([0123])(?:-rc[012])?$/.test(t)}catch{return!1}},Ot=e=>{try{const{openapi:t}=e;return"string"==typeof t&&/^3\.1\.(?:[1-9]\d*|0)$/.test(t)}catch{return!1}},kt=e=>jt(e)||Ot(e),At={name:"openapi-2",match(e){let{spec:t}=e;return(e=>{try{const{swagger:t}=e;return"2.0"===t}catch{return!1}})(t)},normalize(e){let{spec:t}=e;const{spec:n}=Ge({spec:t});return n},resolve:async e=>async function(e){return St(e)}(e)};const Ct={name:"openapi-3-0",match(e){let{spec:t}=e;return jt(t)},normalize(e){let{spec:t}=e;const{spec:n}=Ge({spec:t});return n},resolve:async e=>async function(e){return St(e)}(e)};var Pt=n(43500);class Nt extends Pt.RP{constructor(e,t,n){super(e,t,n),this.element="annotation"}get code(){return this.attributes.get("code")}set code(e){this.attributes.set("code",e)}}const It=Nt;class Tt extends Pt.RP{constructor(e,t,n){super(e,t,n),this.element="comment"}}const Rt=Tt;const Mt=function(){return!1};const Dt=function(){return!0};function Ft(e){return null!=e&&"object"==typeof e&&!0===e["@@functional/placeholder"]}function Lt(e){return function t(n){return 0===arguments.length||Ft(n)?t:e.apply(this,arguments)}}function Bt(e){return function t(n,r){switch(arguments.length){case 0:return t;case 1:return Ft(n)?t:Lt((function(t){return e(n,t)}));default:return Ft(n)&&Ft(r)?t:Ft(n)?Lt((function(t){return e(t,r)})):Ft(r)?Lt((function(t){return e(n,t)})):e(n,r)}}}const $t=Array.isArray||function(e){return null!=e&&e.length>=0&&"[object Array]"===Object.prototype.toString.call(e)};function qt(e,t,n){return function(){if(0===arguments.length)return n();var r=arguments[arguments.length-1];if(!$t(r)){for(var o=0;o=arguments.length)?a=t[i]:(a=arguments[o],o+=1),r[i]=a,Ft(a)||(s-=1),i+=1}return s<=0?n.apply(this,r):Ht(s,Gt(e,r,n))}}const Zt=Bt((function(e,t){return 1===e?Lt(t):Ht(e,Gt(e,[],t))}));function Yt(e){for(var t,n=[];!(t=e.next()).done;)n.push(t.value);return n}function Xt(e,t,n){for(var r=0,o=n.length;r=0;)Qt(t=on[n],e)&&!an(r,t)&&(r[r.length]=t),n-=1;return r})):Lt((function(e){return Object(e)!==e?[]:Object.keys(e)}));const cn=Lt((function(e){return null===e?"Null":void 0===e?"Undefined":Object.prototype.toString.call(e).slice(8,-1)}));function un(e,t,n,r){var o=Yt(e);function s(e,t){return pn(e,t,n.slice(),r.slice())}return!Xt((function(e,t){return!Xt(s,t,e)}),Yt(t),o)}function pn(e,t,n,r){if(en(e,t))return!0;var o,s,i=cn(e);if(i!==cn(t))return!1;if("function"==typeof e["fantasy-land/equals"]||"function"==typeof t["fantasy-land/equals"])return"function"==typeof e["fantasy-land/equals"]&&e["fantasy-land/equals"](t)&&"function"==typeof t["fantasy-land/equals"]&&t["fantasy-land/equals"](e);if("function"==typeof e.equals||"function"==typeof t.equals)return"function"==typeof e.equals&&e.equals(t)&&"function"==typeof t.equals&&t.equals(e);switch(i){case"Arguments":case"Array":case"Object":if("function"==typeof e.constructor&&"Promise"===(o=e.constructor,null==(s=String(o).match(/^function (\w*)/))?"":s[1]))return e===t;break;case"Boolean":case"Number":case"String":if(typeof e!=typeof t||!en(e.valueOf(),t.valueOf()))return!1;break;case"Date":if(!en(e.valueOf(),t.valueOf()))return!1;break;case"Error":return e.name===t.name&&e.message===t.message;case"RegExp":if(e.source!==t.source||e.global!==t.global||e.ignoreCase!==t.ignoreCase||e.multiline!==t.multiline||e.sticky!==t.sticky||e.unicode!==t.unicode)return!1}for(var a=n.length-1;a>=0;){if(n[a]===e)return r[a]===t;a-=1}switch(i){case"Map":return e.size===t.size&&un(e.entries(),t.entries(),n.concat([e]),r.concat([t]));case"Set":return e.size===t.size&&un(e.values(),t.values(),n.concat([e]),r.concat([t]));case"Arguments":case"Array":case"Object":case"Boolean":case"Number":case"String":case"Date":case"Error":case"RegExp":case"Int8Array":case"Uint8Array":case"Uint8ClampedArray":case"Int16Array":case"Uint16Array":case"Int32Array":case"Uint32Array":case"Float32Array":case"Float64Array":case"ArrayBuffer":break;default:return!1}var l=ln(e);if(l.length!==ln(t).length)return!1;var c=n.concat([e]),u=r.concat([t]);for(a=l.length-1;a>=0;){var p=l[a];if(!Qt(p,t)||!pn(t[p],e[p],c,u))return!1;a-=1}return!0}const hn=Bt((function(e,t){return pn(e,t,[],[])}));function fn(e,t){return function(e,t,n){var r,o;if("function"==typeof e.indexOf)switch(typeof t){case"number":if(0===t){for(r=1/t;n=0}function dn(e,t){for(var n=0,r=t.length,o=Array(r);n":jn(n,r)},r=function(e,t){return dn((function(t){return mn(t)+": "+n(e[t])}),t.slice().sort())};switch(Object.prototype.toString.call(e)){case"[object Arguments]":return"(function() { return arguments; }("+dn(n,e).join(", ")+"))";case"[object Array]":return"["+dn(n,e).concat(r(e,_n((function(e){return/^\d+$/.test(e)}),ln(e)))).join(", ")+"]";case"[object Boolean]":return"object"==typeof e?"new Boolean("+n(e.valueOf())+")":e.toString();case"[object Date]":return"new Date("+(isNaN(e.valueOf())?n(NaN):mn(yn(e)))+")";case"[object Map]":return"new Map("+n(Array.from(e))+")";case"[object Null]":return"null";case"[object Number]":return"object"==typeof e?"new Number("+n(e.valueOf())+")":1/e==-1/0?"-0":e.toString(10);case"[object Set]":return"new Set("+n(Array.from(e).sort())+")";case"[object String]":return"object"==typeof e?"new String("+n(e.valueOf())+")":mn(e);case"[object Undefined]":return"undefined";default:if("function"==typeof e.toString){var o=e.toString();if("[object Object]"!==o)return o}return"{"+r(e,ln(e)).join(", ")+"}"}}const On=Lt((function(e){return jn(e,[])}));const kn=Bt((function(e,t){if(e===t)return t;function n(e,t){if(e>t!=t>e)return t>e?t:e}var r=n(e,t);if(void 0!==r)return r;var o=n(typeof e,typeof t);if(void 0!==o)return o===typeof e?e:t;var s=On(e),i=n(s,On(t));return void 0!==i&&i===s?e:t}));var An=function(){function e(e,t){this.xf=t,this.f=e}return e.prototype["@@transducer/init"]=zt,e.prototype["@@transducer/result"]=Vt,e.prototype["@@transducer/step"]=function(e,t){return this.xf["@@transducer/step"](e,this.f(t))},e}();const Cn=Bt(qt(["fantasy-land/map","map"],(function(e){return function(t){return new An(e,t)}}),(function(e,t){switch(Object.prototype.toString.call(t)){case"[object Function]":return Zt(t.length,(function(){return e.call(this,t.apply(this,arguments))}));case"[object Object]":return bn((function(n,r){return n[r]=e(t[r]),n}),{},ln(t));default:return dn(e,t)}}))),Pn=Number.isInteger||function(e){return e<<0===e};function Nn(e){return"[object String]"===Object.prototype.toString.call(e)}const In=Bt((function(e,t){var n=e<0?t.length+e:e;return Nn(t)?t.charAt(n):t[n]}));const Tn=Bt((function(e,t){if(null!=t)return Pn(e)?In(e,t):t[e]}));const Rn=Bt((function(e,t){return Cn(Tn(e),t)}));function Mn(e){return function t(n,r,o){switch(arguments.length){case 0:return t;case 1:return Ft(n)?t:Bt((function(t,r){return e(n,t,r)}));case 2:return Ft(n)&&Ft(r)?t:Ft(n)?Bt((function(t,n){return e(t,r,n)})):Ft(r)?Bt((function(t,r){return e(n,t,r)})):Lt((function(t){return e(n,r,t)}));default:return Ft(n)&&Ft(r)&&Ft(o)?t:Ft(n)&&Ft(r)?Bt((function(t,n){return e(t,n,o)})):Ft(n)&&Ft(o)?Bt((function(t,n){return e(t,r,n)})):Ft(r)&&Ft(o)?Bt((function(t,r){return e(n,t,r)})):Ft(n)?Lt((function(t){return e(t,r,o)})):Ft(r)?Lt((function(t){return e(n,t,o)})):Ft(o)?Lt((function(t){return e(n,r,t)})):e(n,r,o)}}}const Dn=Lt((function(e){return!!$t(e)||!!e&&("object"==typeof e&&(!Nn(e)&&(0===e.length||e.length>0&&(e.hasOwnProperty(0)&&e.hasOwnProperty(e.length-1)))))}));var Fn="undefined"!=typeof Symbol?Symbol.iterator:"@@iterator";function Ln(e,t,n){return function(r,o,s){if(Dn(s))return e(r,o,s);if(null==s)return o;if("function"==typeof s["fantasy-land/reduce"])return t(r,o,s,"fantasy-land/reduce");if(null!=s[Fn])return n(r,o,s[Fn]());if("function"==typeof s.next)return n(r,o,s);if("function"==typeof s.reduce)return t(r,o,s,"reduce");throw new TypeError("reduce: list must be array or iterable")}}function Bn(e,t,n){for(var r=0,o=n.length;r1){var s=!rr(r)&&Qt(o,r)&&"object"==typeof r[o]?r[o]:Pn(t[1])?[]:{};n=e(Array.prototype.slice.call(t,1),n,s)}return function(e,t,n){if(Pn(e)&&$t(n)){var r=[].concat(n);return r[e]=t,r}var o={};for(var s in n)o[s]=n[s];return o[e]=t,o}(o,n,r)}));function sr(e){var t=Object.prototype.toString.call(e);return"[object Function]"===t||"[object AsyncFunction]"===t||"[object GeneratorFunction]"===t||"[object AsyncGeneratorFunction]"===t}const ir=Bt((function(e,t){return e&&t}));const ar=Bt((function(e,t){var n=Zt(e,t);return Zt(e,(function(){return bn(Qn,Cn(n,arguments[0]),Array.prototype.slice.call(arguments,1))}))}));const lr=Lt((function(e){return ar(e.length,e)}));const cr=Bt((function(e,t){return sr(e)?function(){return e.apply(this,arguments)&&t.apply(this,arguments)}:lr(ir)(e,t)}));const ur=Lt((function(e){return function(t,n){return e(t,n)?-1:e(n,t)?1:0}}));const pr=lr(Lt((function(e){return!e})));function hr(e,t){return function(){return t.call(this,e.apply(this,arguments))}}function fr(e,t){return function(){var n=arguments.length;if(0===n)return t();var r=arguments[n-1];return $t(r)||"function"!=typeof r[e]?t.apply(this,arguments):r[e].apply(r,Array.prototype.slice.call(arguments,0,n-1))}}const dr=Mn(fr("slice",(function(e,t,n){return Array.prototype.slice.call(n,e,t)})));const mr=Lt(fr("tail",dr(1,1/0)));function gr(){if(0===arguments.length)throw new Error("pipe requires at least one argument");return Ht(arguments[0].length,Jn(hr,arguments[0],mr(arguments)))}var yr=Bt((function(e,t){return Zt(Jn(kn,0,Rn("length",t)),(function(){var n=arguments,r=this;return e.apply(r,dn((function(e){return e.apply(r,n)}),t))}))}));const vr=yr;function br(e){return new RegExp(e.source,e.flags?e.flags:(e.global?"g":"")+(e.ignoreCase?"i":"")+(e.multiline?"m":"")+(e.sticky?"y":"")+(e.unicode?"u":"")+(e.dotAll?"s":""))}function wr(e,t,n){if(n||(n=new Er),function(e){var t=typeof e;return null==e||"object"!=t&&"function"!=t}(e))return e;var r=function(r){var o=n.get(e);if(o)return o;for(var s in n.set(e,r),e)Object.prototype.hasOwnProperty.call(e,s)&&(r[s]=t?wr(e[s],!0,n):e[s]);return r};switch(cn(e)){case"Object":return r(Object.create(Object.getPrototypeOf(e)));case"Array":return r([]);case"Date":return new Date(e.valueOf());case"RegExp":return br(e);case"Int8Array":case"Uint8Array":case"Uint8ClampedArray":case"Int16Array":case"Uint16Array":case"Int32Array":case"Uint32Array":case"Float32Array":case"Float64Array":case"BigInt64Array":case"BigUint64Array":return e.slice();default:return e}}var Er=function(){function e(){this.map={},this.length=0}return e.prototype.set=function(e,t){const n=this.hash(e);let r=this.map[n];r||(this.map[n]=r=[]),r.push([e,t]),this.length+=1},e.prototype.hash=function(e){let t=[];for(var n in e)t.push(Object.prototype.toString.call(e[n]));return t.join()},e.prototype.get=function(e){if(this.length<=180){for(const t in this.map){const n=this.map[t];for(let t=0;t=0&&this.i>=this.n?Ut(n):n},e}();function Ir(e){return function(t){return new Nr(e,t)}}const Tr=Bt(qt(["take"],Ir,(function(e,t){return dr(0,e<0?1/0:e,t)})));function Rr(e,t){for(var n=t.length-1;n>=0&&e(t[n]);)n-=1;return dr(0,n+1,t)}var Mr=function(){function e(e,t){this.f=e,this.retained=[],this.xf=t}return e.prototype["@@transducer/init"]=zt,e.prototype["@@transducer/result"]=function(e){return this.retained=null,this.xf["@@transducer/result"](e)},e.prototype["@@transducer/step"]=function(e,t){return this.f(t)?this.retain(e,t):this.flush(e,t)},e.prototype.flush=function(e,t){return e=zn(this.xf,e,this.retained),this.retained=[],this.xf["@@transducer/step"](e,t)},e.prototype.retain=function(e,t){return this.retained.push(t),e},e}();function Dr(e){return function(t){return new Mr(e,t)}}const Fr=Bt(qt([],Dr,Rr));var Lr=function(){function e(e,t){this.xf=t,this.f=e}return e.prototype["@@transducer/init"]=zt,e.prototype["@@transducer/result"]=Vt,e.prototype["@@transducer/step"]=function(e,t){if(this.f){if(this.f(t))return e;this.f=null}return this.xf["@@transducer/step"](e,t)},e}();function Br(e){return function(t){return new Lr(e,t)}}const $r=Bt(qt(["dropWhile"],Br,(function(e,t){for(var n=0,r=t.length;ne.classes.contains("api"))).first}get results(){return this.children.filter((e=>e.classes.contains("result")))}get result(){return this.results.first}get annotations(){return this.children.filter((e=>"annotation"===e.element))}get warnings(){return this.children.filter((e=>"annotation"===e.element&&e.classes.contains("warning")))}get errors(){return this.children.filter((e=>"annotation"===e.element&&e.classes.contains("error")))}get isEmpty(){return this.children.reject((e=>"annotation"===e.element)).isEmpty}replaceResult(e){const{result:t}=this;if(qo(t))return!1;const n=this.content.findIndex((e=>e===t));return-1!==n&&(this.content[n]=e,!0)}}const zo=Uo;class Vo extends Pt.ON{constructor(e,t,n){super(e,t,n),this.element="sourceMap"}get positionStart(){return this.children.filter((e=>e.classes.contains("position"))).get(0)}get positionEnd(){return this.children.filter((e=>e.classes.contains("position"))).get(1)}set position(e){if(null===e)return;const t=new Pt.ON([e.start.row,e.start.column,e.start.char]),n=new Pt.ON([e.end.row,e.end.column,e.end.char]);t.classes.push("position"),n.classes.push("position"),this.push(t).push(n)}}const Wo=Vo;var Jo=n(80621),Ko=n(52201),Ho=n(27398);function Go(e){return Go="function"==typeof Ko&&"symbol"==typeof Ho?function(e){return typeof e}:function(e){return e&&"function"==typeof Ko&&e.constructor===Ko&&e!==Ko.prototype?"symbol":typeof e},Go(e)}var Zo=n(26189);function Yo(e){var t=function(e,t){if("object"!==Go(e)||null===e)return e;var n=e[Zo];if(void 0!==n){var r=n.call(e,t||"default");if("object"!==Go(r))return r;throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===t?String:Number)(e)}(e,"string");return"symbol"===Go(t)?t:String(t)}function Xo(e,t,n){return(t=Yo(t))in e?Jo(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}const Qo=Zt(1,gr(cn,Xr("GeneratorFunction")));const es=Zt(1,gr(cn,Xr("AsyncFunction")));const ts=Gn([gr(cn,Xr("Function")),Qo,es]);const ns=pr(ts);const rs=Zt(1,ts(Array.isArray)?Array.isArray:gr(cn,Xr("Array")));const os=cr(rs,so);var ss=Zt(3,(function(e,t,n){var r=uo(e,n),o=uo(ro(e),n);if(!ns(r)&&!os(e)){var s=$n(r,o);return er(s,t)}}));const is=ss;const as=Wr(no),ls=(e,t)=>"function"==typeof(null==t?void 0:t[e]),cs=e=>null!=e&&Object.prototype.hasOwnProperty.call(e,"_storedElement")&&Object.prototype.hasOwnProperty.call(e,"_content"),us=(e,t)=>{var n;return(null==t||null===(n=t.primitive)||void 0===n?void 0:n.call(t))===e},ps=(e,t)=>{var n,r;return(null==t||null===(n=t.classes)||void 0===n||null===(r=n.includes)||void 0===r?void 0:r.call(n,e))||!1},hs=(e,t)=>(null==t?void 0:t.element)===e,fs=e=>e({hasMethod:ls,hasBasicElementProps:cs,primitiveEq:us,isElementType:hs,hasClass:ps}),ds=fs((({hasBasicElementProps:e,primitiveEq:t})=>n=>n instanceof Pt.W_||e(n)&&t(void 0,n))),ms=fs((({hasBasicElementProps:e,primitiveEq:t})=>n=>n instanceof Pt.RP||e(n)&&t("string",n))),gs=fs((({hasBasicElementProps:e,primitiveEq:t})=>n=>n instanceof Pt.VL||e(n)&&t("number",n))),ys=fs((({hasBasicElementProps:e,primitiveEq:t})=>n=>n instanceof Pt.zr||e(n)&&t("null",n))),vs=fs((({hasBasicElementProps:e,primitiveEq:t})=>n=>n instanceof Pt.hh||e(n)&&t("boolean",n))),bs=fs((({hasBasicElementProps:e,primitiveEq:t,hasMethod:n})=>r=>r instanceof Pt.Sb||e(r)&&t("object",r)&&n("keys",r)&&n("values",r)&&n("items",r))),ws=fs((({hasBasicElementProps:e,primitiveEq:t,hasMethod:n})=>r=>r instanceof Pt.ON&&!(r instanceof Pt.Sb)||e(r)&&t("array",r)&&n("push",r)&&n("unshift",r)&&n("map",r)&&n("reduce",r))),Es=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof Pt.c6||e(r)&&t("member",r)&&n(void 0,r))),xs=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof Pt.EA||e(r)&&t("link",r)&&n(void 0,r))),Ss=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof Pt.tK||e(r)&&t("ref",r)&&n(void 0,r))),_s=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof It||e(r)&&t("annotation",r)&&n("array",r))),js=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof Rt||e(r)&&t("comment",r)&&n("string",r))),Os=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof zo||e(r)&&t("parseResult",r)&&n("array",r))),ks=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof Wo||e(r)&&t("sourceMap",r)&&n("array",r))),As=e=>hs("object",e)||hs("array",e)||hs("boolean",e)||hs("number",e)||hs("string",e)||hs("null",e)||hs("member",e),Cs=e=>{var t,n;return ks(null==e||null===(t=e.meta)||void 0===t||null===(n=t.get)||void 0===n?void 0:n.call(t,"sourceMap"))},Ps=(e,t)=>{if(0===e.length)return!0;const n=t.attributes.get("symbols");return!!ws(n)&&Kt(as(n.toValue()),e)},Ns=(e,t)=>0===e.length||Kt(as(t.classes.toValue()),e);const Is=hn(null);const Ts=pr(Is);function Rs(e){return Rs="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},Rs(e)}const Ms=function(e){return"object"===Rs(e)};const Ds=Zt(1,cr(Ts,Ms));var Fs=gr(cn,Xr("Object")),Ls=gr(On,hn(On(Object))),Bs=wo(cr(ts,Ls),["constructor"]);const $s=Zt(1,(function(e){if(!Ds(e)||!Fs(e))return!1;var t=Object.getPrototypeOf(e);return!!Is(t)||Bs(t)}));class qs extends Pt.lS{constructor(){super(),this.register("annotation",It),this.register("comment",Rt),this.register("parseResult",zo),this.register("sourceMap",Wo)}}const Us=new qs,zs=e=>{const t=new qs;return $s(e)&&t.use(e),t},Vs=Us;function Ws(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}const Js=()=>({predicates:function(e){for(var t=1;t=0||(o[n]=e[n]);return o}(e,t);if(Ks){var s=Ks(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}var Ys=n(43992);const Xs=Zt(1,gr(cn,Xr("String"))),Qs=(e,t,n)=>{const r=e[t];if(null!=r){if(!n&&"function"==typeof r)return r;const e=n?r.leave:r.enter;if("function"==typeof e)return e}else{const r=n?e.leave:e.enter;if(null!=r){if("function"==typeof r)return r;const e=r[t];if("function"==typeof e)return e}}return null},ei={},ti=e=>null==e?void 0:e.type,ni=e=>"string"==typeof ti(e),ri=(e,{visitFnGetter:t=Qs,nodeTypeGetter:n=ti}={})=>{const r=new Array(e.length);return{enter(o,...s){for(let i=0;i{const p=n||{};let h,f,d=Array.isArray(e),m=[e],g=-1,y=[];const v=[],b=[];let w=e;do{g+=1;const e=g===m.length;let n,E;const x=e&&0!==y.length;if(e){if(n=0===b.length?void 0:v.pop(),E=f,f=b.pop(),x){E=d?E.slice():Object.create(Object.getPrototypeOf(E),Object.getOwnPropertyDescriptors(E));let e=0;for(let t=0;t{const p=n||{};let h,f,d=Array.isArray(e),m=[e],g=-1,y=[];const v=[],b=[];let w=e;do{g+=1;const e=g===m.length;let n,E;const x=e&&0!==y.length;if(e){if(n=0===b.length?void 0:v.pop(),E=f,f=b.pop(),x){E=d?E.slice():Object.create(Object.getPrototypeOf(E),Object.getOwnPropertyDescriptors(E));let e=0;for(let t=0;tbs(e)?"ObjectElement":ws(e)?"ArrayElement":Es(e)?"MemberElement":ms(e)?"StringElement":vs(e)?"BooleanElement":gs(e)?"NumberElement":ys(e)?"NullElement":xs(e)?"LinkElement":Ss(e)?"RefElement":void 0,ui=gr(ci,Xs),pi={ObjectElement:["content"],ArrayElement:["content"],MemberElement:["key","value"],StringElement:[],BooleanElement:[],NumberElement:[],NullElement:[],RefElement:[],LinkElement:[],Annotation:[],Comment:[],ParseResultElement:["content"],SourceMap:["content"]},hi=Ys({props:{result:[],predicate:Mt,returnOnTrue:void 0,returnOnFalse:void 0},init({predicate:e=this.predicate,returnOnTrue:t=this.returnOnTrue,returnOnFalse:n=this.returnOnFalse}={}){this.result=[],this.predicate=e,this.returnOnTrue=t,this.returnOnFalse=n},methods:{enter(e){return this.predicate(e)?(this.result.push(e),this.returnOnTrue):this.returnOnFalse}}}),fi=(e,t,n={})=>{let{keyMap:r=pi}=n,o=Zs(n,si);return oi(e,t,li({keyMap:r,nodeTypeGetter:ci,nodePredicate:ui},o))};fi[Symbol.for("nodejs.util.promisify.custom")]=async(e,t,n={})=>{let{keyMap:r=pi}=n,o=Zs(n,ii);return oi[Symbol.for("nodejs.util.promisify.custom")](e,t,li({keyMap:r,nodeTypeGetter:ci,nodePredicate:ui},o))};const di=(e,t,n={})=>{if(0===t.length)return e;const r=So(Js,"toolboxCreator",n),o=So({},"visitorOptions",n),s=So(ci,"nodeTypeGetter",o),i=r(),a=t.map((e=>e(i))),l=ri(a.map(So({},"visitor")),{nodeTypeGetter:s});a.forEach(is(["pre"],[]));const c=fi(e,l,o);return a.forEach(is(["post"],[])),c};function mi(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function gi(e){for(var t=1;t{const r=new t(e);return di(r,n,{toolboxCreator:Js,visitorOptions:{nodeTypeGetter:ci}})},vi=e=>(t,n={})=>yi(t,gi(gi({},n),{},{Type:e}));Pt.Sb.refract=vi(Pt.Sb),Pt.ON.refract=vi(Pt.ON),Pt.RP.refract=vi(Pt.RP),Pt.hh.refract=vi(Pt.hh),Pt.zr.refract=vi(Pt.zr),Pt.VL.refract=vi(Pt.VL),Pt.EA.refract=vi(Pt.EA),Pt.tK.refract=vi(Pt.tK),It.refract=vi(It),Rt.refract=vi(Rt),zo.refract=vi(zo),Wo.refract=vi(Wo);const bi=(e,t=new WeakMap)=>(Es(e)?(t.set(e.key,e),bi(e.key,t),t.set(e.value,e),bi(e.value,t)):e.children.forEach((n=>{t.set(n,e),bi(n,t)})),t),wi=Ys.init((function({element:e}){let t;this.transclude=function(n,r){var o;if(n===e)return r;if(n===r)return e;t=null!==(o=t)&&void 0!==o?o:bi(e);const s=t.get(n);return qo(s)?void 0:(bs(s)?((e,t,n)=>{const r=n.get(e);bs(r)&&(r.content=r.map(((o,s,i)=>i===e?(n.delete(e),n.set(t,r),t):i)))})(n,r,t):ws(s)?((e,t,n)=>{const r=n.get(e);ws(r)&&(r.content=r.map((o=>o===e?(n.delete(e),n.set(t,r),t):o)))})(n,r,t):Es(s)&&((e,t,n)=>{const r=n.get(e);Es(r)&&(r.key===e&&(r.key=t,n.delete(e),n.set(t,r)),r.value===e&&(r.value=t,n.delete(e),n.set(t,r)))})(n,r,t),e)}})),Ei=wi,xi=["keyMap"],Si=["keyMap"];function _i(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function ji(e){for(var t=1;t"string"==typeof(null==e?void 0:e.type)?e.type:ci(e),ki=ji({EphemeralObject:["content"],EphemeralArray:["content"]},pi),Ai=(e,t,n={})=>{let{keyMap:r=ki}=n,o=Zs(n,xi);return fi(e,t,ji({keyMap:r,nodeTypeGetter:Oi,nodePredicate:Dt,detectCycles:!1,deleteNodeSymbol:Symbol.for("delete-node"),skipVisitingNodeSymbol:Symbol.for("skip-visiting-node")},o))};Ai[Symbol.for("nodejs.util.promisify.custom")]=async(e,t={})=>{let{keyMap:n=ki}=t,r=Zs(t,Si);return fi[Symbol.for("nodejs.util.promisify.custom")](e,visitor,ji({keyMap:n,nodeTypeGetter:Oi,nodePredicate:Dt,detectCycles:!1,deleteNodeSymbol:Symbol.for("delete-node"),skipVisitingNodeSymbol:Symbol.for("skip-visiting-node")},r))};const Ci=class{constructor(e){Xo(this,"type","EphemeralArray"),Xo(this,"content",[]),Xo(this,"reference",void 0),this.content=e,this.reference=[]}toReference(){return this.reference}toArray(){return this.reference.push(...this.content),this.reference}};const Pi=class{constructor(e){Xo(this,"type","EphemeralObject"),Xo(this,"content",[]),Xo(this,"reference",void 0),this.content=e,this.reference={}}toReference(){return this.reference}toObject(){return Object.assign(this.reference,Object.fromEntries(this.content))}},Ni=Ys.init((function(){const e=new WeakMap;this.BooleanElement=function(e){return e.toValue()},this.NumberElement=function(e){return e.toValue()},this.StringElement=function(e){return e.toValue()},this.NullElement=function(){return null},this.ObjectElement={enter(t){if(e.has(t))return e.get(t).toReference();const n=new Pi(t.content);return e.set(t,n),n}},this.EphemeralObject={leave:e=>e.toObject()},this.MemberElement={enter:e=>[e.key,e.value]},this.ArrayElement={enter(t){if(e.has(t))return e.get(t).toReference();const n=new Ci(t.content);return e.set(t,n),n}},this.EphemeralArray={leave:e=>e.toArray()}})),Ii=(e,t=Vs)=>{if(Xs(e))try{return t.fromRefract(JSON.parse(e))}catch{}return $s(e)&&Hr("element",e)?t.fromRefract(e):t.toElement(e)},Ti=e=>Ai(e,Ni());const Ri=hn("");var Mi=cr(Zt(1,gr(cn,Xr("Number"))),isFinite);var Di=Zt(1,Mi);var Fi=cr(ts(Number.isFinite)?Zt(1,$n(Number.isFinite,Number)):Di,vr(hn,[Math.floor,eo]));var Li=Zt(1,Fi);const Bi=ts(Number.isInteger)?Zt(1,$n(Number.isInteger,Number)):Li;var $i=Or((function(e,t){return gr(Io(""),$r(as(e)),io(""))(t)}));const qi=$i;class Ui extends Error{constructor(e){super(`Invalid $ref pointer "${e}". Pointers must begin with "/"`),this.name=this.constructor.name,this.message=`Invalid $ref pointer "${e}". Pointers must begin with "/"`,"function"==typeof Error.captureStackTrace?Error.captureStackTrace(this,this.constructor):this.stack=new Error(`Invalid $ref pointer "${e}". Pointers must begin with "/"`).stack}}class zi extends Error{constructor(e){super(e),this.name=this.constructor.name,this.message=e,"function"==typeof Error.captureStackTrace?Error.captureStackTrace(this,this.constructor):this.stack=new Error(e).stack}}const Vi=gr(Co(/~/g,"~0"),Co(/\//g,"~1"),encodeURIComponent),Wi=gr(Co(/~1/g,"/"),Co(/~0/g,"~"),(e=>{try{return decodeURIComponent(e)}catch{return e}})),Ji=(e,t)=>{const n=(e=>{if(Ri(e))return[];if(!To("/",e))throw new Ui(e);const t=gr(Io("/"),Cn(Wi))(e);return mr(t)})(e);return n.reduce(((e,t)=>{if(bs(e)){if(!e.hasKey(t))throw new zi(`Evaluation failed on token: "${t}"`);return e.get(t)}if(ws(e)){if(!(t in e.content)||!Bi(Number(t)))throw new zi(`Evaluation failed on token: "${t}"`);return e.get(Number(t))}throw new zi(`Evaluation failed on token: "${t}"`)}),t)},Ki=e=>{const t=(e=>{const t=e.indexOf("#");return-1!==t?e.substring(t):"#"})(e);return qi("#",t)};class Hi extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.element="callback"}}const Gi=Hi;class Zi extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.element="components"}get schemas(){return this.get("schemas")}set schemas(e){this.set("schemas",e)}get responses(){return this.get("responses")}set responses(e){this.set("responses",e)}get parameters(){return this.get("parameters")}set parameters(e){this.set("parameters",e)}get examples(){return this.get("examples")}set examples(e){this.set("examples",e)}get requestBodies(){return this.get("requestBodies")}set requestBodies(e){this.set("requestBodies",e)}get headers(){return this.get("headers")}set headers(e){this.set("headers",e)}get securitySchemes(){return this.get("securitySchemes")}set securitySchemes(e){this.set("securitySchemes",e)}get links(){return this.get("links")}set links(e){this.set("links",e)}get callbacks(){return this.get("callbacks")}set callbacks(e){this.set("callbacks",e)}}const Yi=Zi;class Xi extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.element="contact"}get name(){return this.get("name")}set name(e){this.set("name",e)}get url(){return this.get("url")}set url(e){this.set("url",e)}get email(){return this.get("email")}set email(e){this.set("email",e)}}const Qi=Xi;class ea extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.element="discriminator"}get propertyName(){return this.get("propertyName")}set propertyName(e){this.set("propertyName",e)}get mapping(){return this.get("mapping")}set mapping(e){this.set("mapping",e)}}const ta=ea;class na extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.element="encoding"}get contentType(){return this.get("contentType")}set contentType(e){this.set("contentType",e)}get headers(){return this.get("headers")}set headers(e){this.set("headers",e)}get style(){return this.get("style")}set style(e){this.set("style",e)}get explode(){return this.get("explode")}set explode(e){this.set("explode",e)}get allowedReserved(){return this.get("allowedReserved")}set allowedReserved(e){this.set("allowedReserved",e)}}const ra=na;class oa extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.element="example"}get summary(){return this.get("summary")}set summary(e){this.set("summary",e)}get description(){return this.get("description")}set description(e){this.set("description",e)}get value(){return this.get("value")}set value(e){this.set("value",e)}get externalValue(){return this.get("externalValue")}set externalValue(e){this.set("externalValue",e)}}const sa=oa;class ia extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.element="externalDocumentation"}get description(){return this.get("description")}set description(e){this.set("description",e)}get url(){return this.get("url")}set url(e){this.set("url",e)}}const aa=ia;class la extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.element="header"}get required(){return this.hasKey("required")?this.get("required"):new Pt.hh(!1)}set required(e){this.set("required",e)}get deprecated(){return this.hasKey("deprecated")?this.get("deprecated"):new Pt.hh(!1)}set deprecated(e){this.set("deprecated",e)}get allowEmptyValue(){return this.get("allowEmptyValue")}set allowEmptyValue(e){this.set("allowEmptyValue",e)}get style(){return this.get("style")}set style(e){this.set("style",e)}get explode(){return this.get("explode")}set explode(e){this.set("explode",e)}get allowReserved(){return this.get("allowReserved")}set allowReserved(e){this.set("allowReserved",e)}get schema(){return this.get("schema")}set schema(e){this.set("schema",e)}get example(){return this.get("example")}set example(e){this.set("example",e)}get examples(){return this.get("examples")}set examples(e){this.set("examples",e)}get contentProp(){return this.get("content")}set contentProp(e){this.set("content",e)}}Object.defineProperty(la.prototype,"description",{get(){return this.get("description")},set(e){this.set("description",e)},enumerable:!0});const ca=la;class ua extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.element="info",this.classes.push("info")}get title(){return this.get("title")}set title(e){this.set("title",e)}get description(){return this.get("description")}set description(e){this.set("description",e)}get termsOfService(){return this.get("termsOfService")}set termsOfService(e){this.set("termsOfService",e)}get contact(){return this.get("contact")}set contact(e){this.set("contact",e)}get license(){return this.get("license")}set license(e){this.set("license",e)}get version(){return this.get("version")}set version(e){this.set("version",e)}}const pa=ua;class ha extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.element="license"}get name(){return this.get("name")}set name(e){this.set("name",e)}get url(){return this.get("url")}set url(e){this.set("url",e)}}const fa=ha;class da extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.element="link"}get operationRef(){return this.get("operationRef")}set operationRef(e){this.set("operationRef",e)}get operationId(){return this.get("operationId")}set operationId(e){this.set("operationId",e)}get operation(){var e,t;return ms(this.operationRef)?null===(e=this.operationRef)||void 0===e?void 0:e.meta.get("operation"):ms(this.operationId)?null===(t=this.operationId)||void 0===t?void 0:t.meta.get("operation"):void 0}set operation(e){this.set("operation",e)}get parameters(){return this.get("parameters")}set parameters(e){this.set("parameters",e)}get requestBody(){return this.get("requestBody")}set requestBody(e){this.set("requestBody",e)}get description(){return this.get("description")}set description(e){this.set("description",e)}get server(){return this.get("server")}set server(e){this.set("server",e)}}const ma=da;class ga extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.element="mediaType"}get schema(){return this.get("schema")}set schema(e){this.set("schema",e)}get example(){return this.get("example")}set example(e){this.set("example",e)}get examples(){return this.get("examples")}set examples(e){this.set("examples",e)}get encoding(){return this.get("encoding")}set encoding(e){this.set("encoding",e)}}const ya=ga;class va extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.element="oAuthFlow"}get authorizationUrl(){return this.get("authorizationUrl")}set authorizationUrl(e){this.set("authorizationUrl",e)}get tokenUrl(){return this.get("tokenUrl")}set tokenUrl(e){this.set("tokenUrl",e)}get refreshUrl(){return this.get("refreshUrl")}set refreshUrl(e){this.set("refreshUrl",e)}get scopes(){return this.get("scopes")}set scopes(e){this.set("scopes",e)}}const ba=va;class wa extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.element="oAuthFlows"}get implicit(){return this.get("implicit")}set implicit(e){this.set("implicit",e)}get password(){return this.get("password")}set password(e){this.set("password",e)}get clientCredentials(){return this.get("clientCredentials")}set clientCredentials(e){this.set("clientCredentials",e)}get authorizationCode(){return this.get("authorizationCode")}set authorizationCode(e){this.set("authorizationCode",e)}}const Ea=wa;class xa extends Pt.RP{constructor(e,t,n){super(e,t,n),this.element="openapi",this.classes.push("spec-version"),this.classes.push("version")}}const Sa=xa;class _a extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.element="openApi3_0",this.classes.push("api")}get openapi(){return this.get("openapi")}set openapi(e){this.set("openapi",e)}get info(){return this.get("info")}set info(e){this.set("info",e)}get servers(){return this.get("servers")}set servers(e){this.set("servers",e)}get paths(){return this.get("paths")}set paths(e){this.set("paths",e)}get components(){return this.get("components")}set components(e){this.set("components",e)}get security(){return this.get("security")}set security(e){this.set("security",e)}get tags(){return this.get("tags")}set tags(e){this.set("tags",e)}get externalDocs(){return this.get("externalDocs")}set externalDocs(e){this.set("externalDocs",e)}}const ja=_a;class Oa extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.element="operation"}get tags(){return this.get("tags")}set tags(e){this.set("tags",e)}get summary(){return this.get("summary")}set summary(e){this.set("summary",e)}get description(){return this.get("description")}set description(e){this.set("description",e)}set externalDocs(e){this.set("externalDocs",e)}get externalDocs(){return this.get("externalDocs")}get operationId(){return this.get("operationId")}set operationId(e){this.set("operationId",e)}get parameters(){return this.get("parameters")}set parameters(e){this.set("parameters",e)}get requestBody(){return this.get("requestBody")}set requestBody(e){this.set("requestBody",e)}get responses(){return this.get("responses")}set responses(e){this.set("responses",e)}get callbacks(){return this.get("callbacks")}set callbacks(e){this.set("callbacks",e)}get deprecated(){return this.hasKey("deprecated")?this.get("deprecated"):new Pt.hh(!1)}set deprecated(e){this.set("deprecated",e)}get security(){return this.get("security")}set security(e){this.set("security",e)}get servers(){return this.get("severs")}set servers(e){this.set("servers",e)}}const ka=Oa;class Aa extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.element="parameter"}get name(){return this.get("name")}set name(e){this.set("name",e)}get in(){return this.get("in")}set in(e){this.set("in",e)}get required(){return this.hasKey("required")?this.get("required"):new Pt.hh(!1)}set required(e){this.set("required",e)}get deprecated(){return this.hasKey("deprecated")?this.get("deprecated"):new Pt.hh(!1)}set deprecated(e){this.set("deprecated",e)}get allowEmptyValue(){return this.get("allowEmptyValue")}set allowEmptyValue(e){this.set("allowEmptyValue",e)}get style(){return this.get("style")}set style(e){this.set("style",e)}get explode(){return this.get("explode")}set explode(e){this.set("explode",e)}get allowReserved(){return this.get("allowReserved")}set allowReserved(e){this.set("allowReserved",e)}get schema(){return this.get("schema")}set schema(e){this.set("schema",e)}get example(){return this.get("example")}set example(e){this.set("example",e)}get examples(){return this.get("examples")}set examples(e){this.set("examples",e)}get contentProp(){return this.get("content")}set contentProp(e){this.set("content",e)}}Object.defineProperty(Aa.prototype,"description",{get(){return this.get("description")},set(e){this.set("description",e)},enumerable:!0});const Ca=Aa;class Pa extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.element="pathItem"}get $ref(){return this.get("$ref")}set $ref(e){this.set("$ref",e)}get summary(){return this.get("summary")}set summary(e){this.set("summary",e)}get description(){return this.get("description")}set description(e){this.set("description",e)}get GET(){return this.get("get")}set GET(e){this.set("GET",e)}get PUT(){return this.get("put")}set PUT(e){this.set("PUT",e)}get POST(){return this.get("post")}set POST(e){this.set("POST",e)}get DELETE(){return this.get("delete")}set DELETE(e){this.set("DELETE",e)}get OPTIONS(){return this.get("options")}set OPTIONS(e){this.set("OPTIONS",e)}get HEAD(){return this.get("head")}set HEAD(e){this.set("HEAD",e)}get PATCH(){return this.get("patch")}set PATCH(e){this.set("PATCH",e)}get TRACE(){return this.get("trace")}set TRACE(e){this.set("TRACE",e)}get servers(){return this.get("servers")}set servers(e){this.set("servers",e)}get parameters(){return this.get("parameters")}set parameters(e){this.set("parameters",e)}}const Na=Pa;class Ia extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.element="paths"}}const Ta=Ia;class Ra extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.element="reference",this.classes.push("openapi-reference")}get $ref(){return this.get("$ref")}set $ref(e){this.set("$ref",e)}}const Ma=Ra;class Da extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.element="requestBody"}get description(){return this.get("description")}set description(e){this.set("description",e)}get contentProp(){return this.get("content")}set contentProp(e){this.set("content",e)}get required(){return this.hasKey("required")?this.get("required"):new Pt.hh(!1)}set required(e){this.set("required",e)}}const Fa=Da;class La extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.element="response"}get description(){return this.get("description")}set description(e){this.set("description",e)}get headers(){return this.get("headers")}set headers(e){this.set("headers",e)}get contentProp(){return this.get("content")}set contentProp(e){this.set("content",e)}get links(){return this.get("links")}set links(e){this.set("links",e)}}const Ba=La;class $a extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.element="responses"}get default(){return this.get("default")}set default(e){this.set("default",e)}}const qa=$a;class Ua extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.element="JSONSchemaDraft4"}get idProp(){return this.get("id")}set idProp(e){this.set("id",e)}get $schema(){return this.get("$schema")}set $schema(e){this.set("idProp",e)}get multipleOf(){return this.get("multipleOf")}set multipleOf(e){this.set("multipleOf",e)}get maximum(){return this.get("maximum")}set maximum(e){this.set("maximum",e)}get exclusiveMaximum(){return this.get("exclusiveMaximum")}set exclusiveMaximum(e){this.set("exclusiveMaximum",e)}get minimum(){return this.get("minimum")}set minimum(e){this.set("minimum",e)}get exclusiveMinimum(){return this.get("exclusiveMinimum")}set exclusiveMinimum(e){this.set("exclusiveMinimum",e)}get maxLength(){return this.get("maxLength")}set maxLength(e){this.set("maxLength",e)}get minLength(){return this.get("minLength")}set minLength(e){this.set("minLength",e)}get pattern(){return this.get("pattern")}set pattern(e){this.set("pattern",e)}get additionalItems(){return this.get("additionalItems")}set additionalItems(e){this.set("additionalItems",e)}get items(){return this.get("items")}set items(e){this.set("items",e)}get maxItems(){return this.get("maxItems")}set maxItems(e){this.set("maxItems",e)}get minItems(){return this.get("minItems")}set minItems(e){this.set("minItems",e)}get uniqueItems(){return this.get("uniqueItems")}set uniqueItems(e){this.set("uniqueItems",e)}get maxProperties(){return this.get("maxProperties")}set maxProperties(e){this.set("maxProperties",e)}get minProperties(){return this.get("minProperties")}set minProperties(e){this.set("minProperties",e)}get required(){return this.get("required")}set required(e){this.set("required",e)}get properties(){return this.get("properties")}set properties(e){this.set("properties",e)}get additionalProperties(){return this.get("additionalProperties")}set additionalProperties(e){this.set("additionalProperties",e)}get patternProperties(){return this.get("patternProperties")}set patternProperties(e){this.set("patternProperties",e)}get dependencies(){return this.get("dependencies")}set dependencies(e){this.set("dependencies",e)}get enum(){return this.get("enum")}set enum(e){this.set("enum",e)}get type(){return this.get("type")}set type(e){this.set("type",e)}get allOf(){return this.get("allOf")}set allOf(e){this.set("allOf",e)}get anyOf(){return this.get("anyOf")}set anyOf(e){this.set("anyOf",e)}get oneOf(){return this.get("oneOf")}set oneOf(e){this.set("oneOf",e)}get not(){return this.get("not")}set not(e){this.set("not",e)}get definitions(){return this.get("definitions")}set definitions(e){this.set("definitions",e)}get title(){return this.get("title")}set title(e){this.set("title",e)}get description(){return this.get("description")}set description(e){this.set("description",e)}get default(){return this.get("default")}set default(e){this.set("default",e)}get format(){return this.get("format")}set format(e){this.set("format",e)}get base(){return this.get("base")}set base(e){this.set("base",e)}get links(){return this.get("links")}set links(e){this.set("links",e)}get media(){return this.get("media")}set media(e){this.set("media",e)}get readOnly(){return this.get("readOnly")}set readOnly(e){this.set("readOnly",e)}}const za=Ua;class Va extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.element="JSONReference",this.classes.push("json-reference")}get $ref(){return this.get("$ref")}set $ref(e){this.set("$ref",e)}}const Wa=Va;class Ja extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.element="media"}get binaryEncoding(){return this.get("binaryEncoding")}set binaryEncoding(e){this.set("binaryEncoding",e)}get type(){return this.get("type")}set type(e){this.set("type",e)}}const Ka=Ja;class Ha extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.element="linkDescription"}get href(){return this.get("href")}set href(e){this.set("href",e)}get rel(){return this.get("rel")}set rel(e){this.set("rel",e)}get title(){return this.get("title")}set title(e){this.set("title",e)}get targetSchema(){return this.get("targetSchema")}set targetSchema(e){this.set("targetSchema",e)}get mediaType(){return this.get("mediaType")}set mediaType(e){this.set("mediaType",e)}get method(){return this.get("method")}set method(e){this.set("method",e)}get encType(){return this.get("encType")}set encType(e){this.set("encType",e)}get schema(){return this.get("schema")}set schema(e){this.set("schema",e)}}const Ga=Ha,Za=(e,t)=>{const n=kr(e,t);return po((e=>{if($s(e)&&Hr("$ref",e)&&_o(Xs,"$ref",e)){const t=uo(["$ref"],e),r=qi("#/",t);return uo(r.split("/"),n)}return $s(e)?Za(e,n):e}),e)},Ya=Ys({props:{element:null},methods:{copyMetaAndAttributes(e,t){Cs(e)&&t.meta.set("sourceMap",e.meta.get("sourceMap"))}}}),Xa=Ya,Qa=Ys(Xa,{methods:{enter(e){return this.element=e.clone(),ei}}});const el=Hn($o());function tl(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}const nl=e=>{if(ds(e))return`${e.element.charAt(0).toUpperCase()+e.element.slice(1)}Element`},rl=function(e){for(var t=1;t{if(ms(r)&&n.includes(r.toValue())&&!this.ignoredFields.includes(r.toValue())){const n=this.toRefractedElement([...t,"fixedFields",r.toValue()],e),s=new Pt.c6(r.clone(),n);this.copyMetaAndAttributes(o,s),s.classes.push("fixed-field"),this.element.content.push(s)}else this.ignoredFields.includes(r.toValue())||this.element.content.push(o.clone())})),this.copyMetaAndAttributes(e,this.element),ei}}}),ll=al,cl=Ys(ll,Qa,{props:{specPath:Hn(["document","objects","JSONSchema"])},init(){this.element=new za}}),ul=Qa,pl=Qa,hl=Qa,fl=Qa,dl=Qa,ml=Qa,gl=Qa,yl=Qa,vl=Qa,bl=Qa,wl=Ys({props:{parent:null},init({parent:e=this.parent}){this.parent=e,this.passingOptionsNames=[...this.passingOptionsNames,"parent"]}}),El=e=>bs(e)&&e.hasKey("$ref"),xl=Ys(il,wl,Qa,{methods:{ObjectElement(e){const t=El(e)?["document","objects","JSONReference"]:["document","objects","JSONSchema"];return this.element=this.toRefractedElement(t,e),ei},ArrayElement(e){return this.element=new Pt.ON,this.element.classes.push("json-schema-items"),e.forEach((e=>{const t=El(e)?["document","objects","JSONReference"]:["document","objects","JSONSchema"],n=this.toRefractedElement(t,e);this.element.push(n)})),this.copyMetaAndAttributes(e,this.element),ei}}}),Sl=Qa,_l=Qa,jl=Qa,Ol=Qa,kl=Qa,Al=Ys(Qa,{methods:{ArrayElement(e){return this.element=e.clone(),this.element.classes.push("json-schema-required"),ei}}});const Cl=pr(Zt(1,cr(Ts,Ur(Ms,ts))));const Pl=pr(so);const Nl=Kn([Xs,Cl,Pl]),Il=Ys(il,{props:{fieldPatternPredicate:Mt,specPath:el,ignoredFields:[]},init({specPath:e=this.specPath,ignoredFields:t=this.ignoredFields}={}){this.specPath=e,this.ignoredFields=t},methods:{ObjectElement(e){return e.forEach(((e,t,n)=>{if(!this.ignoredFields.includes(t.toValue())&&this.fieldPatternPredicate(t.toValue())){const r=this.specPath(e),o=this.toRefractedElement(r,e),s=new Pt.c6(t.clone(),o);this.copyMetaAndAttributes(n,s),s.classes.push("patterned-field"),this.element.content.push(s)}else this.ignoredFields.includes(t.toValue())||this.element.content.push(n.clone())})),this.copyMetaAndAttributes(e,this.element),ei}}}),Tl=Ys(Il,{props:{fieldPatternPredicate:Nl}}),Rl=Ys(Tl,wl,Qa,{props:{specPath:e=>El(e)?["document","objects","JSONReference"]:["document","objects","JSONSchema"]},init(){this.element=new Pt.Sb,this.element.classes.push("json-schema-properties")}}),Ml=Ys(Tl,wl,Qa,{props:{specPath:e=>El(e)?["document","objects","JSONReference"]:["document","objects","JSONSchema"]},init(){this.element=new Pt.Sb,this.element.classes.push("json-schema-patternProperties")}}),Dl=Ys(Tl,wl,Qa,{props:{specPath:e=>El(e)?["document","objects","JSONReference"]:["document","objects","JSONSchema"]},init(){this.element=new Pt.Sb,this.element.classes.push("json-schema-dependencies")}}),Fl=Ys(Qa,{methods:{ArrayElement(e){return this.element=e.clone(),this.element.classes.push("json-schema-enum"),ei}}}),Ll=Ys(Qa,{methods:{StringElement(e){return this.element=e.clone(),this.element.classes.push("json-schema-type"),ei},ArrayElement(e){return this.element=e.clone(),this.element.classes.push("json-schema-type"),ei}}}),Bl=Ys(il,wl,Qa,{init(){this.element=new Pt.ON,this.element.classes.push("json-schema-allOf")},methods:{ArrayElement(e){return e.forEach((e=>{const t=El(e)?["document","objects","JSONReference"]:["document","objects","JSONSchema"],n=this.toRefractedElement(t,e);this.element.push(n)})),this.copyMetaAndAttributes(e,this.element),ei}}}),$l=Ys(il,wl,Qa,{init(){this.element=new Pt.ON,this.element.classes.push("json-schema-anyOf")},methods:{ArrayElement(e){return e.forEach((e=>{const t=El(e)?["document","objects","JSONReference"]:["document","objects","JSONSchema"],n=this.toRefractedElement(t,e);this.element.push(n)})),this.copyMetaAndAttributes(e,this.element),ei}}}),ql=Ys(il,wl,Qa,{init(){this.element=new Pt.ON,this.element.classes.push("json-schema-oneOf")},methods:{ArrayElement(e){return e.forEach((e=>{const t=El(e)?["document","objects","JSONReference"]:["document","objects","JSONSchema"],n=this.toRefractedElement(t,e);this.element.push(n)})),this.copyMetaAndAttributes(e,this.element),ei}}}),Ul=Ys(Tl,wl,Qa,{props:{specPath:e=>El(e)?["document","objects","JSONReference"]:["document","objects","JSONSchema"]},init(){this.element=new Pt.Sb,this.element.classes.push("json-schema-definitions")}}),zl=Qa,Vl=Qa,Wl=Qa,Jl=Qa,Kl=Qa,Hl=Ys(il,wl,Qa,{init(){this.element=new Pt.ON,this.element.classes.push("json-schema-links")},methods:{ArrayElement(e){return e.forEach((e=>{const t=this.toRefractedElement(["document","objects","LinkDescription"],e);this.element.push(t)})),this.copyMetaAndAttributes(e,this.element),ei}}}),Gl=Qa,Zl=Ys(ll,Qa,{props:{specPath:Hn(["document","objects","JSONReference"])},init(){this.element=new Wa},methods:{ObjectElement(e){const t=ll.compose.methods.ObjectElement.call(this,e);return ms(this.element.$ref)&&this.element.classes.push("reference-element"),t}}}),Yl=Ys(Qa,{methods:{StringElement(e){return this.element=e.clone(),this.element.classes.push("reference-value"),ei}}});const Xl=pr(rr);const Ql=cr(rs,Pl);function ec(e){return function(e){if(Array.isArray(e))return tc(e)}(e)||function(e){if("undefined"!=typeof Symbol&&null!=e[Symbol.iterator]||null!=e["@@iterator"])return Array.from(e)}(e)||function(e,t){if(!e)return;if("string"==typeof e)return tc(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return tc(e,t)}(e)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function tc(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);nt.length}))),Zr,Tn("length")),rc=Or((function(e,t,n){var r=n.apply(void 0,ec(e));return Xl(r)?Ao(r):t}));const oc=to(Ql,(function(e){var t=nc(e);return Zt(t,(function(){for(var t=arguments.length,n=new Array(t),r=0;rto(e,Hn(t),$o))),n=oc(t)(e);return this.element=this.toRefractedElement(n,e),ei}}}),ic=Ys(sc,{props:{alternator:[{predicate:El,specPath:["document","objects","JSONReference"]},{predicate:Dt,specPath:["document","objects","JSONSchema"]}]}}),ac={visitors:{value:Qa,JSONSchemaOrJSONReferenceVisitor:ic,document:{objects:{JSONSchema:{$visitor:cl,fixedFields:{id:ul,$schema:pl,multipleOf:hl,maximum:fl,exclusiveMaximum:dl,minimum:ml,exclusiveMinimum:gl,maxLength:yl,minLength:vl,pattern:bl,additionalItems:ic,items:xl,maxItems:Sl,minItems:_l,uniqueItems:jl,maxProperties:Ol,minProperties:kl,required:Al,properties:Rl,additionalProperties:ic,patternProperties:Ml,dependencies:Dl,enum:Fl,type:Ll,allOf:Bl,anyOf:$l,oneOf:ql,not:ic,definitions:Ul,title:zl,description:Vl,default:Wl,format:Jl,base:Kl,links:Hl,media:{$ref:"#/visitors/document/objects/Media"},readOnly:Gl}},JSONReference:{$visitor:Zl,fixedFields:{$ref:Yl}},Media:{$visitor:Ys(ll,Qa,{props:{specPath:Hn(["document","objects","Media"])},init(){this.element=new Ka}}),fixedFields:{binaryEncoding:Qa,type:Qa}},LinkDescription:{$visitor:Ys(ll,Qa,{props:{specPath:Hn(["document","objects","LinkDescription"])},init(){this.element=new Ga}}),fixedFields:{href:Qa,rel:Qa,title:Qa,targetSchema:ic,mediaType:Qa,method:Qa,encType:Qa,schema:ic}}}}}},lc=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof za||e(r)&&t("JSONSchemaDraft4",r)&&n("object",r))),cc=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof Wa||e(r)&&t("JSONReference",r)&&n("object",r))),uc=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof Ka||e(r)&&t("media",r)&&n("object",r))),pc=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof Ga||e(r)&&t("linkDescription",r)&&n("object",r))),hc={namespace:e=>{const{base:t}=e;return t.register("jSONSchemaDraft4",za),t.register("jSONReference",Wa),t.register("media",Ka),t.register("linkDescription",Ga),t}};function fc(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function dc(e){for(var t=1;t{const e=zs(hc);return{predicates:dc(dc({},i),{},{isStringElement:ms}),namespace:e}};function gc(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}const yc=(e,{specPath:t=["visitors","document","objects","JSONSchema","$visitor"],plugins:n=[],specificationObj:r=ac}={})=>{const o=(0,Pt.Qc)(e),s=Za(r),i=is(t,[],s);return fi(o,i,{state:{specObj:s}}),di(i.element,n,{toolboxCreator:mc,visitorOptions:{keyMap:rl,nodeTypeGetter:nl}})},vc=e=>(t,n={})=>yc(t,function(e){for(var t=1;t{if(ds(e))return`${e.element.charAt(0).toUpperCase()+e.element.slice(1)}Element`},Dc=function(e){for(var t=1;tbs(e)&&e.hasKey("openapi")&&e.hasKey("info"),qc=e=>bs(e)&&e.hasKey("name")&&e.hasKey("in"),Uc=e=>bs(e)&&e.hasKey("$ref"),zc=e=>bs(e)&&e.hasKey("content"),Vc=e=>bs(e)&&e.hasKey("description"),Wc=bs,Jc=bs,Kc=e=>ms(e.key)&&To("x-",e.key.toValue()),Hc=Ys(Bc,{props:{specPath:el,ignoredFields:[],canSupportSpecificationExtensions:!0,specificationExtensionPredicate:Kc},init({specPath:e=this.specPath,ignoredFields:t=this.ignoredFields,canSupportSpecificationExtensions:n=this.canSupportSpecificationExtensions,specificationExtensionPredicate:r=this.specificationExtensionPredicate}={}){this.specPath=e,this.ignoredFields=t,this.canSupportSpecificationExtensions=n,this.specificationExtensionPredicate=r},methods:{ObjectElement(e){const t=this.specPath(e),n=this.retrieveFixedFields(t);return e.forEach(((e,r,o)=>{if(ms(r)&&n.includes(r.toValue())&&!this.ignoredFields.includes(r.toValue())){const n=this.toRefractedElement([...t,"fixedFields",r.toValue()],e),s=new Pt.c6(r.clone(),n);this.copyMetaAndAttributes(o,s),s.classes.push("fixed-field"),this.element.content.push(s)}else if(this.canSupportSpecificationExtensions&&this.specificationExtensionPredicate(o)){const e=this.toRefractedElement(["document","extension"],o);this.element.content.push(e)}else this.ignoredFields.includes(r.toValue())||this.element.content.push(o.clone())})),this.copyMetaAndAttributes(e,this.element),ei}}}),Gc=Hc,Zc=Ys(Tc,{methods:{enter(e){return this.element=e.clone(),ei}}}),Yc=Ys(Gc,Zc,{props:{specPath:Hn(["document","objects","OpenApi"]),canSupportSpecificationExtensions:!0},init(){this.element=new ja},methods:{ObjectElement(e){return this.unrefractedElement=e,Gc.compose.methods.ObjectElement.call(this,e)}}}),Xc=Ys(Bc,Zc,{methods:{StringElement(e){const t=new Sa(e.toValue());return this.copyMetaAndAttributes(e,t),this.element=t,ei}}}),Qc=Ys(Bc,{methods:{MemberElement(e){return this.element=e.clone(),this.element.classes.push("specification-extension"),ei}}}),eu=Ys(Gc,Zc,{props:{specPath:Hn(["document","objects","Info"]),canSupportSpecificationExtensions:!0},init(){this.element=new pa}}),tu=Zc,nu=Zc,ru=Zc,ou=Ys(Zc,{methods:{StringElement(e){return this.element=e.clone(),this.element.classes.push("api-version"),this.element.classes.push("version"),ei}}}),su=Ys(Gc,Zc,{props:{specPath:Hn(["document","objects","Contact"]),canSupportSpecificationExtensions:!0},init(){this.element=new Qi}}),iu=Zc,au=Zc,lu=Zc,cu=Ys(Gc,Zc,{props:{specPath:Hn(["document","objects","License"]),canSupportSpecificationExtensions:!0},init(){this.element=new fa}}),uu=Zc,pu=Zc,hu=Ys(Gc,Zc,{props:{specPath:Hn(["document","objects","Link"]),canSupportSpecificationExtensions:!0},init(){this.element=new ma},methods:{ObjectElement(e){const t=Gc.compose.methods.ObjectElement.call(this,e);return(ms(this.element.operationId)||ms(this.element.operationRef))&&this.element.classes.push("reference-element"),t}}}),fu=Ys(Zc,{methods:{StringElement(e){return this.element=e.clone(),this.element.classes.push("reference-value"),ei}}}),du=Ys(Zc,{methods:{StringElement(e){return this.element=e.clone(),this.element.classes.push("reference-value"),ei}}}),mu=Ys(Bc,{props:{fieldPatternPredicate:Mt,specPath:el,ignoredFields:[],canSupportSpecificationExtensions:!1,specificationExtensionPredicate:Kc},init({specPath:e=this.specPath,ignoredFields:t=this.ignoredFields,canSupportSpecificationExtensions:n=this.canSupportSpecificationExtensions,specificationExtensionPredicate:r=this.specificationExtensionPredicate}={}){this.specPath=e,this.ignoredFields=t,this.canSupportSpecificationExtensions=n,this.specificationExtensionPredicate=r},methods:{ObjectElement(e){return e.forEach(((e,t,n)=>{if(this.canSupportSpecificationExtensions&&this.specificationExtensionPredicate(n)){const e=this.toRefractedElement(["document","extension"],n);this.element.content.push(e)}else if(!this.ignoredFields.includes(t.toValue())&&this.fieldPatternPredicate(t.toValue())){const r=this.specPath(e),o=this.toRefractedElement(r,e),s=new Pt.c6(t.clone(),o);this.copyMetaAndAttributes(n,s),s.classes.push("patterned-field"),this.element.content.push(s)}else this.ignoredFields.includes(t.toValue())||this.element.content.push(n.clone())})),this.copyMetaAndAttributes(e,this.element),ei}}}),gu=mu,yu=Ys(gu,{props:{fieldPatternPredicate:Nl}});class vu extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.classes.push(vu.primaryClass)}}Xo(vu,"primaryClass","link-parameters");const bu=vu,wu=Ys(yu,Zc,{props:{specPath:Hn(["value"])},init(){this.element=new bu}}),Eu=Zc,xu=Zc,Su=Ys(Gc,Zc,{props:{specPath:Hn(["document","objects","Server"]),canSupportSpecificationExtensions:!0},init(){this.element=new jc}}),_u=Ys(Zc,{methods:{StringElement(e){return this.element=e.clone(),this.element.classes.push("server-url"),ei}}}),ju=Zc;class Ou extends Pt.ON{constructor(e,t,n){super(e,t,n),this.classes.push(Ou.primaryClass)}}Xo(Ou,"primaryClass","servers");const ku=Ou,Au=Ys(Bc,Zc,{init(){this.element=new ku},methods:{ArrayElement(e){return e.forEach((e=>{const t=Wc(e)?["document","objects","Server"]:["value"],n=this.toRefractedElement(t,e);this.element.push(n)})),this.copyMetaAndAttributes(e,this.element),ei}}}),Cu=Ys(Gc,Zc,{props:{specPath:Hn(["document","objects","ServerVariable"]),canSupportSpecificationExtensions:!0},init(){this.element=new kc}}),Pu=Zc,Nu=Zc,Iu=Zc;class Tu extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.classes.push(Tu.primaryClass)}}Xo(Tu,"primaryClass","server-variables");const Ru=Tu,Mu=Ys(yu,Zc,{props:{specPath:Hn(["document","objects","ServerVariable"])},init(){this.element=new Ru}}),Du=Ys(Gc,Zc,{props:{specPath:Hn(["document","objects","MediaType"]),canSupportSpecificationExtensions:!0},init(){this.element=new ya}}),Fu=Ys(Bc,{props:{alternator:[]},methods:{enter(e){const t=this.alternator.map((({predicate:e,specPath:t})=>to(e,Hn(t),$o))),n=oc(t)(e);return this.element=this.toRefractedElement(n,e),ei}}}),Lu=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof Gi||e(r)&&t("callback",r)&&n("object",r))),Bu=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof Yi||e(r)&&t("components",r)&&n("object",r))),$u=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof Qi||e(r)&&t("contact",r)&&n("object",r))),qu=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof sa||e(r)&&t("example",r)&&n("object",r))),Uu=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof aa||e(r)&&t("externalDocumentation",r)&&n("object",r))),zu=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof ca||e(r)&&t("header",r)&&n("object",r))),Vu=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof pa||e(r)&&t("info",r)&&n("object",r))),Wu=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof fa||e(r)&&t("license",r)&&n("object",r))),Ju=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof ma||e(r)&&t("link",r)&&n("object",r))),Ku=e=>{if(!Ju(e))return!1;if(!ms(e.operationRef))return!1;const t=e.operationRef.toValue();return"string"==typeof t&&t.length>0&&!t.startsWith("#")},Hu=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof Sa||e(r)&&t("openapi",r)&&n("string",r))),Gu=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n,hasClass:r})=>o=>o instanceof ja||e(o)&&t("openApi3_0",o)&&n("object",o)&&r("api",o))),Zu=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof ka||e(r)&&t("operation",r)&&n("object",r))),Yu=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof Ca||e(r)&&t("parameter",r)&&n("object",r))),Xu=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof Na||e(r)&&t("pathItem",r)&&n("object",r))),Qu=e=>{if(!Xu(e))return!1;if(!ms(e.$ref))return!1;const t=e.$ref.toValue();return"string"==typeof t&&t.length>0&&!t.startsWith("#")},ep=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof Ta||e(r)&&t("paths",r)&&n("object",r))),tp=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof Ma||e(r)&&t("reference",r)&&n("object",r))),np=e=>{if(!tp(e))return!1;if(!ms(e.$ref))return!1;const t=e.$ref.toValue();return"string"==typeof t&&t.length>0&&!t.startsWith("#")},rp=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof Fa||e(r)&&t("requestBody",r)&&n("object",r))),op=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof Ba||e(r)&&t("response",r)&&n("object",r))),sp=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof qa||e(r)&&t("responses",r)&&n("object",r))),ip=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof bc||e(r)&&t("schema",r)&&n("object",r))),ap=e=>vs(e)&&e.classes.includes("boolean-json-schema"),lp=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof Ec||e(r)&&t("securityRequirement",r)&&n("object",r))),cp=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof jc||e(r)&&t("server",r)&&n("object",r))),up=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof kc||e(r)&&t("serverVariable",r)&&n("object",r))),pp=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof ya||e(r)&&t("mediaType",r)&&n("object",r))),hp=Ys(Fu,Zc,{props:{alternator:[{predicate:Uc,specPath:["document","objects","Reference"]},{predicate:Dt,specPath:["document","objects","Schema"]}]},methods:{ObjectElement(e){const t=Fu.compose.methods.enter.call(this,e);return tp(this.element)&&this.element.setMetaProperty("referenced-element","schema"),t}}}),fp=Zc,dp=Ys(yu,Zc,{props:{specPath:e=>Uc(e)?["document","objects","Reference"]:["document","objects","Example"],canSupportSpecificationExtensions:!0},init(){this.element=new Pt.Sb,this.element.classes.push("examples")},methods:{ObjectElement(e){const t=yu.compose.methods.ObjectElement.call(this,e);return this.element.filter(tp).forEach((e=>{e.setMetaProperty("referenced-element","example")})),t}}});class mp extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.classes.push(mp.primaryClass),this.classes.push("examples")}}Xo(mp,"primaryClass","media-type-examples");const gp=mp,yp=Ys(dp,{init(){this.element=new gp}});class vp extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.classes.push(vp.primaryClass)}}Xo(vp,"primaryClass","media-type-encoding");const bp=vp,wp=Ys(yu,Zc,{props:{specPath:Hn(["document","objects","Encoding"])},init(){this.element=new bp}}),Ep=Ys(yu,Zc,{props:{specPath:Hn(["value"])},init(){this.element=new Ec}});class xp extends Pt.ON{constructor(e,t,n){super(e,t,n),this.classes.push(xp.primaryClass)}}Xo(xp,"primaryClass","security");const Sp=xp,_p=Ys(Bc,Zc,{init(){this.element=new Sp},methods:{ArrayElement(e){return e.forEach((e=>{if(bs(e)){const t=this.toRefractedElement(["document","objects","SecurityRequirement"],e);this.element.push(t)}else this.element.push(e.clone())})),this.copyMetaAndAttributes(e,this.element),ei}}}),jp=Ys(Gc,Zc,{props:{specPath:Hn(["document","objects","Components"]),canSupportSpecificationExtensions:!0},init(){this.element=new Yi}}),Op=Ys(Gc,Zc,{props:{specPath:Hn(["document","objects","Tag"]),canSupportSpecificationExtensions:!0},init(){this.element=new Cc}}),kp=Zc,Ap=Zc,Cp=Ys(Gc,Zc,{props:{specPath:Hn(["document","objects","Reference"]),canSupportSpecificationExtensions:!1},init(){this.element=new Ma},methods:{ObjectElement(e){const t=Gc.compose.methods.ObjectElement.call(this,e);return ms(this.element.$ref)&&this.element.classes.push("reference-element"),t}}}),Pp=Ys(Zc,{methods:{StringElement(e){return this.element=e.clone(),this.element.classes.push("reference-value"),ei}}}),Np=Ys(Gc,Zc,{props:{specPath:Hn(["document","objects","Parameter"]),canSupportSpecificationExtensions:!0},init(){this.element=new Ca},methods:{ObjectElement(e){const t=Gc.compose.methods.ObjectElement.call(this,e);return bs(this.element.contentProp)&&this.element.contentProp.filter(pp).forEach(((e,t)=>{e.setMetaProperty("media-type",t.toValue())})),t}}}),Ip=Zc,Tp=Zc,Rp=Zc,Mp=Zc,Dp=Zc,Fp=Zc,Lp=Zc,Bp=Zc,$p=Zc,qp=Ys(Fu,Zc,{props:{alternator:[{predicate:Uc,specPath:["document","objects","Reference"]},{predicate:Dt,specPath:["document","objects","Schema"]}]},methods:{ObjectElement(e){const t=Fu.compose.methods.enter.call(this,e);return tp(this.element)&&this.element.setMetaProperty("referenced-element","schema"),t}}}),Up=Ys(Gc,Zc,{props:{specPath:Hn(["document","objects","Header"]),canSupportSpecificationExtensions:!0},init(){this.element=new ca}}),zp=Zc,Vp=Zc,Wp=Zc,Jp=Zc,Kp=Zc,Hp=Zc,Gp=Zc,Zp=Ys(Fu,Zc,{props:{alternator:[{predicate:Uc,specPath:["document","objects","Reference"]},{predicate:Dt,specPath:["document","objects","Schema"]}]},methods:{ObjectElement(e){const t=Fu.compose.methods.enter.call(this,e);return tp(this.element)&&this.element.setMetaProperty("referenced-element","schema"),t}}}),Yp=Zc;class Xp extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.classes.push(Xp.primaryClass),this.classes.push("examples")}}Xo(Xp,"primaryClass","header-examples");const Qp=Xp,eh=Ys(dp,{init(){this.element=new Qp}}),th=Ys(yu,Zc,{props:{specPath:Hn(["document","objects","MediaType"])},init(){this.element=new Pt.Sb,this.element.classes.push("content")}});class nh extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.classes.push(nh.primaryClass),this.classes.push("content")}}Xo(nh,"primaryClass","header-content");const rh=nh,oh=Ys(th,{init(){this.element=new rh}}),sh=Ys(Gc,Zc,{props:{specPath:Hn(["document","objects","Schema"]),canSupportSpecificationExtensions:!0},init(){this.element=new bc}}),{items:ih}=ac.visitors.document.objects.JSONSchema.fixedFields,ah=Ys(ih,{methods:{ObjectElement(e){const t=ih.compose.methods.ObjectElement.call(this,e);return tp(this.element)&&this.element.setMetaProperty("referenced-element","schema"),t},ArrayElement(e){return this.element=e.clone(),ei}}}),{properties:lh}=ac.visitors.document.objects.JSONSchema.fixedFields,ch=Ys(lh,{methods:{ObjectElement(e){const t=lh.compose.methods.ObjectElement.call(this,e);return this.element.filter(tp).forEach((e=>{e.setMetaProperty("referenced-element","schema")})),t}}}),{type:uh}=ac.visitors.document.objects.JSONSchema.fixedFields,ph=Ys(uh,{methods:{ArrayElement(e){return this.element=e.clone(),ei}}}),hh=Zc,fh=Zc,dh=Zc,mh=Zc,{JSONSchemaOrJSONReferenceVisitor:gh}=ac.visitors,yh=Ys(gh,{methods:{ObjectElement(e){const t=gh.compose.methods.enter.call(this,e);return tp(this.element)&&this.element.setMetaProperty("referenced-element","schema"),t}}}),vh=Object.fromEntries(Object.entries(ac.visitors.document.objects.JSONSchema.fixedFields).map((([e,t])=>t===ac.visitors.JSONSchemaOrJSONReferenceVisitor?[e,yh]:[e,t]))),bh=Ys(Gc,Zc,{props:{specPath:Hn(["document","objects","Discriminator"]),canSupportSpecificationExtensions:!1},init(){this.element=new ta}}),wh=Zc;class Eh extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.classes.push(Eh.primaryClass)}}Xo(Eh,"primaryClass","discriminator-mapping");const xh=Eh,Sh=Ys(yu,Zc,{props:{specPath:Hn(["value"])},init(){this.element=new xh}}),_h=Ys(Gc,Zc,{props:{specPath:Hn(["document","objects","XML"]),canSupportSpecificationExtensions:!0},init(){this.element=new Nc}}),jh=Zc,Oh=Zc,kh=Zc,Ah=Zc,Ch=Zc,Ph=Zc;class Nh extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.classes.push(Nh.primaryClass),this.classes.push("examples")}}Xo(Nh,"primaryClass","parameter-examples");const Ih=Nh,Th=Ys(dp,{init(){this.element=new Ih}});class Rh extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.classes.push(Rh.primaryClass),this.classes.push("content")}}Xo(Rh,"primaryClass","parameter-content");const Mh=Rh,Dh=Ys(th,{init(){this.element=new Mh}});class Fh extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.classes.push(Fh.primaryClass)}}Xo(Fh,"primaryClass","components-schemas");const Lh=Fh,Bh=Ys(yu,Zc,{props:{specPath:e=>Uc(e)?["document","objects","Reference"]:["document","objects","Schema"]},init(){this.element=new Lh},methods:{ObjectElement(e){const t=yu.compose.methods.ObjectElement.call(this,e);return this.element.filter(tp).forEach((e=>{e.setMetaProperty("referenced-element","schema")})),t}}});class $h extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.classes.push($h.primaryClass)}}Xo($h,"primaryClass","components-responses");const qh=$h,Uh=Ys(yu,Zc,{props:{specPath:e=>Uc(e)?["document","objects","Reference"]:["document","objects","Response"]},init(){this.element=new qh},methods:{ObjectElement(e){const t=yu.compose.methods.ObjectElement.call(this,e);return this.element.filter(tp).forEach((e=>{e.setMetaProperty("referenced-element","response")})),this.element.filter(op).forEach(((e,t)=>{e.setMetaProperty("http-status-code",t.toValue())})),t}}}),zh=Uh;class Vh extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.classes.push(Vh.primaryClass),this.classes.push("parameters")}}Xo(Vh,"primaryClass","components-parameters");const Wh=Vh,Jh=Ys(yu,Zc,{props:{specPath:e=>Uc(e)?["document","objects","Reference"]:["document","objects","Parameter"]},init(){this.element=new Wh},methods:{ObjectElement(e){const t=yu.compose.methods.ObjectElement.call(this,e);return this.element.filter(tp).forEach((e=>{e.setMetaProperty("referenced-element","parameter")})),t}}});class Kh extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.classes.push(Kh.primaryClass),this.classes.push("examples")}}Xo(Kh,"primaryClass","components-examples");const Hh=Kh,Gh=Ys(yu,Zc,{props:{specPath:e=>Uc(e)?["document","objects","Reference"]:["document","objects","Example"]},init(){this.element=new Hh},methods:{ObjectElement(e){const t=yu.compose.methods.ObjectElement.call(this,e);return this.element.filter(tp).forEach((e=>{e.setMetaProperty("referenced-element","example")})),t}}});class Zh extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.classes.push(Zh.primaryClass)}}Xo(Zh,"primaryClass","components-request-bodies");const Yh=Zh,Xh=Ys(yu,Zc,{props:{specPath:e=>Uc(e)?["document","objects","Reference"]:["document","objects","RequestBody"]},init(){this.element=new Yh},methods:{ObjectElement(e){const t=yu.compose.methods.ObjectElement.call(this,e);return this.element.filter(tp).forEach((e=>{e.setMetaProperty("referenced-element","requestBody")})),t}}});class Qh extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.classes.push(Qh.primaryClass)}}Xo(Qh,"primaryClass","components-headers");const ef=Qh,tf=Ys(yu,Zc,{props:{specPath:e=>Uc(e)?["document","objects","Reference"]:["document","objects","Header"]},init(){this.element=new ef},methods:{ObjectElement(e){const t=yu.compose.methods.ObjectElement.call(this,e);return this.element.filter(tp).forEach((e=>{e.setMetaProperty("referenced-element","header")})),this.element.filter(zu).forEach(((e,t)=>{e.setMetaProperty("header-name",t.toValue())})),t}}}),nf=tf;class rf extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.classes.push(rf.primaryClass)}}Xo(rf,"primaryClass","components-security-schemes");const of=rf,sf=Ys(yu,Zc,{props:{specPath:e=>Uc(e)?["document","objects","Reference"]:["document","objects","SecurityScheme"]},init(){this.element=new of},methods:{ObjectElement(e){const t=yu.compose.methods.ObjectElement.call(this,e);return this.element.filter(tp).forEach((e=>{e.setMetaProperty("referenced-element","securityScheme")})),t}}});class af extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.classes.push(af.primaryClass)}}Xo(af,"primaryClass","components-links");const lf=af,cf=Ys(yu,Zc,{props:{specPath:e=>Uc(e)?["document","objects","Reference"]:["document","objects","Link"]},init(){this.element=new lf},methods:{ObjectElement(e){const t=yu.compose.methods.ObjectElement.call(this,e);return this.element.filter(tp).forEach((e=>{e.setMetaProperty("referenced-element","link")})),t}}});class uf extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.classes.push(uf.primaryClass)}}Xo(uf,"primaryClass","components-callbacks");const pf=uf,hf=Ys(yu,Zc,{props:{specPath:e=>Uc(e)?["document","objects","Reference"]:["document","objects","Callback"]},init(){this.element=new pf},methods:{ObjectElement(e){const t=yu.compose.methods.ObjectElement.call(this,e);return this.element.filter(tp).forEach((e=>{e.setMetaProperty("referenced-element","callback")})),t}}}),ff=Ys(Gc,Zc,{props:{specPath:Hn(["document","objects","Example"]),canSupportSpecificationExtensions:!0},init(){this.element=new sa},methods:{ObjectElement(e){const t=Gc.compose.methods.ObjectElement.call(this,e);return ms(this.element.externalValue)&&this.element.classes.push("reference-element"),t}}}),df=Zc,mf=Zc,gf=Zc,yf=Ys(Zc,{methods:{StringElement(e){return this.element=e.clone(),this.element.classes.push("reference-value"),ei}}}),vf=Ys(Gc,Zc,{props:{specPath:Hn(["document","objects","ExternalDocumentation"]),canSupportSpecificationExtensions:!0},init(){this.element=new aa}}),bf=Zc,wf=Zc,Ef=Ys(Gc,Zc,{props:{specPath:Hn(["document","objects","Encoding"]),canSupportSpecificationExtensions:!0},init(){this.element=new ra},methods:{ObjectElement(e){const t=Gc.compose.methods.ObjectElement.call(this,e);return bs(this.element.headers)&&this.element.headers.filter(zu).forEach(((e,t)=>{e.setMetaProperty("header-name",t.toValue())})),t}}}),xf=Zc;class Sf extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.classes.push(Sf.primaryClass)}}Xo(Sf,"primaryClass","encoding-headers");const _f=Sf,jf=Ys(yu,Zc,{props:{specPath:e=>Uc(e)?["document","objects","Reference"]:["document","objects","Header"]},init(){this.element=new _f},methods:{ObjectElement(e){const t=yu.compose.methods.ObjectElement.call(this,e);return this.element.filter(tp).forEach((e=>{e.setMetaProperty("referenced-element","header")})),this.element.forEach(((e,t)=>{if(!zu(e))return;const n=t.toValue();e.setMetaProperty("headerName",n)})),t}}}),Of=jf,kf=Zc,Af=Zc,Cf=Zc,Pf=Ys(gu,Zc,{props:{fieldPatternPredicate:Ro(/^\/(?.*)$/),specPath:Hn(["document","objects","PathItem"]),canSupportSpecificationExtensions:!0},init(){this.element=new Ta},methods:{ObjectElement(e){const t=gu.compose.methods.ObjectElement.call(this,e);return this.element.filter(Xu).forEach(((e,t)=>{e.setMetaProperty("path",t.clone())})),t}}}),Nf=Ys(Gc,Zc,{props:{specPath:Hn(["document","objects","RequestBody"])},init(){this.element=new Fa},methods:{ObjectElement(e){const t=Gc.compose.methods.ObjectElement.call(this,e);return bs(this.element.contentProp)&&this.element.contentProp.filter(pp).forEach(((e,t)=>{e.setMetaProperty("media-type",t.toValue())})),t}}}),If=Zc;class Tf extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.classes.push(Tf.primaryClass),this.classes.push("content")}}Xo(Tf,"primaryClass","request-body-content");const Rf=Tf,Mf=Ys(th,{init(){this.element=new Rf}}),Df=Zc,Ff=Ys(gu,Zc,{props:{fieldPatternPredicate:Ro(/{(?.*)}/),specPath:Hn(["document","objects","PathItem"]),canSupportSpecificationExtensions:!0},init(){this.element=new Gi},methods:{ObjectElement(e){const t=yu.compose.methods.ObjectElement.call(this,e);return this.element.filter(Xu).forEach(((e,t)=>{e.setMetaProperty("runtime-expression",t.toValue())})),t}}}),Lf=Ys(Gc,Zc,{props:{specPath:Hn(["document","objects","Response"])},init(){this.element=new Ba},methods:{ObjectElement(e){const t=Gc.compose.methods.ObjectElement.call(this,e);return bs(this.element.contentProp)&&this.element.contentProp.filter(pp).forEach(((e,t)=>{e.setMetaProperty("media-type",t.toValue())})),bs(this.element.headers)&&this.element.headers.filter(zu).forEach(((e,t)=>{e.setMetaProperty("header-name",t.toValue())})),t}}}),Bf=Zc;class $f extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.classes.push($f.primaryClass)}}Xo($f,"primaryClass","response-headers");const qf=$f,Uf=Ys(yu,Zc,{props:{specPath:e=>Uc(e)?["document","objects","Reference"]:["document","objects","Header"]},init(){this.element=new qf},methods:{ObjectElement(e){const t=yu.compose.methods.ObjectElement.call(this,e);return this.element.filter(tp).forEach((e=>{e.setMetaProperty("referenced-element","header")})),this.element.forEach(((e,t)=>{if(!zu(e))return;const n=t.toValue();e.setMetaProperty("header-name",n)})),t}}}),zf=Uf;class Vf extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.classes.push(Vf.primaryClass),this.classes.push("content")}}Xo(Vf,"primaryClass","response-content");const Wf=Vf,Jf=Ys(th,{init(){this.element=new Wf}});class Kf extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.classes.push(Kf.primaryClass)}}Xo(Kf,"primaryClass","response-links");const Hf=Kf,Gf=Ys(yu,Zc,{props:{specPath:e=>Uc(e)?["document","objects","Reference"]:["document","objects","Link"]},init(){this.element=new Hf},methods:{ObjectElement(e){const t=yu.compose.methods.ObjectElement.call(this,e);return this.element.filter(tp).forEach((e=>{e.setMetaProperty("referenced-element","link")})),t}}}),Zf=Ys(Gc,gu,{props:{specPathFixedFields:el,specPathPatternedFields:el},methods:{ObjectElement(e){const{specPath:t,ignoredFields:n}=this;try{this.specPath=this.specPathFixedFields;const t=this.retrieveFixedFields(this.specPath(e));this.ignoredFields=[...n,...Pr(e.keys(),t)],Gc.compose.methods.ObjectElement.call(this,e),this.specPath=this.specPathPatternedFields,this.ignoredFields=t,gu.compose.methods.ObjectElement.call(this,e)}catch(e){throw this.specPath=t,e}return ei}}}),Yf=Ys(Zf,Zc,{props:{specPathFixedFields:Hn(["document","objects","Responses"]),specPathPatternedFields:e=>Uc(e)?["document","objects","Reference"]:["document","objects","Response"],fieldPatternPredicate:Ro(new RegExp(`^(1XX|2XX|3XX|4XX|5XX|${ko(100,600).join("|")})$`)),canSupportSpecificationExtensions:!0},init(){this.element=new qa},methods:{ObjectElement(e){const t=Zf.compose.methods.ObjectElement.call(this,e);return this.element.filter(tp).forEach((e=>{e.setMetaProperty("referenced-element","response")})),this.element.filter(op).forEach(((e,t)=>{const n=t.clone();this.fieldPatternPredicate(n.toValue())&&e.setMetaProperty("http-status-code",n)})),t}}}),Xf=Yf,Qf=Ys(Fu,Zc,{props:{alternator:[{predicate:Uc,specPath:["document","objects","Reference"]},{predicate:Dt,specPath:["document","objects","Response"]}]},methods:{ObjectElement(e){const t=Fu.compose.methods.enter.call(this,e);return tp(this.element)?this.element.setMetaProperty("referenced-element","response"):op(this.element)&&this.element.setMetaProperty("http-status-code","default"),t}}}),ed=Ys(Gc,Zc,{props:{specPath:Hn(["document","objects","Operation"])},init(){this.element=new ka}});class td extends Pt.ON{constructor(e,t,n){super(e,t,n),this.classes.push(td.primaryClass)}}Xo(td,"primaryClass","operation-tags");const nd=td,rd=Ys(Zc,{init(){this.element=new nd},methods:{ArrayElement(e){return this.element=this.element.concat(e.clone()),ei}}}),od=Zc,sd=Zc,id=Zc;class ad extends Pt.ON{constructor(e,t,n){super(e,t,n),this.classes.push(ad.primaryClass),this.classes.push("parameters")}}Xo(ad,"primaryClass","operation-parameters");const ld=ad,cd=Ys(Bc,Zc,{init(){this.element=new Pt.ON,this.element.classes.push("parameters")},methods:{ArrayElement(e){return e.forEach((e=>{const t=Uc(e)?["document","objects","Reference"]:["document","objects","Parameter"],n=this.toRefractedElement(t,e);tp(n)&&n.setMetaProperty("referenced-element","parameter"),this.element.push(n)})),this.copyMetaAndAttributes(e,this.element),ei}}}),ud=Ys(cd,{init(){this.element=new ld}}),pd=Ys(Fu,{props:{alternator:[{predicate:Uc,specPath:["document","objects","Reference"]},{predicate:Dt,specPath:["document","objects","RequestBody"]}]},methods:{ObjectElement(e){const t=Fu.compose.methods.enter.call(this,e);return tp(this.element)&&this.element.setMetaProperty("referenced-element","requestBody"),t}}});class hd extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.classes.push(hd.primaryClass)}}Xo(hd,"primaryClass","operation-callbacks");const fd=hd,dd=Ys(yu,Zc,{props:{specPath:e=>Uc(e)?["document","objects","Reference"]:["document","objects","Callback"]},init(){this.element=new fd},methods:{ObjectElement(e){const t=yu.compose.methods.ObjectElement.call(this,e);return this.element.filter(tp).forEach((e=>{e.setMetaProperty("referenced-element","callback")})),t}}}),md=Zc;class gd extends Pt.ON{constructor(e,t,n){super(e,t,n),this.classes.push(gd.primaryClass),this.classes.push("security")}}Xo(gd,"primaryClass","operation-security");const yd=gd,vd=Ys(Bc,Zc,{init(){this.element=new yd},methods:{ArrayElement(e){return e.forEach((e=>{const t=bs(e)?["document","objects","SecurityRequirement"]:["value"],n=this.toRefractedElement(t,e);this.element.push(n)})),this.copyMetaAndAttributes(e,this.element),ei}}});class bd extends Pt.ON{constructor(e,t,n){super(e,t,n),this.classes.push(bd.primaryClass),this.classes.push("servers")}}Xo(bd,"primaryClass","operation-servers");const wd=bd,Ed=Ys(Au,{init(){this.element=new wd}}),xd=Ys(Gc,Zc,{props:{specPath:Hn(["document","objects","PathItem"])},init(){this.element=new Na},methods:{ObjectElement(e){const t=Gc.compose.methods.ObjectElement.call(this,e);return this.element.filter(Zu).forEach(((e,t)=>{const n=t.clone();n.content=n.toValue().toUpperCase(),e.setMetaProperty("http-method",n)})),ms(this.element.$ref)&&this.element.classes.push("reference-element"),t}}}),Sd=Ys(Zc,{methods:{StringElement(e){return this.element=e.clone(),this.element.classes.push("reference-value"),ei}}}),_d=Zc,jd=Zc;class Od extends Pt.ON{constructor(e,t,n){super(e,t,n),this.classes.push(Od.primaryClass),this.classes.push("servers")}}Xo(Od,"primaryClass","path-item-servers");const kd=Od,Ad=Ys(Au,{init(){this.element=new kd}});class Cd extends Pt.ON{constructor(e,t,n){super(e,t,n),this.classes.push(Cd.primaryClass),this.classes.push("parameters")}}Xo(Cd,"primaryClass","path-item-parameters");const Pd=Cd,Nd=Ys(cd,{init(){this.element=new Pd}}),Id=Ys(Gc,Zc,{props:{specPath:Hn(["document","objects","SecurityScheme"]),canSupportSpecificationExtensions:!0},init(){this.element=new Sc}}),Td=Zc,Rd=Zc,Md=Zc,Dd=Zc,Fd=Zc,Ld=Zc,Bd=Zc,$d=Ys(Gc,Zc,{props:{specPath:Hn(["document","objects","OAuthFlows"]),canSupportSpecificationExtensions:!0},init(){this.element=new Ea}}),qd=Ys(Gc,Zc,{props:{specPath:Hn(["document","objects","OAuthFlow"]),canSupportSpecificationExtensions:!0},init(){this.element=new ba}}),Ud=Zc,zd=Zc,Vd=Zc;class Wd extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.classes.push(Wd.primaryClass)}}Xo(Wd,"primaryClass","oauth-flow-scopes");const Jd=Wd,Kd=Ys(yu,Zc,{props:{specPath:Hn(["value"])},init(){this.element=new Jd}});class Hd extends Pt.ON{constructor(e,t,n){super(e,t,n),this.classes.push(Hd.primaryClass)}}Xo(Hd,"primaryClass","tags");const Gd=Hd,Zd=Ys(Bc,Zc,{init(){this.element=new Gd},methods:{ArrayElement(e){return e.forEach((e=>{const t=Jc(e)?["document","objects","Tag"]:["value"],n=this.toRefractedElement(t,e);this.element.push(n)})),this.copyMetaAndAttributes(e,this.element),ei}}});function Yd(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function Xd(e){for(var t=1;t{const{base:t}=e;return t.register("callback",Gi),t.register("components",Yi),t.register("contact",Qi),t.register("discriminator",ta),t.register("encoding",ra),t.register("example",sa),t.register("externalDocumentation",aa),t.register("header",ca),t.register("info",pa),t.register("license",fa),t.register("link",ma),t.register("mediaType",ya),t.register("oAuthFlow",ba),t.register("oAuthFlows",Ea),t.register("openapi",Sa),t.register("openApi3_0",ja),t.register("operation",ka),t.register("parameter",Ca),t.register("pathItem",Na),t.register("paths",Ta),t.register("reference",Ma),t.register("requestBody",Fa),t.register("response",Ba),t.register("responses",qa),t.register("schema",bc),t.register("securityRequirement",Ec),t.register("securityScheme",Sc),t.register("server",jc),t.register("serverVariable",kc),t.register("tag",Cc),t.register("xml",Nc),t}};function rm(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function om(e){for(var t=1;t{const e=zs(nm);return{predicates:om(om(om({},a),l),{},{isStringElement:ms}),namespace:e}};function im(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}const am=(e,{specPath:t=["visitors","document","objects","OpenApi","$visitor"],plugins:n=[]}={})=>{const r=(0,Pt.Qc)(e),o=Za(tm),s=is(t,[],o);return fi(r,s,{state:{specObj:o}}),di(s.element,n,{toolboxCreator:sm,visitorOptions:{keyMap:Dc,nodeTypeGetter:Mc}})},lm=e=>(t,n={})=>am(t,function(e){for(var t=1;tr=>r instanceof cm||e(r)&&t("callback",r)&&n("object",r))),_g=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof um||e(r)&&t("components",r)&&n("object",r))),jg=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof pm||e(r)&&t("contact",r)&&n("object",r))),Og=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof dm||e(r)&&t("example",r)&&n("object",r))),kg=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof mm||e(r)&&t("externalDocumentation",r)&&n("object",r))),Ag=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof gm||e(r)&&t("header",r)&&n("object",r))),Cg=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof ym||e(r)&&t("info",r)&&n("object",r))),Pg=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof bm||e(r)&&t("jsonSchemaDialect",r)&&n("string",r))),Ng=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof wm||e(r)&&t("license",r)&&n("object",r))),Ig=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof Em||e(r)&&t("link",r)&&n("object",r))),Tg=e=>{if(!Ig(e))return!1;if(!ms(e.operationRef))return!1;const t=e.operationRef.toValue();return"string"==typeof t&&t.length>0&&!t.startsWith("#")},Rg=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof jm||e(r)&&t("openapi",r)&&n("string",r))),Mg=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n,hasClass:r})=>o=>o instanceof km||e(o)&&t("openApi3_1",o)&&n("object",o)&&r("api",o))),Dg=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof Am||e(r)&&t("operation",r)&&n("object",r))),Fg=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof Cm||e(r)&&t("parameter",r)&&n("object",r))),Lg=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof Pm||e(r)&&t("pathItem",r)&&n("object",r))),Bg=e=>{if(!Lg(e))return!1;if(!ms(e.$ref))return!1;const t=e.$ref.toValue();return"string"==typeof t&&t.length>0&&!t.startsWith("#")},$g=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof Nm||e(r)&&t("paths",r)&&n("object",r))),qg=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof Tm||e(r)&&t("reference",r)&&n("object",r))),Ug=e=>{if(!qg(e))return!1;if(!ms(e.$ref))return!1;const t=e.$ref.toValue();return"string"==typeof t&&t.length>0&&!t.startsWith("#")},zg=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof Rm||e(r)&&t("requestBody",r)&&n("object",r))),Vg=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof Mm||e(r)&&t("response",r)&&n("object",r))),Wg=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof Dm||e(r)&&t("responses",r)&&n("object",r))),Jg=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof Lm||e(r)&&t("schema",r)&&n("object",r))),Kg=e=>vs(e)&&e.classes.includes("boolean-json-schema"),Hg=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof Bm||e(r)&&t("securityRequirement",r)&&n("object",r))),Gg=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof qm||e(r)&&t("server",r)&&n("object",r))),Zg=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof Um||e(r)&&t("serverVariable",r)&&n("object",r))),Yg=fs((({hasBasicElementProps:e,isElementType:t,primitiveEq:n})=>r=>r instanceof xm||e(r)&&t("mediaType",r)&&n("object",r))),Xg=Ys({props:{parent:null},init({parent:e=this.parent}){this.parent=e,this.passingOptionsNames=[...this.passingOptionsNames,"parent"]}}),Qg=Ys(Gc,Xg,Zc,{props:{specPath:Hn(["document","objects","Schema"]),canSupportSpecificationExtensions:!0},init(){const e=()=>{let e;return e=null!==this.openApiSemanticElement&&Pg(this.openApiSemanticElement.jsonSchemaDialect)?this.openApiSemanticElement.jsonSchemaDialect.toValue():null!==this.openApiGenericElement&&ms(this.openApiGenericElement.get("jsonSchemaDialect"))?this.openApiGenericElement.get("jsonSchemaDialect").toValue():bm.default.toValue(),e},t=t=>{if(Is(this.parent)&&!ms(t.get("$schema")))this.element.setMetaProperty("inherited$schema",e());else if(Jg(this.parent)&&!ms(t.get("$schema"))){var n,r;const e=kr(null===(n=this.parent.meta.get("inherited$schema"))||void 0===n?void 0:n.toValue(),null===(r=this.parent.$schema)||void 0===r?void 0:r.toValue());this.element.setMetaProperty("inherited$schema",e)}},n=e=>{var t;const n=null!==this.parent?this.parent.getMetaProperty("inherited$id",[]).clone():new Pt.ON,r=null===(t=e.get("$id"))||void 0===t?void 0:t.toValue();Nl(r)&&n.push(r),this.element.setMetaProperty("inherited$id",n)};this.ObjectElement=function(e){this.element=new Lm,t(e),n(e),this.parent=this.element;const r=Gc.compose.methods.ObjectElement.call(this,e);return ms(this.element.$ref)&&(this.element.classes.push("reference-element"),this.element.setMetaProperty("referenced-element","schema")),r},this.BooleanElement=function(e){return this.element=e.clone(),this.element.classes.push("boolean-json-schema"),ei}}}),ey=Zc,ty=Ys(Zc,{methods:{ObjectElement(e){return this.element=e.clone(),this.element.classes.push("json-schema-$vocabulary"),ei}}}),ny=Zc,ry=Zc,oy=Zc,sy=Zc,iy=Ys(Zc,{methods:{StringElement(e){return this.element=e.clone(),this.element.classes.push("reference-value"),ei}}}),ay=Ys(yu,Xg,Zc,{props:{specPath:Hn(["document","objects","Schema"])},init(){this.element=new Pt.Sb,this.element.classes.push("json-schema-$defs")}}),ly=Zc,cy=Ys(Bc,Xg,Zc,{init(){this.element=new Pt.ON,this.element.classes.push("json-schema-allOf")},methods:{ArrayElement(e){return e.forEach((e=>{if(bs(e)){const t=this.toRefractedElement(["document","objects","Schema"],e);this.element.push(t)}else{const t=e.clone();this.element.push(t)}})),this.copyMetaAndAttributes(e,this.element),ei}}}),uy=Ys(Bc,Xg,Zc,{init(){this.element=new Pt.ON,this.element.classes.push("json-schema-anyOf")},methods:{ArrayElement(e){return e.forEach((e=>{if(bs(e)){const t=this.toRefractedElement(["document","objects","Schema"],e);this.element.push(t)}else{const t=e.clone();this.element.push(t)}})),this.copyMetaAndAttributes(e,this.element),ei}}}),py=Ys(Bc,Xg,Zc,{init(){this.element=new Pt.ON,this.element.classes.push("json-schema-oneOf")},methods:{ArrayElement(e){return e.forEach((e=>{if(bs(e)){const t=this.toRefractedElement(["document","objects","Schema"],e);this.element.push(t)}else{const t=e.clone();this.element.push(t)}})),this.copyMetaAndAttributes(e,this.element),ei}}}),hy=Ys(yu,Xg,Zc,{props:{specPath:Hn(["document","objects","Schema"])},init(){this.element=new Pt.Sb,this.element.classes.push("json-schema-dependentSchemas")}}),fy=Ys(Bc,Xg,Zc,{init(){this.element=new Pt.ON,this.element.classes.push("json-schema-prefixItems")},methods:{ArrayElement(e){return e.forEach((e=>{if(bs(e)){const t=this.toRefractedElement(["document","objects","Schema"],e);this.element.push(t)}else{const t=e.clone();this.element.push(t)}})),this.copyMetaAndAttributes(e,this.element),ei}}}),dy=Ys(yu,Xg,Zc,{props:{specPath:Hn(["document","objects","Schema"])},init(){this.element=new Pt.Sb,this.element.classes.push("json-schema-properties")}}),my=Ys(yu,Xg,Zc,{props:{specPath:Hn(["document","objects","Schema"])},init(){this.element=new Pt.Sb,this.element.classes.push("json-schema-patternProperties")}}),gy=Ys(Zc,{methods:{StringElement(e){return this.element=e.clone(),this.element.classes.push("json-schema-type"),ei},ArrayElement(e){return this.element=e.clone(),this.element.classes.push("json-schema-type"),ei}}}),yy=Ys(Zc,{methods:{ArrayElement(e){return this.element=e.clone(),this.element.classes.push("json-schema-enum"),ei}}}),vy=Zc,by=Zc,wy=Zc,Ey=Zc,xy=Zc,Sy=Zc,_y=Zc,jy=Zc,Oy=Zc,ky=Zc,Ay=Zc,Cy=Zc,Py=Zc,Ny=Zc,Iy=Zc,Ty=Zc,Ry=Ys(Zc,{methods:{ArrayElement(e){return this.element=e.clone(),this.element.classes.push("json-schema-required"),ei}}}),My=Ys(Zc,{methods:{ObjectElement(e){return this.element=e.clone(),this.element.classes.push("json-schema-dependentRequired"),ei}}}),Dy=Zc,Fy=Zc,Ly=Zc,By=Zc,$y=Zc,qy=Zc,Uy=Ys(Zc,{methods:{ArrayElement(e){return this.element=e.clone(),this.element.classes.push("json-schema-examples"),ei}}}),zy=Zc,Vy=Zc,Wy=Zc,Jy=Zc,{visitors:{document:{objects:{Discriminator:{$visitor:Ky}}}}}=tm,Hy=Ys(Ky,{props:{canSupportSpecificationExtensions:!0},init(){this.element=new hm}}),{visitors:{document:{objects:{XML:{$visitor:Gy}}}}}=tm,Zy=Ys(Gy,{init(){this.element=new Vm}}),Yy=Ys(yu,Zc,{props:{specPath:Hn(["document","objects","Schema"])},init(){this.element=new Lh}});class Xy extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.classes.push(Xy.primaryClass)}}Xo(Xy,"primaryClass","components-path-items");const Qy=Xy,ev=Ys(yu,Zc,{props:{specPath:e=>Uc(e)?["document","objects","Reference"]:["document","objects","PathItem"]},init(){this.element=new Qy},methods:{ObjectElement(e){const t=yu.compose.methods.ObjectElement.call(this,e);return this.element.filter(qg).forEach((e=>{e.setMetaProperty("referenced-element","pathItem")})),t}}}),{visitors:{document:{objects:{Example:{$visitor:tv}}}}}=tm,nv=Ys(tv,{init(){this.element=new dm}}),{visitors:{document:{objects:{ExternalDocumentation:{$visitor:rv}}}}}=tm,ov=Ys(rv,{init(){this.element=new mm}}),{visitors:{document:{objects:{Encoding:{$visitor:sv}}}}}=tm,iv=Ys(sv,{init(){this.element=new fm}}),{visitors:{document:{objects:{Paths:{$visitor:av}}}}}=tm,lv=Ys(av,{init(){this.element=new Nm}}),{visitors:{document:{objects:{RequestBody:{$visitor:cv}}}}}=tm,uv=Ys(cv,{init(){this.element=new Rm}}),{visitors:{document:{objects:{Callback:{$visitor:pv}}}}}=tm,hv=Ys(pv,{props:{specPath:e=>Uc(e)?["document","objects","Reference"]:["document","objects","PathItem"]},init(){this.element=new cm},methods:{ObjectElement(e){const t=pv.compose.methods.ObjectElement.call(this,e);return this.element.filter(qg).forEach((e=>{e.setMetaProperty("referenced-element","pathItem")})),t}}}),{visitors:{document:{objects:{Response:{$visitor:fv}}}}}=tm,dv=Ys(fv,{init(){this.element=new Mm}}),{visitors:{document:{objects:{Responses:{$visitor:mv}}}}}=tm,gv=Ys(mv,{init(){this.element=new Dm}}),{visitors:{document:{objects:{Operation:{$visitor:yv}}}}}=tm,vv=Ys(yv,{init(){this.element=new Am}}),{visitors:{document:{objects:{PathItem:{$visitor:bv}}}}}=tm,wv=Ys(bv,{init(){this.element=new Pm}}),{visitors:{document:{objects:{SecurityScheme:{$visitor:Ev}}}}}=tm,xv=Ys(Ev,{init(){this.element=new $m}}),{visitors:{document:{objects:{OAuthFlows:{$visitor:Sv}}}}}=tm,_v=Ys(Sv,{init(){this.element=new _m}}),{visitors:{document:{objects:{OAuthFlow:{$visitor:jv}}}}}=tm,Ov=Ys(jv,{init(){this.element=new Sm}});class kv extends Pt.Sb{constructor(e,t,n){super(e,t,n),this.classes.push(kv.primaryClass)}}Xo(kv,"primaryClass","webhooks");const Av=kv,Cv=Ys(yu,Zc,{props:{specPath:e=>Uc(e)?["document","objects","Reference"]:["document","objects","PathItem"]},init(){this.element=new Av},methods:{ObjectElement(e){const t=yu.compose.methods.ObjectElement.call(this,e);return this.element.filter(qg).forEach((e=>{e.setMetaProperty("referenced-element","pathItem")})),this.element.filter(Lg).forEach(((e,t)=>{e.setMetaProperty("webhook-name",t.toValue())})),t}}}),Pv={visitors:{value:tm.visitors.value,document:{objects:{OpenApi:{$visitor:Wm,fixedFields:{openapi:tm.visitors.document.objects.OpenApi.fixedFields.openapi,info:{$ref:"#/visitors/document/objects/Info"},jsonSchemaDialect:ng,servers:tm.visitors.document.objects.OpenApi.fixedFields.servers,paths:{$ref:"#/visitors/document/objects/Paths"},webhooks:Cv,components:{$ref:"#/visitors/document/objects/Components"},security:tm.visitors.document.objects.OpenApi.fixedFields.security,tags:tm.visitors.document.objects.OpenApi.fixedFields.tags,externalDocs:{$ref:"#/visitors/document/objects/ExternalDocumentation"}}},Info:{$visitor:Km,fixedFields:{title:tm.visitors.document.objects.Info.fixedFields.title,description:tm.visitors.document.objects.Info.fixedFields.description,summary:Hm,termsOfService:tm.visitors.document.objects.Info.fixedFields.termsOfService,contact:{$ref:"#/visitors/document/objects/Contact"},license:{$ref:"#/visitors/document/objects/License"},version:tm.visitors.document.objects.Info.fixedFields.version}},Contact:{$visitor:Zm,fixedFields:{name:tm.visitors.document.objects.Contact.fixedFields.name,url:tm.visitors.document.objects.Contact.fixedFields.url,email:tm.visitors.document.objects.Contact.fixedFields.email}},License:{$visitor:Xm,fixedFields:{name:tm.visitors.document.objects.License.fixedFields.name,identifier:Qm,url:tm.visitors.document.objects.License.fixedFields.url}},Server:{$visitor:og,fixedFields:{url:tm.visitors.document.objects.Server.fixedFields.url,description:tm.visitors.document.objects.Server.fixedFields.description,variables:tm.visitors.document.objects.Server.fixedFields.variables}},ServerVariable:{$visitor:ig,fixedFields:{enum:tm.visitors.document.objects.ServerVariable.fixedFields.enum,default:tm.visitors.document.objects.ServerVariable.fixedFields.default,description:tm.visitors.document.objects.ServerVariable.fixedFields.description}},Components:{$visitor:hg,fixedFields:{schemas:Yy,responses:tm.visitors.document.objects.Components.fixedFields.responses,parameters:tm.visitors.document.objects.Components.fixedFields.parameters,examples:tm.visitors.document.objects.Components.fixedFields.examples,requestBodies:tm.visitors.document.objects.Components.fixedFields.requestBodies,headers:tm.visitors.document.objects.Components.fixedFields.headers,securitySchemes:tm.visitors.document.objects.Components.fixedFields.securitySchemes,links:tm.visitors.document.objects.Components.fixedFields.links,callbacks:tm.visitors.document.objects.Components.fixedFields.callbacks,pathItems:ev}},Paths:{$visitor:lv},PathItem:{$visitor:wv,fixedFields:{$ref:tm.visitors.document.objects.PathItem.fixedFields.$ref,summary:tm.visitors.document.objects.PathItem.fixedFields.summary,description:tm.visitors.document.objects.PathItem.fixedFields.description,get:{$ref:"#/visitors/document/objects/Operation"},put:{$ref:"#/visitors/document/objects/Operation"},post:{$ref:"#/visitors/document/objects/Operation"},delete:{$ref:"#/visitors/document/objects/Operation"},options:{$ref:"#/visitors/document/objects/Operation"},head:{$ref:"#/visitors/document/objects/Operation"},patch:{$ref:"#/visitors/document/objects/Operation"},trace:{$ref:"#/visitors/document/objects/Operation"},servers:tm.visitors.document.objects.PathItem.fixedFields.servers,parameters:tm.visitors.document.objects.PathItem.fixedFields.parameters}},Operation:{$visitor:vv,fixedFields:{tags:tm.visitors.document.objects.Operation.fixedFields.tags,summary:tm.visitors.document.objects.Operation.fixedFields.summary,description:tm.visitors.document.objects.Operation.fixedFields.description,externalDocs:{$ref:"#/visitors/document/objects/ExternalDocumentation"},operationId:tm.visitors.document.objects.Operation.fixedFields.operationId,parameters:tm.visitors.document.objects.Operation.fixedFields.parameters,requestBody:tm.visitors.document.objects.Operation.fixedFields.requestBody,responses:{$ref:"#/visitors/document/objects/Responses"},callbacks:tm.visitors.document.objects.Operation.fixedFields.callbacks,deprecated:tm.visitors.document.objects.Operation.fixedFields.deprecated,security:tm.visitors.document.objects.Operation.fixedFields.security,servers:tm.visitors.document.objects.Operation.fixedFields.servers}},ExternalDocumentation:{$visitor:ov,fixedFields:{description:tm.visitors.document.objects.ExternalDocumentation.fixedFields.description,url:tm.visitors.document.objects.ExternalDocumentation.fixedFields.url}},Parameter:{$visitor:wg,fixedFields:{name:tm.visitors.document.objects.Parameter.fixedFields.name,in:tm.visitors.document.objects.Parameter.fixedFields.in,description:tm.visitors.document.objects.Parameter.fixedFields.description,required:tm.visitors.document.objects.Parameter.fixedFields.required,deprecated:tm.visitors.document.objects.Parameter.fixedFields.deprecated,allowEmptyValue:tm.visitors.document.objects.Parameter.fixedFields.allowEmptyValue,style:tm.visitors.document.objects.Parameter.fixedFields.style,explode:tm.visitors.document.objects.Parameter.fixedFields.explode,allowReserved:tm.visitors.document.objects.Parameter.fixedFields.allowReserved,schema:{$ref:"#/visitors/document/objects/Schema"},example:tm.visitors.document.objects.Parameter.fixedFields.example,examples:tm.visitors.document.objects.Parameter.fixedFields.examples,content:tm.visitors.document.objects.Parameter.fixedFields.content}},RequestBody:{$visitor:uv,fixedFields:{description:tm.visitors.document.objects.RequestBody.fixedFields.description,content:tm.visitors.document.objects.RequestBody.fixedFields.content,required:tm.visitors.document.objects.RequestBody.fixedFields.required}},MediaType:{$visitor:lg,fixedFields:{schema:{$ref:"#/visitors/document/objects/Schema"},example:tm.visitors.document.objects.MediaType.fixedFields.example,examples:tm.visitors.document.objects.MediaType.fixedFields.examples,encoding:tm.visitors.document.objects.MediaType.fixedFields.encoding}},Encoding:{$visitor:iv,fixedFields:{contentType:tm.visitors.document.objects.Encoding.fixedFields.contentType,headers:tm.visitors.document.objects.Encoding.fixedFields.headers,style:tm.visitors.document.objects.Encoding.fixedFields.style,explode:tm.visitors.document.objects.Encoding.fixedFields.explode,allowReserved:tm.visitors.document.objects.Encoding.fixedFields.allowReserved}},Responses:{$visitor:gv,fixedFields:{default:tm.visitors.document.objects.Responses.fixedFields.default}},Response:{$visitor:dv,fixedFields:{description:tm.visitors.document.objects.Response.fixedFields.description,headers:tm.visitors.document.objects.Response.fixedFields.headers,content:tm.visitors.document.objects.Response.fixedFields.content,links:tm.visitors.document.objects.Response.fixedFields.links}},Callback:{$visitor:hv},Example:{$visitor:nv,fixedFields:{summary:tm.visitors.document.objects.Example.fixedFields.summary,description:tm.visitors.document.objects.Example.fixedFields.description,value:tm.visitors.document.objects.Example.fixedFields.value,externalValue:tm.visitors.document.objects.Example.fixedFields.externalValue}},Link:{$visitor:tg,fixedFields:{operationRef:tm.visitors.document.objects.Link.fixedFields.operationRef,operationId:tm.visitors.document.objects.Link.fixedFields.operationId,parameters:tm.visitors.document.objects.Link.fixedFields.parameters,requestBody:tm.visitors.document.objects.Link.fixedFields.requestBody,description:tm.visitors.document.objects.Link.fixedFields.description,server:{$ref:"#/visitors/document/objects/Server"}}},Header:{$visitor:xg,fixedFields:{description:tm.visitors.document.objects.Header.fixedFields.description,required:tm.visitors.document.objects.Header.fixedFields.required,deprecated:tm.visitors.document.objects.Header.fixedFields.deprecated,allowEmptyValue:tm.visitors.document.objects.Header.fixedFields.allowEmptyValue,style:tm.visitors.document.objects.Header.fixedFields.style,explode:tm.visitors.document.objects.Header.fixedFields.explode,allowReserved:tm.visitors.document.objects.Header.fixedFields.allowReserved,schema:{$ref:"#/visitors/document/objects/Schema"},example:tm.visitors.document.objects.Header.fixedFields.example,examples:tm.visitors.document.objects.Header.fixedFields.examples,content:tm.visitors.document.objects.Header.fixedFields.content}},Tag:{$visitor:dg,fixedFields:{name:tm.visitors.document.objects.Tag.fixedFields.name,description:tm.visitors.document.objects.Tag.fixedFields.description,externalDocs:{$ref:"#/visitors/document/objects/ExternalDocumentation"}}},Reference:{$visitor:gg,fixedFields:{$ref:tm.visitors.document.objects.Reference.fixedFields.$ref,summary:yg,description:vg}},Schema:{$visitor:Qg,fixedFields:{$schema:ey,$vocabulary:ty,$id:ny,$anchor:ry,$dynamicAnchor:oy,$dynamicRef:sy,$ref:iy,$defs:ay,$comment:ly,allOf:cy,anyOf:uy,oneOf:py,not:{$ref:"#/visitors/document/objects/Schema"},if:{$ref:"#/visitors/document/objects/Schema"},then:{$ref:"#/visitors/document/objects/Schema"},else:{$ref:"#/visitors/document/objects/Schema"},dependentSchemas:hy,prefixItems:fy,items:{$ref:"#/visitors/document/objects/Schema"},contains:{$ref:"#/visitors/document/objects/Schema"},properties:dy,patternProperties:my,additionalProperties:{$ref:"#/visitors/document/objects/Schema"},propertyNames:{$ref:"#/visitors/document/objects/Schema"},unevaluatedItems:{$ref:"#/visitors/document/objects/Schema"},unevaluatedProperties:{$ref:"#/visitors/document/objects/Schema"},type:gy,enum:yy,const:vy,multipleOf:by,maximum:wy,exclusiveMaximum:Ey,minimum:xy,exclusiveMinimum:Sy,maxLength:_y,minLength:jy,pattern:Oy,maxItems:ky,minItems:Ay,uniqueItems:Cy,maxContains:Py,minContains:Ny,maxProperties:Iy,minProperties:Ty,required:Ry,dependentRequired:My,title:Dy,description:Fy,default:Ly,deprecated:By,readOnly:$y,writeOnly:qy,examples:Uy,format:zy,contentEncoding:Vy,contentMediaType:Wy,contentSchema:{$ref:"#/visitors/document/objects/Schema"},discriminator:{$ref:"#/visitors/document/objects/Discriminator"},xml:{$ref:"#/visitors/document/objects/XML"},externalDocs:{$ref:"#/visitors/document/objects/ExternalDocumentation"},example:Jy}},Discriminator:{$visitor:Hy,fixedFields:{propertyName:tm.visitors.document.objects.Discriminator.fixedFields.propertyName,mapping:tm.visitors.document.objects.Discriminator.fixedFields.mapping}},XML:{$visitor:Zy,fixedFields:{name:tm.visitors.document.objects.XML.fixedFields.name,namespace:tm.visitors.document.objects.XML.fixedFields.namespace,prefix:tm.visitors.document.objects.XML.fixedFields.prefix,attribute:tm.visitors.document.objects.XML.fixedFields.attribute,wrapped:tm.visitors.document.objects.XML.fixedFields.wrapped}},SecurityScheme:{$visitor:xv,fixedFields:{type:tm.visitors.document.objects.SecurityScheme.fixedFields.type,description:tm.visitors.document.objects.SecurityScheme.fixedFields.description,name:tm.visitors.document.objects.SecurityScheme.fixedFields.name,in:tm.visitors.document.objects.SecurityScheme.fixedFields.in,scheme:tm.visitors.document.objects.SecurityScheme.fixedFields.scheme,bearerFormat:tm.visitors.document.objects.SecurityScheme.fixedFields.bearerFormat,flows:{$ref:"#/visitors/document/objects/OAuthFlows"},openIdConnectUrl:tm.visitors.document.objects.SecurityScheme.fixedFields.openIdConnectUrl}},OAuthFlows:{$visitor:_v,fixedFields:{implicit:{$ref:"#/visitors/document/objects/OAuthFlow"},password:{$ref:"#/visitors/document/objects/OAuthFlow"},clientCredentials:{$ref:"#/visitors/document/objects/OAuthFlow"},authorizationCode:{$ref:"#/visitors/document/objects/OAuthFlow"}}},OAuthFlow:{$visitor:Ov,fixedFields:{authorizationUrl:tm.visitors.document.objects.OAuthFlow.fixedFields.authorizationUrl,tokenUrl:tm.visitors.document.objects.OAuthFlow.fixedFields.tokenUrl,refreshUrl:tm.visitors.document.objects.OAuthFlow.fixedFields.refreshUrl,scopes:tm.visitors.document.objects.OAuthFlow.fixedFields.scopes}},SecurityRequirement:{$visitor:ug}},extension:{$visitor:tm.visitors.document.extension.$visitor}}}};function Nv(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}const Iv=e=>{if(ds(e))return`${e.element.charAt(0).toUpperCase()+e.element.slice(1)}Element`},Tv=function(e){for(var t=1;t{const{base:t}=e;return t.register("callback",cm),t.register("components",um),t.register("contact",pm),t.register("discriminator",hm),t.register("encoding",fm),t.register("example",dm),t.register("externalDocumentation",mm),t.register("header",gm),t.register("info",ym),t.register("jsonSchemaDialect",bm),t.register("license",wm),t.register("link",Em),t.register("mediaType",xm),t.register("oAuthFlow",Sm),t.register("oAuthFlows",_m),t.register("openapi",jm),t.register("openApi3_1",km),t.register("operation",Am),t.register("parameter",Cm),t.register("pathItem",Pm),t.register("paths",Nm),t.register("reference",Tm),t.register("requestBody",Rm),t.register("response",Mm),t.register("responses",Dm),t.register("schema",Lm),t.register("securityRequirement",Bm),t.register("securityScheme",$m),t.register("server",qm),t.register("serverVariable",Um),t.register("tag",zm),t.register("xml",Vm),t}};function Mv(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function Dv(e){for(var t=1;t{const e=zs(Rv);return{predicates:Dv(Dv({},c),{},{isStringElement:ms,isArrayElement:ws,isObjectElement:bs,includesClasses:Ns}),namespace:e}};function Lv(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}const Bv=(e,{specPath:t=["visitors","document","objects","OpenApi","$visitor"],plugins:n=[]}={})=>{const r=(0,Pt.Qc)(e),o=Za(Pv),s=is(t,[],o);return fi(r,s,{state:{specObj:o}}),di(s.element,n,{toolboxCreator:Fv,visitorOptions:{keyMap:Tv,nodeTypeGetter:Iv}})},$v=e=>(t,n={})=>Bv(t,function(e){for(var t=1;te.includes(t)))}findBy(e="3.1.0",t="generic"){const n="generic"===t?`vnd.oai.openapi;version=${e}`:`vnd.oai.openapi+${t};version=${e}`;return this.find((e=>e.includes(n)))||this.unknownMediaType}latest(e="generic"){return ao(this.filterByFormat(e))}}const zv=new Uv("application/vnd.oai.openapi;version=3.1.0","application/vnd.oai.openapi+json;version=3.1.0","application/vnd.oai.openapi+yaml;version=3.1.0");var Vv=n(34155),Wv=Or((function(e,t){return gr(Io(""),Fr(as(e)),io(""))(t)}));const Jv=Wv;const Kv=pr(qo);const Hv=Zt(1,gr(cn,Xr("RegExp")));const Gv=Bo(Xs,Co(/[.*+?^${}()|[\]\\-]/g,"\\$&"));var Zv=function(e,t){if("string"!=typeof e&&!(e instanceof String))throw TypeError("`".concat(t,"` must be a string"))};var Yv=Zt(3,(function(e,t,n){!function(e,t,n){if(null==n||null==e||null==t)throw TypeError("Input values must not be `null` or `undefined`")}(e,t,n),Zv(n,"str"),Zv(t,"replaceValue"),function(e){if(!("string"==typeof e||e instanceof String||e instanceof RegExp))throw TypeError("`searchValue` must be a string or an regexp")}(e);var r=new RegExp(Hv(e)?e:Gv(e),"g");return Co(r,t,n)})),Xv=oo(2,"replaceAll");const Qv=ts(String.prototype.replaceAll)?Xv:Yv,eb=()=>wo(Ro(/^win/),["platform"],Vv),tb=e=>{try{const t=new URL(e);return Jv(":",t.protocol)}catch{return}},nb=(gr(tb,Kv),e=>{if(Vv.browser)return!1;const t=tb(e);return qo(t)||"file"===t||/^[a-zA-Z]$/.test(t)}),rb=e=>{const t=tb(e);return"http"===t||"https"===t},ob=(e,t)=>{const n=[/%23/g,"#",/%24/g,"$",/%26/g,"&",/%2C/g,",",/%40/g,"@"],r=So(!1,"keepFileProtocol",t),o=So(eb,"isWindows",t);let s=decodeURI(e);for(let e=0;e{const t=e.indexOf("#");return-1!==t?e.substr(t):"#"},ib=e=>{const t=e.indexOf("#");let n=e;return t>=0&&(n=e.substr(0,t)),n},ab=()=>{if(Vv.browser)return ib(globalThis.location.href);const e=Vv.cwd(),t=ao(e);return["/","\\"].includes(t)?e:e+(eb()?"\\":"/")},lb=(e,t)=>{const n=new URL(t,new URL(e,"resolve://"));if("resolve:"===n.protocol){const{pathname:e,search:t,hash:r}=n;return e+t+r}return n.toString()},cb=e=>nb(e)?(e=>{const t=[/\?/g,"%3F",/#/g,"%23"];let n=e;eb()&&(n=n.replace(/\\/g,"/")),n=encodeURI(n);for(let e=0;enb(e)?ob(e):decodeURI(e),pb=Ys({props:{uri:"",value:null,depth:0,refSet:null,errors:[]},init({depth:e=this.depth,refSet:t=this.refSet,uri:n=this.uri,value:r=this.value}={}){this.uri=n,this.value=r,this.depth=e,this.refSet=t,this.errors=[]}}),hb=pb,fb=Ys({props:{rootRef:null,refs:[],circular:!1},init({refs:e=[]}={}){this.refs=[],e.forEach((e=>this.add(e)))},methods:{get size(){return this.refs.length},add(e){return this.has(e)||(this.refs.push(e),this.rootRef=null===this.rootRef?e:this.rootRef,e.refSet=this),this},merge(e){for(const t of e.values())this.add(t);return this},has(e){const t=Xs(e)?e:e.uri;return Kv(this.find(xo(t,"uri")))},find(e){return this.refs.find(e)},*values(){yield*this.refs},clean(){this.refs.forEach((e=>{e.refSet=null})),this.refs=[]}}}),db=fb,mb={parse:{mediaType:"text/plain",parsers:[],parserOpts:{}},resolve:{baseURI:"",resolvers:[],resolverOpts:{},strategies:[],external:!0,maxDepth:1/0},dereference:{strategies:[],refSet:null,maxDepth:1/0}},gb=lo(uo(["resolve","baseURI"]),or(["resolve","baseURI"])),yb=e=>Ri(e)?ab():e,vb=Ys({props:{uri:null,mediaType:"text/plain",data:null,parseResult:null},init({uri:e=this.uri,mediaType:t=this.mediaType,data:n=this.data,parseResult:r=this.parseResult}={}){this.uri=e,this.mediaType=t,this.data=n,this.parseResult=r},methods:{get extension(){return Xs(this.uri)?(e=>{const t=e.lastIndexOf(".");return t>=0?e.substr(t).toLowerCase():""})(this.uri):""},toString(){if("string"==typeof this.data)return this.data;if(this.data instanceof ArrayBuffer||["ArrayBuffer"].includes(cn(this.data))||ArrayBuffer.isView(this.data)){return new TextDecoder("utf-8").decode(this.data)}return String(this.data)}}});class bb extends Error{constructor(e,t){if(super(e),this.name=this.constructor.name,this.message=e,"function"==typeof Error.captureStackTrace?Error.captureStackTrace(this,this.constructor):this.stack=new Error(e).stack,$s(t)&&Gr("cause",t)&&!Gr("cause",this)){const{cause:e}=t;this.cause=e,Gr("stack",e)&&(this.stack=`${this.stack}\nCAUSE: ${null==e?void 0:e.stack}`)}}}const wb=bb;const Eb=class extends wb{constructor(e,t){super(e,{cause:t.cause}),Xo(this,"plugin",void 0),this.plugin=t.plugin}},xb=async(e,t,n)=>{const r=await Promise.all(n.map(is([e],[t])));return n.filter(((e,t)=>r[t]))},Sb=async(e,t,n)=>{let r;for(const o of n)try{const n=await o[e].call(o,...t);return{plugin:o,result:n}}catch(e){r=new Eb("Error while running plugin",{cause:e,plugin:o})}return Promise.reject(r)};const _b=class extends wb{};const jb=class extends _b{};const Ob=class extends wb{},kb=async(e,t)=>{let n=e,r=!1;if(!Os(e)){const t=new e.constructor(e.content,e.meta.clone(),e.attributes);t.classes.push("result"),n=new zo([t]),r=!0}const o=vb({uri:t.resolve.baseURI,parseResult:n,mediaType:t.parse.mediaType}),s=await xb("canDereference",o,t.dereference.strategies);if(so(s))throw new jb(o.uri);try{const{result:e}=await Sb("dereference",[o,t],s);return r?e.get(0):e}catch(e){throw new Ob(`Error while dereferencing file "${o.uri}"`,{cause:e})}},Ab=async(e,t={})=>{const n=((e,t)=>{const n=mo(e,t);return vo(gb,yb,n)})(mb,t);return kb(e,n)};const Cb=class extends wb{constructor(e="Not Implemented",t){super(e,t)}},Pb=Ys({props:{name:"",allowEmpty:!0,sourceMap:!1,fileExtensions:[],mediaTypes:[]},init({allowEmpty:e=this.allowEmpty,sourceMap:t=this.sourceMap,fileExtensions:n=this.fileExtensions,mediaTypes:r=this.mediaTypes}={}){this.allowEmpty=e,this.sourceMap=t,this.fileExtensions=n,this.mediaTypes=r},methods:{async canParse(){throw new Cb},async parse(){throw new Cb}}}),Nb=Pb,Ib=Ys(Nb,{props:{name:"binary"},methods:{async canParse(e){return 0===this.fileExtensions.length||this.fileExtensions.includes(e.extension)},async parse(e){try{const t=unescape(encodeURIComponent(e.toString())),n=btoa(t),r=new zo;if(0!==n.length){const e=new Pt.RP(n);e.classes.push("result"),r.push(e)}return r}catch(t){throw new _b(`Error parsing "${e.uri}"`,{cause:t})}}}}),Tb=Ys({props:{name:null},methods:{canResolve:()=>!1,async resolve(){throw new Cb}}});const Rb=Zt(1,$n(Promise.all,Promise));const Mb=class extends wb{};const Db=class extends Mb{};const Fb=class extends Ob{};const Lb=class extends Mb{};function Bb(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function $b(e){for(var t=1;t{const n=vb({uri:cb(ib(e)),mediaType:t.parse.mediaType}),r=await(async(e,t)=>{const n=t.resolve.resolvers.map((e=>{const n=Object.create(e);return Object.assign(n,t.resolve.resolverOpts)})),r=await xb("canRead",e,n);if(so(r))throw new Lb(e.uri);try{const{result:t}=await Sb("read",[e],r);return t}catch(t){throw new Mb(`Error while reading file "${e.uri}"`,{cause:t})}})(n,t);return(async(e,t)=>{const n=t.parse.parsers.map((e=>{const n=Object.create(e);return Object.assign(n,t.parse.parserOpts)})),r=await xb("canParse",e,n);if(so(r))throw new Lb(e.uri);try{const{plugin:t,result:n}=await Sb("parse",[e],r);return!t.allowEmpty&&n.isEmpty?Promise.reject(new _b(`Error while parsing file "${e.uri}". File is empty.`)):n}catch(t){throw new _b(`Error while parsing file "${e.uri}"`,{cause:t})}})(vb($b($b({},n),{},{data:r})),t)},Ub=(e,t)=>{const n=hi({predicate:e});return fi(t,n),new Pt.O4(n.result)};class zb extends Error{constructor(e){super(e),this.name=this.constructor.name,this.message=e,"function"==typeof Error.captureStackTrace?Error.captureStackTrace(this,this.constructor):this.stack=new Error(e).stack}}const Vb=(e,t)=>{const n=hi({predicate:e,returnOnTrue:ei});return fi(t,n),bo(void 0,[0],n.result)};const Wb=class extends wb{};class Jb extends Wb{constructor(e){super(`Invalid JSON Schema $anchor "${e}".`)}}class Kb extends Error{constructor(e){super(e),this.name=this.constructor.name,this.message=e,"function"==typeof Error.captureStackTrace?Error.captureStackTrace(this,this.constructor):this.stack=new Error(e).stack}}const Hb=e=>/^[A-Za-z_][A-Za-z_0-9.-]*$/.test(e),Gb=e=>{const t=sb(e);return qi("#",t)},Zb=(e,t)=>{const n=(e=>{if(!Hb(e))throw new Jb(e);return e})(e),r=Vb((e=>{var t;return Jg(e)&&(null===(t=e.$anchor)||void 0===t?void 0:t.toValue())===n}),t);if(qo(r))throw new Kb(`Evaluation failed on token: "${n}"`);return r},Yb=(e,t)=>{if(void 0===t.$ref)return;const n=sb(t.$ref.toValue()),r=t.meta.get("inherited$id").toValue();return`${Jn(((e,t)=>lb(e,cb(ib(t)))),e,[...r,t.$ref.toValue()])}${"#"===n?"":n}`},Xb=e=>{if(Xb.cache.has(e))return Xb.cache.get(e);const t=Lm.refract(e);return Xb.cache.set(e,t),t};Xb.cache=new WeakMap;const Qb=e=>As(e)?Xb(e):e,ew=(e,t)=>{const{cache:n}=ew,r=ib(e),o=e=>Jg(e)&&void 0!==e.$id;if(!n.has(t)){const e=Ub(o,t);n.set(t,Array.from(e))}const s=n.get(t).find((e=>((e,t)=>{if(void 0===t.$id)return;const n=t.meta.get("inherited$id").toValue();return Jn(((e,t)=>lb(e,cb(ib(t)))),e,[...n,t.$id.toValue()])})(r,e)===r));if(qo(s))throw new zb(`Evaluation failed on URI: "${e}"`);let i,a;return Hb(Gb(e))?(i=Zb,a=Gb(e)):(i=Ji,a=Ki(e)),i(a,s)};function tw(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function nw(e){for(var t=1;t=this.options.resolve.maxDepth)throw new Db(`Maximum resolution depth of ${this.options.resolve.maxDepth} has been exceeded by file "${this.reference.uri}"`);const t=this.toBaseURI(e),{refSet:n}=this.reference;if(n.has(t))return n.find(xo(t,"uri"));const r=await qb(ub(t),nw(nw({},this.options),{},{parse:nw(nw({},this.options.parse),{},{mediaType:"text/plain"})})),o=hb({uri:t,value:r,depth:this.reference.depth+1});return n.add(o),o},ReferenceElement(e){var t;if(!this.options.resolve.external&&Ug(e))return!1;const n=null===(t=e.$ref)||void 0===t?void 0:t.toValue(),r=this.toBaseURI(n);Hr(r,this.crawlingMap)||(this.crawlingMap[r]=this.toReference(n)),this.crawledElements.push(e)},PathItemElement(e){var t;if(!ms(e.$ref))return;if(!this.options.resolve.external&&Bg(e))return;const n=null===(t=e.$ref)||void 0===t?void 0:t.toValue(),r=this.toBaseURI(n);Hr(r,this.crawlingMap)||(this.crawlingMap[r]=this.toReference(n)),this.crawledElements.push(e)},LinkElement(e){if((ms(e.operationRef)||ms(e.operationId))&&(this.options.resolve.external||!Tg(e))){if(ms(e.operationRef)&&ms(e.operationId))throw new Error("LinkElement operationRef and operationId are mutually exclusive.");if(Tg(e)){var t;const n=null===(t=e.operationRef)||void 0===t?void 0:t.toValue(),r=this.toBaseURI(n);Hr(r,this.crawlingMap)||(this.crawlingMap[r]=this.toReference(n))}}},ExampleElement(e){var t;if(!ms(e.externalValue))return;if(!this.options.resolve.external&&ms(e.externalValue))return;if(e.hasKey("value")&&ms(e.externalValue))throw new Error("ExampleElement value and externalValue fields are mutually exclusive.");const n=null===(t=e.externalValue)||void 0===t?void 0:t.toValue(),r=this.toBaseURI(n);Hr(r,this.crawlingMap)||(this.crawlingMap[r]=this.toReference(n))},SchemaElement(e){if(this.visited.has(e))return!1;if(!ms(e.$ref))return void this.visited.add(e);const t=this.reference.uri,n=Yb(t,e),r=ib(n),o=vb({uri:r}),s=go((e=>e.canRead(o)),this.options.resolve.resolvers),i=!s,a=!s&&this.reference.uri!==r;if(this.options.resolve.external||!a){if(!Hr(r,this.crawlingMap))try{this.crawlingMap[r]=s||i?this.reference:this.toReference(ub(n))}catch(e){if(!(i&&e instanceof zb))throw e;this.crawlingMap[r]=this.toReference(ub(n))}this.crawledElements.push(e)}else this.visited.add(e)},async crawlReferenceElement(e){var t;const n=await this.toReference(e.$ref.toValue());this.indirections.push(e);const r=Ki(null===(t=e.$ref)||void 0===t?void 0:t.toValue());let o=Ji(r,n.value.result);if(As(o)){const t=e.meta.get("referenced-element").toValue();if(Uc(o))o=Tm.refract(o),o.setMetaProperty("referenced-element",t);else{o=this.namespace.getElementClass(t).refract(o)}}if(this.indirections.includes(o))throw new Error("Recursive Reference Object detected");if(this.indirections.length>this.options.dereference.maxDepth)throw new Fb(`Maximum dereference depth of "${this.options.dereference.maxDepth}" has been exceeded in file "${this.reference.uri}"`);const s=ow({reference:n,namespace:this.namespace,indirections:[...this.indirections],options:this.options});await rw(o,s,{keyMap:Tv,nodeTypeGetter:Iv}),await s.crawl(),this.indirections.pop()},async crawlPathItemElement(e){var t;const n=await this.toReference(e.$ref.toValue());this.indirections.push(e);const r=Ki(null===(t=e.$ref)||void 0===t?void 0:t.toValue());let o=Ji(r,n.value.result);if(As(o)&&(o=Pm.refract(o)),this.indirections.includes(o))throw new Error("Recursive Path Item Object reference detected");if(this.indirections.length>this.options.dereference.maxDepth)throw new Fb(`Maximum dereference depth of "${this.options.dereference.maxDepth}" has been exceeded in file "${this.reference.uri}"`);const s=ow({reference:n,namespace:this.namespace,indirections:[...this.indirections],options:this.options});await rw(o,s,{keyMap:Tv,nodeTypeGetter:Iv}),await s.crawl(),this.indirections.pop()},async crawlSchemaElement(e){const t=this.reference.uri,n=Yb(t,e),r=ib(n),o=vb({uri:r}),s=go((e=>e.canRead(o)),this.options.resolve.resolvers),i=!s;let a,l;this.indirections.push(e);try{if(s||i){a=this.reference;l=ew(n,Qb(a.value.result))}else{a=await this.toReference(ub(n));const e=Ki(n);l=Qb(Ji(e,a.value.result))}}catch(e){if(!(i&&e instanceof zb))throw e;if(Hb(Gb(n))){a=await this.toReference(ub(n));const e=Gb(n);l=Zb(e,Qb(a.value.result))}else{a=await this.toReference(ub(n));const e=Ki(n);l=Qb(Ji(e,a.value.result))}}if(this.visited.add(e),this.indirections.includes(l))throw new Error("Recursive Schema Object reference detected");if(this.indirections.length>this.options.dereference.maxDepth)throw new Fb(`Maximum dereference depth of "${this.options.dereference.maxDepth}" has been exceeded in file "${this.reference.uri}"`);const c=ow({reference:a,namespace:this.namespace,indirections:[...this.indirections],options:this.options,visited:this.visited});await rw(l,c,{keyMap:Tv,nodeTypeGetter:Iv}),await c.crawl(),this.indirections.pop()},async crawl(){await gr(nr,Rb)(this.crawlingMap),this.crawlingMap=null;for(const e of this.crawledElements)qg(e)?await this.crawlReferenceElement(e):Jg(e)?await this.crawlSchemaElement(e):Lg(e)&&await this.crawlPathItemElement(e)}}}),sw=ow,iw=fi[Symbol.for("nodejs.util.promisify.custom")],aw=Ys(Tb,{init(){this.name="openapi-3-1"},methods:{canResolve(e){var t;return"text/plain"!==e.mediaType?zv.includes(e.mediaType):Mg(null===(t=e.parseResult)||void 0===t?void 0:t.result)},async resolve(e,t){const n=zs(Rv),r=hb({uri:e.uri,value:e.parseResult}),o=sw({reference:r,namespace:n,options:t}),s=db();return s.add(r),await iw(s.rootRef.value,o,{keyMap:Tv,nodeTypeGetter:Iv}),await o.crawl(),s}}}),lw=aw,cw=e=>e.replace(/\s/g,""),uw=e=>e.replace(/\W/gi,"_"),pw=(e,t,n)=>{const r=cw(e);return r.length>0?uw(r):((e,t)=>`${uw(cw(t.toLowerCase()))}${uw(cw(e))}`)(t,n)},hw=({operationIdNormalizer:e=pw}={})=>({predicates:t,namespace:n})=>{const r=[],o=[],s=[];return{visitor:{OpenApi3_1Element:{leave(){const e=Jr((e=>Ti(e.operationId)),o);Object.entries(e).forEach((([e,t])=>{t.length<=1||t.forEach(((t,r)=>{const o=`${e}${r+1}`;t.operationId=new n.elements.String(o)}))})),s.forEach((e=>{var t;if(void 0===e.operationId)return;const n=String(Ti(e.operationId)),r=o.find((e=>Ti(e.meta.get("originalOperationId"))===n));void 0!==r&&(e.operationId=null===(t=r.operationId)||void 0===t?void 0:t.clone(),e.meta.set("originalOperationId",n),e.set("__originalOperationId",n))})),o.length=0,s.length=0}},PathItemElement:{enter(e){const t=kr("path",Ti(e.meta.get("path")));r.push(t)},leave(){r.pop()}},OperationElement:{enter(t){if(void 0===t.operationId)return;const s=String(Ti(t.operationId)),i=ao(r),a=kr("method",Ti(t.meta.get("http-method"))),l=e(s,i,a);s!==l&&(t.operationId=new n.elements.String(l),t.set("__originalOperationId",s),t.meta.set("originalOperationId",s),o.push(t))}},LinkElement:{leave(e){t.isLinkElement(e)&&void 0!==e.operationId&&s.push(e)}}}}},fw=()=>({predicates:e})=>{const t=(t,n)=>!!e.isParameterElement(t)&&(!!e.isParameterElement(n)&&(!!e.isStringElement(t.name)&&(!!e.isStringElement(t.in)&&(!!e.isStringElement(n.name)&&(!!e.isStringElement(n.in)&&(Ti(t.name)===Ti(n.name)&&Ti(t.in)===Ti(n.in))))))),n=[];return{visitor:{PathItemElement:{enter(t,r,o,s,i){if(i.some(e.isComponentsElement))return;const{parameters:a}=t;e.isArrayElement(a)?n.push([...a.content]):n.push([])},leave(){n.pop()}},OperationElement:{leave(e){const r=ao(n);if(!Array.isArray(r)||0===r.length)return;const o=bo([],["parameters","content"],e),s=Lo(t,[...o,...r]);e.parameters=new ld(s)}}}}},dw=()=>({predicates:e})=>{let t;return{visitor:{OpenApi3_1Element:{enter(n){e.isArrayElement(n.security)&&(t=n.security)},leave(){t=void 0}},OperationElement:{leave(n,r,o,s,i){if(i.some(e.isComponentsElement))return;var a;void 0===n.security&&void 0!==t&&(n.security=new yd(null===(a=t)||void 0===a?void 0:a.content))}}}}},mw=()=>({predicates:e})=>{let t;const n=[];return{visitor:{OpenApi3_1Element:{enter(n){var r;e.isArrayElement(n.servers)&&(t=null===(r=n.servers)||void 0===r?void 0:r.content)},leave(){t=void 0}},PathItemElement:{enter(r,o,s,i,a){if(a.some(e.isComponentsElement))return;void 0===r.servers&&void 0!==t&&(r.servers=new kd(t));const{servers:l}=r;void 0!==l&&e.isArrayElement(l)?n.push([...l.content]):n.push(void 0)},leave(){n.pop()}},OperationElement:{enter(t){const r=ao(n);void 0!==r&&(e.isArrayElement(t.servers)||(t.servers=new wd(r)))}}}}},gw=()=>({predicates:e})=>({visitor:{ParameterElement:{leave(t,n,r,o,s){var i,a;if(!s.some(e.isComponentsElement)&&void 0!==t.schema&&e.isSchemaElement(t.schema)&&(void 0!==(null===(i=t.schema)||void 0===i?void 0:i.example)||void 0!==(null===(a=t.schema)||void 0===a?void 0:a.examples))){if(void 0!==t.examples&&e.isObjectElement(t.examples)){const e=t.examples.map((e=>{var t;return null===(t=e.value)||void 0===t?void 0:t.clone()}));return void 0!==t.schema.examples&&t.schema.set("examples",e),void(void 0!==t.schema.example&&t.schema.set("example",e))}void 0!==t.example&&(void 0!==t.schema.examples&&t.schema.set("examples",[t.example.clone()]),void 0!==t.schema.example&&t.schema.set("example",t.example.clone()))}}}}}),yw=()=>({predicates:e})=>({visitor:{HeaderElement:{leave(t,n,r,o,s){var i,a;if(!s.some(e.isComponentsElement)&&void 0!==t.schema&&e.isSchemaElement(t.schema)&&(void 0!==(null===(i=t.schema)||void 0===i?void 0:i.example)||void 0!==(null===(a=t.schema)||void 0===a?void 0:a.examples))){if(void 0!==t.examples&&e.isObjectElement(t.examples)){const e=t.examples.map((e=>{var t;return null===(t=e.value)||void 0===t?void 0:t.clone()}));return void 0!==t.schema.examples&&t.schema.set("examples",e),void(void 0!==t.schema.example&&t.schema.set("example",e))}void 0!==t.example&&(void 0!==t.schema.examples&&t.schema.set("examples",[t.example.clone()]),void 0!==t.schema.example&&t.schema.set("example",t.example.clone()))}}}}}),vw=e=>t=>{if(t?.$$normalized)return t;if(vw.cache.has(t))return t;const n=km.refract(t),r=e(n),o=Ti(r);return vw.cache.set(t,o),o};vw.cache=new WeakMap;const bw=e=>{if(!bs(e))return e;if(e.hasKey("$$normalized"))return e;const t=[hw({operationIdNormalizer:(e,t,n)=>(0,He.Z)({operationId:e},t,n,{v2OperationIdCompatibilityMode:!1})}),fw(),dw(),mw(),gw(),yw()],n=di(e,t,{toolboxCreator:Fv,visitorOptions:{keyMap:Tv,nodeTypeGetter:Iv}});return n.set("$$normalized",!0),n},ww=Ys({props:{name:null},methods:{canRead:()=>!1,async read(){throw new Cb}}}),Ew=Ys(ww,{props:{timeout:5e3,redirects:5,withCredentials:!1},init({timeout:e=this.timeout,redirects:t=this.redirects,withCredentials:n=this.withCredentials}={}){this.timeout=e,this.redirects=t,this.withCredentials=n},methods:{canRead:e=>rb(e.uri),async read(){throw new Cb},getHttpClient(){throw new Cb}}}).compose({props:{name:"http-swagger-client",swaggerHTTPClient:ct,swaggerHTTPClientConfig:{}},init(){let{swaggerHTTPClient:e=this.swaggerHTTPClient}=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};this.swaggerHTTPClient=e},methods:{getHttpClient(){return this.swaggerHTTPClient},async read(e){const t=this.getHttpClient(),n=new AbortController,{signal:r}=n,o=setTimeout((()=>{n.abort()}),this.timeout),s=this.getHttpClient().withCredentials||this.withCredentials?"include":"same-origin",i=0===this.redirects?"error":"follow",a=this.redirects>0?this.redirects:void 0;try{return(await t(f()({url:e.uri,signal:r,userFetch:async(e,t)=>{let n=await fetch(e,t);try{n.headers.delete("Content-Type")}catch{n=new Response(n.body,f()(f()({},n),{},{headers:new Headers(n.headers)})),n.headers.delete("Content-Type")}return n},credentials:s,redirects:i,follow:a},this.swaggerHTTPClientConfig))).text.arrayBuffer()}catch(t){throw new Mb(`Error downloading "${e.uri}"`,{cause:t})}finally{clearTimeout(o)}}}}),xw=Nb.compose({props:{name:"json-swagger-client",fileExtensions:[".json"],mediaTypes:["application/json"]},methods:{async canParse(e){const t=0===this.fileExtensions.length||this.fileExtensions.includes(e.extension),n=this.mediaTypes.includes(e.mediaType);if(!t)return!1;if(n)return!0;if(!n)try{return JSON.parse(e.toString()),!0}catch(e){return!1}return!1},async parse(e){if(this.sourceMap)throw new _b("json-swagger-client parser plugin doesn't support sourceMaps option");const t=new zo,n=e.toString();if(this.allowEmpty&&""===n.trim())return t;try{const e=Ii(JSON.parse(n));return e.classes.push("result"),t.push(e),t}catch(t){throw new _b(`Error parsing "${e.uri}"`,{cause:t})}}}}),Sw=Nb.compose({props:{name:"yaml-1-2-swagger-client",fileExtensions:[".yaml",".yml"],mediaTypes:["text/yaml","application/yaml"]},methods:{async canParse(e){const t=0===this.fileExtensions.length||this.fileExtensions.includes(e.extension),n=this.mediaTypes.includes(e.mediaType);if(!t)return!1;if(n)return!0;if(!n)try{return le.ZP.load(e.toString(),{schema:le.A8}),!0}catch(e){return!1}return!1},async parse(e){if(this.sourceMap)throw new _b("yaml-1-2-swagger-client parser plugin doesn't support sourceMaps option");const t=new zo,n=e.toString();try{const e=le.ZP.load(n,{schema:le.A8});if(this.allowEmpty&&void 0===e)return t;const r=Ii(e);return r.classes.push("result"),t.push(r),t}catch(t){throw new _b(`Error parsing "${e.uri}"`,{cause:t})}}}}),_w=Nb.compose({props:{name:"openapi-json-3-1-swagger-client",fileExtensions:[".json"],mediaTypes:new Uv(...zv.filterByFormat("generic"),...zv.filterByFormat("json")),detectionRegExp:/"openapi"\s*:\s*"(?3\.1\.(?:[1-9]\d*|0))"/},methods:{async canParse(e){const t=0===this.fileExtensions.length||this.fileExtensions.includes(e.extension),n=this.mediaTypes.includes(e.mediaType);if(!t)return!1;if(n)return!0;if(!n)try{const t=e.toString();return JSON.parse(t),this.detectionRegExp.test(t)}catch(e){return!1}return!1},async parse(e){if(this.sourceMap)throw new _b("openapi-json-3-1-swagger-client parser plugin doesn't support sourceMaps option");const t=new zo,n=e.toString();if(this.allowEmpty&&""===n.trim())return t;try{const e=JSON.parse(n),r=km.refract(e,this.refractorOpts);return r.classes.push("result"),t.push(r),t}catch(t){throw new _b(`Error parsing "${e.uri}"`,{cause:t})}}}}),jw=Nb.compose({props:{name:"openapi-yaml-3-1-swagger-client",fileExtensions:[".yaml",".yml"],mediaTypes:new Uv(...zv.filterByFormat("generic"),...zv.filterByFormat("yaml")),detectionRegExp:/(?^(["']?)openapi\2\s*:\s*(["']?)(?3\.1\.(?:[1-9]\d*|0))\3(?:\s+|$))|(?"openapi"\s*:\s*"(?3\.1\.(?:[1-9]\d*|0))")/m},methods:{async canParse(e){const t=0===this.fileExtensions.length||this.fileExtensions.includes(e.extension),n=this.mediaTypes.includes(e.mediaType);if(!t)return!1;if(n)return!0;if(!n)try{const t=e.toString();return le.ZP.load(t),this.detectionRegExp.test(t)}catch(e){return!1}return!1},async parse(e){if(this.sourceMap)throw new _b("openapi-yaml-3-1-swagger-client parser plugin doesn't support sourceMaps option");const t=new zo,n=e.toString();try{const e=le.ZP.load(n,{schema:le.A8});if(this.allowEmpty&&void 0===e)return t;const r=km.refract(e,this.refractorOpts);return r.classes.push("result"),t.push(r),t}catch(t){throw new _b(`Error parsing "${e.uri}"`,{cause:t})}}}}),Ow=Ys({props:{name:null},methods:{canDereference:()=>!1,async dereference(){throw new Cb}}});function kw(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function Aw(e){for(var t=1;t=this.options.resolve.maxDepth)throw new Db(`Maximum resolution depth of ${this.options.resolve.maxDepth} has been exceeded by file "${this.reference.uri}"`);const t=this.toBaseURI(e),{refSet:n}=this.reference;if(n.has(t))return n.find(xo(t,"uri"));const r=await qb(ub(t),Aw(Aw({},this.options),{},{parse:Aw(Aw({},this.options.parse),{},{mediaType:"text/plain"})})),o=hb({uri:t,value:r,depth:this.reference.depth+1});return n.add(o),o},async ReferenceElement(e,t,n,r,o){var s,i,a,l,c;const[u,p]=this.toAncestorLineage([...o,n]);if(u.some((t=>t.has(e))))return!1;if(!this.options.resolve.external&&Ug(e))return!1;const h=await this.toReference(null===(s=e.$ref)||void 0===s?void 0:s.toValue()),f=h.uri,d=lb(f,null===(i=e.$ref)||void 0===i?void 0:i.toValue());this.indirections.push(e);const m=Ki(d);let g=Ji(m,h.value.result);if(As(g)){const t=e.meta.get("referenced-element").toValue();if(Uc(g))g=Tm.refract(g),g.setMetaProperty("referenced-element",t);else{g=this.namespace.getElementClass(t).refract(g)}}if(this.indirections.includes(g))throw new Error("Recursive Reference Object detected");if(this.indirections.length>this.options.dereference.maxDepth)throw new Fb(`Maximum dereference depth of "${this.options.dereference.maxDepth}" has been exceeded in file "${this.reference.uri}"`);p.add(e);const y=Pw({reference:h,namespace:this.namespace,indirections:[...this.indirections],options:this.options,ancestors:u});g=await Cw(g,y,{keyMap:Tv,nodeTypeGetter:Iv}),p.delete(e),this.indirections.pop(),g=g.clone(),g.setMetaProperty("ref-fields",{$ref:null===(a=e.$ref)||void 0===a?void 0:a.toValue(),description:null===(l=e.description)||void 0===l?void 0:l.toValue(),summary:null===(c=e.summary)||void 0===c?void 0:c.toValue()}),g.setMetaProperty("ref-origin",h.uri);const v=wo(Kv,["description"],e),b=wo(Kv,["summary"],e);return v&&Gr("description",g)&&(g.description=e.description),b&&Gr("summary",g)&&(g.summary=e.summary),this.indirections.pop(),g},async PathItemElement(e,t,n,r,o){var s,i,a;const[l,c]=this.toAncestorLineage([...o,n]);if(!ms(e.$ref))return;if(l.some((t=>t.has(e))))return!1;if(!this.options.resolve.external&&Bg(e))return;const u=await this.toReference(null===(s=e.$ref)||void 0===s?void 0:s.toValue()),p=u.uri,h=lb(p,null===(i=e.$ref)||void 0===i?void 0:i.toValue());this.indirections.push(e);const f=Ki(h);let d=Ji(f,u.value.result);if(As(d)&&(d=Pm.refract(d)),this.indirections.includes(d))throw new Error("Recursive Path Item Object reference detected");if(this.indirections.length>this.options.dereference.maxDepth)throw new Fb(`Maximum dereference depth of "${this.options.dereference.maxDepth}" has been exceeded in file "${this.reference.uri}"`);c.add(e);const m=Pw({reference:u,namespace:this.namespace,indirections:[...this.indirections],options:this.options,ancestors:l});d=await Cw(d,m,{keyMap:Tv,nodeTypeGetter:Iv}),c.delete(e),this.indirections.pop();const g=new Pm([...d.content],d.meta.clone(),d.attributes.clone());return e.forEach(((e,t,n)=>{g.remove(t.toValue()),g.content.push(n)})),g.remove("$ref"),g.setMetaProperty("ref-fields",{$ref:null===(a=e.$ref)||void 0===a?void 0:a.toValue()}),g.setMetaProperty("ref-origin",u.uri),g},async LinkElement(e){if(!ms(e.operationRef)&&!ms(e.operationId))return;if(!this.options.resolve.external&&Tg(e))return;if(ms(e.operationRef)&&ms(e.operationId))throw new Error("LinkElement operationRef and operationId fields are mutually exclusive.");let t;if(ms(e.operationRef)){var n,r,o;const s=Ki(null===(n=e.operationRef)||void 0===n?void 0:n.toValue()),i=await this.toReference(null===(r=e.operationRef)||void 0===r?void 0:r.toValue());t=Ji(s,i.value.result),As(t)&&(t=Am.refract(t)),t=new Am([...t.content],t.meta.clone(),t.attributes.clone()),t.setMetaProperty("ref-origin",i.uri),null===(o=e.operationRef)||void 0===o||o.meta.set("operation",t)}else if(ms(e.operationId)){var s,i;const n=null===(s=e.operationId)||void 0===s?void 0:s.toValue();if(t=Vb((e=>Dg(e)&&e.operationId.equals(n)),this.reference.value.result),qo(t))throw new Error(`OperationElement(operationId=${n}) not found.`);null===(i=e.operationId)||void 0===i||i.meta.set("operation",t)}},async ExampleElement(e){var t;if(!ms(e.externalValue))return;if(!this.options.resolve.external&&ms(e.externalValue))return;if(e.hasKey("value")&&ms(e.externalValue))throw new Error("ExampleElement value and externalValue fields are mutually exclusive.");const n=await this.toReference(null===(t=e.externalValue)||void 0===t?void 0:t.toValue()),r=new n.value.result.constructor(n.value.result.content,n.value.result.meta.clone(),n.value.result.attributes.clone());r.setMetaProperty("ref-origin",n.uri),e.value=r},async SchemaElement(e,t,n,r,o){var s;const[i,a]=this.toAncestorLineage([...o,n]);if(!ms(e.$ref))return;if(i.some((t=>t.has(e))))return!1;let{reference:l}=this,{uri:c}=l;const u=Yb(c,e),p=ib(u),h=vb({uri:p}),f=go((e=>e.canRead(h)),this.options.resolve.resolvers),d=!f,m=d&&c!==p;if(!this.options.resolve.external&&m)return;let g;this.indirections.push(e);try{if(f||d){g=ew(u,Qb(l.value.result))}else{l=await this.toReference(ub(u));const e=Ki(u);g=Qb(Ji(e,l.value.result))}}catch(e){if(!(d&&e instanceof zb))throw e;if(Hb(Gb(u))){l=await this.toReference(ub(u)),c=l.uri;const e=Gb(u);g=Zb(e,Qb(l.value.result))}else{l=await this.toReference(ub(u)),c=l.uri;const e=Ki(u);g=Qb(Ji(e,l.value.result))}}if(this.indirections.includes(g))throw new Error("Recursive Schema Object reference detected");if(this.indirections.length>this.options.dereference.maxDepth)throw new Fb(`Maximum dereference depth of "${this.options.dereference.maxDepth}" has been exceeded in file "${this.reference.uri}"`);a.add(e);const y=Pw({reference:l,namespace:this.namespace,indirections:[...this.indirections],options:this.options,ancestors:i});if(g=await Cw(g,y,{keyMap:Tv,nodeTypeGetter:Iv}),a.delete(e),this.indirections.pop(),Kg(g)){var v;const t=g.clone();return t.setMetaProperty("ref-fields",{$ref:null===(v=e.$ref)||void 0===v?void 0:v.toValue()}),t.setMetaProperty("ref-origin",l.uri),t}const b=new Lm([...g.content],g.meta.clone(),g.attributes.clone());return e.forEach(((e,t,n)=>{b.remove(t.toValue()),b.content.push(n)})),b.remove("$ref"),b.setMetaProperty("ref-fields",{$ref:null===(s=e.$ref)||void 0===s?void 0:s.toValue()}),b.setMetaProperty("ref-origin",l.uri),b}}}),Nw=Pw,Iw=fi[Symbol.for("nodejs.util.promisify.custom")],Tw=Ys(Ow,{init(){this.name="openapi-3-1"},methods:{canDereference(e){var t;return"text/plain"!==e.mediaType?zv.includes(e.mediaType):Mg(null===(t=e.parseResult)||void 0===t?void 0:t.result)},async dereference(e,t){const n=zs(Rv),r=kr(db(),t.dereference.refSet);let o;r.has(e.uri)?o=r.find(xo(e.uri,"uri")):(o=hb({uri:e.uri,value:e.parseResult}),r.add(o));const s=Nw({reference:o,namespace:n,options:t}),i=await Iw(r.rootRef.value,s,{keyMap:Tv,nodeTypeGetter:Iv});return null===t.dereference.refSet&&r.clean(),i}}}),Rw=Tw,Mw=e=>{const t=(e=>e.slice(2))(e);return t.reduce(((e,n,r)=>{if(Es(n)){const t=String(n.key.toValue());e.push(t)}else if(ws(t[r-2])){const o=t[r-2].content.indexOf(n);e.push(o)}return e}),[])},Dw=e=>{if(null==e.cause)return e;let{cause:t}=e;for(;null!=t.cause;)t=t.cause;return t},Fw=ue("SchemaRefError",(function(e,t,n){this.originalError=n,Object.assign(this,t||{})})),{wrapError:Lw}=ke,Bw=fi[Symbol.for("nodejs.util.promisify.custom")],$w=Nw.compose({props:{useCircularStructures:!0,allowMetaPatches:!1,basePath:null},init(e){let{allowMetaPatches:t=this.allowMetaPatches,useCircularStructures:n=this.useCircularStructures,basePath:r=this.basePath}=e;this.allowMetaPatches=t,this.useCircularStructures=n,this.basePath=r},methods:{async ReferenceElement(e,t,n,r,o){try{const[t,r]=this.toAncestorLineage([...o,n]);if(Ns(["cycle"],e.$ref))return!1;if(t.some((t=>t.has(e))))return!1;if(!this.options.resolve.external&&Ug(e))return!1;const s=await this.toReference(e.$ref.toValue()),i=s.uri,a=lb(i,e.$ref.toValue());this.indirections.push(e);const l=Ki(a);let c=Ji(l,s.value.result);if(As(c)){const t=e.meta.get("referenced-element").toValue();if(Uc(c))c=Tm.refract(c),c.setMetaProperty("referenced-element",t);else{const e=this.namespace.getElementClass(t);c=e.refract(c)}}if(this.indirections.includes(c))throw new Error("Recursive JSON Pointer detected");if(this.indirections.length>this.options.dereference.maxDepth)throw new Fb(`Maximum dereference depth of "${this.options.dereference.maxDepth}" has been exceeded in file "${this.reference.uri}"`);if(!this.useCircularStructures){if(t.some((e=>e.has(c)))){if(rb(i)||nb(i)){const t=new Tm({$ref:a},e.meta.clone(),e.attributes.clone());return t.get("$ref").classes.push("cycle"),t}return!1}}r.add(e);const u=$w({reference:s,namespace:this.namespace,indirections:[...this.indirections],options:this.options,ancestors:t,allowMetaPatches:this.allowMetaPatches,useCircularStructures:this.useCircularStructures,basePath:this.basePath??[...Mw([...o,n,e]),"$ref"]});c=await Bw(c,u,{keyMap:Tv,nodeTypeGetter:Iv}),r.delete(e),this.indirections.pop(),c=c.clone(),c.setMetaProperty("ref-fields",{$ref:e.$ref?.toValue(),description:e.description?.toValue(),summary:e.summary?.toValue()}),c.setMetaProperty("ref-origin",s.uri);const p=void 0!==e.description,h=void 0!==e.summary;if(p&&"description"in c&&(c.description=e.description),h&&"summary"in c&&(c.summary=e.summary),this.allowMetaPatches&&bs(c)){const e=c;if(void 0===e.get("$$ref")){const t=lb(i,a);e.set("$$ref",t)}}return c}catch(t){const r=Dw(t),s=Lw(r,{baseDoc:this.reference.uri,$ref:e.$ref.toValue(),pointer:Ki(e.$ref.toValue()),fullPath:this.basePath??[...Mw([...o,n,e]),"$ref"]});return void this.options.dereference.dereferenceOpts?.errors?.push?.(s)}},async PathItemElement(e,t,n,r,o){try{const[t,r]=this.toAncestorLineage([...o,n]);if(!ms(e.$ref))return;if(Ns(["cycle"],e.$ref))return!1;if(t.some((t=>t.has(e))))return!1;if(!this.options.resolve.external&&Bg(e))return;const s=await this.toReference(e.$ref.toValue()),i=s.uri,a=lb(i,e.$ref.toValue());this.indirections.push(e);const l=Ki(a);let c=Ji(l,s.value.result);if(As(c)&&(c=Pm.refract(c)),this.indirections.includes(c))throw new Error("Recursive JSON Pointer detected");if(this.indirections.length>this.options.dereference.maxDepth)throw new Fb(`Maximum dereference depth of "${this.options.dereference.maxDepth}" has been exceeded in file "${this.reference.uri}"`);if(!this.useCircularStructures){if(t.some((e=>e.has(c)))){if(rb(i)||nb(i)){const t=new Pm({$ref:a},e.meta.clone(),e.attributes.clone());return t.get("$ref").classes.push("cycle"),t}return!1}}r.add(e);const u=$w({reference:s,namespace:this.namespace,indirections:[...this.indirections],options:this.options,ancestors:t,allowMetaPatches:this.allowMetaPatches,useCircularStructures:this.useCircularStructures,basePath:this.basePath??[...Mw([...o,n,e]),"$ref"]});c=await Bw(c,u,{keyMap:Tv,nodeTypeGetter:Iv}),r.delete(e),this.indirections.pop();const p=new Pm([...c.content],c.meta.clone(),c.attributes.clone());if(e.forEach(((e,t,n)=>{p.remove(t.toValue()),p.content.push(n)})),p.remove("$ref"),p.setMetaProperty("ref-fields",{$ref:e.$ref?.toValue()}),p.setMetaProperty("ref-origin",s.uri),this.allowMetaPatches&&void 0===p.get("$$ref")){const e=lb(i,a);p.set("$$ref",e)}return p}catch(t){const r=Dw(t),s=Lw(r,{baseDoc:this.reference.uri,$ref:e.$ref.toValue(),pointer:Ki(e.$ref.toValue()),fullPath:this.basePath??[...Mw([...o,n,e]),"$ref"]});return void this.options.dereference.dereferenceOpts?.errors?.push?.(s)}},async SchemaElement(e,t,n,r,o){try{const[t,r]=this.toAncestorLineage([...o,n]);if(!ms(e.$ref))return;if(Ns(["cycle"],e.$ref))return!1;if(t.some((t=>t.has(e))))return!1;let{reference:s}=this,{uri:i}=s;const a=Yb(i,e),l=ib(a),c=vb({uri:l}),u=!this.options.resolve.resolvers.some((e=>e.canRead(c))),p=!u,h=p&&i!==l;if(!this.options.resolve.external&&h)return;let f;this.indirections.push(e);try{if(u||p){f=ew(a,Qb(s.value.result))}else{s=await this.toReference(ub(a)),i=s.uri;const e=Ki(a);f=Qb(Ji(e,s.value.result))}}catch(e){if(!(p&&e instanceof zb))throw e;if(Hb(Gb(a))){s=await this.toReference(ub(a)),i=s.uri;const e=Gb(a);f=Zb(e,Qb(s.value.result))}else{s=await this.toReference(ub(a)),i=s.uri;const e=Ki(a);f=Qb(Ji(e,s.value.result))}}if(this.indirections.includes(f))throw new Error("Recursive Schema Object reference detected");if(this.indirections.length>this.options.dereference.maxDepth)throw new Fb(`Maximum dereference depth of "${this.options.dereference.maxDepth}" has been exceeded in file "${this.reference.uri}"`);if(!this.useCircularStructures){if(t.some((e=>e.has(f)))){if(rb(i)||nb(i)){const t=lb(i,a),n=new Lm({$ref:t},e.meta.clone(),e.attributes.clone());return n.get("$ref").classes.push("cycle"),n}return!1}}r.add(e);const d=$w({reference:s,namespace:this.namespace,indirections:[...this.indirections],options:this.options,useCircularStructures:this.useCircularStructures,allowMetaPatches:this.allowMetaPatches,ancestors:t,basePath:this.basePath??[...Mw([...o,n,e]),"$ref"]});if(f=await Bw(f,d,{keyMap:Tv,nodeTypeGetter:Iv}),r.delete(e),this.indirections.pop(),Kg(f)){const t=f.clone();return t.setMetaProperty("ref-fields",{$ref:e.$ref?.toValue()}),t.setMetaProperty("ref-origin",i),t}const m=new Lm([...f.content],f.meta.clone(),f.attributes.clone());if(e.forEach(((e,t,n)=>{m.remove(t.toValue()),m.content.push(n)})),m.remove("$ref"),m.setMetaProperty("ref-fields",{$ref:e.$ref?.toValue()}),m.setMetaProperty("ref-origin",i),this.allowMetaPatches&&void 0===m.get("$$ref")){const e=lb(i,a);m.set("$$ref",e)}return m}catch(t){const r=Dw(t),s=new Fw(`Could not resolve reference: ${r.message}`,{baseDoc:this.reference.uri,$ref:e.$ref.toValue(),fullPath:this.basePath??[...Mw([...o,n,e]),"$ref"]},r);return void this.options.dereference.dereferenceOpts?.errors?.push?.(s)}},async LinkElement(){},async ExampleElement(e,t,n,r,o){try{return await Nw.compose.methods.ExampleElement.call(this,e,t,n,r,o)}catch(t){const r=Dw(t),s=Lw(r,{baseDoc:this.reference.uri,externalValue:e.externalValue?.toValue(),fullPath:this.basePath??[...Mw([...o,n,e]),"externalValue"]});return void this.options.dereference.dereferenceOpts?.errors?.push?.(s)}}}}),qw=$w,Uw=Rw.compose.bind(),zw=Uw({init(e){let{parameterMacro:t,options:n}=e;this.parameterMacro=t,this.options=n},props:{parameterMacro:null,options:null,macroOperation:null,OperationElement:{enter(e){this.macroOperation=e},leave(){this.macroOperation=null}},ParameterElement:{leave(e,t,n,r,o){const s=null===this.macroOperation?null:Ti(this.macroOperation),i=Ti(e);try{const t=this.parameterMacro(s,i);e.set("default",t)}catch(e){const t=new Error(e,{cause:e});t.fullPath=Mw([...o,n]),this.options.dereference.dereferenceOpts?.errors?.push?.(t)}}}}}),Vw=Uw({init(e){let{modelPropertyMacro:t,options:n}=e;this.modelPropertyMacro=t,this.options=n},props:{modelPropertyMacro:null,options:null,SchemaElement:{leave(e,t,n,r,o){void 0!==e.properties&&bs(e.properties)&&e.properties.forEach((t=>{if(bs(t))try{const e=this.modelPropertyMacro(Ti(t));t.set("default",e)}catch(t){const r=new Error(t,{cause:t});r.fullPath=[...Mw([...o,n,e]),"properties"],this.options.dereference.dereferenceOpts?.errors?.push?.(r)}}))}}}});function Ww(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function Jw(e){for(var t=1;t{const t=e.meta.clone(),n=e.attributes.clone();return new e.constructor(void 0,t,n)},Hw=e=>new Pt.c6(e.key,e.value,e.meta.clone(),e.attributes.clone()),Gw=(e,t)=>t.clone&&t.isMergeableElement(e)?Xw(Kw(e),e,t):e,Zw=(e,t,n)=>e.concat(t)["fantasy-land/map"]((e=>Gw(e,n))),Yw=(e,t,n)=>{const r=bs(e)?Kw(e):Kw(t);return bs(e)&&e.forEach(((e,t,o)=>{const s=Hw(o);s.value=Gw(e,n),r.content.push(s)})),t.forEach(((t,o,s)=>{const i=o.toValue();let a;if(bs(e)&&e.hasKey(i)&&n.isMergeableElement(t)){const r=e.get(i);a=Hw(s),a.value=((e,t)=>{if("function"!=typeof t.customMerge)return Xw;const n=t.customMerge(e,t);return"function"==typeof n?n:Xw})(o,n)(r,t)}else a=Hw(s),a.value=Gw(t,n);r.remove(i),r.content.push(a)})),r};function Xw(e,t,n){var r,o,s;const i={clone:!0,isMergeableElement:e=>bs(e)||ws(e),arrayElementMerge:Zw,objectElementMerge:Yw,customMerge:void 0},a=Jw(Jw({},i),n);a.isMergeableElement=null!==(r=a.isMergeableElement)&&void 0!==r?r:i.isMergeableElement,a.arrayElementMerge=null!==(o=a.arrayElementMerge)&&void 0!==o?o:i.arrayElementMerge,a.objectElementMerge=null!==(s=a.objectElementMerge)&&void 0!==s?s:i.objectElementMerge;const l=ws(t);return l===ws(e)?l&&"function"==typeof a.arrayElementMerge?a.arrayElementMerge(e,t,a):a.objectElementMerge(e,t,a):Gw(t,a)}Xw.all=(e,t)=>{if(!Array.isArray(e))throw new Error("first argument should be an array");return 0===e.length?new Pt.Sb:e.reduce(((e,n)=>Xw(e,n,t)),Kw(e[0]))};const Qw=Uw({init(e){let{options:t}=e;this.options=t},props:{options:null,SchemaElement:{leave(e,t,n,r,o){if(void 0===e.allOf)return;if(!ws(e.allOf)){const t=new TypeError("allOf must be an array");return t.fullPath=[...Mw([...o,n,e]),"allOf"],void this.options.dereference.dereferenceOpts?.errors?.push?.(t)}if(e.allOf.isEmpty)return new Lm(e.content.filter((e=>"allOf"!==e.key.toValue())),e.meta.clone(),e.attributes.clone());if(!e.allOf.content.every(Jg)){const t=new TypeError("Elements in allOf must be objects");return t.fullPath=[...Mw([...o,n,e]),"allOf"],void this.options.dereference.dereferenceOpts?.errors?.push?.(t)}const s=Xw.all([...e.allOf.content,e]);if(e.hasKey("$$ref")||s.remove("$$ref"),e.hasKey("example")){s.getMember("example").value=e.get("example")}if(e.hasKey("examples")){s.getMember("examples").value=e.get("examples")}return s.remove("allOf"),s}}}}),eE=fi[Symbol.for("nodejs.util.promisify.custom")],tE=Rw.compose({props:{useCircularStructures:!0,allowMetaPatches:!1,parameterMacro:null,modelPropertyMacro:null,mode:"non-strict",ancestors:null},init(){let{useCircularStructures:e=this.useCircularStructures,allowMetaPatches:t=this.allowMetaPatches,parameterMacro:n=this.parameterMacro,modelPropertyMacro:r=this.modelPropertyMacro,mode:o=this.mode,ancestors:s=[]}=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};this.name="openapi-3-1-swagger-client",this.useCircularStructures=e,this.allowMetaPatches=t,this.parameterMacro=n,this.modelPropertyMacro=r,this.mode=o,this.ancestors=[...s]},methods:{async dereference(e,t){const n=[],r=zs(Rv),o=t.dereference.refSet??db();let s;o.has(e.uri)?s=o.find((t=>t.uri===e.uri)):(s=hb({uri:e.uri,value:e.parseResult}),o.add(s));const i=qw({reference:s,namespace:r,options:t,useCircularStructures:this.useCircularStructures,allowMetaPatches:this.allowMetaPatches,ancestors:this.ancestors});if(n.push(i),"function"==typeof this.parameterMacro){const e=zw({parameterMacro:this.parameterMacro,options:t});n.push(e)}if("function"==typeof this.modelPropertyMacro){const e=Vw({modelPropertyMacro:this.modelPropertyMacro,options:t});n.push(e)}if("strict"!==this.mode){const e=Qw({options:t});n.push(e)}const a=ri(n,{nodeTypeGetter:Iv}),l=await eE(o.rootRef.value,a,{keyMap:Tv,nodeTypeGetter:Iv});return null===t.dereference.refSet&&o.clean(),l}}}),nE=tE,rE=async e=>{const{spec:t,timeout:n,redirects:r,requestInterceptor:o,responseInterceptor:s,pathDiscriminator:i=[],allowMetaPatches:a=!1,useCircularStructures:l=!1,skipNormalization:c=!1,parameterMacro:u=null,modelPropertyMacro:p=null,mode:h="non-strict"}=e;try{const{cache:d}=rE,m=rb(ab())?ab():"https://smartbear.com/",g=Et(e),y=lb(m,g);let v;d.has(t)?v=d.get(t):(v=km.refract(t),v.classes.push("result"),d.set(t,v));const b=new zo([v]),w=0===(f=i).length?"":`/${f.map(Vi).join("/")}`,E=""===w?"":`#${w}`,x=Ji(w,v),S=hb({uri:y,value:b}),_=db({refs:[S]});""!==w&&(_.rootRef=null);const j=[new WeakSet([x])],O=[],k=((e,t,n)=>Ei({element:n}).transclude(e,t))(x,await Ab(x,{resolve:{baseURI:`${y}${E}`,resolvers:[Ew({timeout:n||1e4,redirects:r||10})],resolverOpts:{swaggerHTTPClientConfig:{requestInterceptor:o,responseInterceptor:s}},strategies:[lw()]},parse:{mediaType:zv.latest(),parsers:[_w({allowEmpty:!1,sourceMap:!1}),jw({allowEmpty:!1,sourceMap:!1}),xw({allowEmpty:!1,sourceMap:!1}),Sw({allowEmpty:!1,sourceMap:!1}),Ib({allowEmpty:!1,sourceMap:!1})]},dereference:{maxDepth:100,strategies:[nE({allowMetaPatches:a,useCircularStructures:l,parameterMacro:u,modelPropertyMacro:p,mode:h,ancestors:j})],refSet:_,dereferenceOpts:{errors:O}}}),v),A=c?k:bw(k);return{spec:Ti(A),errors:O}}catch(e){if(e instanceof Ui||e instanceof zi)return{spec:null,errors:[]};throw e}var f};rE.cache=new WeakMap;const oE=rE,sE={name:"openapi-3-1-apidom",match(e){let{spec:t}=e;return Ot(t)},normalize(e){let{spec:t}=e;return vw(bw)(t)},resolve:async e=>oE(e)},iE=e=>async t=>(async e=>{const{spec:t,requestInterceptor:n,responseInterceptor:r}=e,o=Et(e),s=xt(e),i=t||await Ze(s,{requestInterceptor:n,responseInterceptor:r})(o),a=f()(f()({},e),{},{spec:i});return e.strategies.find((e=>e.match(a))).resolve(a)})(f()(f()({},e),t)),aE=iE({strategies:[Ct,At,_t]});var lE=n(88436),cE=n.n(lE),uE=n(27361),pE=n.n(uE),hE=n(76489);function fE(e){return"[object Object]"===Object.prototype.toString.call(e)}function dE(e){var t,n;return!1!==fE(e)&&(void 0===(t=e.constructor)||!1!==fE(n=t.prototype)&&!1!==n.hasOwnProperty("isPrototypeOf"))}const mE={body:function(e){let{req:t,value:n}=e;t.body=n},header:function(e){let{req:t,parameter:n,value:r}=e;t.headers=t.headers||{},void 0!==r&&(t.headers[n.name]=r)},query:function(e){let{req:t,value:n,parameter:r}=e;t.query=t.query||{},!1===n&&"boolean"===r.type&&(n="false");0===n&&["number","integer"].indexOf(r.type)>-1&&(n="0");if(n)t.query[r.name]={collectionFormat:r.collectionFormat,value:n};else if(r.allowEmptyValue&&void 0!==n){const e=r.name;t.query[e]=t.query[e]||{},t.query[e].allowEmptyValue=!0}},path:function(e){let{req:t,value:n,parameter:r}=e;t.url=t.url.split(`{${r.name}}`).join(encodeURIComponent(n))},formData:function(e){let{req:t,value:n,parameter:r}=e;(n||r.allowEmptyValue)&&(t.form=t.form||{},t.form[r.name]={value:n,allowEmptyValue:r.allowEmptyValue,collectionFormat:r.collectionFormat})}};function gE(e,t){return t.includes("application/json")?"string"==typeof e?e:JSON.stringify(e):e.toString()}function yE(e){let{req:t,value:n,parameter:r}=e;const{name:o,style:s,explode:i,content:a}=r;if(a){const e=Object.keys(a)[0];return void(t.url=t.url.split(`{${o}}`).join(st(gE(n,e),{escape:!0})))}const l=it({key:r.name,value:n,style:s||"simple",explode:i||!1,escape:!0});t.url=t.url.split(`{${o}}`).join(l)}function vE(e){let{req:t,value:n,parameter:r}=e;if(t.query=t.query||{},r.content){const e=gE(n,Object.keys(r.content)[0]);if(e)t.query[r.name]=e;else if(r.allowEmptyValue&&void 0!==n){const e=r.name;t.query[e]=t.query[e]||{},t.query[e].allowEmptyValue=!0}}else if(!1===n&&(n="false"),0===n&&(n="0"),n){const{style:e,explode:o,allowReserved:s}=r;t.query[r.name]={value:n,serializationOption:{style:e,explode:o,allowReserved:s}}}else if(r.allowEmptyValue&&void 0!==n){const e=r.name;t.query[e]=t.query[e]||{},t.query[e].allowEmptyValue=!0}}const bE=["accept","authorization","content-type"];function wE(e){let{req:t,parameter:n,value:r}=e;if(t.headers=t.headers||{},!(bE.indexOf(n.name.toLowerCase())>-1))if(n.content){const e=Object.keys(n.content)[0];t.headers[n.name]=gE(r,e)}else void 0!==r&&(t.headers[n.name]=it({key:n.name,value:r,style:n.style||"simple",explode:void 0!==n.explode&&n.explode,escape:!1}))}function EE(e){let{req:t,parameter:n,value:r}=e;t.headers=t.headers||{};const o=typeof r;if(n.content){const e=Object.keys(n.content)[0];t.headers.Cookie=`${n.name}=${gE(r,e)}`}else if("undefined"!==o){const e="object"===o&&!Array.isArray(r)&&n.explode?"":`${n.name}=`;t.headers.Cookie=e+it({key:n.name,value:r,escape:!1,style:n.style||"form",explode:void 0!==n.explode&&n.explode})}}const xE="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:window,{btoa:SE}=xE,_E=SE;function jE(e,t){const{operation:n,requestBody:r,securities:o,spec:s,attachContentTypeForEmptyPayload:i}=e;let{requestContentType:a}=e;t=function(e){let{request:t,securities:n={},operation:r={},spec:o}=e;const s=f()({},t),{authorized:i={}}=n,a=r.security||o.security||[],l=i&&!!Object.keys(i).length,c=pE()(o,["components","securitySchemes"])||{};if(s.headers=s.headers||{},s.query=s.query||{},!Object.keys(n).length||!l||!a||Array.isArray(r.security)&&!r.security.length)return t;return a.forEach((e=>{Object.keys(e).forEach((e=>{const t=i[e],n=c[e];if(!t)return;const r=t.value||t,{type:o}=n;if(t)if("apiKey"===o)"query"===n.in&&(s.query[n.name]=r),"header"===n.in&&(s.headers[n.name]=r),"cookie"===n.in&&(s.cookies[n.name]=r);else if("http"===o){if(/^basic$/i.test(n.scheme)){const e=r.username||"",t=r.password||"",n=_E(`${e}:${t}`);s.headers.Authorization=`Basic ${n}`}/^bearer$/i.test(n.scheme)&&(s.headers.Authorization=`Bearer ${r}`)}else if("oauth2"===o||"openIdConnect"===o){const e=t.token||{},r=e[n["x-tokenName"]||"access_token"];let o=e.token_type;o&&"bearer"!==o.toLowerCase()||(o="Bearer"),s.headers.Authorization=`${o} ${r}`}}))})),s}({request:t,securities:o,operation:n,spec:s});const l=n.requestBody||{},c=Object.keys(l.content||{}),u=a&&c.indexOf(a)>-1;if(r||i){if(a&&u)t.headers["Content-Type"]=a;else if(!a){const e=c[0];e&&(t.headers["Content-Type"]=e,a=e)}}else a&&u&&(t.headers["Content-Type"]=a);if(!e.responseContentType&&n.responses){const e=Object.entries(n.responses).filter((e=>{let[t,n]=e;const r=parseInt(t,10);return r>=200&&r<300&&dE(n.content)})).reduce(((e,t)=>{let[,n]=t;return e.concat(Object.keys(n.content))}),[]);e.length>0&&(t.headers.accept=e.join(", "))}if(r)if(a){if(c.indexOf(a)>-1)if("application/x-www-form-urlencoded"===a||"multipart/form-data"===a)if("object"==typeof r){const e=(l.content[a]||{}).encoding||{};t.form={},Object.keys(r).forEach((n=>{t.form[n]={value:r[n],encoding:e[n]||{}}}))}else t.form=r;else t.body=r}else t.body=r;return t}function OE(e,t){const{spec:n,operation:r,securities:o,requestContentType:s,responseContentType:i,attachContentTypeForEmptyPayload:a}=e;if(t=function(e){let{request:t,securities:n={},operation:r={},spec:o}=e;const s=f()({},t),{authorized:i={},specSecurity:a=[]}=n,l=r.security||a,c=i&&!!Object.keys(i).length,u=o.securityDefinitions;if(s.headers=s.headers||{},s.query=s.query||{},!Object.keys(n).length||!c||!l||Array.isArray(r.security)&&!r.security.length)return t;return l.forEach((e=>{Object.keys(e).forEach((e=>{const t=i[e];if(!t)return;const{token:n}=t,r=t.value||t,o=u[e],{type:a}=o,l=o["x-tokenName"]||"access_token",c=n&&n[l];let p=n&&n.token_type;if(t)if("apiKey"===a){const e="query"===o.in?"query":"headers";s[e]=s[e]||{},s[e][o.name]=r}else if("basic"===a)if(r.header)s.headers.authorization=r.header;else{const e=r.username||"",t=r.password||"";r.base64=_E(`${e}:${t}`),s.headers.authorization=`Basic ${r.base64}`}else"oauth2"===a&&c&&(p=p&&"bearer"!==p.toLowerCase()?p:"Bearer",s.headers.authorization=`${p} ${c}`)}))})),s}({request:t,securities:o,operation:r,spec:n}),t.body||t.form||a)s?t.headers["Content-Type"]=s:Array.isArray(r.consumes)?[t.headers["Content-Type"]]=r.consumes:Array.isArray(n.consumes)?[t.headers["Content-Type"]]=n.consumes:r.parameters&&r.parameters.filter((e=>"file"===e.type)).length?t.headers["Content-Type"]="multipart/form-data":r.parameters&&r.parameters.filter((e=>"formData"===e.in)).length&&(t.headers["Content-Type"]="application/x-www-form-urlencoded");else if(s){const e=r.parameters&&r.parameters.filter((e=>"body"===e.in)).length>0,n=r.parameters&&r.parameters.filter((e=>"formData"===e.in)).length>0;(e||n)&&(t.headers["Content-Type"]=s)}return!i&&Array.isArray(r.produces)&&r.produces.length>0&&(t.headers.accept=r.produces.join(", ")),t}function kE(e,t){return`${t.toLowerCase()}-${e}`}const AE=["http","fetch","spec","operationId","pathName","method","parameters","securities"],CE=e=>Array.isArray(e)?e:[],PE=ue("OperationNotFoundError",(function(e,t,n){this.originalError=n,Object.assign(this,t||{})})),NE=(e,t)=>t.filter((t=>t.name===e)),IE=e=>{const t={};e.forEach((e=>{t[e.in]||(t[e.in]={}),t[e.in][e.name]=e}));const n=[];return Object.keys(t).forEach((e=>{Object.keys(t[e]).forEach((r=>{n.push(t[e][r])}))})),n},TE={buildRequest:ME};function RE(e){let{http:t,fetch:n,spec:r,operationId:o,pathName:s,method:i,parameters:a,securities:l}=e,c=cE()(e,AE);const u=t||n||ct;s&&i&&!o&&(o=kE(s,i));const p=TE.buildRequest(f()({spec:r,operationId:o,parameters:a,securities:l,http:u},c));return p.body&&(dE(p.body)||Array.isArray(p.body))&&(p.body=JSON.stringify(p.body)),u(p)}function ME(e){const{spec:t,operationId:n,responseContentType:r,scheme:o,requestInterceptor:s,responseInterceptor:i,contextUrl:a,userFetch:l,server:c,serverVariables:p,http:h,signal:d}=e;let{parameters:m,parameterBuilders:g}=e;const y=kt(t);g||(g=y?u:mE);let v={url:"",credentials:h&&h.withCredentials?"include":"same-origin",headers:{},cookies:{}};d&&(v.signal=d),s&&(v.requestInterceptor=s),i&&(v.responseInterceptor=i),l&&(v.userFetch=l);const b=function(e,t){return e&&e.paths?function(e,t){return function(e,t,n){if(!e||"object"!=typeof e||!e.paths||"object"!=typeof e.paths)return null;const{paths:r}=e;for(const o in r)for(const s in r[o]){if("PARAMETERS"===s.toUpperCase())continue;const i=r[o][s];if(!i||"object"!=typeof i)continue;const a={spec:e,pathName:o,method:s.toUpperCase(),operation:i},l=t(a);if(n&&l)return a}}(e,t,!0)||null}(e,(e=>{let{pathName:n,method:r,operation:o}=e;if(!o||"object"!=typeof o)return!1;const s=o.operationId;return[(0,He.Z)(o,n,r),kE(n,r),s].some((e=>e&&e===t))})):null}(t,n);if(!b)throw new PE(`Operation ${n} not found`);const{operation:w={},method:E,pathName:x}=b;if(v.url+=function(e){const t=kt(e.spec);return t?function(e){let{spec:t,pathName:n,method:r,server:o,contextUrl:s,serverVariables:i={}}=e;const a=pE()(t,["paths",n,(r||"").toLowerCase(),"servers"])||pE()(t,["paths",n,"servers"])||pE()(t,["servers"]);let l="",c=null;if(o&&a&&a.length){const e=a.map((e=>e.url));e.indexOf(o)>-1&&(l=o,c=a[e.indexOf(o)])}!l&&a&&a.length&&(l=a[0].url,[c]=a);if(l.indexOf("{")>-1){(function(e){const t=[],n=/{([^}]+)}/g;let r;for(;r=n.exec(e);)t.push(r[1]);return t})(l).forEach((e=>{if(c.variables&&c.variables[e]){const t=c.variables[e],n=i[e]||t.default,r=new RegExp(`{${e}}`,"g");l=l.replace(r,n)}}))}return function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"",t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";const n=e&&t?ce.parse(ce.resolve(t,e)):ce.parse(e),r=ce.parse(t),o=DE(n.protocol)||DE(r.protocol)||"",s=n.host||r.host,i=n.pathname||"";let a;a=o&&s?`${o}://${s+i}`:i;return"/"===a[a.length-1]?a.slice(0,-1):a}(l,s)}(e):function(e){let{spec:t,scheme:n,contextUrl:r=""}=e;const o=ce.parse(r),s=Array.isArray(t.schemes)?t.schemes[0]:null,i=n||s||DE(o.protocol)||"http",a=t.host||o.host||"",l=t.basePath||"";let c;c=i&&a?`${i}://${a+l}`:l;return"/"===c[c.length-1]?c.slice(0,-1):c}(e)}({spec:t,scheme:o,contextUrl:a,server:c,serverVariables:p,pathName:x,method:E}),!n)return delete v.cookies,v;v.url+=x,v.method=`${E}`.toUpperCase(),m=m||{};const S=t.paths[x]||{};r&&(v.headers.accept=r);const _=IE([].concat(CE(w.parameters)).concat(CE(S.parameters)));_.forEach((e=>{const n=g[e.in];let r;if("body"===e.in&&e.schema&&e.schema.properties&&(r=m),r=e&&e.name&&m[e.name],void 0===r?r=e&&e.name&&m[`${e.in}.${e.name}`]:NE(e.name,_).length>1&&console.warn(`Parameter '${e.name}' is ambiguous because the defined spec has more than one parameter with the name: '${e.name}' and the passed-in parameter values did not define an 'in' value.`),null!==r){if(void 0!==e.default&&void 0===r&&(r=e.default),void 0===r&&e.required&&!e.allowEmptyValue)throw new Error(`Required parameter ${e.name} is not provided`);if(y&&e.schema&&"object"===e.schema.type&&"string"==typeof r)try{r=JSON.parse(r)}catch(e){throw new Error("Could not parse object parameter value string as JSON")}n&&n({req:v,parameter:e,value:r,operation:w,spec:t})}}));const j=f()(f()({},e),{},{operation:w});if(v=y?jE(j,v):OE(j,v),v.cookies&&Object.keys(v.cookies).length){const e=Object.keys(v.cookies).reduce(((e,t)=>{const n=v.cookies[t];return e+(e?"&":"")+hE.serialize(t,n)}),"");v.headers.Cookie=e}return v.cookies&&delete v.cookies,wt(v),v}const DE=e=>e?e.replace(/\W/g,""):null;const FE=e=>async function(t,n){let r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return async function(e,t){let n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};const{returnEntireTree:r,baseDoc:o,requestInterceptor:s,responseInterceptor:i,parameterMacro:a,modelPropertyMacro:l,useCircularStructures:c,strategies:u}=n,p={spec:e,pathDiscriminator:t,baseDoc:o,requestInterceptor:s,responseInterceptor:i,parameterMacro:a,modelPropertyMacro:l,useCircularStructures:c,strategies:u},h=u.find((e=>e.match(p))).normalize(p),d=await aE(f()(f()({},p),{},{spec:h,allowMetaPatches:!0,skipNormalization:!0}));return!r&&Array.isArray(t)&&t.length&&(d.spec=pE()(d.spec,t)||null),d}(t,n,f()(f()({},e),r))};FE({strategies:[Ct,At,_t]});var LE=n(34852);function BE(e){let{configs:t,getConfigs:n}=e;return{fn:{fetch:(r=ct,o=t.preFetch,s=t.postFetch,s=s||(e=>e),o=o||(e=>e),e=>("string"==typeof e&&(e={url:e}),lt.mergeInQueryOrForm(e),e=o(e),s(r(e)))),buildRequest:ME,execute:RE,resolve:iE({strategies:[sE,Ct,At,_t]}),resolveSubtree:async function(e,t){let r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};const o=n(),s={modelPropertyMacro:o.modelPropertyMacro,parameterMacro:o.parameterMacro,requestInterceptor:o.requestInterceptor,responseInterceptor:o.responseInterceptor,strategies:[sE,Ct,At,_t]};return FE(s)(e,t,r)},serializeRes:pt,opId:He.Z},statePlugins:{configs:{wrapActions:{loaded:LE.loaded}}}};var r,o,s}},98525:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(90242);function o(){return{fn:{shallowEqualKeys:r.be}}}},48347:(e,t,n)=>{"use strict";n.r(t),n.d(t,{getDisplayName:()=>r});const r=e=>e.displayName||e.name||"Component"},73420:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>c});var r=n(35627),o=n.n(r),s=n(90242),i=n(11092),a=n(48347),l=n(60314);const c=e=>{let{getComponents:t,getStore:n,getSystem:r}=e;const c=(u=(0,i.getComponent)(r,n,t),(0,s.HP)(u,(function(){for(var e=arguments.length,t=new Array(e),n=0;n(0,l.Z)(e,(function(){for(var e=arguments.length,t=new Array(e),n=0;n{"use strict";n.r(t),n.d(t,{getComponent:()=>X,render:()=>Y,withMappedContainer:()=>Z});var r=n(23101),o=n.n(r),s=n(28222),i=n.n(s),a=n(67294),l=n(73935),c=n(97779),u=n(61688),p=n(52798);let h=function(e){e()};const f=()=>h,d=(0,a.createContext)(null);let m=null;var g=n(87462),y=n(63366),v=n(8679),b=n.n(v),w=n(59864);const E=["initMapStateToProps","initMapDispatchToProps","initMergeProps"];function x(e,t,n,r,{areStatesEqual:o,areOwnPropsEqual:s,areStatePropsEqual:i}){let a,l,c,u,p,h=!1;function f(h,f){const d=!s(f,l),m=!o(h,a,f,l);return a=h,l=f,d&&m?(c=e(a,l),t.dependsOnOwnProps&&(u=t(r,l)),p=n(c,u,l),p):d?(e.dependsOnOwnProps&&(c=e(a,l)),t.dependsOnOwnProps&&(u=t(r,l)),p=n(c,u,l),p):m?function(){const t=e(a,l),r=!i(t,c);return c=t,r&&(p=n(c,u,l)),p}():p}return function(o,s){return h?f(o,s):(a=o,l=s,c=e(a,l),u=t(r,l),p=n(c,u,l),h=!0,p)}}function S(e){return function(t){const n=e(t);function r(){return n}return r.dependsOnOwnProps=!1,r}}function _(e){return e.dependsOnOwnProps?Boolean(e.dependsOnOwnProps):1!==e.length}function j(e,t){return function(t,{displayName:n}){const r=function(e,t){return r.dependsOnOwnProps?r.mapToProps(e,t):r.mapToProps(e,void 0)};return r.dependsOnOwnProps=!0,r.mapToProps=function(t,n){r.mapToProps=e,r.dependsOnOwnProps=_(e);let o=r(t,n);return"function"==typeof o&&(r.mapToProps=o,r.dependsOnOwnProps=_(o),o=r(t,n)),o},r}}function O(e,t){return(n,r)=>{throw new Error(`Invalid value of type ${typeof e} for ${t} argument when connecting component ${r.wrappedComponentName}.`)}}function k(e,t,n){return(0,g.Z)({},n,e,t)}const A={notify(){},get:()=>[]};function C(e,t){let n,r=A;function o(){i.onStateChange&&i.onStateChange()}function s(){n||(n=t?t.addNestedSub(o):e.subscribe(o),r=function(){const e=f();let t=null,n=null;return{clear(){t=null,n=null},notify(){e((()=>{let e=t;for(;e;)e.callback(),e=e.next}))},get(){let e=[],n=t;for(;n;)e.push(n),n=n.next;return e},subscribe(e){let r=!0,o=n={callback:e,next:null,prev:n};return o.prev?o.prev.next=o:t=o,function(){r&&null!==t&&(r=!1,o.next?o.next.prev=o.prev:n=o.prev,o.prev?o.prev.next=o.next:t=o.next)}}}}())}const i={addNestedSub:function(e){return s(),r.subscribe(e)},notifyNestedSubs:function(){r.notify()},handleChangeWrapper:o,isSubscribed:function(){return Boolean(n)},trySubscribe:s,tryUnsubscribe:function(){n&&(n(),n=void 0,r.clear(),r=A)},getListeners:()=>r};return i}const P=!("undefined"==typeof window||void 0===window.document||void 0===window.document.createElement)?a.useLayoutEffect:a.useEffect;function N(e,t){return e===t?0!==e||0!==t||1/e==1/t:e!=e&&t!=t}function I(e,t){if(N(e,t))return!0;if("object"!=typeof e||null===e||"object"!=typeof t||null===t)return!1;const n=Object.keys(e),r=Object.keys(t);if(n.length!==r.length)return!1;for(let r=0;r{throw new Error("uSES not initialized!")};const M=[null,null];function D(e,t,n,r,o,s){e.current=r,n.current=!1,o.current&&(o.current=null,s())}function F(e,t){return e===t}const L=function(e,t,n,{pure:r,areStatesEqual:o=F,areOwnPropsEqual:s=I,areStatePropsEqual:i=I,areMergedPropsEqual:l=I,forwardRef:c=!1,context:u=d}={}){const p=u,h=function(e){return e?"function"==typeof e?j(e):O(e,"mapStateToProps"):S((()=>({})))}(e),f=function(e){return e&&"object"==typeof e?S((t=>function(e,t){const n={};for(const r in e){const o=e[r];"function"==typeof o&&(n[r]=(...e)=>t(o(...e)))}return n}(e,t))):e?"function"==typeof e?j(e):O(e,"mapDispatchToProps"):S((e=>({dispatch:e})))}(t),m=function(e){return e?"function"==typeof e?function(e){return function(t,{displayName:n,areMergedPropsEqual:r}){let o,s=!1;return function(t,n,i){const a=e(t,n,i);return s?r(a,o)||(o=a):(s=!0,o=a),o}}}(e):O(e,"mergeProps"):()=>k}(n),v=Boolean(e);return e=>{const t=e.displayName||e.name||"Component",n=`Connect(${t})`,r={shouldHandleStateChanges:v,displayName:n,wrappedComponentName:t,WrappedComponent:e,initMapStateToProps:h,initMapDispatchToProps:f,initMergeProps:m,areStatesEqual:o,areStatePropsEqual:i,areOwnPropsEqual:s,areMergedPropsEqual:l};function u(t){const[n,o,s]=(0,a.useMemo)((()=>{const{reactReduxForwardedRef:e}=t,n=(0,y.Z)(t,T);return[t.context,e,n]}),[t]),i=(0,a.useMemo)((()=>n&&n.Consumer&&(0,w.isContextConsumer)(a.createElement(n.Consumer,null))?n:p),[n,p]),l=(0,a.useContext)(i),c=Boolean(t.store)&&Boolean(t.store.getState)&&Boolean(t.store.dispatch),u=Boolean(l)&&Boolean(l.store);const h=c?t.store:l.store,f=u?l.getServerState:h.getState,d=(0,a.useMemo)((()=>function(e,t){let{initMapStateToProps:n,initMapDispatchToProps:r,initMergeProps:o}=t,s=(0,y.Z)(t,E);return x(n(e,s),r(e,s),o(e,s),e,s)}(h.dispatch,r)),[h]),[m,b]=(0,a.useMemo)((()=>{if(!v)return M;const e=C(h,c?void 0:l.subscription),t=e.notifyNestedSubs.bind(e);return[e,t]}),[h,c,l]),S=(0,a.useMemo)((()=>c?l:(0,g.Z)({},l,{subscription:m})),[c,l,m]),_=(0,a.useRef)(),j=(0,a.useRef)(s),O=(0,a.useRef)(),k=(0,a.useRef)(!1),A=((0,a.useRef)(!1),(0,a.useRef)(!1)),N=(0,a.useRef)();P((()=>(A.current=!0,()=>{A.current=!1})),[]);const I=(0,a.useMemo)((()=>()=>O.current&&s===j.current?O.current:d(h.getState(),s)),[h,s]),F=(0,a.useMemo)((()=>e=>m?function(e,t,n,r,o,s,i,a,l,c,u){if(!e)return()=>{};let p=!1,h=null;const f=()=>{if(p||!a.current)return;const e=t.getState();let n,f;try{n=r(e,o.current)}catch(e){f=e,h=e}f||(h=null),n===s.current?i.current||c():(s.current=n,l.current=n,i.current=!0,u())};return n.onStateChange=f,n.trySubscribe(),f(),()=>{if(p=!0,n.tryUnsubscribe(),n.onStateChange=null,h)throw h}}(v,h,m,d,j,_,k,A,O,b,e):()=>{}),[m]);var L,B,$;let q;L=D,B=[j,_,k,s,O,b],P((()=>L(...B)),$);try{q=R(F,I,f?()=>d(f(),s):I)}catch(e){throw N.current&&(e.message+=`\nThe error may be correlated with this previous error:\n${N.current.stack}\n\n`),e}P((()=>{N.current=void 0,O.current=void 0,_.current=q}));const U=(0,a.useMemo)((()=>a.createElement(e,(0,g.Z)({},q,{ref:o}))),[o,e,q]);return(0,a.useMemo)((()=>v?a.createElement(i.Provider,{value:S},U):U),[i,U,S])}const d=a.memo(u);if(d.WrappedComponent=e,d.displayName=u.displayName=n,c){const t=a.forwardRef((function(e,t){return a.createElement(d,(0,g.Z)({},e,{reactReduxForwardedRef:t}))}));return t.displayName=n,t.WrappedComponent=e,b()(t,e)}return b()(d,e)}};const B=function({store:e,context:t,children:n,serverState:r}){const o=(0,a.useMemo)((()=>{const t=C(e);return{store:e,subscription:t,getServerState:r?()=>r:void 0}}),[e,r]),s=(0,a.useMemo)((()=>e.getState()),[e]);P((()=>{const{subscription:t}=o;return t.onStateChange=t.notifyNestedSubs,t.trySubscribe(),s!==e.getState()&&t.notifyNestedSubs(),()=>{t.tryUnsubscribe(),t.onStateChange=void 0}}),[o,s]);const i=t||d;return a.createElement(i.Provider,{value:o},n)};var $,q;$=p.useSyncExternalStoreWithSelector,m=$,(e=>{R=e})(u.useSyncExternalStore),q=l.unstable_batchedUpdates,h=q;var U=n(57557),z=n.n(U),V=n(6557),W=n.n(V);const J=e=>t=>{const{fn:n}=e();class r extends a.Component{render(){return a.createElement(t,o()({},e(),this.props,this.context))}}return r.displayName=`WithSystem(${n.getDisplayName(t)})`,r},K=(e,t)=>n=>{const{fn:r}=e();class s extends a.Component{render(){return a.createElement(B,{store:t},a.createElement(n,o()({},this.props,this.context)))}}return s.displayName=`WithRoot(${r.getDisplayName(n)})`,s},H=(e,t,n)=>(0,c.qC)(n?K(e,n):W(),L(((n,r)=>{var o;const s={...r,...e()},i=(null===(o=t.prototype)||void 0===o?void 0:o.mapStateToProps)||(e=>({state:e}));return i(n,s)})),J(e))(t),G=(e,t,n,r)=>{for(const o in t){const s=t[o];"function"==typeof s&&s(n[o],r[o],e())}},Z=(e,t,n)=>(t,r)=>{const{fn:o}=e(),s=n(t,"root");class l extends a.Component{constructor(t,n){super(t,n),G(e,r,t,{})}UNSAFE_componentWillReceiveProps(t){G(e,r,t,this.props)}render(){const e=z()(this.props,r?i()(r):[]);return a.createElement(s,e)}}return l.displayName=`WithMappedContainer(${o.getDisplayName(s)})`,l},Y=(e,t,n,r)=>o=>{const s=n(e,t,r)("App","root");l.render(a.createElement(s,null),o)},X=(e,t,n)=>function(r,o){let s=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};if("string"!=typeof r)throw new TypeError("Need a string, to fetch a component. Was given a "+typeof r);const i=n(r);return i?o?"root"===o?H(e,i,t()):H(e,i):i:(s.failSilently||e().log.warn("Could not find component:",r),null)}},33424:(e,t,n)=>{"use strict";n.d(t,{d3:()=>D,C2:()=>ee});var r=n(28222),o=n.n(r),s=n(58118),i=n.n(s),a=n(63366);function l(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n1&&void 0!==arguments[1]?arguments[1]:{},n=arguments.length>2?arguments[2]:void 0;return function(e){if(0===e.length||1===e.length)return e;var t,n,r=e.join(".");return m[r]||(m[r]=0===(n=(t=e).length)||1===n?t:2===n?[t[0],t[1],"".concat(t[0],".").concat(t[1]),"".concat(t[1],".").concat(t[0])]:3===n?[t[0],t[1],t[2],"".concat(t[0],".").concat(t[1]),"".concat(t[0],".").concat(t[2]),"".concat(t[1],".").concat(t[0]),"".concat(t[1],".").concat(t[2]),"".concat(t[2],".").concat(t[0]),"".concat(t[2],".").concat(t[1]),"".concat(t[0],".").concat(t[1],".").concat(t[2]),"".concat(t[0],".").concat(t[2],".").concat(t[1]),"".concat(t[1],".").concat(t[0],".").concat(t[2]),"".concat(t[1],".").concat(t[2],".").concat(t[0]),"".concat(t[2],".").concat(t[0],".").concat(t[1]),"".concat(t[2],".").concat(t[1],".").concat(t[0])]:n>=4?[t[0],t[1],t[2],t[3],"".concat(t[0],".").concat(t[1]),"".concat(t[0],".").concat(t[2]),"".concat(t[0],".").concat(t[3]),"".concat(t[1],".").concat(t[0]),"".concat(t[1],".").concat(t[2]),"".concat(t[1],".").concat(t[3]),"".concat(t[2],".").concat(t[0]),"".concat(t[2],".").concat(t[1]),"".concat(t[2],".").concat(t[3]),"".concat(t[3],".").concat(t[0]),"".concat(t[3],".").concat(t[1]),"".concat(t[3],".").concat(t[2]),"".concat(t[0],".").concat(t[1],".").concat(t[2]),"".concat(t[0],".").concat(t[1],".").concat(t[3]),"".concat(t[0],".").concat(t[2],".").concat(t[1]),"".concat(t[0],".").concat(t[2],".").concat(t[3]),"".concat(t[0],".").concat(t[3],".").concat(t[1]),"".concat(t[0],".").concat(t[3],".").concat(t[2]),"".concat(t[1],".").concat(t[0],".").concat(t[2]),"".concat(t[1],".").concat(t[0],".").concat(t[3]),"".concat(t[1],".").concat(t[2],".").concat(t[0]),"".concat(t[1],".").concat(t[2],".").concat(t[3]),"".concat(t[1],".").concat(t[3],".").concat(t[0]),"".concat(t[1],".").concat(t[3],".").concat(t[2]),"".concat(t[2],".").concat(t[0],".").concat(t[1]),"".concat(t[2],".").concat(t[0],".").concat(t[3]),"".concat(t[2],".").concat(t[1],".").concat(t[0]),"".concat(t[2],".").concat(t[1],".").concat(t[3]),"".concat(t[2],".").concat(t[3],".").concat(t[0]),"".concat(t[2],".").concat(t[3],".").concat(t[1]),"".concat(t[3],".").concat(t[0],".").concat(t[1]),"".concat(t[3],".").concat(t[0],".").concat(t[2]),"".concat(t[3],".").concat(t[1],".").concat(t[0]),"".concat(t[3],".").concat(t[1],".").concat(t[2]),"".concat(t[3],".").concat(t[2],".").concat(t[0]),"".concat(t[3],".").concat(t[2],".").concat(t[1]),"".concat(t[0],".").concat(t[1],".").concat(t[2],".").concat(t[3]),"".concat(t[0],".").concat(t[1],".").concat(t[3],".").concat(t[2]),"".concat(t[0],".").concat(t[2],".").concat(t[1],".").concat(t[3]),"".concat(t[0],".").concat(t[2],".").concat(t[3],".").concat(t[1]),"".concat(t[0],".").concat(t[3],".").concat(t[1],".").concat(t[2]),"".concat(t[0],".").concat(t[3],".").concat(t[2],".").concat(t[1]),"".concat(t[1],".").concat(t[0],".").concat(t[2],".").concat(t[3]),"".concat(t[1],".").concat(t[0],".").concat(t[3],".").concat(t[2]),"".concat(t[1],".").concat(t[2],".").concat(t[0],".").concat(t[3]),"".concat(t[1],".").concat(t[2],".").concat(t[3],".").concat(t[0]),"".concat(t[1],".").concat(t[3],".").concat(t[0],".").concat(t[2]),"".concat(t[1],".").concat(t[3],".").concat(t[2],".").concat(t[0]),"".concat(t[2],".").concat(t[0],".").concat(t[1],".").concat(t[3]),"".concat(t[2],".").concat(t[0],".").concat(t[3],".").concat(t[1]),"".concat(t[2],".").concat(t[1],".").concat(t[0],".").concat(t[3]),"".concat(t[2],".").concat(t[1],".").concat(t[3],".").concat(t[0]),"".concat(t[2],".").concat(t[3],".").concat(t[0],".").concat(t[1]),"".concat(t[2],".").concat(t[3],".").concat(t[1],".").concat(t[0]),"".concat(t[3],".").concat(t[0],".").concat(t[1],".").concat(t[2]),"".concat(t[3],".").concat(t[0],".").concat(t[2],".").concat(t[1]),"".concat(t[3],".").concat(t[1],".").concat(t[0],".").concat(t[2]),"".concat(t[3],".").concat(t[1],".").concat(t[2],".").concat(t[0]),"".concat(t[3],".").concat(t[2],".").concat(t[0],".").concat(t[1]),"".concat(t[3],".").concat(t[2],".").concat(t[1],".").concat(t[0])]:void 0),m[r]}(e.filter((function(e){return"token"!==e}))).reduce((function(e,t){return d(d({},e),n[t])}),t)}function y(e){return e.join(" ")}function v(e){var t=e.node,n=e.stylesheet,r=e.style,o=void 0===r?{}:r,s=e.useInlineStyles,i=e.key,a=t.properties,l=t.type,c=t.tagName,u=t.value;if("text"===l)return u;if(c){var f,m=function(e,t){var n=0;return function(r){return n+=1,r.map((function(r,o){return v({node:r,stylesheet:e,useInlineStyles:t,key:"code-segment-".concat(n,"-").concat(o)})}))}}(n,s);if(s){var b=Object.keys(n).reduce((function(e,t){return t.split(".").forEach((function(t){e.includes(t)||e.push(t)})),e}),[]),w=a.className&&a.className.includes("token")?["token"]:[],E=a.className&&w.concat(a.className.filter((function(e){return!b.includes(e)})));f=d(d({},a),{},{className:y(E)||void 0,style:g(a.className,Object.assign({},a.style,o),n)})}else f=d(d({},a),{},{className:y(a.className)});var x=m(t.children);return p.createElement(c,(0,h.Z)({key:i},f),x)}}const b=function(e,t){return-1!==e.listLanguages().indexOf(t)};var w=["language","children","style","customStyle","codeTagProps","useInlineStyles","showLineNumbers","showInlineLineNumbers","startingLineNumber","lineNumberContainerStyle","lineNumberStyle","wrapLines","wrapLongLines","lineProps","renderer","PreTag","CodeTag","code","astGenerator"];function E(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function x(e){for(var t=1;t1&&void 0!==arguments[1]?arguments[1]:[],n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:[],r=0;r2&&void 0!==arguments[2]?arguments[2]:[];return t||c.length>0?function(e,t){return k({children:e,lineNumber:t,lineNumberStyle:a,largestLineNumber:i,showInlineLineNumbers:o,lineProps:n,className:arguments.length>2&&void 0!==arguments[2]?arguments[2]:[],showLineNumbers:r,wrapLongLines:l})}(e,s,c):function(e,t){if(r&&t&&o){var n=O(a,t,i);e.unshift(j(t,n))}return e}(e,s)}for(var m=function(){var e=u[f],t=e.children[0].value;if(t.match(S)){var n=t.split("\n");n.forEach((function(t,o){var i=r&&p.length+s,a={type:"text",value:"".concat(t,"\n")};if(0===o){var l=d(u.slice(h+1,f).concat(k({children:[a],className:e.properties.className})),i);p.push(l)}else if(o===n.length-1){var c=u[f+1]&&u[f+1].children&&u[f+1].children[0],m={type:"text",value:"".concat(t)};if(c){var g=k({children:[m],className:e.properties.className});u.splice(f+1,0,g)}else{var y=d([m],i,e.properties.className);p.push(y)}}else{var v=d([a],i,e.properties.className);p.push(v)}})),h=f}f++};f=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}(e,w);z=z||I;var W=d?p.createElement(_,{containerStyle:E,codeStyle:c.style||{},numberStyle:j,startingLineNumber:v,codeString:U}):null,J=o.hljs||o['pre[class*="language-"]']||{backgroundColor:"#fff"},K=N(z)?"hljs":"prismjs",H=h?Object.assign({},V,{style:Object.assign({},J,i)}):Object.assign({},V,{className:V.className?"".concat(K," ").concat(V.className):K,style:Object.assign({},i)});if(c.style=x(x({},c.style),{},A?{whiteSpace:"pre-wrap"}:{whiteSpace:"pre"}),!z)return p.createElement(L,H,W,p.createElement($,c,U));(void 0===O&&D||A)&&(O=!0),D=D||P;var G=[{type:"text",value:U}],Z=function(e){var t=e.astGenerator,n=e.language,r=e.code,o=e.defaultCodeValue;if(N(t)){var s=b(t,n);return"text"===n?{value:o,language:"text"}:s?t.highlight(n,r):t.highlightAuto(r)}try{return n&&"text"!==n?{value:t.highlight(r,n)}:{value:o}}catch(e){return{value:o}}}({astGenerator:z,language:t,code:U,defaultCodeValue:G});null===Z.language&&(Z.value=G);var Y=C(Z,O,M,d,g,v,Z.value.length+v,j,A);return p.createElement(L,H,p.createElement($,c,!g&&W,D({rows:Y,stylesheet:o,useInlineStyles:h})))});M.registerLanguage=R.registerLanguage;const D=M;var F=n(96344);const L=n.n(F)();var B=n(82026);const $=n.n(B)();var q=n(42157);const U=n.n(q)();var z=n(61519);const V=n.n(z)();var W=n(54587);const J=n.n(W)();var K=n(30786);const H=n.n(K)();var G=n(66336);const Z=n.n(G)(),Y={hljs:{display:"block",overflowX:"auto",padding:"0.5em",background:"#333",color:"white"},"hljs-name":{fontWeight:"bold"},"hljs-strong":{fontWeight:"bold"},"hljs-code":{fontStyle:"italic",color:"#888"},"hljs-emphasis":{fontStyle:"italic"},"hljs-tag":{color:"#62c8f3"},"hljs-variable":{color:"#ade5fc"},"hljs-template-variable":{color:"#ade5fc"},"hljs-selector-id":{color:"#ade5fc"},"hljs-selector-class":{color:"#ade5fc"},"hljs-string":{color:"#a2fca2"},"hljs-bullet":{color:"#d36363"},"hljs-type":{color:"#ffa"},"hljs-title":{color:"#ffa"},"hljs-section":{color:"#ffa"},"hljs-attribute":{color:"#ffa"},"hljs-quote":{color:"#ffa"},"hljs-built_in":{color:"#ffa"},"hljs-builtin-name":{color:"#ffa"},"hljs-number":{color:"#d36363"},"hljs-symbol":{color:"#d36363"},"hljs-keyword":{color:"#fcc28c"},"hljs-selector-tag":{color:"#fcc28c"},"hljs-literal":{color:"#fcc28c"},"hljs-comment":{color:"#888"},"hljs-deletion":{color:"#333",backgroundColor:"#fc9b9b"},"hljs-regexp":{color:"#c6b4f0"},"hljs-link":{color:"#c6b4f0"},"hljs-meta":{color:"#fc9b9b"},"hljs-addition":{backgroundColor:"#a2fca2",color:"#333"}};D.registerLanguage("json",$),D.registerLanguage("js",L),D.registerLanguage("xml",U),D.registerLanguage("yaml",J),D.registerLanguage("http",H),D.registerLanguage("bash",V),D.registerLanguage("powershell",Z),D.registerLanguage("javascript",L);const X={agate:Y,arta:{hljs:{display:"block",overflowX:"auto",padding:"0.5em",background:"#222",color:"#aaa"},"hljs-subst":{color:"#aaa"},"hljs-section":{color:"#fff",fontWeight:"bold"},"hljs-comment":{color:"#444"},"hljs-quote":{color:"#444"},"hljs-meta":{color:"#444"},"hljs-string":{color:"#ffcc33"},"hljs-symbol":{color:"#ffcc33"},"hljs-bullet":{color:"#ffcc33"},"hljs-regexp":{color:"#ffcc33"},"hljs-number":{color:"#00cc66"},"hljs-addition":{color:"#00cc66"},"hljs-built_in":{color:"#32aaee"},"hljs-builtin-name":{color:"#32aaee"},"hljs-literal":{color:"#32aaee"},"hljs-type":{color:"#32aaee"},"hljs-template-variable":{color:"#32aaee"},"hljs-attribute":{color:"#32aaee"},"hljs-link":{color:"#32aaee"},"hljs-keyword":{color:"#6644aa"},"hljs-selector-tag":{color:"#6644aa"},"hljs-name":{color:"#6644aa"},"hljs-selector-id":{color:"#6644aa"},"hljs-selector-class":{color:"#6644aa"},"hljs-title":{color:"#bb1166"},"hljs-variable":{color:"#bb1166"},"hljs-deletion":{color:"#bb1166"},"hljs-template-tag":{color:"#bb1166"},"hljs-doctag":{fontWeight:"bold"},"hljs-strong":{fontWeight:"bold"},"hljs-emphasis":{fontStyle:"italic"}},monokai:{hljs:{display:"block",overflowX:"auto",padding:"0.5em",background:"#272822",color:"#ddd"},"hljs-tag":{color:"#f92672"},"hljs-keyword":{color:"#f92672",fontWeight:"bold"},"hljs-selector-tag":{color:"#f92672",fontWeight:"bold"},"hljs-literal":{color:"#f92672",fontWeight:"bold"},"hljs-strong":{color:"#f92672"},"hljs-name":{color:"#f92672"},"hljs-code":{color:"#66d9ef"},"hljs-class .hljs-title":{color:"white"},"hljs-attribute":{color:"#bf79db"},"hljs-symbol":{color:"#bf79db"},"hljs-regexp":{color:"#bf79db"},"hljs-link":{color:"#bf79db"},"hljs-string":{color:"#a6e22e"},"hljs-bullet":{color:"#a6e22e"},"hljs-subst":{color:"#a6e22e"},"hljs-title":{color:"#a6e22e",fontWeight:"bold"},"hljs-section":{color:"#a6e22e",fontWeight:"bold"},"hljs-emphasis":{color:"#a6e22e"},"hljs-type":{color:"#a6e22e",fontWeight:"bold"},"hljs-built_in":{color:"#a6e22e"},"hljs-builtin-name":{color:"#a6e22e"},"hljs-selector-attr":{color:"#a6e22e"},"hljs-selector-pseudo":{color:"#a6e22e"},"hljs-addition":{color:"#a6e22e"},"hljs-variable":{color:"#a6e22e"},"hljs-template-tag":{color:"#a6e22e"},"hljs-template-variable":{color:"#a6e22e"},"hljs-comment":{color:"#75715e"},"hljs-quote":{color:"#75715e"},"hljs-deletion":{color:"#75715e"},"hljs-meta":{color:"#75715e"},"hljs-doctag":{fontWeight:"bold"},"hljs-selector-id":{fontWeight:"bold"}},nord:{hljs:{display:"block",overflowX:"auto",padding:"0.5em",background:"#2E3440",color:"#D8DEE9"},"hljs-subst":{color:"#D8DEE9"},"hljs-selector-tag":{color:"#81A1C1"},"hljs-selector-id":{color:"#8FBCBB",fontWeight:"bold"},"hljs-selector-class":{color:"#8FBCBB"},"hljs-selector-attr":{color:"#8FBCBB"},"hljs-selector-pseudo":{color:"#88C0D0"},"hljs-addition":{backgroundColor:"rgba(163, 190, 140, 0.5)"},"hljs-deletion":{backgroundColor:"rgba(191, 97, 106, 0.5)"},"hljs-built_in":{color:"#8FBCBB"},"hljs-type":{color:"#8FBCBB"},"hljs-class":{color:"#8FBCBB"},"hljs-function":{color:"#88C0D0"},"hljs-function > .hljs-title":{color:"#88C0D0"},"hljs-keyword":{color:"#81A1C1"},"hljs-literal":{color:"#81A1C1"},"hljs-symbol":{color:"#81A1C1"},"hljs-number":{color:"#B48EAD"},"hljs-regexp":{color:"#EBCB8B"},"hljs-string":{color:"#A3BE8C"},"hljs-title":{color:"#8FBCBB"},"hljs-params":{color:"#D8DEE9"},"hljs-bullet":{color:"#81A1C1"},"hljs-code":{color:"#8FBCBB"},"hljs-emphasis":{fontStyle:"italic"},"hljs-formula":{color:"#8FBCBB"},"hljs-strong":{fontWeight:"bold"},"hljs-link:hover":{textDecoration:"underline"},"hljs-quote":{color:"#4C566A"},"hljs-comment":{color:"#4C566A"},"hljs-doctag":{color:"#8FBCBB"},"hljs-meta":{color:"#5E81AC"},"hljs-meta-keyword":{color:"#5E81AC"},"hljs-meta-string":{color:"#A3BE8C"},"hljs-attr":{color:"#8FBCBB"},"hljs-attribute":{color:"#D8DEE9"},"hljs-builtin-name":{color:"#81A1C1"},"hljs-name":{color:"#81A1C1"},"hljs-section":{color:"#88C0D0"},"hljs-tag":{color:"#81A1C1"},"hljs-variable":{color:"#D8DEE9"},"hljs-template-variable":{color:"#D8DEE9"},"hljs-template-tag":{color:"#5E81AC"},"abnf .hljs-attribute":{color:"#88C0D0"},"abnf .hljs-symbol":{color:"#EBCB8B"},"apache .hljs-attribute":{color:"#88C0D0"},"apache .hljs-section":{color:"#81A1C1"},"arduino .hljs-built_in":{color:"#88C0D0"},"aspectj .hljs-meta":{color:"#D08770"},"aspectj > .hljs-title":{color:"#88C0D0"},"bnf .hljs-attribute":{color:"#8FBCBB"},"clojure .hljs-name":{color:"#88C0D0"},"clojure .hljs-symbol":{color:"#EBCB8B"},"coq .hljs-built_in":{color:"#88C0D0"},"cpp .hljs-meta-string":{color:"#8FBCBB"},"css .hljs-built_in":{color:"#88C0D0"},"css .hljs-keyword":{color:"#D08770"},"diff .hljs-meta":{color:"#8FBCBB"},"ebnf .hljs-attribute":{color:"#8FBCBB"},"glsl .hljs-built_in":{color:"#88C0D0"},"groovy .hljs-meta:not(:first-child)":{color:"#D08770"},"haxe .hljs-meta":{color:"#D08770"},"java .hljs-meta":{color:"#D08770"},"ldif .hljs-attribute":{color:"#8FBCBB"},"lisp .hljs-name":{color:"#88C0D0"},"lua .hljs-built_in":{color:"#88C0D0"},"moonscript .hljs-built_in":{color:"#88C0D0"},"nginx .hljs-attribute":{color:"#88C0D0"},"nginx .hljs-section":{color:"#5E81AC"},"pf .hljs-built_in":{color:"#88C0D0"},"processing .hljs-built_in":{color:"#88C0D0"},"scss .hljs-keyword":{color:"#81A1C1"},"stylus .hljs-keyword":{color:"#81A1C1"},"swift .hljs-meta":{color:"#D08770"},"vim .hljs-built_in":{color:"#88C0D0",fontStyle:"italic"},"yaml .hljs-meta":{color:"#D08770"}},obsidian:{hljs:{display:"block",overflowX:"auto",padding:"0.5em",background:"#282b2e",color:"#e0e2e4"},"hljs-keyword":{color:"#93c763",fontWeight:"bold"},"hljs-selector-tag":{color:"#93c763",fontWeight:"bold"},"hljs-literal":{color:"#93c763",fontWeight:"bold"},"hljs-selector-id":{color:"#93c763"},"hljs-number":{color:"#ffcd22"},"hljs-attribute":{color:"#668bb0"},"hljs-code":{color:"white"},"hljs-class .hljs-title":{color:"white"},"hljs-section":{color:"white",fontWeight:"bold"},"hljs-regexp":{color:"#d39745"},"hljs-link":{color:"#d39745"},"hljs-meta":{color:"#557182"},"hljs-tag":{color:"#8cbbad"},"hljs-name":{color:"#8cbbad",fontWeight:"bold"},"hljs-bullet":{color:"#8cbbad"},"hljs-subst":{color:"#8cbbad"},"hljs-emphasis":{color:"#8cbbad"},"hljs-type":{color:"#8cbbad",fontWeight:"bold"},"hljs-built_in":{color:"#8cbbad"},"hljs-selector-attr":{color:"#8cbbad"},"hljs-selector-pseudo":{color:"#8cbbad"},"hljs-addition":{color:"#8cbbad"},"hljs-variable":{color:"#8cbbad"},"hljs-template-tag":{color:"#8cbbad"},"hljs-template-variable":{color:"#8cbbad"},"hljs-string":{color:"#ec7600"},"hljs-symbol":{color:"#ec7600"},"hljs-comment":{color:"#818e96"},"hljs-quote":{color:"#818e96"},"hljs-deletion":{color:"#818e96"},"hljs-selector-class":{color:"#A082BD"},"hljs-doctag":{fontWeight:"bold"},"hljs-title":{fontWeight:"bold"},"hljs-strong":{fontWeight:"bold"}},"tomorrow-night":{"hljs-comment":{color:"#969896"},"hljs-quote":{color:"#969896"},"hljs-variable":{color:"#cc6666"},"hljs-template-variable":{color:"#cc6666"},"hljs-tag":{color:"#cc6666"},"hljs-name":{color:"#cc6666"},"hljs-selector-id":{color:"#cc6666"},"hljs-selector-class":{color:"#cc6666"},"hljs-regexp":{color:"#cc6666"},"hljs-deletion":{color:"#cc6666"},"hljs-number":{color:"#de935f"},"hljs-built_in":{color:"#de935f"},"hljs-builtin-name":{color:"#de935f"},"hljs-literal":{color:"#de935f"},"hljs-type":{color:"#de935f"},"hljs-params":{color:"#de935f"},"hljs-meta":{color:"#de935f"},"hljs-link":{color:"#de935f"},"hljs-attribute":{color:"#f0c674"},"hljs-string":{color:"#b5bd68"},"hljs-symbol":{color:"#b5bd68"},"hljs-bullet":{color:"#b5bd68"},"hljs-addition":{color:"#b5bd68"},"hljs-title":{color:"#81a2be"},"hljs-section":{color:"#81a2be"},"hljs-keyword":{color:"#b294bb"},"hljs-selector-tag":{color:"#b294bb"},hljs:{display:"block",overflowX:"auto",background:"#1d1f21",color:"#c5c8c6",padding:"0.5em"},"hljs-emphasis":{fontStyle:"italic"},"hljs-strong":{fontWeight:"bold"}}},Q=o()(X),ee=e=>i()(Q).call(Q,e)?X[e]:(console.warn(`Request style '${e}' is not available, returning default instead`),Y)},90242:(e,t,n)=>{"use strict";n.d(t,{AF:()=>ae,Ay:()=>fe,D$:()=>De,DR:()=>ve,GZ:()=>je,HP:()=>he,Ik:()=>Ee,J6:()=>Ne,Kn:()=>ce,LQ:()=>le,Nm:()=>ke,O2:()=>Ue,Pz:()=>Me,Q2:()=>de,QG:()=>Ce,UG:()=>xe,Uj:()=>Be,V9:()=>Fe,Wl:()=>ue,XV:()=>Re,Xb:()=>$e,Zl:()=>be,_5:()=>me,be:()=>Oe,cz:()=>Le,gp:()=>ye,hW:()=>Ae,iQ:()=>ge,kJ:()=>pe,mz:()=>se,nX:()=>Ie,oG:()=>ie,oJ:()=>Pe,po:()=>Te,r3:()=>Se,wh:()=>_e});var r=n(58309),o=n.n(r),s=n(97606),i=n.n(s),a=n(74386),l=n.n(a),c=n(86),u=n.n(c),p=n(14418),h=n.n(p),f=n(28222),d=n.n(f),m=(n(11189),n(24282)),g=n.n(m),y=n(76986),v=n.n(y),b=n(2578),w=n.n(b),E=(n(24278),n(39022),n(92039)),x=n.n(E),S=(n(58118),n(11882)),_=n.n(S),j=n(51679),O=n.n(j),k=n(27043),A=n.n(k),C=n(81607),P=n.n(C),N=n(35627),I=n.n(N),T=n(43393),R=n.n(T),M=n(17967),D=n(68929),F=n.n(D),L=n(11700),B=n.n(L),$=n(88306),q=n.n($),U=n(13311),z=n.n(U),V=(n(59704),n(77813)),W=n.n(V),J=n(23560),K=n.n(J),H=n(27504),G=n(8269),Z=n.n(G),Y=n(19069),X=n(92282),Q=n.n(X),ee=n(89072),te=n.n(ee),ne=n(48764).Buffer;const re="default",oe=e=>R().Iterable.isIterable(e);function se(e){return ce(e)?oe(e)?e.toJS():e:{}}function ie(e){var t,n;if(oe(e))return e;if(e instanceof H.Z.File)return e;if(!ce(e))return e;if(o()(e))return i()(n=R().Seq(e)).call(n,ie).toList();if(K()(l()(e))){var r;const t=function(e){if(!K()(l()(e)))return e;const t={},n="_**[]",r={};for(let o of l()(e).call(e))if(t[o[0]]||r[o[0]]&&r[o[0]].containsMultiple){if(!r[o[0]]){r[o[0]]={containsMultiple:!0,length:1},t[`${o[0]}${n}${r[o[0]].length}`]=t[o[0]],delete t[o[0]]}r[o[0]].length+=1,t[`${o[0]}${n}${r[o[0]].length}`]=o[1]}else t[o[0]]=o[1];return t}(e);return i()(r=R().OrderedMap(t)).call(r,ie)}return i()(t=R().OrderedMap(e)).call(t,ie)}function ae(e){return o()(e)?e:[e]}function le(e){return"function"==typeof e}function ce(e){return!!e&&"object"==typeof e}function ue(e){return"function"==typeof e}function pe(e){return o()(e)}const he=q();function fe(e,t){var n;return g()(n=d()(e)).call(n,((n,r)=>(n[r]=t(e[r],r),n)),{})}function de(e,t){var n;return g()(n=d()(e)).call(n,((n,r)=>{let o=t(e[r],r);return o&&"object"==typeof o&&v()(n,o),n}),{})}function me(e){return t=>{let{dispatch:n,getState:r}=t;return t=>n=>"function"==typeof n?n(e()):t(n)}}function ge(e){var t;let n=e.keySeq();return n.contains(re)?re:w()(t=h()(n).call(n,(e=>"2"===(e+"")[0]))).call(t).first()}function ye(e,t){if(!R().Iterable.isIterable(e))return R().List();let n=e.getIn(o()(t)?t:[t]);return R().List.isList(n)?n:R().List()}function ve(e){let t,n=[/filename\*=[^']+'\w*'"([^"]+)";?/i,/filename\*=[^']+'\w*'([^;]+);?/i,/filename="([^;]*);?"/i,/filename=([^;]*);?/i];if(x()(n).call(n,(n=>(t=n.exec(e),null!==t))),null!==t&&t.length>1)try{return decodeURIComponent(t[1])}catch(e){console.error(e)}return null}function be(e){return t=e.replace(/\.[^./]*$/,""),B()(F()(t));var t}function we(e,t,n,r,s){if(!t)return[];let a=[],l=t.get("nullable"),c=t.get("required"),p=t.get("maximum"),f=t.get("minimum"),d=t.get("type"),m=t.get("format"),g=t.get("maxLength"),y=t.get("minLength"),v=t.get("uniqueItems"),b=t.get("maxItems"),w=t.get("minItems"),E=t.get("pattern");const S=n||!0===c,_=null!=e;if(l&&null===e||!d||!(S||_&&"array"===d||!(!S&&!_)))return[];let j="string"===d&&e,O="array"===d&&o()(e)&&e.length,k="array"===d&&R().List.isList(e)&&e.count();const A=[j,O,k,"array"===d&&"string"==typeof e&&e,"file"===d&&e instanceof H.Z.File,"boolean"===d&&(e||!1===e),"number"===d&&(e||0===e),"integer"===d&&(e||0===e),"object"===d&&"object"==typeof e&&null!==e,"object"===d&&"string"==typeof e&&e],C=x()(A).call(A,(e=>!!e));if(S&&!C&&!r)return a.push("Required field is not provided"),a;if("object"===d&&(null===s||"application/json"===s)){let n=e;if("string"==typeof e)try{n=JSON.parse(e)}catch(e){return a.push("Parameter string value must be valid JSON"),a}var P;if(t&&t.has("required")&&ue(c.isList)&&c.isList()&&u()(c).call(c,(e=>{void 0===n[e]&&a.push({propKey:e,error:"Required property not found"})})),t&&t.has("properties"))u()(P=t.get("properties")).call(P,((e,t)=>{const o=we(n[t],e,!1,r,s);a.push(...i()(o).call(o,(e=>({propKey:t,error:e}))))}))}if(E){let t=((e,t)=>{if(!new RegExp(t).test(e))return"Value must follow pattern "+t})(e,E);t&&a.push(t)}if(w&&"array"===d){let t=((e,t)=>{if(!e&&t>=1||e&&e.length{if(e&&e.length>t)return`Array must not contain more then ${t} item${1===t?"":"s"}`})(e,b);t&&a.push({needRemove:!0,error:t})}if(v&&"array"===d){let t=((e,t)=>{if(e&&("true"===t||!0===t)){const t=(0,T.fromJS)(e),n=t.toSet();if(e.length>n.size){let e=(0,T.Set)();if(u()(t).call(t,((n,r)=>{h()(t).call(t,(e=>ue(e.equals)?e.equals(n):e===n)).size>1&&(e=e.add(r))})),0!==e.size)return i()(e).call(e,(e=>({index:e,error:"No duplicates allowed."}))).toArray()}}})(e,v);t&&a.push(...t)}if(g||0===g){let t=((e,t)=>{if(e.length>t)return`Value must be no longer than ${t} character${1!==t?"s":""}`})(e,g);t&&a.push(t)}if(y){let t=((e,t)=>{if(e.length{if(e>t)return`Value must be less than ${t}`})(e,p);t&&a.push(t)}if(f||0===f){let t=((e,t)=>{if(e{if(isNaN(Date.parse(e)))return"Value must be a DateTime"})(e):"uuid"===m?(e=>{if(e=e.toString().toLowerCase(),!/^[{(]?[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}[)}]?$/.test(e))return"Value must be a Guid"})(e):(e=>{if(e&&"string"!=typeof e)return"Value must be a string"})(e),!t)return a;a.push(t)}else if("boolean"===d){let t=(e=>{if("true"!==e&&"false"!==e&&!0!==e&&!1!==e)return"Value must be a boolean"})(e);if(!t)return a;a.push(t)}else if("number"===d){let t=(e=>{if(!/^-?\d+(\.?\d+)?$/.test(e))return"Value must be a number"})(e);if(!t)return a;a.push(t)}else if("integer"===d){let t=(e=>{if(!/^-?\d+$/.test(e))return"Value must be an integer"})(e);if(!t)return a;a.push(t)}else if("array"===d){if(!O&&!k)return a;e&&u()(e).call(e,((e,n)=>{const o=we(e,t.get("items"),!1,r,s);a.push(...i()(o).call(o,(e=>({index:n,error:e}))))}))}else if("file"===d){let t=(e=>{if(e&&!(e instanceof H.Z.File))return"Value must be a file"})(e);if(!t)return a;a.push(t)}return a}const Ee=function(e,t){let{isOAS3:n=!1,bypassRequiredCheck:r=!1}=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},o=e.get("required"),{schema:s,parameterContentMediaType:i}=(0,Y.Z)(e,{isOAS3:n});return we(t,s,o,r,i)},xe=()=>{let e={},t=H.Z.location.search;if(!t)return{};if(""!=t){let n=t.substr(1).split("&");for(let t in n)Object.prototype.hasOwnProperty.call(n,t)&&(t=n[t].split("="),e[decodeURIComponent(t[0])]=t[1]&&decodeURIComponent(t[1])||"")}return e},Se=e=>{let t;return t=e instanceof ne?e:ne.from(e.toString(),"utf-8"),t.toString("base64")},_e={operationsSorter:{alpha:(e,t)=>e.get("path").localeCompare(t.get("path")),method:(e,t)=>e.get("method").localeCompare(t.get("method"))},tagsSorter:{alpha:(e,t)=>e.localeCompare(t)}},je=e=>{let t=[];for(let n in e){let r=e[n];void 0!==r&&""!==r&&t.push([n,"=",encodeURIComponent(r).replace(/%20/g,"+")].join(""))}return t.join("&")},Oe=(e,t,n)=>!!z()(n,(n=>W()(e[n],t[n])));function ke(e){return"string"!=typeof e||""===e?"":(0,M.N)(e)}function Ae(e){return!(!e||_()(e).call(e,"localhost")>=0||_()(e).call(e,"127.0.0.1")>=0||"none"===e)}function Ce(e){if(!R().OrderedMap.isOrderedMap(e))return null;if(!e.size)return null;const t=O()(e).call(e,((e,t)=>A()(t).call(t,"2")&&d()(e.get("content")||{}).length>0)),n=e.get("default")||R().OrderedMap(),r=(n.get("content")||R().OrderedMap()).keySeq().toJS().length?n:null;return t||r}const Pe=e=>"string"==typeof e||e instanceof String?P()(e).call(e).replace(/\s/g,"%20"):"",Ne=e=>Z()(Pe(e).replace(/%20/g,"_")),Ie=e=>h()(e).call(e,((e,t)=>/^x-/.test(t))),Te=e=>h()(e).call(e,((e,t)=>/^pattern|maxLength|minLength|maximum|minimum/.test(t)));function Re(e,t){var n;let r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:()=>!0;if("object"!=typeof e||o()(e)||null===e||!t)return e;const s=v()({},e);return u()(n=d()(s)).call(n,(e=>{e===t&&r(s[e],e)?delete s[e]:s[e]=Re(s[e],t,r)})),s}function Me(e){if("string"==typeof e)return e;if(e&&e.toJS&&(e=e.toJS()),"object"==typeof e&&null!==e)try{return I()(e,null,2)}catch(t){return String(e)}return null==e?"":e.toString()}function De(e){return"number"==typeof e?e.toString():e}function Fe(e){let{returnAll:t=!1,allowHashes:n=!0}=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};if(!R().Map.isMap(e))throw new Error("paramToIdentifier: received a non-Im.Map parameter as input");const r=e.get("name"),o=e.get("in");let s=[];return e&&e.hashCode&&o&&r&&n&&s.push(`${o}.${r}.hash-${e.hashCode()}`),o&&r&&s.push(`${o}.${r}`),s.push(r),t?s:s[0]||""}function Le(e,t){var n;const r=Fe(e,{returnAll:!0});return h()(n=i()(r).call(r,(e=>t[e]))).call(n,(e=>void 0!==e))[0]}function Be(){return qe(Q()(32).toString("base64"))}function $e(e){return qe(te()("sha256").update(e).digest("base64"))}function qe(e){return e.replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}const Ue=e=>!e||!(!oe(e)||!e.isEmpty())},2518:(e,t,n)=>{"use strict";function r(e){return function(e){try{return!!JSON.parse(e)}catch(e){return null}}(e)?"json":null}n.d(t,{O:()=>r})},63543:(e,t,n)=>{"use strict";n.d(t,{mn:()=>a});var r=n(63460),o=n.n(r);function s(e){return e.match(/^(?:[a-z]+:)?\/\//i)}function i(e,t){return e?s(e)?function(e){return e.match(/^\/\//i)?`${window.location.protocol}${e}`:e}(e):new(o())(e,t).href:t}function a(e,t){let{selectedServer:n=""}=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};try{return function(e,t){let{selectedServer:n=""}=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};if(!e)return;if(s(e))return e;const r=i(n,t);return s(r)?new(o())(e,r).href:new(o())(e,window.location.href).href}(e,t,{selectedServer:n})}catch{return}}},27504:(e,t,n)=>{"use strict";n.d(t,{Z:()=>r});const r=function(){var e={location:{},history:{},open:()=>{},close:()=>{},File:function(){}};if("undefined"==typeof window)return e;try{e=window;for(var t of["File","Blob","FormData"])t in window&&(e[t]=window[t])}catch(e){console.error(e)}return e}()},19069:(e,t,n)=>{"use strict";n.d(t,{Z:()=>u});var r=n(14418),o=n.n(r),s=n(58118),i=n.n(s),a=n(43393),l=n.n(a);const c=l().Set.of("type","format","items","default","maximum","exclusiveMaximum","minimum","exclusiveMinimum","maxLength","minLength","pattern","maxItems","minItems","uniqueItems","enum","multipleOf");function u(e){let{isOAS3:t}=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};if(!l().Map.isMap(e))return{schema:l().Map(),parameterContentMediaType:null};if(!t)return"body"===e.get("in")?{schema:e.get("schema",l().Map()),parameterContentMediaType:null}:{schema:o()(e).call(e,((e,t)=>i()(c).call(c,t))),parameterContentMediaType:null};if(e.get("content")){const t=e.get("content",l().Map({})).keySeq().first();return{schema:e.getIn(["content",t,"schema"],l().Map()),parameterContentMediaType:t}}return{schema:e.get("schema")?e.get("schema",l().Map()):l().Map(),parameterContentMediaType:null}}},60314:(e,t,n)=>{"use strict";n.d(t,{Z:()=>x});var r=n(58309),o=n.n(r),s=n(2250),i=n.n(s),a=n(25110),l=n.n(a),c=n(8712),u=n.n(c),p=n(51679),h=n.n(p),f=n(12373),d=n.n(f),m=n(18492),g=n.n(m),y=n(88306),v=n.n(y);const b=e=>t=>o()(e)&&o()(t)&&e.length===t.length&&i()(e).call(e,((e,n)=>e===t[n])),w=function(){for(var e=arguments.length,t=new Array(e),n=0;n1&&void 0!==arguments[1]?arguments[1]:w;const{Cache:n}=v();v().Cache=E;const r=v()(e,t);return v().Cache=n,r}},79742:(e,t)=>{"use strict";t.byteLength=function(e){var t=a(e),n=t[0],r=t[1];return 3*(n+r)/4-r},t.toByteArray=function(e){var t,n,s=a(e),i=s[0],l=s[1],c=new o(function(e,t,n){return 3*(t+n)/4-n}(0,i,l)),u=0,p=l>0?i-4:i;for(n=0;n>16&255,c[u++]=t>>8&255,c[u++]=255&t;2===l&&(t=r[e.charCodeAt(n)]<<2|r[e.charCodeAt(n+1)]>>4,c[u++]=255&t);1===l&&(t=r[e.charCodeAt(n)]<<10|r[e.charCodeAt(n+1)]<<4|r[e.charCodeAt(n+2)]>>2,c[u++]=t>>8&255,c[u++]=255&t);return c},t.fromByteArray=function(e){for(var t,r=e.length,o=r%3,s=[],i=16383,a=0,c=r-o;ac?c:a+i));1===o?(t=e[r-1],s.push(n[t>>2]+n[t<<4&63]+"==")):2===o&&(t=(e[r-2]<<8)+e[r-1],s.push(n[t>>10]+n[t>>4&63]+n[t<<2&63]+"="));return s.join("")};for(var n=[],r=[],o="undefined"!=typeof Uint8Array?Uint8Array:Array,s="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",i=0;i<64;++i)n[i]=s[i],r[s.charCodeAt(i)]=i;function a(e){var t=e.length;if(t%4>0)throw new Error("Invalid string. Length must be a multiple of 4");var n=e.indexOf("=");return-1===n&&(n=t),[n,n===t?0:4-n%4]}function l(e,t,r){for(var o,s,i=[],a=t;a>18&63]+n[s>>12&63]+n[s>>6&63]+n[63&s]);return i.join("")}r["-".charCodeAt(0)]=62,r["_".charCodeAt(0)]=63},48764:(e,t,n)=>{"use strict";const r=n(79742),o=n(80645),s="function"==typeof Symbol&&"function"==typeof Symbol.for?Symbol.for("nodejs.util.inspect.custom"):null;t.Buffer=l,t.SlowBuffer=function(e){+e!=e&&(e=0);return l.alloc(+e)},t.INSPECT_MAX_BYTES=50;const i=2147483647;function a(e){if(e>i)throw new RangeError('The value "'+e+'" is invalid for option "size"');const t=new Uint8Array(e);return Object.setPrototypeOf(t,l.prototype),t}function l(e,t,n){if("number"==typeof e){if("string"==typeof t)throw new TypeError('The "string" argument must be of type string. Received type number');return p(e)}return c(e,t,n)}function c(e,t,n){if("string"==typeof e)return function(e,t){"string"==typeof t&&""!==t||(t="utf8");if(!l.isEncoding(t))throw new TypeError("Unknown encoding: "+t);const n=0|m(e,t);let r=a(n);const o=r.write(e,t);o!==n&&(r=r.slice(0,o));return r}(e,t);if(ArrayBuffer.isView(e))return function(e){if(G(e,Uint8Array)){const t=new Uint8Array(e);return f(t.buffer,t.byteOffset,t.byteLength)}return h(e)}(e);if(null==e)throw new TypeError("The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type "+typeof e);if(G(e,ArrayBuffer)||e&&G(e.buffer,ArrayBuffer))return f(e,t,n);if("undefined"!=typeof SharedArrayBuffer&&(G(e,SharedArrayBuffer)||e&&G(e.buffer,SharedArrayBuffer)))return f(e,t,n);if("number"==typeof e)throw new TypeError('The "value" argument must not be of type number. Received type number');const r=e.valueOf&&e.valueOf();if(null!=r&&r!==e)return l.from(r,t,n);const o=function(e){if(l.isBuffer(e)){const t=0|d(e.length),n=a(t);return 0===n.length||e.copy(n,0,0,t),n}if(void 0!==e.length)return"number"!=typeof e.length||Z(e.length)?a(0):h(e);if("Buffer"===e.type&&Array.isArray(e.data))return h(e.data)}(e);if(o)return o;if("undefined"!=typeof Symbol&&null!=Symbol.toPrimitive&&"function"==typeof e[Symbol.toPrimitive])return l.from(e[Symbol.toPrimitive]("string"),t,n);throw new TypeError("The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type "+typeof e)}function u(e){if("number"!=typeof e)throw new TypeError('"size" argument must be of type number');if(e<0)throw new RangeError('The value "'+e+'" is invalid for option "size"')}function p(e){return u(e),a(e<0?0:0|d(e))}function h(e){const t=e.length<0?0:0|d(e.length),n=a(t);for(let r=0;r=i)throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+i.toString(16)+" bytes");return 0|e}function m(e,t){if(l.isBuffer(e))return e.length;if(ArrayBuffer.isView(e)||G(e,ArrayBuffer))return e.byteLength;if("string"!=typeof e)throw new TypeError('The "string" argument must be one of type string, Buffer, or ArrayBuffer. Received type '+typeof e);const n=e.length,r=arguments.length>2&&!0===arguments[2];if(!r&&0===n)return 0;let o=!1;for(;;)switch(t){case"ascii":case"latin1":case"binary":return n;case"utf8":case"utf-8":return J(e).length;case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return 2*n;case"hex":return n>>>1;case"base64":return K(e).length;default:if(o)return r?-1:J(e).length;t=(""+t).toLowerCase(),o=!0}}function g(e,t,n){let r=!1;if((void 0===t||t<0)&&(t=0),t>this.length)return"";if((void 0===n||n>this.length)&&(n=this.length),n<=0)return"";if((n>>>=0)<=(t>>>=0))return"";for(e||(e="utf8");;)switch(e){case"hex":return P(this,t,n);case"utf8":case"utf-8":return O(this,t,n);case"ascii":return A(this,t,n);case"latin1":case"binary":return C(this,t,n);case"base64":return j(this,t,n);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return N(this,t,n);default:if(r)throw new TypeError("Unknown encoding: "+e);e=(e+"").toLowerCase(),r=!0}}function y(e,t,n){const r=e[t];e[t]=e[n],e[n]=r}function v(e,t,n,r,o){if(0===e.length)return-1;if("string"==typeof n?(r=n,n=0):n>2147483647?n=2147483647:n<-2147483648&&(n=-2147483648),Z(n=+n)&&(n=o?0:e.length-1),n<0&&(n=e.length+n),n>=e.length){if(o)return-1;n=e.length-1}else if(n<0){if(!o)return-1;n=0}if("string"==typeof t&&(t=l.from(t,r)),l.isBuffer(t))return 0===t.length?-1:b(e,t,n,r,o);if("number"==typeof t)return t&=255,"function"==typeof Uint8Array.prototype.indexOf?o?Uint8Array.prototype.indexOf.call(e,t,n):Uint8Array.prototype.lastIndexOf.call(e,t,n):b(e,[t],n,r,o);throw new TypeError("val must be string, number or Buffer")}function b(e,t,n,r,o){let s,i=1,a=e.length,l=t.length;if(void 0!==r&&("ucs2"===(r=String(r).toLowerCase())||"ucs-2"===r||"utf16le"===r||"utf-16le"===r)){if(e.length<2||t.length<2)return-1;i=2,a/=2,l/=2,n/=2}function c(e,t){return 1===i?e[t]:e.readUInt16BE(t*i)}if(o){let r=-1;for(s=n;sa&&(n=a-l),s=n;s>=0;s--){let n=!0;for(let r=0;ro&&(r=o):r=o;const s=t.length;let i;for(r>s/2&&(r=s/2),i=0;i>8,o=n%256,s.push(o),s.push(r);return s}(t,e.length-n),e,n,r)}function j(e,t,n){return 0===t&&n===e.length?r.fromByteArray(e):r.fromByteArray(e.slice(t,n))}function O(e,t,n){n=Math.min(e.length,n);const r=[];let o=t;for(;o239?4:t>223?3:t>191?2:1;if(o+i<=n){let n,r,a,l;switch(i){case 1:t<128&&(s=t);break;case 2:n=e[o+1],128==(192&n)&&(l=(31&t)<<6|63&n,l>127&&(s=l));break;case 3:n=e[o+1],r=e[o+2],128==(192&n)&&128==(192&r)&&(l=(15&t)<<12|(63&n)<<6|63&r,l>2047&&(l<55296||l>57343)&&(s=l));break;case 4:n=e[o+1],r=e[o+2],a=e[o+3],128==(192&n)&&128==(192&r)&&128==(192&a)&&(l=(15&t)<<18|(63&n)<<12|(63&r)<<6|63&a,l>65535&&l<1114112&&(s=l))}}null===s?(s=65533,i=1):s>65535&&(s-=65536,r.push(s>>>10&1023|55296),s=56320|1023&s),r.push(s),o+=i}return function(e){const t=e.length;if(t<=k)return String.fromCharCode.apply(String,e);let n="",r=0;for(;rr.length?(l.isBuffer(t)||(t=l.from(t)),t.copy(r,o)):Uint8Array.prototype.set.call(r,t,o);else{if(!l.isBuffer(t))throw new TypeError('"list" argument must be an Array of Buffers');t.copy(r,o)}o+=t.length}return r},l.byteLength=m,l.prototype._isBuffer=!0,l.prototype.swap16=function(){const e=this.length;if(e%2!=0)throw new RangeError("Buffer size must be a multiple of 16-bits");for(let t=0;tn&&(e+=" ... "),""},s&&(l.prototype[s]=l.prototype.inspect),l.prototype.compare=function(e,t,n,r,o){if(G(e,Uint8Array)&&(e=l.from(e,e.offset,e.byteLength)),!l.isBuffer(e))throw new TypeError('The "target" argument must be one of type Buffer or Uint8Array. Received type '+typeof e);if(void 0===t&&(t=0),void 0===n&&(n=e?e.length:0),void 0===r&&(r=0),void 0===o&&(o=this.length),t<0||n>e.length||r<0||o>this.length)throw new RangeError("out of range index");if(r>=o&&t>=n)return 0;if(r>=o)return-1;if(t>=n)return 1;if(this===e)return 0;let s=(o>>>=0)-(r>>>=0),i=(n>>>=0)-(t>>>=0);const a=Math.min(s,i),c=this.slice(r,o),u=e.slice(t,n);for(let e=0;e>>=0,isFinite(n)?(n>>>=0,void 0===r&&(r="utf8")):(r=n,n=void 0)}const o=this.length-t;if((void 0===n||n>o)&&(n=o),e.length>0&&(n<0||t<0)||t>this.length)throw new RangeError("Attempt to write outside buffer bounds");r||(r="utf8");let s=!1;for(;;)switch(r){case"hex":return w(this,e,t,n);case"utf8":case"utf-8":return E(this,e,t,n);case"ascii":case"latin1":case"binary":return x(this,e,t,n);case"base64":return S(this,e,t,n);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return _(this,e,t,n);default:if(s)throw new TypeError("Unknown encoding: "+r);r=(""+r).toLowerCase(),s=!0}},l.prototype.toJSON=function(){return{type:"Buffer",data:Array.prototype.slice.call(this._arr||this,0)}};const k=4096;function A(e,t,n){let r="";n=Math.min(e.length,n);for(let o=t;or)&&(n=r);let o="";for(let r=t;rn)throw new RangeError("Trying to access beyond buffer length")}function T(e,t,n,r,o,s){if(!l.isBuffer(e))throw new TypeError('"buffer" argument must be a Buffer instance');if(t>o||te.length)throw new RangeError("Index out of range")}function R(e,t,n,r,o){U(t,r,o,e,n,7);let s=Number(t&BigInt(4294967295));e[n++]=s,s>>=8,e[n++]=s,s>>=8,e[n++]=s,s>>=8,e[n++]=s;let i=Number(t>>BigInt(32)&BigInt(4294967295));return e[n++]=i,i>>=8,e[n++]=i,i>>=8,e[n++]=i,i>>=8,e[n++]=i,n}function M(e,t,n,r,o){U(t,r,o,e,n,7);let s=Number(t&BigInt(4294967295));e[n+7]=s,s>>=8,e[n+6]=s,s>>=8,e[n+5]=s,s>>=8,e[n+4]=s;let i=Number(t>>BigInt(32)&BigInt(4294967295));return e[n+3]=i,i>>=8,e[n+2]=i,i>>=8,e[n+1]=i,i>>=8,e[n]=i,n+8}function D(e,t,n,r,o,s){if(n+r>e.length)throw new RangeError("Index out of range");if(n<0)throw new RangeError("Index out of range")}function F(e,t,n,r,s){return t=+t,n>>>=0,s||D(e,0,n,4),o.write(e,t,n,r,23,4),n+4}function L(e,t,n,r,s){return t=+t,n>>>=0,s||D(e,0,n,8),o.write(e,t,n,r,52,8),n+8}l.prototype.slice=function(e,t){const n=this.length;(e=~~e)<0?(e+=n)<0&&(e=0):e>n&&(e=n),(t=void 0===t?n:~~t)<0?(t+=n)<0&&(t=0):t>n&&(t=n),t>>=0,t>>>=0,n||I(e,t,this.length);let r=this[e],o=1,s=0;for(;++s>>=0,t>>>=0,n||I(e,t,this.length);let r=this[e+--t],o=1;for(;t>0&&(o*=256);)r+=this[e+--t]*o;return r},l.prototype.readUint8=l.prototype.readUInt8=function(e,t){return e>>>=0,t||I(e,1,this.length),this[e]},l.prototype.readUint16LE=l.prototype.readUInt16LE=function(e,t){return e>>>=0,t||I(e,2,this.length),this[e]|this[e+1]<<8},l.prototype.readUint16BE=l.prototype.readUInt16BE=function(e,t){return e>>>=0,t||I(e,2,this.length),this[e]<<8|this[e+1]},l.prototype.readUint32LE=l.prototype.readUInt32LE=function(e,t){return e>>>=0,t||I(e,4,this.length),(this[e]|this[e+1]<<8|this[e+2]<<16)+16777216*this[e+3]},l.prototype.readUint32BE=l.prototype.readUInt32BE=function(e,t){return e>>>=0,t||I(e,4,this.length),16777216*this[e]+(this[e+1]<<16|this[e+2]<<8|this[e+3])},l.prototype.readBigUInt64LE=X((function(e){z(e>>>=0,"offset");const t=this[e],n=this[e+7];void 0!==t&&void 0!==n||V(e,this.length-8);const r=t+256*this[++e]+65536*this[++e]+this[++e]*2**24,o=this[++e]+256*this[++e]+65536*this[++e]+n*2**24;return BigInt(r)+(BigInt(o)<>>=0,"offset");const t=this[e],n=this[e+7];void 0!==t&&void 0!==n||V(e,this.length-8);const r=t*2**24+65536*this[++e]+256*this[++e]+this[++e],o=this[++e]*2**24+65536*this[++e]+256*this[++e]+n;return(BigInt(r)<>>=0,t>>>=0,n||I(e,t,this.length);let r=this[e],o=1,s=0;for(;++s=o&&(r-=Math.pow(2,8*t)),r},l.prototype.readIntBE=function(e,t,n){e>>>=0,t>>>=0,n||I(e,t,this.length);let r=t,o=1,s=this[e+--r];for(;r>0&&(o*=256);)s+=this[e+--r]*o;return o*=128,s>=o&&(s-=Math.pow(2,8*t)),s},l.prototype.readInt8=function(e,t){return e>>>=0,t||I(e,1,this.length),128&this[e]?-1*(255-this[e]+1):this[e]},l.prototype.readInt16LE=function(e,t){e>>>=0,t||I(e,2,this.length);const n=this[e]|this[e+1]<<8;return 32768&n?4294901760|n:n},l.prototype.readInt16BE=function(e,t){e>>>=0,t||I(e,2,this.length);const n=this[e+1]|this[e]<<8;return 32768&n?4294901760|n:n},l.prototype.readInt32LE=function(e,t){return e>>>=0,t||I(e,4,this.length),this[e]|this[e+1]<<8|this[e+2]<<16|this[e+3]<<24},l.prototype.readInt32BE=function(e,t){return e>>>=0,t||I(e,4,this.length),this[e]<<24|this[e+1]<<16|this[e+2]<<8|this[e+3]},l.prototype.readBigInt64LE=X((function(e){z(e>>>=0,"offset");const t=this[e],n=this[e+7];void 0!==t&&void 0!==n||V(e,this.length-8);const r=this[e+4]+256*this[e+5]+65536*this[e+6]+(n<<24);return(BigInt(r)<>>=0,"offset");const t=this[e],n=this[e+7];void 0!==t&&void 0!==n||V(e,this.length-8);const r=(t<<24)+65536*this[++e]+256*this[++e]+this[++e];return(BigInt(r)<>>=0,t||I(e,4,this.length),o.read(this,e,!0,23,4)},l.prototype.readFloatBE=function(e,t){return e>>>=0,t||I(e,4,this.length),o.read(this,e,!1,23,4)},l.prototype.readDoubleLE=function(e,t){return e>>>=0,t||I(e,8,this.length),o.read(this,e,!0,52,8)},l.prototype.readDoubleBE=function(e,t){return e>>>=0,t||I(e,8,this.length),o.read(this,e,!1,52,8)},l.prototype.writeUintLE=l.prototype.writeUIntLE=function(e,t,n,r){if(e=+e,t>>>=0,n>>>=0,!r){T(this,e,t,n,Math.pow(2,8*n)-1,0)}let o=1,s=0;for(this[t]=255&e;++s>>=0,n>>>=0,!r){T(this,e,t,n,Math.pow(2,8*n)-1,0)}let o=n-1,s=1;for(this[t+o]=255&e;--o>=0&&(s*=256);)this[t+o]=e/s&255;return t+n},l.prototype.writeUint8=l.prototype.writeUInt8=function(e,t,n){return e=+e,t>>>=0,n||T(this,e,t,1,255,0),this[t]=255&e,t+1},l.prototype.writeUint16LE=l.prototype.writeUInt16LE=function(e,t,n){return e=+e,t>>>=0,n||T(this,e,t,2,65535,0),this[t]=255&e,this[t+1]=e>>>8,t+2},l.prototype.writeUint16BE=l.prototype.writeUInt16BE=function(e,t,n){return e=+e,t>>>=0,n||T(this,e,t,2,65535,0),this[t]=e>>>8,this[t+1]=255&e,t+2},l.prototype.writeUint32LE=l.prototype.writeUInt32LE=function(e,t,n){return e=+e,t>>>=0,n||T(this,e,t,4,4294967295,0),this[t+3]=e>>>24,this[t+2]=e>>>16,this[t+1]=e>>>8,this[t]=255&e,t+4},l.prototype.writeUint32BE=l.prototype.writeUInt32BE=function(e,t,n){return e=+e,t>>>=0,n||T(this,e,t,4,4294967295,0),this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e,t+4},l.prototype.writeBigUInt64LE=X((function(e,t=0){return R(this,e,t,BigInt(0),BigInt("0xffffffffffffffff"))})),l.prototype.writeBigUInt64BE=X((function(e,t=0){return M(this,e,t,BigInt(0),BigInt("0xffffffffffffffff"))})),l.prototype.writeIntLE=function(e,t,n,r){if(e=+e,t>>>=0,!r){const r=Math.pow(2,8*n-1);T(this,e,t,n,r-1,-r)}let o=0,s=1,i=0;for(this[t]=255&e;++o>0)-i&255;return t+n},l.prototype.writeIntBE=function(e,t,n,r){if(e=+e,t>>>=0,!r){const r=Math.pow(2,8*n-1);T(this,e,t,n,r-1,-r)}let o=n-1,s=1,i=0;for(this[t+o]=255&e;--o>=0&&(s*=256);)e<0&&0===i&&0!==this[t+o+1]&&(i=1),this[t+o]=(e/s>>0)-i&255;return t+n},l.prototype.writeInt8=function(e,t,n){return e=+e,t>>>=0,n||T(this,e,t,1,127,-128),e<0&&(e=255+e+1),this[t]=255&e,t+1},l.prototype.writeInt16LE=function(e,t,n){return e=+e,t>>>=0,n||T(this,e,t,2,32767,-32768),this[t]=255&e,this[t+1]=e>>>8,t+2},l.prototype.writeInt16BE=function(e,t,n){return e=+e,t>>>=0,n||T(this,e,t,2,32767,-32768),this[t]=e>>>8,this[t+1]=255&e,t+2},l.prototype.writeInt32LE=function(e,t,n){return e=+e,t>>>=0,n||T(this,e,t,4,2147483647,-2147483648),this[t]=255&e,this[t+1]=e>>>8,this[t+2]=e>>>16,this[t+3]=e>>>24,t+4},l.prototype.writeInt32BE=function(e,t,n){return e=+e,t>>>=0,n||T(this,e,t,4,2147483647,-2147483648),e<0&&(e=4294967295+e+1),this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e,t+4},l.prototype.writeBigInt64LE=X((function(e,t=0){return R(this,e,t,-BigInt("0x8000000000000000"),BigInt("0x7fffffffffffffff"))})),l.prototype.writeBigInt64BE=X((function(e,t=0){return M(this,e,t,-BigInt("0x8000000000000000"),BigInt("0x7fffffffffffffff"))})),l.prototype.writeFloatLE=function(e,t,n){return F(this,e,t,!0,n)},l.prototype.writeFloatBE=function(e,t,n){return F(this,e,t,!1,n)},l.prototype.writeDoubleLE=function(e,t,n){return L(this,e,t,!0,n)},l.prototype.writeDoubleBE=function(e,t,n){return L(this,e,t,!1,n)},l.prototype.copy=function(e,t,n,r){if(!l.isBuffer(e))throw new TypeError("argument should be a Buffer");if(n||(n=0),r||0===r||(r=this.length),t>=e.length&&(t=e.length),t||(t=0),r>0&&r=this.length)throw new RangeError("Index out of range");if(r<0)throw new RangeError("sourceEnd out of bounds");r>this.length&&(r=this.length),e.length-t>>=0,n=void 0===n?this.length:n>>>0,e||(e=0),"number"==typeof e)for(o=t;o=r+4;n-=3)t=`_${e.slice(n-3,n)}${t}`;return`${e.slice(0,n)}${t}`}function U(e,t,n,r,o,s){if(e>n||e3?0===t||t===BigInt(0)?`>= 0${r} and < 2${r} ** ${8*(s+1)}${r}`:`>= -(2${r} ** ${8*(s+1)-1}${r}) and < 2 ** ${8*(s+1)-1}${r}`:`>= ${t}${r} and <= ${n}${r}`,new B.ERR_OUT_OF_RANGE("value",o,e)}!function(e,t,n){z(t,"offset"),void 0!==e[t]&&void 0!==e[t+n]||V(t,e.length-(n+1))}(r,o,s)}function z(e,t){if("number"!=typeof e)throw new B.ERR_INVALID_ARG_TYPE(t,"number",e)}function V(e,t,n){if(Math.floor(e)!==e)throw z(e,n),new B.ERR_OUT_OF_RANGE(n||"offset","an integer",e);if(t<0)throw new B.ERR_BUFFER_OUT_OF_BOUNDS;throw new B.ERR_OUT_OF_RANGE(n||"offset",`>= ${n?1:0} and <= ${t}`,e)}$("ERR_BUFFER_OUT_OF_BOUNDS",(function(e){return e?`${e} is outside of buffer bounds`:"Attempt to access memory outside buffer bounds"}),RangeError),$("ERR_INVALID_ARG_TYPE",(function(e,t){return`The "${e}" argument must be of type number. Received type ${typeof t}`}),TypeError),$("ERR_OUT_OF_RANGE",(function(e,t,n){let r=`The value of "${e}" is out of range.`,o=n;return Number.isInteger(n)&&Math.abs(n)>2**32?o=q(String(n)):"bigint"==typeof n&&(o=String(n),(n>BigInt(2)**BigInt(32)||n<-(BigInt(2)**BigInt(32)))&&(o=q(o)),o+="n"),r+=` It must be ${t}. Received ${o}`,r}),RangeError);const W=/[^+/0-9A-Za-z-_]/g;function J(e,t){let n;t=t||1/0;const r=e.length;let o=null;const s=[];for(let i=0;i55295&&n<57344){if(!o){if(n>56319){(t-=3)>-1&&s.push(239,191,189);continue}if(i+1===r){(t-=3)>-1&&s.push(239,191,189);continue}o=n;continue}if(n<56320){(t-=3)>-1&&s.push(239,191,189),o=n;continue}n=65536+(o-55296<<10|n-56320)}else o&&(t-=3)>-1&&s.push(239,191,189);if(o=null,n<128){if((t-=1)<0)break;s.push(n)}else if(n<2048){if((t-=2)<0)break;s.push(n>>6|192,63&n|128)}else if(n<65536){if((t-=3)<0)break;s.push(n>>12|224,n>>6&63|128,63&n|128)}else{if(!(n<1114112))throw new Error("Invalid code point");if((t-=4)<0)break;s.push(n>>18|240,n>>12&63|128,n>>6&63|128,63&n|128)}}return s}function K(e){return r.toByteArray(function(e){if((e=(e=e.split("=")[0]).trim().replace(W,"")).length<2)return"";for(;e.length%4!=0;)e+="=";return e}(e))}function H(e,t,n,r){let o;for(o=0;o=t.length||o>=e.length);++o)t[o+n]=e[o];return o}function G(e,t){return e instanceof t||null!=e&&null!=e.constructor&&null!=e.constructor.name&&e.constructor.name===t.name}function Z(e){return e!=e}const Y=function(){const e="0123456789abcdef",t=new Array(256);for(let n=0;n<16;++n){const r=16*n;for(let o=0;o<16;++o)t[r+o]=e[n]+e[o]}return t}();function X(e){return"undefined"==typeof BigInt?Q:e}function Q(){throw new Error("BigInt not supported")}},21924:(e,t,n)=>{"use strict";var r=n(40210),o=n(55559),s=o(r("String.prototype.indexOf"));e.exports=function(e,t){var n=r(e,!!t);return"function"==typeof n&&s(e,".prototype.")>-1?o(n):n}},55559:(e,t,n)=>{"use strict";var r=n(58612),o=n(40210),s=o("%Function.prototype.apply%"),i=o("%Function.prototype.call%"),a=o("%Reflect.apply%",!0)||r.call(i,s),l=o("%Object.getOwnPropertyDescriptor%",!0),c=o("%Object.defineProperty%",!0),u=o("%Math.max%");if(c)try{c({},"a",{value:1})}catch(e){c=null}e.exports=function(e){var t=a(r,i,arguments);l&&c&&(l(t,"length").configurable&&c(t,"length",{value:1+u(0,e.length-(arguments.length-1))}));return t};var p=function(){return a(r,s,arguments)};c?c(e.exports,"apply",{value:p}):e.exports.apply=p},94184:(e,t)=>{var n;!function(){"use strict";var r={}.hasOwnProperty;function o(){for(var e=[],t=0;t{"use strict";t.parse=function(e,t){if("string"!=typeof e)throw new TypeError("argument str must be a string");var n={},r=(t||{}).decode||o,s=0;for(;s{"use strict";var r=n(11742),o={"text/plain":"Text","text/html":"Url",default:"Text"};e.exports=function(e,t){var n,s,i,a,l,c,u=!1;t||(t={}),n=t.debug||!1;try{if(i=r(),a=document.createRange(),l=document.getSelection(),(c=document.createElement("span")).textContent=e,c.ariaHidden="true",c.style.all="unset",c.style.position="fixed",c.style.top=0,c.style.clip="rect(0, 0, 0, 0)",c.style.whiteSpace="pre",c.style.webkitUserSelect="text",c.style.MozUserSelect="text",c.style.msUserSelect="text",c.style.userSelect="text",c.addEventListener("copy",(function(r){if(r.stopPropagation(),t.format)if(r.preventDefault(),void 0===r.clipboardData){n&&console.warn("unable to use e.clipboardData"),n&&console.warn("trying IE specific stuff"),window.clipboardData.clearData();var s=o[t.format]||o.default;window.clipboardData.setData(s,e)}else r.clipboardData.clearData(),r.clipboardData.setData(t.format,e);t.onCopy&&(r.preventDefault(),t.onCopy(r.clipboardData))})),document.body.appendChild(c),a.selectNodeContents(c),l.addRange(a),!document.execCommand("copy"))throw new Error("copy command was unsuccessful");u=!0}catch(r){n&&console.error("unable to copy using execCommand: ",r),n&&console.warn("trying IE specific stuff");try{window.clipboardData.setData(t.format||"text",e),t.onCopy&&t.onCopy(window.clipboardData),u=!0}catch(r){n&&console.error("unable to copy using clipboardData: ",r),n&&console.error("falling back to prompt"),s=function(e){var t=(/mac os x/i.test(navigator.userAgent)?"⌘":"Ctrl")+"+C";return e.replace(/#{\s*key\s*}/g,t)}("message"in t?t.message:"Copy to clipboard: #{key}, Enter"),window.prompt(s,e)}}finally{l&&("function"==typeof l.removeRange?l.removeRange(a):l.removeAllRanges()),c&&document.body.removeChild(c),i()}return u}},90093:(e,t,n)=>{var r=n(28196);e.exports=r},3688:(e,t,n)=>{var r=n(11955);e.exports=r},83838:(e,t,n)=>{var r=n(46279);e.exports=r},15684:(e,t,n)=>{var r=n(19373);e.exports=r},81331:(e,t,n)=>{var r=n(52759);e.exports=r},65362:(e,t,n)=>{var r=n(63383);e.exports=r},91254:(e,t,n)=>{var r=n(57396);e.exports=r},43536:(e,t,n)=>{var r=n(41910);e.exports=r},37331:(e,t,n)=>{var r=n(79427);e.exports=r},68522:(e,t,n)=>{var r=n(62857);e.exports=r},73151:(e,t,n)=>{var r=n(9534);e.exports=r},45012:(e,t,n)=>{var r=n(23059);e.exports=r},80281:(e,t,n)=>{var r=n(92547);n(97522),n(43975),n(45414),e.exports=r},40031:(e,t,n)=>{var r=n(46509);e.exports=r},17487:(e,t,n)=>{var r=n(35774);e.exports=r},54493:(e,t,n)=>{n(77971),n(53242);var r=n(54058);e.exports=r.Array.from},24034:(e,t,n)=>{n(92737);var r=n(54058);e.exports=r.Array.isArray},15367:(e,t,n)=>{n(85906);var r=n(35703);e.exports=r("Array").concat},12710:(e,t,n)=>{n(66274),n(55967);var r=n(35703);e.exports=r("Array").entries},51459:(e,t,n)=>{n(48851);var r=n(35703);e.exports=r("Array").every},6172:(e,t,n)=>{n(80290);var r=n(35703);e.exports=r("Array").fill},62383:(e,t,n)=>{n(21501);var r=n(35703);e.exports=r("Array").filter},60009:(e,t,n)=>{n(44929);var r=n(35703);e.exports=r("Array").findIndex},17671:(e,t,n)=>{n(80833);var r=n(35703);e.exports=r("Array").find},99324:(e,t,n)=>{n(2437);var r=n(35703);e.exports=r("Array").forEach},80991:(e,t,n)=>{n(97690);var r=n(35703);e.exports=r("Array").includes},8700:(e,t,n)=>{n(99076);var r=n(35703);e.exports=r("Array").indexOf},95909:(e,t,n)=>{n(66274),n(55967);var r=n(35703);e.exports=r("Array").keys},6442:(e,t,n)=>{n(75915);var r=n(35703);e.exports=r("Array").lastIndexOf},23866:(e,t,n)=>{n(68787);var r=n(35703);e.exports=r("Array").map},9896:(e,t,n)=>{n(48528);var r=n(35703);e.exports=r("Array").push},52999:(e,t,n)=>{n(81876);var r=n(35703);e.exports=r("Array").reduce},24900:(e,t,n)=>{n(60186);var r=n(35703);e.exports=r("Array").slice},3824:(e,t,n)=>{n(36026);var r=n(35703);e.exports=r("Array").some},2948:(e,t,n)=>{n(4115);var r=n(35703);e.exports=r("Array").sort},78209:(e,t,n)=>{n(98611);var r=n(35703);e.exports=r("Array").splice},14423:(e,t,n)=>{n(66274),n(55967);var r=n(35703);e.exports=r("Array").values},81103:(e,t,n)=>{n(95160);var r=n(54058);e.exports=r.Date.now},27700:(e,t,n)=>{n(73381);var r=n(35703);e.exports=r("Function").bind},16246:(e,t,n)=>{var r=n(7046),o=n(27700),s=Function.prototype;e.exports=function(e){var t=e.bind;return e===s||r(s,e)&&t===s.bind?o:t}},56043:(e,t,n)=>{var r=n(7046),o=n(15367),s=Array.prototype;e.exports=function(e){var t=e.concat;return e===s||r(s,e)&&t===s.concat?o:t}},13160:(e,t,n)=>{var r=n(7046),o=n(51459),s=Array.prototype;e.exports=function(e){var t=e.every;return e===s||r(s,e)&&t===s.every?o:t}},80446:(e,t,n)=>{var r=n(7046),o=n(6172),s=Array.prototype;e.exports=function(e){var t=e.fill;return e===s||r(s,e)&&t===s.fill?o:t}},2480:(e,t,n)=>{var r=n(7046),o=n(62383),s=Array.prototype;e.exports=function(e){var t=e.filter;return e===s||r(s,e)&&t===s.filter?o:t}},7147:(e,t,n)=>{var r=n(7046),o=n(60009),s=Array.prototype;e.exports=function(e){var t=e.findIndex;return e===s||r(s,e)&&t===s.findIndex?o:t}},32236:(e,t,n)=>{var r=n(7046),o=n(17671),s=Array.prototype;e.exports=function(e){var t=e.find;return e===s||r(s,e)&&t===s.find?o:t}},58557:(e,t,n)=>{var r=n(7046),o=n(80991),s=n(21631),i=Array.prototype,a=String.prototype;e.exports=function(e){var t=e.includes;return e===i||r(i,e)&&t===i.includes?o:"string"==typeof e||e===a||r(a,e)&&t===a.includes?s:t}},34570:(e,t,n)=>{var r=n(7046),o=n(8700),s=Array.prototype;e.exports=function(e){var t=e.indexOf;return e===s||r(s,e)&&t===s.indexOf?o:t}},57564:(e,t,n)=>{var r=n(7046),o=n(6442),s=Array.prototype;e.exports=function(e){var t=e.lastIndexOf;return e===s||r(s,e)&&t===s.lastIndexOf?o:t}},88287:(e,t,n)=>{var r=n(7046),o=n(23866),s=Array.prototype;e.exports=function(e){var t=e.map;return e===s||r(s,e)&&t===s.map?o:t}},93993:(e,t,n)=>{var r=n(7046),o=n(9896),s=Array.prototype;e.exports=function(e){var t=e.push;return e===s||r(s,e)&&t===s.push?o:t}},68025:(e,t,n)=>{var r=n(7046),o=n(52999),s=Array.prototype;e.exports=function(e){var t=e.reduce;return e===s||r(s,e)&&t===s.reduce?o:t}},59257:(e,t,n)=>{var r=n(7046),o=n(80454),s=String.prototype;e.exports=function(e){var t=e.repeat;return"string"==typeof e||e===s||r(s,e)&&t===s.repeat?o:t}},69601:(e,t,n)=>{var r=n(7046),o=n(24900),s=Array.prototype;e.exports=function(e){var t=e.slice;return e===s||r(s,e)&&t===s.slice?o:t}},28299:(e,t,n)=>{var r=n(7046),o=n(3824),s=Array.prototype;e.exports=function(e){var t=e.some;return e===s||r(s,e)&&t===s.some?o:t}},69355:(e,t,n)=>{var r=n(7046),o=n(2948),s=Array.prototype;e.exports=function(e){var t=e.sort;return e===s||r(s,e)&&t===s.sort?o:t}},18339:(e,t,n)=>{var r=n(7046),o=n(78209),s=Array.prototype;e.exports=function(e){var t=e.splice;return e===s||r(s,e)&&t===s.splice?o:t}},71611:(e,t,n)=>{var r=n(7046),o=n(3269),s=String.prototype;e.exports=function(e){var t=e.startsWith;return"string"==typeof e||e===s||r(s,e)&&t===s.startsWith?o:t}},62774:(e,t,n)=>{var r=n(7046),o=n(13348),s=String.prototype;e.exports=function(e){var t=e.trim;return"string"==typeof e||e===s||r(s,e)&&t===s.trim?o:t}},84426:(e,t,n)=>{n(32619);var r=n(54058),o=n(79730);r.JSON||(r.JSON={stringify:JSON.stringify}),e.exports=function(e,t,n){return o(r.JSON.stringify,null,arguments)}},91018:(e,t,n)=>{n(66274),n(37501),n(55967),n(77971);var r=n(54058);e.exports=r.Map},97849:(e,t,n)=>{n(54973),e.exports=Math.pow(2,-52)},3820:(e,t,n)=>{n(30800);var r=n(54058);e.exports=r.Number.isInteger},45999:(e,t,n)=>{n(49221);var r=n(54058);e.exports=r.Object.assign},7702:(e,t,n)=>{n(74979);var r=n(54058).Object,o=e.exports=function(e,t){return r.defineProperties(e,t)};r.defineProperties.sham&&(o.sham=!0)},48171:(e,t,n)=>{n(86450);var r=n(54058).Object,o=e.exports=function(e,t,n){return r.defineProperty(e,t,n)};r.defineProperty.sham&&(o.sham=!0)},73081:(e,t,n)=>{n(94366);var r=n(54058);e.exports=r.Object.entries},7699:(e,t,n)=>{n(66274),n(28387);var r=n(54058);e.exports=r.Object.fromEntries},286:(e,t,n)=>{n(46924);var r=n(54058).Object,o=e.exports=function(e,t){return r.getOwnPropertyDescriptor(e,t)};r.getOwnPropertyDescriptor.sham&&(o.sham=!0)},92766:(e,t,n)=>{n(88482);var r=n(54058);e.exports=r.Object.getOwnPropertyDescriptors},30498:(e,t,n)=>{n(35824);var r=n(54058);e.exports=r.Object.getOwnPropertySymbols},48494:(e,t,n)=>{n(21724);var r=n(54058);e.exports=r.Object.keys},98430:(e,t,n)=>{n(26614);var r=n(54058);e.exports=r.Object.values},52956:(e,t,n)=>{n(47627),n(66274),n(55967),n(98881),n(4560),n(91302),n(44349),n(77971);var r=n(54058);e.exports=r.Promise},76998:(e,t,n)=>{n(66274),n(55967),n(69008),n(77971);var r=n(54058);e.exports=r.Set},97089:(e,t,n)=>{n(74679);var r=n(54058);e.exports=r.String.raw},21631:(e,t,n)=>{n(11035);var r=n(35703);e.exports=r("String").includes},80454:(e,t,n)=>{n(60986);var r=n(35703);e.exports=r("String").repeat},3269:(e,t,n)=>{n(94761);var r=n(35703);e.exports=r("String").startsWith},13348:(e,t,n)=>{n(57398);var r=n(35703);e.exports=r("String").trim},57473:(e,t,n)=>{n(85906),n(55967),n(35824),n(8555),n(52615),n(21732),n(35903),n(1825),n(28394),n(45915),n(61766),n(62737),n(89911),n(74315),n(63131),n(64714),n(70659),n(69120),n(79413),n(1502);var r=n(54058);e.exports=r.Symbol},24227:(e,t,n)=>{n(66274),n(55967),n(77971),n(1825);var r=n(11477);e.exports=r.f("iterator")},62978:(e,t,n)=>{n(18084),n(63131);var r=n(11477);e.exports=r.f("toPrimitive")},32304:(e,t,n)=>{n(66274),n(55967),n(54334);var r=n(54058);e.exports=r.WeakMap},29567:(e,t,n)=>{n(66274),n(55967),n(1773);var r=n(54058);e.exports=r.WeakSet},14122:(e,t,n)=>{e.exports=n(89097)},44442:(e,t,n)=>{e.exports=n(51675)},57152:(e,t,n)=>{e.exports=n(82507)},69447:(e,t,n)=>{e.exports=n(628)},1449:(e,t,n)=>{e.exports=n(34501)},60269:(e,t,n)=>{e.exports=n(76936)},70573:(e,t,n)=>{e.exports=n(18180)},73685:(e,t,n)=>{e.exports=n(80621)},27533:(e,t,n)=>{e.exports=n(22948)},39057:(e,t,n)=>{e.exports=n(82108)},84710:(e,t,n)=>{e.exports=n(14058)},93799:(e,t,n)=>{e.exports=n(92093)},86600:(e,t,n)=>{e.exports=n(52201)},9759:(e,t,n)=>{e.exports=n(27398)},71384:(e,t,n)=>{e.exports=n(26189)},89097:(e,t,n)=>{var r=n(90093);e.exports=r},51675:(e,t,n)=>{var r=n(3688);e.exports=r},82507:(e,t,n)=>{var r=n(83838);e.exports=r},628:(e,t,n)=>{var r=n(15684);e.exports=r},34501:(e,t,n)=>{var r=n(81331);e.exports=r},76936:(e,t,n)=>{var r=n(65362);e.exports=r},18180:(e,t,n)=>{var r=n(91254);e.exports=r},80621:(e,t,n)=>{var r=n(43536);e.exports=r},22948:(e,t,n)=>{var r=n(37331);e.exports=r},82108:(e,t,n)=>{var r=n(68522);e.exports=r},14058:(e,t,n)=>{var r=n(73151);e.exports=r},92093:(e,t,n)=>{var r=n(45012);e.exports=r},52201:(e,t,n)=>{var r=n(80281);n(28783),n(97618),n(6989),n(65799),n(46774),n(22731),n(85605),n(31943),n(80620),n(36172),e.exports=r},27398:(e,t,n)=>{var r=n(40031);e.exports=r},26189:(e,t,n)=>{var r=n(17487);e.exports=r},24883:(e,t,n)=>{var r=n(57475),o=n(69826),s=TypeError;e.exports=function(e){if(r(e))return e;throw s(o(e)+" is not a function")}},174:(e,t,n)=>{var r=n(24284),o=n(69826),s=TypeError;e.exports=function(e){if(r(e))return e;throw s(o(e)+" is not a constructor")}},11851:(e,t,n)=>{var r=n(57475),o=String,s=TypeError;e.exports=function(e){if("object"==typeof e||r(e))return e;throw s("Can't set "+o(e)+" as a prototype")}},18479:e=>{e.exports=function(){}},5743:(e,t,n)=>{var r=n(7046),o=TypeError;e.exports=function(e,t){if(r(t,e))return e;throw o("Incorrect invocation")}},96059:(e,t,n)=>{var r=n(10941),o=String,s=TypeError;e.exports=function(e){if(r(e))return e;throw s(o(e)+" is not an object")}},97135:(e,t,n)=>{var r=n(95981);e.exports=r((function(){if("function"==typeof ArrayBuffer){var e=new ArrayBuffer(8);Object.isExtensible(e)&&Object.defineProperty(e,"a",{value:8})}}))},91860:(e,t,n)=>{"use strict";var r=n(89678),o=n(59413),s=n(10623);e.exports=function(e){for(var t=r(this),n=s(t),i=arguments.length,a=o(i>1?arguments[1]:void 0,n),l=i>2?arguments[2]:void 0,c=void 0===l?n:o(l,n);c>a;)t[a++]=e;return t}},56837:(e,t,n)=>{"use strict";var r=n(3610).forEach,o=n(34194)("forEach");e.exports=o?[].forEach:function(e){return r(this,e,arguments.length>1?arguments[1]:void 0)}},11354:(e,t,n)=>{"use strict";var r=n(86843),o=n(78834),s=n(89678),i=n(75196),a=n(6782),l=n(24284),c=n(10623),u=n(55449),p=n(53476),h=n(22902),f=Array;e.exports=function(e){var t=s(e),n=l(this),d=arguments.length,m=d>1?arguments[1]:void 0,g=void 0!==m;g&&(m=r(m,d>2?arguments[2]:void 0));var y,v,b,w,E,x,S=h(t),_=0;if(!S||this===f&&a(S))for(y=c(t),v=n?new this(y):f(y);y>_;_++)x=g?m(t[_],_):t[_],u(v,_,x);else for(E=(w=p(t,S)).next,v=n?new this:[];!(b=o(E,w)).done;_++)x=g?i(w,m,[b.value,_],!0):b.value,u(v,_,x);return v.length=_,v}},31692:(e,t,n)=>{var r=n(74529),o=n(59413),s=n(10623),i=function(e){return function(t,n,i){var a,l=r(t),c=s(l),u=o(i,c);if(e&&n!=n){for(;c>u;)if((a=l[u++])!=a)return!0}else for(;c>u;u++)if((e||u in l)&&l[u]===n)return e||u||0;return!e&&-1}};e.exports={includes:i(!0),indexOf:i(!1)}},3610:(e,t,n)=>{var r=n(86843),o=n(95329),s=n(37026),i=n(89678),a=n(10623),l=n(64692),c=o([].push),u=function(e){var t=1==e,n=2==e,o=3==e,u=4==e,p=6==e,h=7==e,f=5==e||p;return function(d,m,g,y){for(var v,b,w=i(d),E=s(w),x=r(m,g),S=a(E),_=0,j=y||l,O=t?j(d,S):n||h?j(d,0):void 0;S>_;_++)if((f||_ in E)&&(b=x(v=E[_],_,w),e))if(t)O[_]=b;else if(b)switch(e){case 3:return!0;case 5:return v;case 6:return _;case 2:c(O,v)}else switch(e){case 4:return!1;case 7:c(O,v)}return p?-1:o||u?u:O}};e.exports={forEach:u(0),map:u(1),filter:u(2),some:u(3),every:u(4),find:u(5),findIndex:u(6),filterReject:u(7)}},67145:(e,t,n)=>{"use strict";var r=n(79730),o=n(74529),s=n(62435),i=n(10623),a=n(34194),l=Math.min,c=[].lastIndexOf,u=!!c&&1/[1].lastIndexOf(1,-0)<0,p=a("lastIndexOf"),h=u||!p;e.exports=h?function(e){if(u)return r(c,this,arguments)||0;var t=o(this),n=i(t),a=n-1;for(arguments.length>1&&(a=l(a,s(arguments[1]))),a<0&&(a=n+a);a>=0;a--)if(a in t&&t[a]===e)return a||0;return-1}:c},50568:(e,t,n)=>{var r=n(95981),o=n(99813),s=n(53385),i=o("species");e.exports=function(e){return s>=51||!r((function(){var t=[];return(t.constructor={})[i]=function(){return{foo:1}},1!==t[e](Boolean).foo}))}},34194:(e,t,n)=>{"use strict";var r=n(95981);e.exports=function(e,t){var n=[][e];return!!n&&r((function(){n.call(null,t||function(){return 1},1)}))}},46499:(e,t,n)=>{var r=n(24883),o=n(89678),s=n(37026),i=n(10623),a=TypeError,l=function(e){return function(t,n,l,c){r(n);var u=o(t),p=s(u),h=i(u),f=e?h-1:0,d=e?-1:1;if(l<2)for(;;){if(f in p){c=p[f],f+=d;break}if(f+=d,e?f<0:h<=f)throw a("Reduce of empty array with no initial value")}for(;e?f>=0:h>f;f+=d)f in p&&(c=n(c,p[f],f,u));return c}};e.exports={left:l(!1),right:l(!0)}},89779:(e,t,n)=>{"use strict";var r=n(55746),o=n(1052),s=TypeError,i=Object.getOwnPropertyDescriptor,a=r&&!function(){if(void 0!==this)return!0;try{Object.defineProperty([],"length",{writable:!1}).length=1}catch(e){return e instanceof TypeError}}();e.exports=a?function(e,t){if(o(e)&&!i(e,"length").writable)throw s("Cannot set read only .length");return e.length=t}:function(e,t){return e.length=t}},15790:(e,t,n)=>{var r=n(59413),o=n(10623),s=n(55449),i=Array,a=Math.max;e.exports=function(e,t,n){for(var l=o(e),c=r(t,l),u=r(void 0===n?l:n,l),p=i(a(u-c,0)),h=0;c{var r=n(95329);e.exports=r([].slice)},61388:(e,t,n)=>{var r=n(15790),o=Math.floor,s=function(e,t){var n=e.length,l=o(n/2);return n<8?i(e,t):a(e,s(r(e,0,l),t),s(r(e,l),t),t)},i=function(e,t){for(var n,r,o=e.length,s=1;s0;)e[r]=e[--r];r!==s++&&(e[r]=n)}return e},a=function(e,t,n,r){for(var o=t.length,s=n.length,i=0,a=0;i{var r=n(1052),o=n(24284),s=n(10941),i=n(99813)("species"),a=Array;e.exports=function(e){var t;return r(e)&&(t=e.constructor,(o(t)&&(t===a||r(t.prototype))||s(t)&&null===(t=t[i]))&&(t=void 0)),void 0===t?a:t}},64692:(e,t,n)=>{var r=n(5693);e.exports=function(e,t){return new(r(e))(0===t?0:t)}},75196:(e,t,n)=>{var r=n(96059),o=n(7609);e.exports=function(e,t,n,s){try{return s?t(r(n)[0],n[1]):t(n)}catch(t){o(e,"throw",t)}}},21385:(e,t,n)=>{var r=n(99813)("iterator"),o=!1;try{var s=0,i={next:function(){return{done:!!s++}},return:function(){o=!0}};i[r]=function(){return this},Array.from(i,(function(){throw 2}))}catch(e){}e.exports=function(e,t){if(!t&&!o)return!1;var n=!1;try{var s={};s[r]=function(){return{next:function(){return{done:n=!0}}}},e(s)}catch(e){}return n}},82532:(e,t,n)=>{var r=n(95329),o=r({}.toString),s=r("".slice);e.exports=function(e){return s(o(e),8,-1)}},9697:(e,t,n)=>{var r=n(22885),o=n(57475),s=n(82532),i=n(99813)("toStringTag"),a=Object,l="Arguments"==s(function(){return arguments}());e.exports=r?s:function(e){var t,n,r;return void 0===e?"Undefined":null===e?"Null":"string"==typeof(n=function(e,t){try{return e[t]}catch(e){}}(t=a(e),i))?n:l?s(t):"Object"==(r=s(t))&&o(t.callee)?"Arguments":r}},85616:(e,t,n)=>{"use strict";var r=n(29290),o=n(29202),s=n(94380),i=n(86843),a=n(5743),l=n(82119),c=n(93091),u=n(75105),p=n(23538),h=n(94431),f=n(55746),d=n(21647).fastKey,m=n(45402),g=m.set,y=m.getterFor;e.exports={getConstructor:function(e,t,n,u){var p=e((function(e,o){a(e,h),g(e,{type:t,index:r(null),first:void 0,last:void 0,size:0}),f||(e.size=0),l(o)||c(o,e[u],{that:e,AS_ENTRIES:n})})),h=p.prototype,m=y(t),v=function(e,t,n){var r,o,s=m(e),i=b(e,t);return i?i.value=n:(s.last=i={index:o=d(t,!0),key:t,value:n,previous:r=s.last,next:void 0,removed:!1},s.first||(s.first=i),r&&(r.next=i),f?s.size++:e.size++,"F"!==o&&(s.index[o]=i)),e},b=function(e,t){var n,r=m(e),o=d(t);if("F"!==o)return r.index[o];for(n=r.first;n;n=n.next)if(n.key==t)return n};return s(h,{clear:function(){for(var e=m(this),t=e.index,n=e.first;n;)n.removed=!0,n.previous&&(n.previous=n.previous.next=void 0),delete t[n.index],n=n.next;e.first=e.last=void 0,f?e.size=0:this.size=0},delete:function(e){var t=this,n=m(t),r=b(t,e);if(r){var o=r.next,s=r.previous;delete n.index[r.index],r.removed=!0,s&&(s.next=o),o&&(o.previous=s),n.first==r&&(n.first=o),n.last==r&&(n.last=s),f?n.size--:t.size--}return!!r},forEach:function(e){for(var t,n=m(this),r=i(e,arguments.length>1?arguments[1]:void 0);t=t?t.next:n.first;)for(r(t.value,t.key,this);t&&t.removed;)t=t.previous},has:function(e){return!!b(this,e)}}),s(h,n?{get:function(e){var t=b(this,e);return t&&t.value},set:function(e,t){return v(this,0===e?0:e,t)}}:{add:function(e){return v(this,e=0===e?0:e,e)}}),f&&o(h,"size",{configurable:!0,get:function(){return m(this).size}}),p},setStrong:function(e,t,n){var r=t+" Iterator",o=y(t),s=y(r);u(e,t,(function(e,t){g(this,{type:r,target:e,state:o(e),kind:t,last:void 0})}),(function(){for(var e=s(this),t=e.kind,n=e.last;n&&n.removed;)n=n.previous;return e.target&&(e.last=n=n?n.next:e.state.first)?p("keys"==t?n.key:"values"==t?n.value:[n.key,n.value],!1):(e.target=void 0,p(void 0,!0))}),n?"entries":"values",!n,!0),h(t)}}},8850:(e,t,n)=>{"use strict";var r=n(95329),o=n(94380),s=n(21647).getWeakData,i=n(5743),a=n(96059),l=n(82119),c=n(10941),u=n(93091),p=n(3610),h=n(90953),f=n(45402),d=f.set,m=f.getterFor,g=p.find,y=p.findIndex,v=r([].splice),b=0,w=function(e){return e.frozen||(e.frozen=new E)},E=function(){this.entries=[]},x=function(e,t){return g(e.entries,(function(e){return e[0]===t}))};E.prototype={get:function(e){var t=x(this,e);if(t)return t[1]},has:function(e){return!!x(this,e)},set:function(e,t){var n=x(this,e);n?n[1]=t:this.entries.push([e,t])},delete:function(e){var t=y(this.entries,(function(t){return t[0]===e}));return~t&&v(this.entries,t,1),!!~t}},e.exports={getConstructor:function(e,t,n,r){var p=e((function(e,o){i(e,f),d(e,{type:t,id:b++,frozen:void 0}),l(o)||u(o,e[r],{that:e,AS_ENTRIES:n})})),f=p.prototype,g=m(t),y=function(e,t,n){var r=g(e),o=s(a(t),!0);return!0===o?w(r).set(t,n):o[r.id]=n,e};return o(f,{delete:function(e){var t=g(this);if(!c(e))return!1;var n=s(e);return!0===n?w(t).delete(e):n&&h(n,t.id)&&delete n[t.id]},has:function(e){var t=g(this);if(!c(e))return!1;var n=s(e);return!0===n?w(t).has(e):n&&h(n,t.id)}}),o(f,n?{get:function(e){var t=g(this);if(c(e)){var n=s(e);return!0===n?w(t).get(e):n?n[t.id]:void 0}},set:function(e,t){return y(this,e,t)}}:{add:function(e){return y(this,e,!0)}}),p}}},24683:(e,t,n)=>{"use strict";var r=n(76887),o=n(21899),s=n(21647),i=n(95981),a=n(32029),l=n(93091),c=n(5743),u=n(57475),p=n(10941),h=n(90904),f=n(65988).f,d=n(3610).forEach,m=n(55746),g=n(45402),y=g.set,v=g.getterFor;e.exports=function(e,t,n){var g,b=-1!==e.indexOf("Map"),w=-1!==e.indexOf("Weak"),E=b?"set":"add",x=o[e],S=x&&x.prototype,_={};if(m&&u(x)&&(w||S.forEach&&!i((function(){(new x).entries().next()})))){var j=(g=t((function(t,n){y(c(t,j),{type:e,collection:new x}),null!=n&&l(n,t[E],{that:t,AS_ENTRIES:b})}))).prototype,O=v(e);d(["add","clear","delete","forEach","get","has","set","keys","values","entries"],(function(e){var t="add"==e||"set"==e;!(e in S)||w&&"clear"==e||a(j,e,(function(n,r){var o=O(this).collection;if(!t&&w&&!p(n))return"get"==e&&void 0;var s=o[e](0===n?0:n,r);return t?this:s}))})),w||f(j,"size",{configurable:!0,get:function(){return O(this).collection.size}})}else g=n.getConstructor(t,e,b,E),s.enable();return h(g,e,!1,!0),_[e]=g,r({global:!0,forced:!0},_),w||n.setStrong(g,e,b),g}},23489:(e,t,n)=>{var r=n(90953),o=n(31136),s=n(49677),i=n(65988);e.exports=function(e,t,n){for(var a=o(t),l=i.f,c=s.f,u=0;u{var r=n(99813)("match");e.exports=function(e){var t=/./;try{"/./"[e](t)}catch(n){try{return t[r]=!1,"/./"[e](t)}catch(e){}}return!1}},64160:(e,t,n)=>{var r=n(95981);e.exports=!r((function(){function e(){}return e.prototype.constructor=null,Object.getPrototypeOf(new e)!==e.prototype}))},23538:e=>{e.exports=function(e,t){return{value:e,done:t}}},32029:(e,t,n)=>{var r=n(55746),o=n(65988),s=n(31887);e.exports=r?function(e,t,n){return o.f(e,t,s(1,n))}:function(e,t,n){return e[t]=n,e}},31887:e=>{e.exports=function(e,t){return{enumerable:!(1&e),configurable:!(2&e),writable:!(4&e),value:t}}},55449:(e,t,n)=>{"use strict";var r=n(83894),o=n(65988),s=n(31887);e.exports=function(e,t,n){var i=r(t);i in e?o.f(e,i,s(0,n)):e[i]=n}},29202:(e,t,n)=>{var r=n(65988);e.exports=function(e,t,n){return r.f(e,t,n)}},95929:(e,t,n)=>{var r=n(32029);e.exports=function(e,t,n,o){return o&&o.enumerable?e[t]=n:r(e,t,n),e}},94380:(e,t,n)=>{var r=n(95929);e.exports=function(e,t,n){for(var o in t)n&&n.unsafe&&e[o]?e[o]=t[o]:r(e,o,t[o],n);return e}},75609:(e,t,n)=>{var r=n(21899),o=Object.defineProperty;e.exports=function(e,t){try{o(r,e,{value:t,configurable:!0,writable:!0})}catch(n){r[e]=t}return t}},15863:(e,t,n)=>{"use strict";var r=n(69826),o=TypeError;e.exports=function(e,t){if(!delete e[t])throw o("Cannot delete property "+r(t)+" of "+r(e))}},55746:(e,t,n)=>{var r=n(95981);e.exports=!r((function(){return 7!=Object.defineProperty({},1,{get:function(){return 7}})[1]}))},76616:e=>{var t="object"==typeof document&&document.all,n=void 0===t&&void 0!==t;e.exports={all:t,IS_HTMLDDA:n}},61333:(e,t,n)=>{var r=n(21899),o=n(10941),s=r.document,i=o(s)&&o(s.createElement);e.exports=function(e){return i?s.createElement(e):{}}},66796:e=>{var t=TypeError;e.exports=function(e){if(e>9007199254740991)throw t("Maximum allowed index exceeded");return e}},63281:e=>{e.exports={CSSRuleList:0,CSSStyleDeclaration:0,CSSValueList:0,ClientRectList:0,DOMRectList:0,DOMStringList:0,DOMTokenList:1,DataTransferItemList:0,FileList:0,HTMLAllCollection:0,HTMLCollection:0,HTMLFormElement:0,HTMLSelectElement:0,MediaList:0,MimeTypeArray:0,NamedNodeMap:0,NodeList:1,PaintRequestList:0,Plugin:0,PluginArray:0,SVGLengthList:0,SVGNumberList:0,SVGPathSegList:0,SVGPointList:0,SVGStringList:0,SVGTransformList:0,SourceBufferList:0,StyleSheetList:0,TextTrackCueList:0,TextTrackList:0,TouchList:0}},34342:(e,t,n)=>{var r=n(2861).match(/firefox\/(\d+)/i);e.exports=!!r&&+r[1]},23321:(e,t,n)=>{var r=n(48501),o=n(6049);e.exports=!r&&!o&&"object"==typeof window&&"object"==typeof document},56491:e=>{e.exports="function"==typeof Bun&&Bun&&"string"==typeof Bun.version},48501:e=>{e.exports="object"==typeof Deno&&Deno&&"object"==typeof Deno.version},81046:(e,t,n)=>{var r=n(2861);e.exports=/MSIE|Trident/.test(r)},4470:(e,t,n)=>{var r=n(2861);e.exports=/ipad|iphone|ipod/i.test(r)&&"undefined"!=typeof Pebble},22749:(e,t,n)=>{var r=n(2861);e.exports=/(?:ipad|iphone|ipod).*applewebkit/i.test(r)},6049:(e,t,n)=>{var r=n(34155),o=n(82532);e.exports=void 0!==r&&"process"==o(r)},58045:(e,t,n)=>{var r=n(2861);e.exports=/web0s(?!.*chrome)/i.test(r)},2861:e=>{e.exports="undefined"!=typeof navigator&&String(navigator.userAgent)||""},53385:(e,t,n)=>{var r,o,s=n(21899),i=n(2861),a=s.process,l=s.Deno,c=a&&a.versions||l&&l.version,u=c&&c.v8;u&&(o=(r=u.split("."))[0]>0&&r[0]<4?1:+(r[0]+r[1])),!o&&i&&(!(r=i.match(/Edge\/(\d+)/))||r[1]>=74)&&(r=i.match(/Chrome\/(\d+)/))&&(o=+r[1]),e.exports=o},18938:(e,t,n)=>{var r=n(2861).match(/AppleWebKit\/(\d+)\./);e.exports=!!r&&+r[1]},35703:(e,t,n)=>{var r=n(54058);e.exports=function(e){return r[e+"Prototype"]}},56759:e=>{e.exports=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"]},53995:(e,t,n)=>{var r=n(95329),o=Error,s=r("".replace),i=String(o("zxcasd").stack),a=/\n\s*at [^:]*:[^\n]*/,l=a.test(i);e.exports=function(e,t){if(l&&"string"==typeof e&&!o.prepareStackTrace)for(;t--;)e=s(e,a,"");return e}},79585:(e,t,n)=>{var r=n(32029),o=n(53995),s=n(18780),i=Error.captureStackTrace;e.exports=function(e,t,n,a){s&&(i?i(e,t):r(e,"stack",o(n,a)))}},18780:(e,t,n)=>{var r=n(95981),o=n(31887);e.exports=!r((function(){var e=Error("a");return!("stack"in e)||(Object.defineProperty(e,"stack",o(1,7)),7!==e.stack)}))},76887:(e,t,n)=>{"use strict";var r=n(21899),o=n(79730),s=n(97484),i=n(57475),a=n(49677).f,l=n(37252),c=n(54058),u=n(86843),p=n(32029),h=n(90953),f=function(e){var t=function(n,r,s){if(this instanceof t){switch(arguments.length){case 0:return new e;case 1:return new e(n);case 2:return new e(n,r)}return new e(n,r,s)}return o(e,this,arguments)};return t.prototype=e.prototype,t};e.exports=function(e,t){var n,o,d,m,g,y,v,b,w,E=e.target,x=e.global,S=e.stat,_=e.proto,j=x?r:S?r[E]:(r[E]||{}).prototype,O=x?c:c[E]||p(c,E,{})[E],k=O.prototype;for(m in t)o=!(n=l(x?m:E+(S?".":"#")+m,e.forced))&&j&&h(j,m),y=O[m],o&&(v=e.dontCallGetSet?(w=a(j,m))&&w.value:j[m]),g=o&&v?v:t[m],o&&typeof y==typeof g||(b=e.bind&&o?u(g,r):e.wrap&&o?f(g):_&&i(g)?s(g):g,(e.sham||g&&g.sham||y&&y.sham)&&p(b,"sham",!0),p(O,m,b),_&&(h(c,d=E+"Prototype")||p(c,d,{}),p(c[d],m,g),e.real&&k&&(n||!k[m])&&p(k,m,g)))}},95981:e=>{e.exports=function(e){try{return!!e()}catch(e){return!0}}},45602:(e,t,n)=>{var r=n(95981);e.exports=!r((function(){return Object.isExtensible(Object.preventExtensions({}))}))},79730:(e,t,n)=>{var r=n(18285),o=Function.prototype,s=o.apply,i=o.call;e.exports="object"==typeof Reflect&&Reflect.apply||(r?i.bind(s):function(){return i.apply(s,arguments)})},86843:(e,t,n)=>{var r=n(97484),o=n(24883),s=n(18285),i=r(r.bind);e.exports=function(e,t){return o(e),void 0===t?e:s?i(e,t):function(){return e.apply(t,arguments)}}},18285:(e,t,n)=>{var r=n(95981);e.exports=!r((function(){var e=function(){}.bind();return"function"!=typeof e||e.hasOwnProperty("prototype")}))},98308:(e,t,n)=>{"use strict";var r=n(95329),o=n(24883),s=n(10941),i=n(90953),a=n(93765),l=n(18285),c=Function,u=r([].concat),p=r([].join),h={};e.exports=l?c.bind:function(e){var t=o(this),n=t.prototype,r=a(arguments,1),l=function(){var n=u(r,a(arguments));return this instanceof l?function(e,t,n){if(!i(h,t)){for(var r=[],o=0;o{var r=n(18285),o=Function.prototype.call;e.exports=r?o.bind(o):function(){return o.apply(o,arguments)}},79417:(e,t,n)=>{var r=n(55746),o=n(90953),s=Function.prototype,i=r&&Object.getOwnPropertyDescriptor,a=o(s,"name"),l=a&&"something"===function(){}.name,c=a&&(!r||r&&i(s,"name").configurable);e.exports={EXISTS:a,PROPER:l,CONFIGURABLE:c}},45526:(e,t,n)=>{var r=n(95329),o=n(24883);e.exports=function(e,t,n){try{return r(o(Object.getOwnPropertyDescriptor(e,t)[n]))}catch(e){}}},97484:(e,t,n)=>{var r=n(82532),o=n(95329);e.exports=function(e){if("Function"===r(e))return o(e)}},95329:(e,t,n)=>{var r=n(18285),o=Function.prototype,s=o.call,i=r&&o.bind.bind(s,s);e.exports=r?i:function(e){return function(){return s.apply(e,arguments)}}},626:(e,t,n)=>{var r=n(54058),o=n(21899),s=n(57475),i=function(e){return s(e)?e:void 0};e.exports=function(e,t){return arguments.length<2?i(r[e])||i(o[e]):r[e]&&r[e][t]||o[e]&&o[e][t]}},22902:(e,t,n)=>{var r=n(9697),o=n(14229),s=n(82119),i=n(12077),a=n(99813)("iterator");e.exports=function(e){if(!s(e))return o(e,a)||o(e,"@@iterator")||i[r(e)]}},53476:(e,t,n)=>{var r=n(78834),o=n(24883),s=n(96059),i=n(69826),a=n(22902),l=TypeError;e.exports=function(e,t){var n=arguments.length<2?a(e):t;if(o(n))return s(r(n,e));throw l(i(e)+" is not iterable")}},33323:(e,t,n)=>{var r=n(95329),o=n(1052),s=n(57475),i=n(82532),a=n(85803),l=r([].push);e.exports=function(e){if(s(e))return e;if(o(e)){for(var t=e.length,n=[],r=0;r{var r=n(24883),o=n(82119);e.exports=function(e,t){var n=e[t];return o(n)?void 0:r(n)}},21899:function(e,t,n){var r=function(e){return e&&e.Math==Math&&e};e.exports=r("object"==typeof globalThis&&globalThis)||r("object"==typeof window&&window)||r("object"==typeof self&&self)||r("object"==typeof n.g&&n.g)||function(){return this}()||this||Function("return this")()},90953:(e,t,n)=>{var r=n(95329),o=n(89678),s=r({}.hasOwnProperty);e.exports=Object.hasOwn||function(e,t){return s(o(e),t)}},27748:e=>{e.exports={}},34845:e=>{e.exports=function(e,t){try{1==arguments.length?console.error(e):console.error(e,t)}catch(e){}}},15463:(e,t,n)=>{var r=n(626);e.exports=r("document","documentElement")},2840:(e,t,n)=>{var r=n(55746),o=n(95981),s=n(61333);e.exports=!r&&!o((function(){return 7!=Object.defineProperty(s("div"),"a",{get:function(){return 7}}).a}))},37026:(e,t,n)=>{var r=n(95329),o=n(95981),s=n(82532),i=Object,a=r("".split);e.exports=o((function(){return!i("z").propertyIsEnumerable(0)}))?function(e){return"String"==s(e)?a(e,""):i(e)}:i},81302:(e,t,n)=>{var r=n(95329),o=n(57475),s=n(63030),i=r(Function.toString);o(s.inspectSource)||(s.inspectSource=function(e){return i(e)}),e.exports=s.inspectSource},53794:(e,t,n)=>{var r=n(10941),o=n(32029);e.exports=function(e,t){r(t)&&"cause"in t&&o(e,"cause",t.cause)}},21647:(e,t,n)=>{var r=n(76887),o=n(95329),s=n(27748),i=n(10941),a=n(90953),l=n(65988).f,c=n(10946),u=n(684),p=n(91584),h=n(99418),f=n(45602),d=!1,m=h("meta"),g=0,y=function(e){l(e,m,{value:{objectID:"O"+g++,weakData:{}}})},v=e.exports={enable:function(){v.enable=function(){},d=!0;var e=c.f,t=o([].splice),n={};n[m]=1,e(n).length&&(c.f=function(n){for(var r=e(n),o=0,s=r.length;o{var r,o,s,i=n(47093),a=n(21899),l=n(10941),c=n(32029),u=n(90953),p=n(63030),h=n(44262),f=n(27748),d="Object already initialized",m=a.TypeError,g=a.WeakMap;if(i||p.state){var y=p.state||(p.state=new g);y.get=y.get,y.has=y.has,y.set=y.set,r=function(e,t){if(y.has(e))throw m(d);return t.facade=e,y.set(e,t),t},o=function(e){return y.get(e)||{}},s=function(e){return y.has(e)}}else{var v=h("state");f[v]=!0,r=function(e,t){if(u(e,v))throw m(d);return t.facade=e,c(e,v,t),t},o=function(e){return u(e,v)?e[v]:{}},s=function(e){return u(e,v)}}e.exports={set:r,get:o,has:s,enforce:function(e){return s(e)?o(e):r(e,{})},getterFor:function(e){return function(t){var n;if(!l(t)||(n=o(t)).type!==e)throw m("Incompatible receiver, "+e+" required");return n}}}},6782:(e,t,n)=>{var r=n(99813),o=n(12077),s=r("iterator"),i=Array.prototype;e.exports=function(e){return void 0!==e&&(o.Array===e||i[s]===e)}},1052:(e,t,n)=>{var r=n(82532);e.exports=Array.isArray||function(e){return"Array"==r(e)}},57475:(e,t,n)=>{var r=n(76616),o=r.all;e.exports=r.IS_HTMLDDA?function(e){return"function"==typeof e||e===o}:function(e){return"function"==typeof e}},24284:(e,t,n)=>{var r=n(95329),o=n(95981),s=n(57475),i=n(9697),a=n(626),l=n(81302),c=function(){},u=[],p=a("Reflect","construct"),h=/^\s*(?:class|function)\b/,f=r(h.exec),d=!h.exec(c),m=function(e){if(!s(e))return!1;try{return p(c,u,e),!0}catch(e){return!1}},g=function(e){if(!s(e))return!1;switch(i(e)){case"AsyncFunction":case"GeneratorFunction":case"AsyncGeneratorFunction":return!1}try{return d||!!f(h,l(e))}catch(e){return!0}};g.sham=!0,e.exports=!p||o((function(){var e;return m(m.call)||!m(Object)||!m((function(){e=!0}))||e}))?g:m},37252:(e,t,n)=>{var r=n(95981),o=n(57475),s=/#|\.prototype\./,i=function(e,t){var n=l[a(e)];return n==u||n!=c&&(o(t)?r(t):!!t)},a=i.normalize=function(e){return String(e).replace(s,".").toLowerCase()},l=i.data={},c=i.NATIVE="N",u=i.POLYFILL="P";e.exports=i},54639:(e,t,n)=>{var r=n(10941),o=Math.floor;e.exports=Number.isInteger||function(e){return!r(e)&&isFinite(e)&&o(e)===e}},82119:e=>{e.exports=function(e){return null==e}},10941:(e,t,n)=>{var r=n(57475),o=n(76616),s=o.all;e.exports=o.IS_HTMLDDA?function(e){return"object"==typeof e?null!==e:r(e)||e===s}:function(e){return"object"==typeof e?null!==e:r(e)}},82529:e=>{e.exports=!0},60685:(e,t,n)=>{var r=n(10941),o=n(82532),s=n(99813)("match");e.exports=function(e){var t;return r(e)&&(void 0!==(t=e[s])?!!t:"RegExp"==o(e))}},56664:(e,t,n)=>{var r=n(626),o=n(57475),s=n(7046),i=n(32302),a=Object;e.exports=i?function(e){return"symbol"==typeof e}:function(e){var t=r("Symbol");return o(t)&&s(t.prototype,a(e))}},93091:(e,t,n)=>{var r=n(86843),o=n(78834),s=n(96059),i=n(69826),a=n(6782),l=n(10623),c=n(7046),u=n(53476),p=n(22902),h=n(7609),f=TypeError,d=function(e,t){this.stopped=e,this.result=t},m=d.prototype;e.exports=function(e,t,n){var g,y,v,b,w,E,x,S=n&&n.that,_=!(!n||!n.AS_ENTRIES),j=!(!n||!n.IS_RECORD),O=!(!n||!n.IS_ITERATOR),k=!(!n||!n.INTERRUPTED),A=r(t,S),C=function(e){return g&&h(g,"normal",e),new d(!0,e)},P=function(e){return _?(s(e),k?A(e[0],e[1],C):A(e[0],e[1])):k?A(e,C):A(e)};if(j)g=e.iterator;else if(O)g=e;else{if(!(y=p(e)))throw f(i(e)+" is not iterable");if(a(y)){for(v=0,b=l(e);b>v;v++)if((w=P(e[v]))&&c(m,w))return w;return new d(!1)}g=u(e,y)}for(E=j?e.next:g.next;!(x=o(E,g)).done;){try{w=P(x.value)}catch(e){h(g,"throw",e)}if("object"==typeof w&&w&&c(m,w))return w}return new d(!1)}},7609:(e,t,n)=>{var r=n(78834),o=n(96059),s=n(14229);e.exports=function(e,t,n){var i,a;o(e);try{if(!(i=s(e,"return"))){if("throw"===t)throw n;return n}i=r(i,e)}catch(e){a=!0,i=e}if("throw"===t)throw n;if(a)throw i;return o(i),n}},53847:(e,t,n)=>{"use strict";var r=n(35143).IteratorPrototype,o=n(29290),s=n(31887),i=n(90904),a=n(12077),l=function(){return this};e.exports=function(e,t,n,c){var u=t+" Iterator";return e.prototype=o(r,{next:s(+!c,n)}),i(e,u,!1,!0),a[u]=l,e}},75105:(e,t,n)=>{"use strict";var r=n(76887),o=n(78834),s=n(82529),i=n(79417),a=n(57475),l=n(53847),c=n(249),u=n(88929),p=n(90904),h=n(32029),f=n(95929),d=n(99813),m=n(12077),g=n(35143),y=i.PROPER,v=i.CONFIGURABLE,b=g.IteratorPrototype,w=g.BUGGY_SAFARI_ITERATORS,E=d("iterator"),x="keys",S="values",_="entries",j=function(){return this};e.exports=function(e,t,n,i,d,g,O){l(n,t,i);var k,A,C,P=function(e){if(e===d&&M)return M;if(!w&&e in T)return T[e];switch(e){case x:case S:case _:return function(){return new n(this,e)}}return function(){return new n(this)}},N=t+" Iterator",I=!1,T=e.prototype,R=T[E]||T["@@iterator"]||d&&T[d],M=!w&&R||P(d),D="Array"==t&&T.entries||R;if(D&&(k=c(D.call(new e)))!==Object.prototype&&k.next&&(s||c(k)===b||(u?u(k,b):a(k[E])||f(k,E,j)),p(k,N,!0,!0),s&&(m[N]=j)),y&&d==S&&R&&R.name!==S&&(!s&&v?h(T,"name",S):(I=!0,M=function(){return o(R,this)})),d)if(A={values:P(S),keys:g?M:P(x),entries:P(_)},O)for(C in A)(w||I||!(C in T))&&f(T,C,A[C]);else r({target:t,proto:!0,forced:w||I},A);return s&&!O||T[E]===M||f(T,E,M,{name:d}),m[t]=M,A}},35143:(e,t,n)=>{"use strict";var r,o,s,i=n(95981),a=n(57475),l=n(10941),c=n(29290),u=n(249),p=n(95929),h=n(99813),f=n(82529),d=h("iterator"),m=!1;[].keys&&("next"in(s=[].keys())?(o=u(u(s)))!==Object.prototype&&(r=o):m=!0),!l(r)||i((function(){var e={};return r[d].call(e)!==e}))?r={}:f&&(r=c(r)),a(r[d])||p(r,d,(function(){return this})),e.exports={IteratorPrototype:r,BUGGY_SAFARI_ITERATORS:m}},12077:e=>{e.exports={}},10623:(e,t,n)=>{var r=n(43057);e.exports=function(e){return r(e.length)}},35331:e=>{var t=Math.ceil,n=Math.floor;e.exports=Math.trunc||function(e){var r=+e;return(r>0?n:t)(r)}},66132:(e,t,n)=>{var r,o,s,i,a,l=n(21899),c=n(86843),u=n(49677).f,p=n(42941).set,h=n(18397),f=n(22749),d=n(4470),m=n(58045),g=n(6049),y=l.MutationObserver||l.WebKitMutationObserver,v=l.document,b=l.process,w=l.Promise,E=u(l,"queueMicrotask"),x=E&&E.value;if(!x){var S=new h,_=function(){var e,t;for(g&&(e=b.domain)&&e.exit();t=S.get();)try{t()}catch(e){throw S.head&&r(),e}e&&e.enter()};f||g||m||!y||!v?!d&&w&&w.resolve?((i=w.resolve(void 0)).constructor=w,a=c(i.then,i),r=function(){a(_)}):g?r=function(){b.nextTick(_)}:(p=c(p,l),r=function(){p(_)}):(o=!0,s=v.createTextNode(""),new y(_).observe(s,{characterData:!0}),r=function(){s.data=o=!o}),x=function(e){S.head||r(),S.add(e)}}e.exports=x},69520:(e,t,n)=>{"use strict";var r=n(24883),o=TypeError,s=function(e){var t,n;this.promise=new e((function(e,r){if(void 0!==t||void 0!==n)throw o("Bad Promise constructor");t=e,n=r})),this.resolve=r(t),this.reject=r(n)};e.exports.f=function(e){return new s(e)}},14649:(e,t,n)=>{var r=n(85803);e.exports=function(e,t){return void 0===e?arguments.length<2?"":t:r(e)}},70344:(e,t,n)=>{var r=n(60685),o=TypeError;e.exports=function(e){if(r(e))throw o("The method doesn't accept regular expressions");return e}},24420:(e,t,n)=>{"use strict";var r=n(55746),o=n(95329),s=n(78834),i=n(95981),a=n(14771),l=n(87857),c=n(36760),u=n(89678),p=n(37026),h=Object.assign,f=Object.defineProperty,d=o([].concat);e.exports=!h||i((function(){if(r&&1!==h({b:1},h(f({},"a",{enumerable:!0,get:function(){f(this,"b",{value:3,enumerable:!1})}}),{b:2})).b)return!0;var e={},t={},n=Symbol(),o="abcdefghijklmnopqrst";return e[n]=7,o.split("").forEach((function(e){t[e]=e})),7!=h({},e)[n]||a(h({},t)).join("")!=o}))?function(e,t){for(var n=u(e),o=arguments.length,i=1,h=l.f,f=c.f;o>i;)for(var m,g=p(arguments[i++]),y=h?d(a(g),h(g)):a(g),v=y.length,b=0;v>b;)m=y[b++],r&&!s(f,g,m)||(n[m]=g[m]);return n}:h},29290:(e,t,n)=>{var r,o=n(96059),s=n(59938),i=n(56759),a=n(27748),l=n(15463),c=n(61333),u=n(44262),p="prototype",h="script",f=u("IE_PROTO"),d=function(){},m=function(e){return"<"+h+">"+e+""},g=function(e){e.write(m("")),e.close();var t=e.parentWindow.Object;return e=null,t},y=function(){try{r=new ActiveXObject("htmlfile")}catch(e){}var e,t,n;y="undefined"!=typeof document?document.domain&&r?g(r):(t=c("iframe"),n="java"+h+":",t.style.display="none",l.appendChild(t),t.src=String(n),(e=t.contentWindow.document).open(),e.write(m("document.F=Object")),e.close(),e.F):g(r);for(var o=i.length;o--;)delete y[p][i[o]];return y()};a[f]=!0,e.exports=Object.create||function(e,t){var n;return null!==e?(d[p]=o(e),n=new d,d[p]=null,n[f]=e):n=y(),void 0===t?n:s.f(n,t)}},59938:(e,t,n)=>{var r=n(55746),o=n(83937),s=n(65988),i=n(96059),a=n(74529),l=n(14771);t.f=r&&!o?Object.defineProperties:function(e,t){i(e);for(var n,r=a(t),o=l(t),c=o.length,u=0;c>u;)s.f(e,n=o[u++],r[n]);return e}},65988:(e,t,n)=>{var r=n(55746),o=n(2840),s=n(83937),i=n(96059),a=n(83894),l=TypeError,c=Object.defineProperty,u=Object.getOwnPropertyDescriptor,p="enumerable",h="configurable",f="writable";t.f=r?s?function(e,t,n){if(i(e),t=a(t),i(n),"function"==typeof e&&"prototype"===t&&"value"in n&&f in n&&!n[f]){var r=u(e,t);r&&r[f]&&(e[t]=n.value,n={configurable:h in n?n[h]:r[h],enumerable:p in n?n[p]:r[p],writable:!1})}return c(e,t,n)}:c:function(e,t,n){if(i(e),t=a(t),i(n),o)try{return c(e,t,n)}catch(e){}if("get"in n||"set"in n)throw l("Accessors not supported");return"value"in n&&(e[t]=n.value),e}},49677:(e,t,n)=>{var r=n(55746),o=n(78834),s=n(36760),i=n(31887),a=n(74529),l=n(83894),c=n(90953),u=n(2840),p=Object.getOwnPropertyDescriptor;t.f=r?p:function(e,t){if(e=a(e),t=l(t),u)try{return p(e,t)}catch(e){}if(c(e,t))return i(!o(s.f,e,t),e[t])}},684:(e,t,n)=>{var r=n(82532),o=n(74529),s=n(10946).f,i=n(15790),a="object"==typeof window&&window&&Object.getOwnPropertyNames?Object.getOwnPropertyNames(window):[];e.exports.f=function(e){return a&&"Window"==r(e)?function(e){try{return s(e)}catch(e){return i(a)}}(e):s(o(e))}},10946:(e,t,n)=>{var r=n(55629),o=n(56759).concat("length","prototype");t.f=Object.getOwnPropertyNames||function(e){return r(e,o)}},87857:(e,t)=>{t.f=Object.getOwnPropertySymbols},249:(e,t,n)=>{var r=n(90953),o=n(57475),s=n(89678),i=n(44262),a=n(64160),l=i("IE_PROTO"),c=Object,u=c.prototype;e.exports=a?c.getPrototypeOf:function(e){var t=s(e);if(r(t,l))return t[l];var n=t.constructor;return o(n)&&t instanceof n?n.prototype:t instanceof c?u:null}},91584:(e,t,n)=>{var r=n(95981),o=n(10941),s=n(82532),i=n(97135),a=Object.isExtensible,l=r((function(){a(1)}));e.exports=l||i?function(e){return!!o(e)&&((!i||"ArrayBuffer"!=s(e))&&(!a||a(e)))}:a},7046:(e,t,n)=>{var r=n(95329);e.exports=r({}.isPrototypeOf)},55629:(e,t,n)=>{var r=n(95329),o=n(90953),s=n(74529),i=n(31692).indexOf,a=n(27748),l=r([].push);e.exports=function(e,t){var n,r=s(e),c=0,u=[];for(n in r)!o(a,n)&&o(r,n)&&l(u,n);for(;t.length>c;)o(r,n=t[c++])&&(~i(u,n)||l(u,n));return u}},14771:(e,t,n)=>{var r=n(55629),o=n(56759);e.exports=Object.keys||function(e){return r(e,o)}},36760:(e,t)=>{"use strict";var n={}.propertyIsEnumerable,r=Object.getOwnPropertyDescriptor,o=r&&!n.call({1:2},1);t.f=o?function(e){var t=r(this,e);return!!t&&t.enumerable}:n},88929:(e,t,n)=>{var r=n(45526),o=n(96059),s=n(11851);e.exports=Object.setPrototypeOf||("__proto__"in{}?function(){var e,t=!1,n={};try{(e=r(Object.prototype,"__proto__","set"))(n,[]),t=n instanceof Array}catch(e){}return function(n,r){return o(n),s(r),t?e(n,r):n.__proto__=r,n}}():void 0)},88810:(e,t,n)=>{var r=n(55746),o=n(95981),s=n(95329),i=n(249),a=n(14771),l=n(74529),c=s(n(36760).f),u=s([].push),p=r&&o((function(){var e=Object.create(null);return e[2]=2,!c(e,2)})),h=function(e){return function(t){for(var n,o=l(t),s=a(o),h=p&&null===i(o),f=s.length,d=0,m=[];f>d;)n=s[d++],r&&!(h?n in o:c(o,n))||u(m,e?[n,o[n]]:o[n]);return m}};e.exports={entries:h(!0),values:h(!1)}},95623:(e,t,n)=>{"use strict";var r=n(22885),o=n(9697);e.exports=r?{}.toString:function(){return"[object "+o(this)+"]"}},39811:(e,t,n)=>{var r=n(78834),o=n(57475),s=n(10941),i=TypeError;e.exports=function(e,t){var n,a;if("string"===t&&o(n=e.toString)&&!s(a=r(n,e)))return a;if(o(n=e.valueOf)&&!s(a=r(n,e)))return a;if("string"!==t&&o(n=e.toString)&&!s(a=r(n,e)))return a;throw i("Can't convert object to primitive value")}},31136:(e,t,n)=>{var r=n(626),o=n(95329),s=n(10946),i=n(87857),a=n(96059),l=o([].concat);e.exports=r("Reflect","ownKeys")||function(e){var t=s.f(a(e)),n=i.f;return n?l(t,n(e)):t}},54058:e=>{e.exports={}},40002:e=>{e.exports=function(e){try{return{error:!1,value:e()}}catch(e){return{error:!0,value:e}}}},67742:(e,t,n)=>{var r=n(21899),o=n(6991),s=n(57475),i=n(37252),a=n(81302),l=n(99813),c=n(23321),u=n(48501),p=n(82529),h=n(53385),f=o&&o.prototype,d=l("species"),m=!1,g=s(r.PromiseRejectionEvent),y=i("Promise",(function(){var e=a(o),t=e!==String(o);if(!t&&66===h)return!0;if(p&&(!f.catch||!f.finally))return!0;if(!h||h<51||!/native code/.test(e)){var n=new o((function(e){e(1)})),r=function(e){e((function(){}),(function(){}))};if((n.constructor={})[d]=r,!(m=n.then((function(){}))instanceof r))return!0}return!t&&(c||u)&&!g}));e.exports={CONSTRUCTOR:y,REJECTION_EVENT:g,SUBCLASSING:m}},6991:(e,t,n)=>{var r=n(21899);e.exports=r.Promise},56584:(e,t,n)=>{var r=n(96059),o=n(10941),s=n(69520);e.exports=function(e,t){if(r(e),o(t)&&t.constructor===e)return t;var n=s.f(e);return(0,n.resolve)(t),n.promise}},31542:(e,t,n)=>{var r=n(6991),o=n(21385),s=n(67742).CONSTRUCTOR;e.exports=s||!o((function(e){r.all(e).then(void 0,(function(){}))}))},18397:e=>{var t=function(){this.head=null,this.tail=null};t.prototype={add:function(e){var t={item:e,next:null},n=this.tail;n?n.next=t:this.head=t,this.tail=t},get:function(){var e=this.head;if(e)return null===(this.head=e.next)&&(this.tail=null),e.item}},e.exports=t},48219:(e,t,n)=>{var r=n(82119),o=TypeError;e.exports=function(e){if(r(e))throw o("Can't call method on "+e);return e}},37620:(e,t,n)=>{"use strict";var r,o=n(21899),s=n(79730),i=n(57475),a=n(56491),l=n(2861),c=n(93765),u=n(18348),p=o.Function,h=/MSIE .\./.test(l)||a&&((r=o.Bun.version.split(".")).length<3||0==r[0]&&(r[1]<3||3==r[1]&&0==r[2]));e.exports=function(e,t){var n=t?2:1;return h?function(r,o){var a=u(arguments.length,1)>n,l=i(r)?r:p(r),h=a?c(arguments,n):[],f=a?function(){s(l,this,h)}:l;return t?e(f,o):e(f)}:e}},94431:(e,t,n)=>{"use strict";var r=n(626),o=n(29202),s=n(99813),i=n(55746),a=s("species");e.exports=function(e){var t=r(e);i&&t&&!t[a]&&o(t,a,{configurable:!0,get:function(){return this}})}},90904:(e,t,n)=>{var r=n(22885),o=n(65988).f,s=n(32029),i=n(90953),a=n(95623),l=n(99813)("toStringTag");e.exports=function(e,t,n,c){if(e){var u=n?e:e.prototype;i(u,l)||o(u,l,{configurable:!0,value:t}),c&&!r&&s(u,"toString",a)}}},44262:(e,t,n)=>{var r=n(68726),o=n(99418),s=r("keys");e.exports=function(e){return s[e]||(s[e]=o(e))}},63030:(e,t,n)=>{var r=n(21899),o=n(75609),s="__core-js_shared__",i=r[s]||o(s,{});e.exports=i},68726:(e,t,n)=>{var r=n(82529),o=n(63030);(e.exports=function(e,t){return o[e]||(o[e]=void 0!==t?t:{})})("versions",[]).push({version:"3.31.0",mode:r?"pure":"global",copyright:"© 2014-2023 Denis Pushkarev (zloirock.ru)",license:"https://github.com/zloirock/core-js/blob/v3.31.0/LICENSE",source:"https://github.com/zloirock/core-js"})},70487:(e,t,n)=>{var r=n(96059),o=n(174),s=n(82119),i=n(99813)("species");e.exports=function(e,t){var n,a=r(e).constructor;return void 0===a||s(n=r(a)[i])?t:o(n)}},64620:(e,t,n)=>{var r=n(95329),o=n(62435),s=n(85803),i=n(48219),a=r("".charAt),l=r("".charCodeAt),c=r("".slice),u=function(e){return function(t,n){var r,u,p=s(i(t)),h=o(n),f=p.length;return h<0||h>=f?e?"":void 0:(r=l(p,h))<55296||r>56319||h+1===f||(u=l(p,h+1))<56320||u>57343?e?a(p,h):r:e?c(p,h,h+2):u-56320+(r-55296<<10)+65536}};e.exports={codeAt:u(!1),charAt:u(!0)}},73291:(e,t,n)=>{var r=n(95329),o=2147483647,s=/[^\0-\u007E]/,i=/[.\u3002\uFF0E\uFF61]/g,a="Overflow: input needs wider integers to process",l=RangeError,c=r(i.exec),u=Math.floor,p=String.fromCharCode,h=r("".charCodeAt),f=r([].join),d=r([].push),m=r("".replace),g=r("".split),y=r("".toLowerCase),v=function(e){return e+22+75*(e<26)},b=function(e,t,n){var r=0;for(e=n?u(e/700):e>>1,e+=u(e/t);e>455;)e=u(e/35),r+=36;return u(r+36*e/(e+38))},w=function(e){var t=[];e=function(e){for(var t=[],n=0,r=e.length;n=55296&&o<=56319&&n=i&&ru((o-c)/E))throw l(a);for(c+=(w-i)*E,i=w,n=0;no)throw l(a);if(r==i){for(var x=c,S=36;;){var _=S<=m?1:S>=m+26?26:S-m;if(x<_)break;var j=x-_,O=36-_;d(t,p(v(_+j%O))),x=u(j/O),S+=36}d(t,p(v(x))),m=b(c,E,y==g),c=0,y++}}c++,i++}return f(t,"")};e.exports=function(e){var t,n,r=[],o=g(m(y(e),i,"."),".");for(t=0;t{"use strict";var r=n(62435),o=n(85803),s=n(48219),i=RangeError;e.exports=function(e){var t=o(s(this)),n="",a=r(e);if(a<0||a==1/0)throw i("Wrong number of repetitions");for(;a>0;(a>>>=1)&&(t+=t))1&a&&(n+=t);return n}},93093:(e,t,n)=>{var r=n(79417).PROPER,o=n(95981),s=n(73483);e.exports=function(e){return o((function(){return!!s[e]()||"​…᠎"!=="​…᠎"[e]()||r&&s[e].name!==e}))}},74853:(e,t,n)=>{var r=n(95329),o=n(48219),s=n(85803),i=n(73483),a=r("".replace),l=RegExp("^["+i+"]+"),c=RegExp("(^|[^"+i+"])["+i+"]+$"),u=function(e){return function(t){var n=s(o(t));return 1&e&&(n=a(n,l,"")),2&e&&(n=a(n,c,"$1")),n}};e.exports={start:u(1),end:u(2),trim:u(3)}},63405:(e,t,n)=>{var r=n(53385),o=n(95981),s=n(21899).String;e.exports=!!Object.getOwnPropertySymbols&&!o((function(){var e=Symbol();return!s(e)||!(Object(e)instanceof Symbol)||!Symbol.sham&&r&&r<41}))},29630:(e,t,n)=>{var r=n(78834),o=n(626),s=n(99813),i=n(95929);e.exports=function(){var e=o("Symbol"),t=e&&e.prototype,n=t&&t.valueOf,a=s("toPrimitive");t&&!t[a]&&i(t,a,(function(e){return r(n,this)}),{arity:1})}},32087:(e,t,n)=>{var r=n(626),o=n(95329),s=r("Symbol"),i=s.keyFor,a=o(s.prototype.valueOf);e.exports=s.isRegisteredSymbol||function(e){try{return void 0!==i(a(e))}catch(e){return!1}}},96559:(e,t,n)=>{for(var r=n(68726),o=n(626),s=n(95329),i=n(56664),a=n(99813),l=o("Symbol"),c=l.isWellKnownSymbol,u=o("Object","getOwnPropertyNames"),p=s(l.prototype.valueOf),h=r("wks"),f=0,d=u(l),m=d.length;f{var r=n(63405);e.exports=r&&!!Symbol.for&&!!Symbol.keyFor},42941:(e,t,n)=>{var r,o,s,i,a=n(21899),l=n(79730),c=n(86843),u=n(57475),p=n(90953),h=n(95981),f=n(15463),d=n(93765),m=n(61333),g=n(18348),y=n(22749),v=n(6049),b=a.setImmediate,w=a.clearImmediate,E=a.process,x=a.Dispatch,S=a.Function,_=a.MessageChannel,j=a.String,O=0,k={},A="onreadystatechange";h((function(){r=a.location}));var C=function(e){if(p(k,e)){var t=k[e];delete k[e],t()}},P=function(e){return function(){C(e)}},N=function(e){C(e.data)},I=function(e){a.postMessage(j(e),r.protocol+"//"+r.host)};b&&w||(b=function(e){g(arguments.length,1);var t=u(e)?e:S(e),n=d(arguments,1);return k[++O]=function(){l(t,void 0,n)},o(O),O},w=function(e){delete k[e]},v?o=function(e){E.nextTick(P(e))}:x&&x.now?o=function(e){x.now(P(e))}:_&&!y?(i=(s=new _).port2,s.port1.onmessage=N,o=c(i.postMessage,i)):a.addEventListener&&u(a.postMessage)&&!a.importScripts&&r&&"file:"!==r.protocol&&!h(I)?(o=I,a.addEventListener("message",N,!1)):o=A in m("script")?function(e){f.appendChild(m("script"))[A]=function(){f.removeChild(this),C(e)}}:function(e){setTimeout(P(e),0)}),e.exports={set:b,clear:w}},59413:(e,t,n)=>{var r=n(62435),o=Math.max,s=Math.min;e.exports=function(e,t){var n=r(e);return n<0?o(n+t,0):s(n,t)}},74529:(e,t,n)=>{var r=n(37026),o=n(48219);e.exports=function(e){return r(o(e))}},62435:(e,t,n)=>{var r=n(35331);e.exports=function(e){var t=+e;return t!=t||0===t?0:r(t)}},43057:(e,t,n)=>{var r=n(62435),o=Math.min;e.exports=function(e){return e>0?o(r(e),9007199254740991):0}},89678:(e,t,n)=>{var r=n(48219),o=Object;e.exports=function(e){return o(r(e))}},46935:(e,t,n)=>{var r=n(78834),o=n(10941),s=n(56664),i=n(14229),a=n(39811),l=n(99813),c=TypeError,u=l("toPrimitive");e.exports=function(e,t){if(!o(e)||s(e))return e;var n,l=i(e,u);if(l){if(void 0===t&&(t="default"),n=r(l,e,t),!o(n)||s(n))return n;throw c("Can't convert object to primitive value")}return void 0===t&&(t="number"),a(e,t)}},83894:(e,t,n)=>{var r=n(46935),o=n(56664);e.exports=function(e){var t=r(e,"string");return o(t)?t:t+""}},22885:(e,t,n)=>{var r={};r[n(99813)("toStringTag")]="z",e.exports="[object z]"===String(r)},85803:(e,t,n)=>{var r=n(9697),o=String;e.exports=function(e){if("Symbol"===r(e))throw TypeError("Cannot convert a Symbol value to a string");return o(e)}},69826:e=>{var t=String;e.exports=function(e){try{return t(e)}catch(e){return"Object"}}},99418:(e,t,n)=>{var r=n(95329),o=0,s=Math.random(),i=r(1..toString);e.exports=function(e){return"Symbol("+(void 0===e?"":e)+")_"+i(++o+s,36)}},14766:(e,t,n)=>{var r=n(95981),o=n(99813),s=n(55746),i=n(82529),a=o("iterator");e.exports=!r((function(){var e=new URL("b?a=1&b=2&c=3","http://a"),t=e.searchParams,n=new URLSearchParams("a=1&a=2"),r="";return e.pathname="c%20d",t.forEach((function(e,n){t.delete("b"),r+=n+e})),n.delete("a",2),i&&(!e.toJSON||!n.has("a",1)||n.has("a",2))||!t.size&&(i||!s)||!t.sort||"http://a/c%20d?a=1&c=3"!==e.href||"3"!==t.get("c")||"a=1"!==String(new URLSearchParams("?a=1"))||!t[a]||"a"!==new URL("https://a@b").username||"b"!==new URLSearchParams(new URLSearchParams("a=b")).get("a")||"xn--e1aybc"!==new URL("http://тест").host||"#%D0%B1"!==new URL("http://a#б").hash||"a1c3"!==r||"x"!==new URL("http://x",void 0).host}))},32302:(e,t,n)=>{var r=n(63405);e.exports=r&&!Symbol.sham&&"symbol"==typeof Symbol.iterator},83937:(e,t,n)=>{var r=n(55746),o=n(95981);e.exports=r&&o((function(){return 42!=Object.defineProperty((function(){}),"prototype",{value:42,writable:!1}).prototype}))},18348:e=>{var t=TypeError;e.exports=function(e,n){if(e{var r=n(21899),o=n(57475),s=r.WeakMap;e.exports=o(s)&&/native code/.test(String(s))},73464:(e,t,n)=>{var r=n(54058),o=n(90953),s=n(11477),i=n(65988).f;e.exports=function(e){var t=r.Symbol||(r.Symbol={});o(t,e)||i(t,e,{value:s.f(e)})}},11477:(e,t,n)=>{var r=n(99813);t.f=r},99813:(e,t,n)=>{var r=n(21899),o=n(68726),s=n(90953),i=n(99418),a=n(63405),l=n(32302),c=r.Symbol,u=o("wks"),p=l?c.for||c:c&&c.withoutSetter||i;e.exports=function(e){return s(u,e)||(u[e]=a&&s(c,e)?c[e]:p("Symbol."+e)),u[e]}},73483:e=>{e.exports="\t\n\v\f\r                 \u2028\u2029\ufeff"},49812:(e,t,n)=>{"use strict";var r=n(76887),o=n(7046),s=n(249),i=n(88929),a=n(23489),l=n(29290),c=n(32029),u=n(31887),p=n(53794),h=n(79585),f=n(93091),d=n(14649),m=n(99813)("toStringTag"),g=Error,y=[].push,v=function(e,t){var n,r=o(b,this);i?n=i(g(),r?s(this):b):(n=r?this:l(b),c(n,m,"Error")),void 0!==t&&c(n,"message",d(t)),h(n,v,n.stack,1),arguments.length>2&&p(n,arguments[2]);var a=[];return f(e,y,{that:a}),c(n,"errors",a),n};i?i(v,g):a(v,g,{name:!0});var b=v.prototype=l(g.prototype,{constructor:u(1,v),message:u(1,""),name:u(1,"AggregateError")});r({global:!0,constructor:!0,arity:2},{AggregateError:v})},47627:(e,t,n)=>{n(49812)},85906:(e,t,n)=>{"use strict";var r=n(76887),o=n(95981),s=n(1052),i=n(10941),a=n(89678),l=n(10623),c=n(66796),u=n(55449),p=n(64692),h=n(50568),f=n(99813),d=n(53385),m=f("isConcatSpreadable"),g=d>=51||!o((function(){var e=[];return e[m]=!1,e.concat()[0]!==e})),y=function(e){if(!i(e))return!1;var t=e[m];return void 0!==t?!!t:s(e)};r({target:"Array",proto:!0,arity:1,forced:!g||!h("concat")},{concat:function(e){var t,n,r,o,s,i=a(this),h=p(i,0),f=0;for(t=-1,r=arguments.length;t{"use strict";var r=n(76887),o=n(3610).every;r({target:"Array",proto:!0,forced:!n(34194)("every")},{every:function(e){return o(this,e,arguments.length>1?arguments[1]:void 0)}})},80290:(e,t,n)=>{var r=n(76887),o=n(91860),s=n(18479);r({target:"Array",proto:!0},{fill:o}),s("fill")},21501:(e,t,n)=>{"use strict";var r=n(76887),o=n(3610).filter;r({target:"Array",proto:!0,forced:!n(50568)("filter")},{filter:function(e){return o(this,e,arguments.length>1?arguments[1]:void 0)}})},44929:(e,t,n)=>{"use strict";var r=n(76887),o=n(3610).findIndex,s=n(18479),i="findIndex",a=!0;i in[]&&Array(1)[i]((function(){a=!1})),r({target:"Array",proto:!0,forced:a},{findIndex:function(e){return o(this,e,arguments.length>1?arguments[1]:void 0)}}),s(i)},80833:(e,t,n)=>{"use strict";var r=n(76887),o=n(3610).find,s=n(18479),i="find",a=!0;i in[]&&Array(1)[i]((function(){a=!1})),r({target:"Array",proto:!0,forced:a},{find:function(e){return o(this,e,arguments.length>1?arguments[1]:void 0)}}),s(i)},2437:(e,t,n)=>{"use strict";var r=n(76887),o=n(56837);r({target:"Array",proto:!0,forced:[].forEach!=o},{forEach:o})},53242:(e,t,n)=>{var r=n(76887),o=n(11354);r({target:"Array",stat:!0,forced:!n(21385)((function(e){Array.from(e)}))},{from:o})},97690:(e,t,n)=>{"use strict";var r=n(76887),o=n(31692).includes,s=n(95981),i=n(18479);r({target:"Array",proto:!0,forced:s((function(){return!Array(1).includes()}))},{includes:function(e){return o(this,e,arguments.length>1?arguments[1]:void 0)}}),i("includes")},99076:(e,t,n)=>{"use strict";var r=n(76887),o=n(97484),s=n(31692).indexOf,i=n(34194),a=o([].indexOf),l=!!a&&1/a([1],1,-0)<0;r({target:"Array",proto:!0,forced:l||!i("indexOf")},{indexOf:function(e){var t=arguments.length>1?arguments[1]:void 0;return l?a(this,e,t)||0:s(this,e,t)}})},92737:(e,t,n)=>{n(76887)({target:"Array",stat:!0},{isArray:n(1052)})},66274:(e,t,n)=>{"use strict";var r=n(74529),o=n(18479),s=n(12077),i=n(45402),a=n(65988).f,l=n(75105),c=n(23538),u=n(82529),p=n(55746),h="Array Iterator",f=i.set,d=i.getterFor(h);e.exports=l(Array,"Array",(function(e,t){f(this,{type:h,target:r(e),index:0,kind:t})}),(function(){var e=d(this),t=e.target,n=e.kind,r=e.index++;return!t||r>=t.length?(e.target=void 0,c(void 0,!0)):c("keys"==n?r:"values"==n?t[r]:[r,t[r]],!1)}),"values");var m=s.Arguments=s.Array;if(o("keys"),o("values"),o("entries"),!u&&p&&"values"!==m.name)try{a(m,"name",{value:"values"})}catch(e){}},75915:(e,t,n)=>{var r=n(76887),o=n(67145);r({target:"Array",proto:!0,forced:o!==[].lastIndexOf},{lastIndexOf:o})},68787:(e,t,n)=>{"use strict";var r=n(76887),o=n(3610).map;r({target:"Array",proto:!0,forced:!n(50568)("map")},{map:function(e){return o(this,e,arguments.length>1?arguments[1]:void 0)}})},48528:(e,t,n)=>{"use strict";var r=n(76887),o=n(89678),s=n(10623),i=n(89779),a=n(66796);r({target:"Array",proto:!0,arity:1,forced:n(95981)((function(){return 4294967297!==[].push.call({length:4294967296},1)}))||!function(){try{Object.defineProperty([],"length",{writable:!1}).push()}catch(e){return e instanceof TypeError}}()},{push:function(e){var t=o(this),n=s(t),r=arguments.length;a(n+r);for(var l=0;l{"use strict";var r=n(76887),o=n(46499).left,s=n(34194),i=n(53385);r({target:"Array",proto:!0,forced:!n(6049)&&i>79&&i<83||!s("reduce")},{reduce:function(e){var t=arguments.length;return o(this,e,t,t>1?arguments[1]:void 0)}})},60186:(e,t,n)=>{"use strict";var r=n(76887),o=n(1052),s=n(24284),i=n(10941),a=n(59413),l=n(10623),c=n(74529),u=n(55449),p=n(99813),h=n(50568),f=n(93765),d=h("slice"),m=p("species"),g=Array,y=Math.max;r({target:"Array",proto:!0,forced:!d},{slice:function(e,t){var n,r,p,h=c(this),d=l(h),v=a(e,d),b=a(void 0===t?d:t,d);if(o(h)&&(n=h.constructor,(s(n)&&(n===g||o(n.prototype))||i(n)&&null===(n=n[m]))&&(n=void 0),n===g||void 0===n))return f(h,v,b);for(r=new(void 0===n?g:n)(y(b-v,0)),p=0;v{"use strict";var r=n(76887),o=n(3610).some;r({target:"Array",proto:!0,forced:!n(34194)("some")},{some:function(e){return o(this,e,arguments.length>1?arguments[1]:void 0)}})},4115:(e,t,n)=>{"use strict";var r=n(76887),o=n(95329),s=n(24883),i=n(89678),a=n(10623),l=n(15863),c=n(85803),u=n(95981),p=n(61388),h=n(34194),f=n(34342),d=n(81046),m=n(53385),g=n(18938),y=[],v=o(y.sort),b=o(y.push),w=u((function(){y.sort(void 0)})),E=u((function(){y.sort(null)})),x=h("sort"),S=!u((function(){if(m)return m<70;if(!(f&&f>3)){if(d)return!0;if(g)return g<603;var e,t,n,r,o="";for(e=65;e<76;e++){switch(t=String.fromCharCode(e),e){case 66:case 69:case 70:case 72:n=3;break;case 68:case 71:n=4;break;default:n=2}for(r=0;r<47;r++)y.push({k:t+r,v:n})}for(y.sort((function(e,t){return t.v-e.v})),r=0;rc(n)?1:-1}}(e)),n=a(o),r=0;r{"use strict";var r=n(76887),o=n(89678),s=n(59413),i=n(62435),a=n(10623),l=n(89779),c=n(66796),u=n(64692),p=n(55449),h=n(15863),f=n(50568)("splice"),d=Math.max,m=Math.min;r({target:"Array",proto:!0,forced:!f},{splice:function(e,t){var n,r,f,g,y,v,b=o(this),w=a(b),E=s(e,w),x=arguments.length;for(0===x?n=r=0:1===x?(n=0,r=w-E):(n=x-2,r=m(d(i(t),0),w-E)),c(w+n-r),f=u(b,r),g=0;gw-r+n;g--)h(b,g-1)}else if(n>r)for(g=w-r;g>E;g--)v=g+n-1,(y=g+r-1)in b?b[v]=b[y]:h(b,v);for(g=0;g{var r=n(76887),o=n(95329),s=Date,i=o(s.prototype.getTime);r({target:"Date",stat:!0},{now:function(){return i(new s)}})},18084:()=>{},73381:(e,t,n)=>{var r=n(76887),o=n(98308);r({target:"Function",proto:!0,forced:Function.bind!==o},{bind:o})},32619:(e,t,n)=>{var r=n(76887),o=n(626),s=n(79730),i=n(78834),a=n(95329),l=n(95981),c=n(57475),u=n(56664),p=n(93765),h=n(33323),f=n(63405),d=String,m=o("JSON","stringify"),g=a(/./.exec),y=a("".charAt),v=a("".charCodeAt),b=a("".replace),w=a(1..toString),E=/[\uD800-\uDFFF]/g,x=/^[\uD800-\uDBFF]$/,S=/^[\uDC00-\uDFFF]$/,_=!f||l((function(){var e=o("Symbol")();return"[null]"!=m([e])||"{}"!=m({a:e})||"{}"!=m(Object(e))})),j=l((function(){return'"\\udf06\\ud834"'!==m("\udf06\ud834")||'"\\udead"'!==m("\udead")})),O=function(e,t){var n=p(arguments),r=h(t);if(c(r)||void 0!==e&&!u(e))return n[1]=function(e,t){if(c(r)&&(t=i(r,this,d(e),t)),!u(t))return t},s(m,null,n)},k=function(e,t,n){var r=y(n,t-1),o=y(n,t+1);return g(x,e)&&!g(S,o)||g(S,e)&&!g(x,r)?"\\u"+w(v(e,0),16):e};m&&r({target:"JSON",stat:!0,arity:3,forced:_||j},{stringify:function(e,t,n){var r=p(arguments),o=s(_?O:m,null,r);return j&&"string"==typeof o?b(o,E,k):o}})},69120:(e,t,n)=>{var r=n(21899);n(90904)(r.JSON,"JSON",!0)},23112:(e,t,n)=>{"use strict";n(24683)("Map",(function(e){return function(){return e(this,arguments.length?arguments[0]:void 0)}}),n(85616))},37501:(e,t,n)=>{n(23112)},79413:()=>{},54973:(e,t,n)=>{n(76887)({target:"Number",stat:!0,nonConfigurable:!0,nonWritable:!0},{EPSILON:Math.pow(2,-52)})},30800:(e,t,n)=>{n(76887)({target:"Number",stat:!0},{isInteger:n(54639)})},49221:(e,t,n)=>{var r=n(76887),o=n(24420);r({target:"Object",stat:!0,arity:2,forced:Object.assign!==o},{assign:o})},74979:(e,t,n)=>{var r=n(76887),o=n(55746),s=n(59938).f;r({target:"Object",stat:!0,forced:Object.defineProperties!==s,sham:!o},{defineProperties:s})},86450:(e,t,n)=>{var r=n(76887),o=n(55746),s=n(65988).f;r({target:"Object",stat:!0,forced:Object.defineProperty!==s,sham:!o},{defineProperty:s})},94366:(e,t,n)=>{var r=n(76887),o=n(88810).entries;r({target:"Object",stat:!0},{entries:function(e){return o(e)}})},28387:(e,t,n)=>{var r=n(76887),o=n(93091),s=n(55449);r({target:"Object",stat:!0},{fromEntries:function(e){var t={};return o(e,(function(e,n){s(t,e,n)}),{AS_ENTRIES:!0}),t}})},46924:(e,t,n)=>{var r=n(76887),o=n(95981),s=n(74529),i=n(49677).f,a=n(55746);r({target:"Object",stat:!0,forced:!a||o((function(){i(1)})),sham:!a},{getOwnPropertyDescriptor:function(e,t){return i(s(e),t)}})},88482:(e,t,n)=>{var r=n(76887),o=n(55746),s=n(31136),i=n(74529),a=n(49677),l=n(55449);r({target:"Object",stat:!0,sham:!o},{getOwnPropertyDescriptors:function(e){for(var t,n,r=i(e),o=a.f,c=s(r),u={},p=0;c.length>p;)void 0!==(n=o(r,t=c[p++]))&&l(u,t,n);return u}})},37144:(e,t,n)=>{var r=n(76887),o=n(63405),s=n(95981),i=n(87857),a=n(89678);r({target:"Object",stat:!0,forced:!o||s((function(){i.f(1)}))},{getOwnPropertySymbols:function(e){var t=i.f;return t?t(a(e)):[]}})},21724:(e,t,n)=>{var r=n(76887),o=n(89678),s=n(14771);r({target:"Object",stat:!0,forced:n(95981)((function(){s(1)}))},{keys:function(e){return s(o(e))}})},55967:()=>{},26614:(e,t,n)=>{var r=n(76887),o=n(88810).values;r({target:"Object",stat:!0},{values:function(e){return o(e)}})},4560:(e,t,n)=>{"use strict";var r=n(76887),o=n(78834),s=n(24883),i=n(69520),a=n(40002),l=n(93091);r({target:"Promise",stat:!0,forced:n(31542)},{allSettled:function(e){var t=this,n=i.f(t),r=n.resolve,c=n.reject,u=a((function(){var n=s(t.resolve),i=[],a=0,c=1;l(e,(function(e){var s=a++,l=!1;c++,o(n,t,e).then((function(e){l||(l=!0,i[s]={status:"fulfilled",value:e},--c||r(i))}),(function(e){l||(l=!0,i[s]={status:"rejected",reason:e},--c||r(i))}))})),--c||r(i)}));return u.error&&c(u.value),n.promise}})},16890:(e,t,n)=>{"use strict";var r=n(76887),o=n(78834),s=n(24883),i=n(69520),a=n(40002),l=n(93091);r({target:"Promise",stat:!0,forced:n(31542)},{all:function(e){var t=this,n=i.f(t),r=n.resolve,c=n.reject,u=a((function(){var n=s(t.resolve),i=[],a=0,u=1;l(e,(function(e){var s=a++,l=!1;u++,o(n,t,e).then((function(e){l||(l=!0,i[s]=e,--u||r(i))}),c)})),--u||r(i)}));return u.error&&c(u.value),n.promise}})},91302:(e,t,n)=>{"use strict";var r=n(76887),o=n(78834),s=n(24883),i=n(626),a=n(69520),l=n(40002),c=n(93091),u=n(31542),p="No one promise resolved";r({target:"Promise",stat:!0,forced:u},{any:function(e){var t=this,n=i("AggregateError"),r=a.f(t),u=r.resolve,h=r.reject,f=l((function(){var r=s(t.resolve),i=[],a=0,l=1,f=!1;c(e,(function(e){var s=a++,c=!1;l++,o(r,t,e).then((function(e){c||f||(f=!0,u(e))}),(function(e){c||f||(c=!0,i[s]=e,--l||h(new n(i,p)))}))})),--l||h(new n(i,p))}));return f.error&&h(f.value),r.promise}})},83376:(e,t,n)=>{"use strict";var r=n(76887),o=n(82529),s=n(67742).CONSTRUCTOR,i=n(6991),a=n(626),l=n(57475),c=n(95929),u=i&&i.prototype;if(r({target:"Promise",proto:!0,forced:s,real:!0},{catch:function(e){return this.then(void 0,e)}}),!o&&l(i)){var p=a("Promise").prototype.catch;u.catch!==p&&c(u,"catch",p,{unsafe:!0})}},26934:(e,t,n)=>{"use strict";var r,o,s,i=n(76887),a=n(82529),l=n(6049),c=n(21899),u=n(78834),p=n(95929),h=n(88929),f=n(90904),d=n(94431),m=n(24883),g=n(57475),y=n(10941),v=n(5743),b=n(70487),w=n(42941).set,E=n(66132),x=n(34845),S=n(40002),_=n(18397),j=n(45402),O=n(6991),k=n(67742),A=n(69520),C="Promise",P=k.CONSTRUCTOR,N=k.REJECTION_EVENT,I=k.SUBCLASSING,T=j.getterFor(C),R=j.set,M=O&&O.prototype,D=O,F=M,L=c.TypeError,B=c.document,$=c.process,q=A.f,U=q,z=!!(B&&B.createEvent&&c.dispatchEvent),V="unhandledrejection",W=function(e){var t;return!(!y(e)||!g(t=e.then))&&t},J=function(e,t){var n,r,o,s=t.value,i=1==t.state,a=i?e.ok:e.fail,l=e.resolve,c=e.reject,p=e.domain;try{a?(i||(2===t.rejection&&Y(t),t.rejection=1),!0===a?n=s:(p&&p.enter(),n=a(s),p&&(p.exit(),o=!0)),n===e.promise?c(L("Promise-chain cycle")):(r=W(n))?u(r,n,l,c):l(n)):c(s)}catch(e){p&&!o&&p.exit(),c(e)}},K=function(e,t){e.notified||(e.notified=!0,E((function(){for(var n,r=e.reactions;n=r.get();)J(n,e);e.notified=!1,t&&!e.rejection&&G(e)})))},H=function(e,t,n){var r,o;z?((r=B.createEvent("Event")).promise=t,r.reason=n,r.initEvent(e,!1,!0),c.dispatchEvent(r)):r={promise:t,reason:n},!N&&(o=c["on"+e])?o(r):e===V&&x("Unhandled promise rejection",n)},G=function(e){u(w,c,(function(){var t,n=e.facade,r=e.value;if(Z(e)&&(t=S((function(){l?$.emit("unhandledRejection",r,n):H(V,n,r)})),e.rejection=l||Z(e)?2:1,t.error))throw t.value}))},Z=function(e){return 1!==e.rejection&&!e.parent},Y=function(e){u(w,c,(function(){var t=e.facade;l?$.emit("rejectionHandled",t):H("rejectionhandled",t,e.value)}))},X=function(e,t,n){return function(r){e(t,r,n)}},Q=function(e,t,n){e.done||(e.done=!0,n&&(e=n),e.value=t,e.state=2,K(e,!0))},ee=function(e,t,n){if(!e.done){e.done=!0,n&&(e=n);try{if(e.facade===t)throw L("Promise can't be resolved itself");var r=W(t);r?E((function(){var n={done:!1};try{u(r,t,X(ee,n,e),X(Q,n,e))}catch(t){Q(n,t,e)}})):(e.value=t,e.state=1,K(e,!1))}catch(t){Q({done:!1},t,e)}}};if(P&&(F=(D=function(e){v(this,F),m(e),u(r,this);var t=T(this);try{e(X(ee,t),X(Q,t))}catch(e){Q(t,e)}}).prototype,(r=function(e){R(this,{type:C,done:!1,notified:!1,parent:!1,reactions:new _,rejection:!1,state:0,value:void 0})}).prototype=p(F,"then",(function(e,t){var n=T(this),r=q(b(this,D));return n.parent=!0,r.ok=!g(e)||e,r.fail=g(t)&&t,r.domain=l?$.domain:void 0,0==n.state?n.reactions.add(r):E((function(){J(r,n)})),r.promise})),o=function(){var e=new r,t=T(e);this.promise=e,this.resolve=X(ee,t),this.reject=X(Q,t)},A.f=q=function(e){return e===D||undefined===e?new o(e):U(e)},!a&&g(O)&&M!==Object.prototype)){s=M.then,I||p(M,"then",(function(e,t){var n=this;return new D((function(e,t){u(s,n,e,t)})).then(e,t)}),{unsafe:!0});try{delete M.constructor}catch(e){}h&&h(M,F)}i({global:!0,constructor:!0,wrap:!0,forced:P},{Promise:D}),f(D,C,!1,!0),d(C)},44349:(e,t,n)=>{"use strict";var r=n(76887),o=n(82529),s=n(6991),i=n(95981),a=n(626),l=n(57475),c=n(70487),u=n(56584),p=n(95929),h=s&&s.prototype;if(r({target:"Promise",proto:!0,real:!0,forced:!!s&&i((function(){h.finally.call({then:function(){}},(function(){}))}))},{finally:function(e){var t=c(this,a("Promise")),n=l(e);return this.then(n?function(n){return u(t,e()).then((function(){return n}))}:e,n?function(n){return u(t,e()).then((function(){throw n}))}:e)}}),!o&&l(s)){var f=a("Promise").prototype.finally;h.finally!==f&&p(h,"finally",f,{unsafe:!0})}},98881:(e,t,n)=>{n(26934),n(16890),n(83376),n(55921),n(64069),n(14482)},55921:(e,t,n)=>{"use strict";var r=n(76887),o=n(78834),s=n(24883),i=n(69520),a=n(40002),l=n(93091);r({target:"Promise",stat:!0,forced:n(31542)},{race:function(e){var t=this,n=i.f(t),r=n.reject,c=a((function(){var i=s(t.resolve);l(e,(function(e){o(i,t,e).then(n.resolve,r)}))}));return c.error&&r(c.value),n.promise}})},64069:(e,t,n)=>{"use strict";var r=n(76887),o=n(78834),s=n(69520);r({target:"Promise",stat:!0,forced:n(67742).CONSTRUCTOR},{reject:function(e){var t=s.f(this);return o(t.reject,void 0,e),t.promise}})},14482:(e,t,n)=>{"use strict";var r=n(76887),o=n(626),s=n(82529),i=n(6991),a=n(67742).CONSTRUCTOR,l=n(56584),c=o("Promise"),u=s&&!a;r({target:"Promise",stat:!0,forced:s||a},{resolve:function(e){return l(u&&this===c?i:this,e)}})},1502:()=>{},82266:(e,t,n)=>{"use strict";n(24683)("Set",(function(e){return function(){return e(this,arguments.length?arguments[0]:void 0)}}),n(85616))},69008:(e,t,n)=>{n(82266)},11035:(e,t,n)=>{"use strict";var r=n(76887),o=n(95329),s=n(70344),i=n(48219),a=n(85803),l=n(67772),c=o("".indexOf);r({target:"String",proto:!0,forced:!l("includes")},{includes:function(e){return!!~c(a(i(this)),a(s(e)),arguments.length>1?arguments[1]:void 0)}})},77971:(e,t,n)=>{"use strict";var r=n(64620).charAt,o=n(85803),s=n(45402),i=n(75105),a=n(23538),l="String Iterator",c=s.set,u=s.getterFor(l);i(String,"String",(function(e){c(this,{type:l,string:o(e),index:0})}),(function(){var e,t=u(this),n=t.string,o=t.index;return o>=n.length?a(void 0,!0):(e=r(n,o),t.index+=e.length,a(e,!1))}))},74679:(e,t,n)=>{var r=n(76887),o=n(95329),s=n(74529),i=n(89678),a=n(85803),l=n(10623),c=o([].push),u=o([].join);r({target:"String",stat:!0},{raw:function(e){var t=s(i(e).raw),n=l(t);if(!n)return"";for(var r=arguments.length,o=[],p=0;;){if(c(o,a(t[p++])),p===n)return u(o,"");p{n(76887)({target:"String",proto:!0},{repeat:n(16178)})},94761:(e,t,n)=>{"use strict";var r,o=n(76887),s=n(97484),i=n(49677).f,a=n(43057),l=n(85803),c=n(70344),u=n(48219),p=n(67772),h=n(82529),f=s("".startsWith),d=s("".slice),m=Math.min,g=p("startsWith");o({target:"String",proto:!0,forced:!!(h||g||(r=i(String.prototype,"startsWith"),!r||r.writable))&&!g},{startsWith:function(e){var t=l(u(this));c(e);var n=a(m(arguments.length>1?arguments[1]:void 0,t.length)),r=l(e);return f?f(t,r,n):d(t,n,n+r.length)===r}})},57398:(e,t,n)=>{"use strict";var r=n(76887),o=n(74853).trim;r({target:"String",proto:!0,forced:n(93093)("trim")},{trim:function(){return o(this)}})},8555:(e,t,n)=>{n(73464)("asyncIterator")},48616:(e,t,n)=>{"use strict";var r=n(76887),o=n(21899),s=n(78834),i=n(95329),a=n(82529),l=n(55746),c=n(63405),u=n(95981),p=n(90953),h=n(7046),f=n(96059),d=n(74529),m=n(83894),g=n(85803),y=n(31887),v=n(29290),b=n(14771),w=n(10946),E=n(684),x=n(87857),S=n(49677),_=n(65988),j=n(59938),O=n(36760),k=n(95929),A=n(29202),C=n(68726),P=n(44262),N=n(27748),I=n(99418),T=n(99813),R=n(11477),M=n(73464),D=n(29630),F=n(90904),L=n(45402),B=n(3610).forEach,$=P("hidden"),q="Symbol",U="prototype",z=L.set,V=L.getterFor(q),W=Object[U],J=o.Symbol,K=J&&J[U],H=o.TypeError,G=o.QObject,Z=S.f,Y=_.f,X=E.f,Q=O.f,ee=i([].push),te=C("symbols"),ne=C("op-symbols"),re=C("wks"),oe=!G||!G[U]||!G[U].findChild,se=l&&u((function(){return 7!=v(Y({},"a",{get:function(){return Y(this,"a",{value:7}).a}})).a}))?function(e,t,n){var r=Z(W,t);r&&delete W[t],Y(e,t,n),r&&e!==W&&Y(W,t,r)}:Y,ie=function(e,t){var n=te[e]=v(K);return z(n,{type:q,tag:e,description:t}),l||(n.description=t),n},ae=function(e,t,n){e===W&&ae(ne,t,n),f(e);var r=m(t);return f(n),p(te,r)?(n.enumerable?(p(e,$)&&e[$][r]&&(e[$][r]=!1),n=v(n,{enumerable:y(0,!1)})):(p(e,$)||Y(e,$,y(1,{})),e[$][r]=!0),se(e,r,n)):Y(e,r,n)},le=function(e,t){f(e);var n=d(t),r=b(n).concat(he(n));return B(r,(function(t){l&&!s(ce,n,t)||ae(e,t,n[t])})),e},ce=function(e){var t=m(e),n=s(Q,this,t);return!(this===W&&p(te,t)&&!p(ne,t))&&(!(n||!p(this,t)||!p(te,t)||p(this,$)&&this[$][t])||n)},ue=function(e,t){var n=d(e),r=m(t);if(n!==W||!p(te,r)||p(ne,r)){var o=Z(n,r);return!o||!p(te,r)||p(n,$)&&n[$][r]||(o.enumerable=!0),o}},pe=function(e){var t=X(d(e)),n=[];return B(t,(function(e){p(te,e)||p(N,e)||ee(n,e)})),n},he=function(e){var t=e===W,n=X(t?ne:d(e)),r=[];return B(n,(function(e){!p(te,e)||t&&!p(W,e)||ee(r,te[e])})),r};c||(k(K=(J=function(){if(h(K,this))throw H("Symbol is not a constructor");var e=arguments.length&&void 0!==arguments[0]?g(arguments[0]):void 0,t=I(e),n=function(e){this===W&&s(n,ne,e),p(this,$)&&p(this[$],t)&&(this[$][t]=!1),se(this,t,y(1,e))};return l&&oe&&se(W,t,{configurable:!0,set:n}),ie(t,e)})[U],"toString",(function(){return V(this).tag})),k(J,"withoutSetter",(function(e){return ie(I(e),e)})),O.f=ce,_.f=ae,j.f=le,S.f=ue,w.f=E.f=pe,x.f=he,R.f=function(e){return ie(T(e),e)},l&&(A(K,"description",{configurable:!0,get:function(){return V(this).description}}),a||k(W,"propertyIsEnumerable",ce,{unsafe:!0}))),r({global:!0,constructor:!0,wrap:!0,forced:!c,sham:!c},{Symbol:J}),B(b(re),(function(e){M(e)})),r({target:q,stat:!0,forced:!c},{useSetter:function(){oe=!0},useSimple:function(){oe=!1}}),r({target:"Object",stat:!0,forced:!c,sham:!l},{create:function(e,t){return void 0===t?v(e):le(v(e),t)},defineProperty:ae,defineProperties:le,getOwnPropertyDescriptor:ue}),r({target:"Object",stat:!0,forced:!c},{getOwnPropertyNames:pe}),D(),F(J,q),N[$]=!0},52615:()=>{},64523:(e,t,n)=>{var r=n(76887),o=n(626),s=n(90953),i=n(85803),a=n(68726),l=n(34680),c=a("string-to-symbol-registry"),u=a("symbol-to-string-registry");r({target:"Symbol",stat:!0,forced:!l},{for:function(e){var t=i(e);if(s(c,t))return c[t];var n=o("Symbol")(t);return c[t]=n,u[n]=t,n}})},21732:(e,t,n)=>{n(73464)("hasInstance")},35903:(e,t,n)=>{n(73464)("isConcatSpreadable")},1825:(e,t,n)=>{n(73464)("iterator")},35824:(e,t,n)=>{n(48616),n(64523),n(38608),n(32619),n(37144)},38608:(e,t,n)=>{var r=n(76887),o=n(90953),s=n(56664),i=n(69826),a=n(68726),l=n(34680),c=a("symbol-to-string-registry");r({target:"Symbol",stat:!0,forced:!l},{keyFor:function(e){if(!s(e))throw TypeError(i(e)+" is not a symbol");if(o(c,e))return c[e]}})},45915:(e,t,n)=>{n(73464)("matchAll")},28394:(e,t,n)=>{n(73464)("match")},61766:(e,t,n)=>{n(73464)("replace")},62737:(e,t,n)=>{n(73464)("search")},89911:(e,t,n)=>{n(73464)("species")},74315:(e,t,n)=>{n(73464)("split")},63131:(e,t,n)=>{var r=n(73464),o=n(29630);r("toPrimitive"),o()},64714:(e,t,n)=>{var r=n(626),o=n(73464),s=n(90904);o("toStringTag"),s(r("Symbol"),"Symbol")},70659:(e,t,n)=>{n(73464)("unscopables")},94776:(e,t,n)=>{"use strict";var r,o=n(45602),s=n(21899),i=n(95329),a=n(94380),l=n(21647),c=n(24683),u=n(8850),p=n(10941),h=n(45402).enforce,f=n(95981),d=n(47093),m=Object,g=Array.isArray,y=m.isExtensible,v=m.isFrozen,b=m.isSealed,w=m.freeze,E=m.seal,x={},S={},_=!s.ActiveXObject&&"ActiveXObject"in s,j=function(e){return function(){return e(this,arguments.length?arguments[0]:void 0)}},O=c("WeakMap",j,u),k=O.prototype,A=i(k.set);if(d)if(_){r=u.getConstructor(j,"WeakMap",!0),l.enable();var C=i(k.delete),P=i(k.has),N=i(k.get);a(k,{delete:function(e){if(p(e)&&!y(e)){var t=h(this);return t.frozen||(t.frozen=new r),C(this,e)||t.frozen.delete(e)}return C(this,e)},has:function(e){if(p(e)&&!y(e)){var t=h(this);return t.frozen||(t.frozen=new r),P(this,e)||t.frozen.has(e)}return P(this,e)},get:function(e){if(p(e)&&!y(e)){var t=h(this);return t.frozen||(t.frozen=new r),P(this,e)?N(this,e):t.frozen.get(e)}return N(this,e)},set:function(e,t){if(p(e)&&!y(e)){var n=h(this);n.frozen||(n.frozen=new r),P(this,e)?A(this,e,t):n.frozen.set(e,t)}else A(this,e,t);return this}})}else o&&f((function(){var e=w([]);return A(new O,e,1),!v(e)}))&&a(k,{set:function(e,t){var n;return g(e)&&(v(e)?n=x:b(e)&&(n=S)),A(this,e,t),n==x&&w(e),n==S&&E(e),this}})},54334:(e,t,n)=>{n(94776)},31115:(e,t,n)=>{"use strict";n(24683)("WeakSet",(function(e){return function(){return e(this,arguments.length?arguments[0]:void 0)}}),n(8850))},1773:(e,t,n)=>{n(31115)},97522:(e,t,n)=>{var r=n(99813),o=n(65988).f,s=r("metadata"),i=Function.prototype;void 0===i[s]&&o(i,s,{value:null})},28783:(e,t,n)=>{n(73464)("asyncDispose")},43975:(e,t,n)=>{n(73464)("dispose")},97618:(e,t,n)=>{n(76887)({target:"Symbol",stat:!0},{isRegisteredSymbol:n(32087)})},22731:(e,t,n)=>{n(76887)({target:"Symbol",stat:!0,name:"isRegisteredSymbol"},{isRegistered:n(32087)})},6989:(e,t,n)=>{n(76887)({target:"Symbol",stat:!0,forced:!0},{isWellKnownSymbol:n(96559)})},85605:(e,t,n)=>{n(76887)({target:"Symbol",stat:!0,name:"isWellKnownSymbol",forced:!0},{isWellKnown:n(96559)})},65799:(e,t,n)=>{n(73464)("matcher")},31943:(e,t,n)=>{n(73464)("metadataKey")},45414:(e,t,n)=>{n(73464)("metadata")},46774:(e,t,n)=>{n(73464)("observable")},80620:(e,t,n)=>{n(73464)("patternMatch")},36172:(e,t,n)=>{n(73464)("replaceAll")},7634:(e,t,n)=>{n(66274);var r=n(63281),o=n(21899),s=n(9697),i=n(32029),a=n(12077),l=n(99813)("toStringTag");for(var c in r){var u=o[c],p=u&&u.prototype;p&&s(p)!==l&&i(p,l,c),a[c]=a.Array}},79229:(e,t,n)=>{var r=n(76887),o=n(21899),s=n(37620)(o.setInterval,!0);r({global:!0,bind:!0,forced:o.setInterval!==s},{setInterval:s})},17749:(e,t,n)=>{var r=n(76887),o=n(21899),s=n(37620)(o.setTimeout,!0);r({global:!0,bind:!0,forced:o.setTimeout!==s},{setTimeout:s})},71249:(e,t,n)=>{n(79229),n(17749)},62524:(e,t,n)=>{"use strict";n(66274);var r=n(76887),o=n(21899),s=n(78834),i=n(95329),a=n(55746),l=n(14766),c=n(95929),u=n(29202),p=n(94380),h=n(90904),f=n(53847),d=n(45402),m=n(5743),g=n(57475),y=n(90953),v=n(86843),b=n(9697),w=n(96059),E=n(10941),x=n(85803),S=n(29290),_=n(31887),j=n(53476),O=n(22902),k=n(18348),A=n(99813),C=n(61388),P=A("iterator"),N="URLSearchParams",I=N+"Iterator",T=d.set,R=d.getterFor(N),M=d.getterFor(I),D=Object.getOwnPropertyDescriptor,F=function(e){if(!a)return o[e];var t=D(o,e);return t&&t.value},L=F("fetch"),B=F("Request"),$=F("Headers"),q=B&&B.prototype,U=$&&$.prototype,z=o.RegExp,V=o.TypeError,W=o.decodeURIComponent,J=o.encodeURIComponent,K=i("".charAt),H=i([].join),G=i([].push),Z=i("".replace),Y=i([].shift),X=i([].splice),Q=i("".split),ee=i("".slice),te=/\+/g,ne=Array(4),re=function(e){return ne[e-1]||(ne[e-1]=z("((?:%[\\da-f]{2}){"+e+"})","gi"))},oe=function(e){try{return W(e)}catch(t){return e}},se=function(e){var t=Z(e,te," "),n=4;try{return W(t)}catch(e){for(;n;)t=Z(t,re(n--),oe);return t}},ie=/[!'()~]|%20/g,ae={"!":"%21","'":"%27","(":"%28",")":"%29","~":"%7E","%20":"+"},le=function(e){return ae[e]},ce=function(e){return Z(J(e),ie,le)},ue=f((function(e,t){T(this,{type:I,iterator:j(R(e).entries),kind:t})}),"Iterator",(function(){var e=M(this),t=e.kind,n=e.iterator.next(),r=n.value;return n.done||(n.value="keys"===t?r.key:"values"===t?r.value:[r.key,r.value]),n}),!0),pe=function(e){this.entries=[],this.url=null,void 0!==e&&(E(e)?this.parseObject(e):this.parseQuery("string"==typeof e?"?"===K(e,0)?ee(e,1):e:x(e)))};pe.prototype={type:N,bindURL:function(e){this.url=e,this.update()},parseObject:function(e){var t,n,r,o,i,a,l,c=O(e);if(c)for(n=(t=j(e,c)).next;!(r=s(n,t)).done;){if(i=(o=j(w(r.value))).next,(a=s(i,o)).done||(l=s(i,o)).done||!s(i,o).done)throw V("Expected sequence with length 2");G(this.entries,{key:x(a.value),value:x(l.value)})}else for(var u in e)y(e,u)&&G(this.entries,{key:u,value:x(e[u])})},parseQuery:function(e){if(e)for(var t,n,r=Q(e,"&"),o=0;o0?arguments[0]:void 0));a||(this.size=e.entries.length)},fe=he.prototype;if(p(fe,{append:function(e,t){var n=R(this);k(arguments.length,2),G(n.entries,{key:x(e),value:x(t)}),a||this.length++,n.updateURL()},delete:function(e){for(var t=R(this),n=k(arguments.length,1),r=t.entries,o=x(e),s=n<2?void 0:arguments[1],i=void 0===s?s:x(s),l=0;lt.key?1:-1})),e.updateURL()},forEach:function(e){for(var t,n=R(this).entries,r=v(e,arguments.length>1?arguments[1]:void 0),o=0;o1?ge(arguments[1]):{})}}),g(B)){var ye=function(e){return m(this,q),new B(e,arguments.length>1?ge(arguments[1]):{})};q.constructor=ye,ye.prototype=q,r({global:!0,constructor:!0,dontCallGetSet:!0,forced:!0},{Request:ye})}}e.exports={URLSearchParams:he,getState:R}},16454:()=>{},73305:()=>{},95304:(e,t,n)=>{n(62524)},62337:()=>{},84630:(e,t,n)=>{var r=n(76887),o=n(626),s=n(95981),i=n(18348),a=n(85803),l=n(14766),c=o("URL");r({target:"URL",stat:!0,forced:!(l&&s((function(){c.canParse()})))},{canParse:function(e){var t=i(arguments.length,1),n=a(e),r=t<2||void 0===arguments[1]?void 0:a(arguments[1]);try{return!!new c(n,r)}catch(e){return!1}}})},47250:(e,t,n)=>{"use strict";n(77971);var r,o=n(76887),s=n(55746),i=n(14766),a=n(21899),l=n(86843),c=n(95329),u=n(95929),p=n(29202),h=n(5743),f=n(90953),d=n(24420),m=n(11354),g=n(15790),y=n(64620).codeAt,v=n(73291),b=n(85803),w=n(90904),E=n(18348),x=n(62524),S=n(45402),_=S.set,j=S.getterFor("URL"),O=x.URLSearchParams,k=x.getState,A=a.URL,C=a.TypeError,P=a.parseInt,N=Math.floor,I=Math.pow,T=c("".charAt),R=c(/./.exec),M=c([].join),D=c(1..toString),F=c([].pop),L=c([].push),B=c("".replace),$=c([].shift),q=c("".split),U=c("".slice),z=c("".toLowerCase),V=c([].unshift),W="Invalid scheme",J="Invalid host",K="Invalid port",H=/[a-z]/i,G=/[\d+-.a-z]/i,Z=/\d/,Y=/^0x/i,X=/^[0-7]+$/,Q=/^\d+$/,ee=/^[\da-f]+$/i,te=/[\0\t\n\r #%/:<>?@[\\\]^|]/,ne=/[\0\t\n\r #/:<>?@[\\\]^|]/,re=/^[\u0000-\u0020]+/,oe=/(^|[^\u0000-\u0020])[\u0000-\u0020]+$/,se=/[\t\n\r]/g,ie=function(e){var t,n,r,o;if("number"==typeof e){for(t=[],n=0;n<4;n++)V(t,e%256),e=N(e/256);return M(t,".")}if("object"==typeof e){for(t="",r=function(e){for(var t=null,n=1,r=null,o=0,s=0;s<8;s++)0!==e[s]?(o>n&&(t=r,n=o),r=null,o=0):(null===r&&(r=s),++o);return o>n&&(t=r,n=o),t}(e),n=0;n<8;n++)o&&0===e[n]||(o&&(o=!1),r===n?(t+=n?":":"::",o=!0):(t+=D(e[n],16),n<7&&(t+=":")));return"["+t+"]"}return e},ae={},le=d({},ae,{" ":1,'"':1,"<":1,">":1,"`":1}),ce=d({},le,{"#":1,"?":1,"{":1,"}":1}),ue=d({},ce,{"/":1,":":1,";":1,"=":1,"@":1,"[":1,"\\":1,"]":1,"^":1,"|":1}),pe=function(e,t){var n=y(e,0);return n>32&&n<127&&!f(t,e)?e:encodeURIComponent(e)},he={ftp:21,file:null,http:80,https:443,ws:80,wss:443},fe=function(e,t){var n;return 2==e.length&&R(H,T(e,0))&&(":"==(n=T(e,1))||!t&&"|"==n)},de=function(e){var t;return e.length>1&&fe(U(e,0,2))&&(2==e.length||"/"===(t=T(e,2))||"\\"===t||"?"===t||"#"===t)},me=function(e){return"."===e||"%2e"===z(e)},ge={},ye={},ve={},be={},we={},Ee={},xe={},Se={},_e={},je={},Oe={},ke={},Ae={},Ce={},Pe={},Ne={},Ie={},Te={},Re={},Me={},De={},Fe=function(e,t,n){var r,o,s,i=b(e);if(t){if(o=this.parse(i))throw C(o);this.searchParams=null}else{if(void 0!==n&&(r=new Fe(n,!0)),o=this.parse(i,null,r))throw C(o);(s=k(new O)).bindURL(this),this.searchParams=s}};Fe.prototype={type:"URL",parse:function(e,t,n){var o,s,i,a,l,c=this,u=t||ge,p=0,h="",d=!1,y=!1,v=!1;for(e=b(e),t||(c.scheme="",c.username="",c.password="",c.host=null,c.port=null,c.path=[],c.query=null,c.fragment=null,c.cannotBeABaseURL=!1,e=B(e,re,""),e=B(e,oe,"$1")),e=B(e,se,""),o=m(e);p<=o.length;){switch(s=o[p],u){case ge:if(!s||!R(H,s)){if(t)return W;u=ve;continue}h+=z(s),u=ye;break;case ye:if(s&&(R(G,s)||"+"==s||"-"==s||"."==s))h+=z(s);else{if(":"!=s){if(t)return W;h="",u=ve,p=0;continue}if(t&&(c.isSpecial()!=f(he,h)||"file"==h&&(c.includesCredentials()||null!==c.port)||"file"==c.scheme&&!c.host))return;if(c.scheme=h,t)return void(c.isSpecial()&&he[c.scheme]==c.port&&(c.port=null));h="","file"==c.scheme?u=Ce:c.isSpecial()&&n&&n.scheme==c.scheme?u=be:c.isSpecial()?u=Se:"/"==o[p+1]?(u=we,p++):(c.cannotBeABaseURL=!0,L(c.path,""),u=Re)}break;case ve:if(!n||n.cannotBeABaseURL&&"#"!=s)return W;if(n.cannotBeABaseURL&&"#"==s){c.scheme=n.scheme,c.path=g(n.path),c.query=n.query,c.fragment="",c.cannotBeABaseURL=!0,u=De;break}u="file"==n.scheme?Ce:Ee;continue;case be:if("/"!=s||"/"!=o[p+1]){u=Ee;continue}u=_e,p++;break;case we:if("/"==s){u=je;break}u=Te;continue;case Ee:if(c.scheme=n.scheme,s==r)c.username=n.username,c.password=n.password,c.host=n.host,c.port=n.port,c.path=g(n.path),c.query=n.query;else if("/"==s||"\\"==s&&c.isSpecial())u=xe;else if("?"==s)c.username=n.username,c.password=n.password,c.host=n.host,c.port=n.port,c.path=g(n.path),c.query="",u=Me;else{if("#"!=s){c.username=n.username,c.password=n.password,c.host=n.host,c.port=n.port,c.path=g(n.path),c.path.length--,u=Te;continue}c.username=n.username,c.password=n.password,c.host=n.host,c.port=n.port,c.path=g(n.path),c.query=n.query,c.fragment="",u=De}break;case xe:if(!c.isSpecial()||"/"!=s&&"\\"!=s){if("/"!=s){c.username=n.username,c.password=n.password,c.host=n.host,c.port=n.port,u=Te;continue}u=je}else u=_e;break;case Se:if(u=_e,"/"!=s||"/"!=T(h,p+1))continue;p++;break;case _e:if("/"!=s&&"\\"!=s){u=je;continue}break;case je:if("@"==s){d&&(h="%40"+h),d=!0,i=m(h);for(var w=0;w65535)return K;c.port=c.isSpecial()&&S===he[c.scheme]?null:S,h=""}if(t)return;u=Ie;continue}return K}h+=s;break;case Ce:if(c.scheme="file","/"==s||"\\"==s)u=Pe;else{if(!n||"file"!=n.scheme){u=Te;continue}if(s==r)c.host=n.host,c.path=g(n.path),c.query=n.query;else if("?"==s)c.host=n.host,c.path=g(n.path),c.query="",u=Me;else{if("#"!=s){de(M(g(o,p),""))||(c.host=n.host,c.path=g(n.path),c.shortenPath()),u=Te;continue}c.host=n.host,c.path=g(n.path),c.query=n.query,c.fragment="",u=De}}break;case Pe:if("/"==s||"\\"==s){u=Ne;break}n&&"file"==n.scheme&&!de(M(g(o,p),""))&&(fe(n.path[0],!0)?L(c.path,n.path[0]):c.host=n.host),u=Te;continue;case Ne:if(s==r||"/"==s||"\\"==s||"?"==s||"#"==s){if(!t&&fe(h))u=Te;else if(""==h){if(c.host="",t)return;u=Ie}else{if(a=c.parseHost(h))return a;if("localhost"==c.host&&(c.host=""),t)return;h="",u=Ie}continue}h+=s;break;case Ie:if(c.isSpecial()){if(u=Te,"/"!=s&&"\\"!=s)continue}else if(t||"?"!=s)if(t||"#"!=s){if(s!=r&&(u=Te,"/"!=s))continue}else c.fragment="",u=De;else c.query="",u=Me;break;case Te:if(s==r||"/"==s||"\\"==s&&c.isSpecial()||!t&&("?"==s||"#"==s)){if(".."===(l=z(l=h))||"%2e."===l||".%2e"===l||"%2e%2e"===l?(c.shortenPath(),"/"==s||"\\"==s&&c.isSpecial()||L(c.path,"")):me(h)?"/"==s||"\\"==s&&c.isSpecial()||L(c.path,""):("file"==c.scheme&&!c.path.length&&fe(h)&&(c.host&&(c.host=""),h=T(h,0)+":"),L(c.path,h)),h="","file"==c.scheme&&(s==r||"?"==s||"#"==s))for(;c.path.length>1&&""===c.path[0];)$(c.path);"?"==s?(c.query="",u=Me):"#"==s&&(c.fragment="",u=De)}else h+=pe(s,ce);break;case Re:"?"==s?(c.query="",u=Me):"#"==s?(c.fragment="",u=De):s!=r&&(c.path[0]+=pe(s,ae));break;case Me:t||"#"!=s?s!=r&&("'"==s&&c.isSpecial()?c.query+="%27":c.query+="#"==s?"%23":pe(s,ae)):(c.fragment="",u=De);break;case De:s!=r&&(c.fragment+=pe(s,le))}p++}},parseHost:function(e){var t,n,r;if("["==T(e,0)){if("]"!=T(e,e.length-1))return J;if(t=function(e){var t,n,r,o,s,i,a,l=[0,0,0,0,0,0,0,0],c=0,u=null,p=0,h=function(){return T(e,p)};if(":"==h()){if(":"!=T(e,1))return;p+=2,u=++c}for(;h();){if(8==c)return;if(":"!=h()){for(t=n=0;n<4&&R(ee,h());)t=16*t+P(h(),16),p++,n++;if("."==h()){if(0==n)return;if(p-=n,c>6)return;for(r=0;h();){if(o=null,r>0){if(!("."==h()&&r<4))return;p++}if(!R(Z,h()))return;for(;R(Z,h());){if(s=P(h(),10),null===o)o=s;else{if(0==o)return;o=10*o+s}if(o>255)return;p++}l[c]=256*l[c]+o,2!=++r&&4!=r||c++}if(4!=r)return;break}if(":"==h()){if(p++,!h())return}else if(h())return;l[c++]=t}else{if(null!==u)return;p++,u=++c}}if(null!==u)for(i=c-u,c=7;0!=c&&i>0;)a=l[c],l[c--]=l[u+i-1],l[u+--i]=a;else if(8!=c)return;return l}(U(e,1,-1)),!t)return J;this.host=t}else if(this.isSpecial()){if(e=v(e),R(te,e))return J;if(t=function(e){var t,n,r,o,s,i,a,l=q(e,".");if(l.length&&""==l[l.length-1]&&l.length--,(t=l.length)>4)return e;for(n=[],r=0;r1&&"0"==T(o,0)&&(s=R(Y,o)?16:8,o=U(o,8==s?1:2)),""===o)i=0;else{if(!R(10==s?Q:8==s?X:ee,o))return e;i=P(o,s)}L(n,i)}for(r=0;r=I(256,5-t))return null}else if(i>255)return null;for(a=F(n),r=0;r1?arguments[1]:void 0,r=_(t,new Fe(e,!1,n));s||(t.href=r.serialize(),t.origin=r.getOrigin(),t.protocol=r.getProtocol(),t.username=r.getUsername(),t.password=r.getPassword(),t.host=r.getHost(),t.hostname=r.getHostname(),t.port=r.getPort(),t.pathname=r.getPathname(),t.search=r.getSearch(),t.searchParams=r.getSearchParams(),t.hash=r.getHash())},Be=Le.prototype,$e=function(e,t){return{get:function(){return j(this)[e]()},set:t&&function(e){return j(this)[t](e)},configurable:!0,enumerable:!0}};if(s&&(p(Be,"href",$e("serialize","setHref")),p(Be,"origin",$e("getOrigin")),p(Be,"protocol",$e("getProtocol","setProtocol")),p(Be,"username",$e("getUsername","setUsername")),p(Be,"password",$e("getPassword","setPassword")),p(Be,"host",$e("getHost","setHost")),p(Be,"hostname",$e("getHostname","setHostname")),p(Be,"port",$e("getPort","setPort")),p(Be,"pathname",$e("getPathname","setPathname")),p(Be,"search",$e("getSearch","setSearch")),p(Be,"searchParams",$e("getSearchParams")),p(Be,"hash",$e("getHash","setHash"))),u(Be,"toJSON",(function(){return j(this).serialize()}),{enumerable:!0}),u(Be,"toString",(function(){return j(this).serialize()}),{enumerable:!0}),A){var qe=A.createObjectURL,Ue=A.revokeObjectURL;qe&&u(Le,"createObjectURL",l(qe,A)),Ue&&u(Le,"revokeObjectURL",l(Ue,A))}w(Le,"URL"),o({global:!0,constructor:!0,forced:!i,sham:!s},{URL:Le})},33601:(e,t,n)=>{n(47250)},98947:()=>{},24848:(e,t,n)=>{var r=n(54493);e.exports=r},83363:(e,t,n)=>{var r=n(24034);e.exports=r},62908:(e,t,n)=>{var r=n(12710);e.exports=r},49216:(e,t,n)=>{var r=n(99324);e.exports=r},56668:(e,t,n)=>{var r=n(95909);e.exports=r},74719:(e,t,n)=>{var r=n(14423);e.exports=r},57784:(e,t,n)=>{var r=n(81103);e.exports=r},28196:(e,t,n)=>{var r=n(16246);e.exports=r},8065:(e,t,n)=>{var r=n(56043);e.exports=r},57448:(e,t,n)=>{n(7634);var r=n(9697),o=n(90953),s=n(7046),i=n(62908),a=Array.prototype,l={DOMTokenList:!0,NodeList:!0};e.exports=function(e){var t=e.entries;return e===a||s(a,e)&&t===a.entries||o(l,r(e))?i:t}},29455:(e,t,n)=>{var r=n(13160);e.exports=r},69743:(e,t,n)=>{var r=n(80446);e.exports=r},11955:(e,t,n)=>{var r=n(2480);e.exports=r},96064:(e,t,n)=>{var r=n(7147);e.exports=r},61577:(e,t,n)=>{var r=n(32236);e.exports=r},46279:(e,t,n)=>{n(7634);var r=n(9697),o=n(90953),s=n(7046),i=n(49216),a=Array.prototype,l={DOMTokenList:!0,NodeList:!0};e.exports=function(e){var t=e.forEach;return e===a||s(a,e)&&t===a.forEach||o(l,r(e))?i:t}},33778:(e,t,n)=>{var r=n(58557);e.exports=r},19373:(e,t,n)=>{var r=n(34570);e.exports=r},73819:(e,t,n)=>{n(7634);var r=n(9697),o=n(90953),s=n(7046),i=n(56668),a=Array.prototype,l={DOMTokenList:!0,NodeList:!0};e.exports=function(e){var t=e.keys;return e===a||s(a,e)&&t===a.keys||o(l,r(e))?i:t}},11022:(e,t,n)=>{var r=n(57564);e.exports=r},61798:(e,t,n)=>{var r=n(88287);e.exports=r},52759:(e,t,n)=>{var r=n(93993);e.exports=r},52527:(e,t,n)=>{var r=n(68025);e.exports=r},36857:(e,t,n)=>{var r=n(59257);e.exports=r},82073:(e,t,n)=>{var r=n(69601);e.exports=r},45286:(e,t,n)=>{var r=n(28299);e.exports=r},62856:(e,t,n)=>{var r=n(69355);e.exports=r},2348:(e,t,n)=>{var r=n(18339);e.exports=r},35178:(e,t,n)=>{var r=n(71611);e.exports=r},76361:(e,t,n)=>{var r=n(62774);e.exports=r},71815:(e,t,n)=>{n(7634);var r=n(9697),o=n(90953),s=n(7046),i=n(74719),a=Array.prototype,l={DOMTokenList:!0,NodeList:!0};e.exports=function(e){var t=e.values;return e===a||s(a,e)&&t===a.values||o(l,r(e))?i:t}},8933:(e,t,n)=>{var r=n(84426);e.exports=r},15868:(e,t,n)=>{var r=n(91018);n(7634),e.exports=r},14873:(e,t,n)=>{var r=n(97849);e.exports=r},38849:(e,t,n)=>{var r=n(3820);e.exports=r},63383:(e,t,n)=>{var r=n(45999);e.exports=r},57396:(e,t,n)=>{var r=n(7702);e.exports=r},41910:(e,t,n)=>{var r=n(48171);e.exports=r},86209:(e,t,n)=>{var r=n(73081);e.exports=r},53402:(e,t,n)=>{var r=n(7699);n(7634),e.exports=r},79427:(e,t,n)=>{var r=n(286);e.exports=r},62857:(e,t,n)=>{var r=n(92766);e.exports=r},9534:(e,t,n)=>{var r=n(30498);e.exports=r},23059:(e,t,n)=>{var r=n(48494);e.exports=r},47795:(e,t,n)=>{var r=n(98430);e.exports=r},27460:(e,t,n)=>{var r=n(52956);n(7634),e.exports=r},27989:(e,t,n)=>{n(71249);var r=n(54058);e.exports=r.setTimeout},5519:(e,t,n)=>{var r=n(76998);n(7634),e.exports=r},23452:(e,t,n)=>{var r=n(97089);e.exports=r},92547:(e,t,n)=>{var r=n(57473);n(7634),e.exports=r},46509:(e,t,n)=>{var r=n(24227);n(7634),e.exports=r},35774:(e,t,n)=>{var r=n(62978);e.exports=r},57641:(e,t,n)=>{var r=n(71459);e.exports=r},72010:(e,t,n)=>{var r=n(32304);n(7634),e.exports=r},93726:(e,t,n)=>{var r=n(29567);n(7634),e.exports=r},47610:(e,t,n)=>{n(95304),n(16454),n(73305),n(62337);var r=n(54058);e.exports=r.URLSearchParams},71459:(e,t,n)=>{n(47610),n(33601),n(84630),n(98947);var r=n(54058);e.exports=r.URL},31905:function(){!function(e){!function(t){var n="URLSearchParams"in e,r="Symbol"in e&&"iterator"in Symbol,o="FileReader"in e&&"Blob"in e&&function(){try{return new Blob,!0}catch(e){return!1}}(),s="FormData"in e,i="ArrayBuffer"in e;if(i)var a=["[object Int8Array]","[object Uint8Array]","[object Uint8ClampedArray]","[object Int16Array]","[object Uint16Array]","[object Int32Array]","[object Uint32Array]","[object Float32Array]","[object Float64Array]"],l=ArrayBuffer.isView||function(e){return e&&a.indexOf(Object.prototype.toString.call(e))>-1};function c(e){if("string"!=typeof e&&(e=String(e)),/[^a-z0-9\-#$%&'*+.^_`|~]/i.test(e))throw new TypeError("Invalid character in header field name");return e.toLowerCase()}function u(e){return"string"!=typeof e&&(e=String(e)),e}function p(e){var t={next:function(){var t=e.shift();return{done:void 0===t,value:t}}};return r&&(t[Symbol.iterator]=function(){return t}),t}function h(e){this.map={},e instanceof h?e.forEach((function(e,t){this.append(t,e)}),this):Array.isArray(e)?e.forEach((function(e){this.append(e[0],e[1])}),this):e&&Object.getOwnPropertyNames(e).forEach((function(t){this.append(t,e[t])}),this)}function f(e){if(e.bodyUsed)return Promise.reject(new TypeError("Already read"));e.bodyUsed=!0}function d(e){return new Promise((function(t,n){e.onload=function(){t(e.result)},e.onerror=function(){n(e.error)}}))}function m(e){var t=new FileReader,n=d(t);return t.readAsArrayBuffer(e),n}function g(e){if(e.slice)return e.slice(0);var t=new Uint8Array(e.byteLength);return t.set(new Uint8Array(e)),t.buffer}function y(){return this.bodyUsed=!1,this._initBody=function(e){var t;this._bodyInit=e,e?"string"==typeof e?this._bodyText=e:o&&Blob.prototype.isPrototypeOf(e)?this._bodyBlob=e:s&&FormData.prototype.isPrototypeOf(e)?this._bodyFormData=e:n&&URLSearchParams.prototype.isPrototypeOf(e)?this._bodyText=e.toString():i&&o&&((t=e)&&DataView.prototype.isPrototypeOf(t))?(this._bodyArrayBuffer=g(e.buffer),this._bodyInit=new Blob([this._bodyArrayBuffer])):i&&(ArrayBuffer.prototype.isPrototypeOf(e)||l(e))?this._bodyArrayBuffer=g(e):this._bodyText=e=Object.prototype.toString.call(e):this._bodyText="",this.headers.get("content-type")||("string"==typeof e?this.headers.set("content-type","text/plain;charset=UTF-8"):this._bodyBlob&&this._bodyBlob.type?this.headers.set("content-type",this._bodyBlob.type):n&&URLSearchParams.prototype.isPrototypeOf(e)&&this.headers.set("content-type","application/x-www-form-urlencoded;charset=UTF-8"))},o&&(this.blob=function(){var e=f(this);if(e)return e;if(this._bodyBlob)return Promise.resolve(this._bodyBlob);if(this._bodyArrayBuffer)return Promise.resolve(new Blob([this._bodyArrayBuffer]));if(this._bodyFormData)throw new Error("could not read FormData body as blob");return Promise.resolve(new Blob([this._bodyText]))},this.arrayBuffer=function(){return this._bodyArrayBuffer?f(this)||Promise.resolve(this._bodyArrayBuffer):this.blob().then(m)}),this.text=function(){var e,t,n,r=f(this);if(r)return r;if(this._bodyBlob)return e=this._bodyBlob,t=new FileReader,n=d(t),t.readAsText(e),n;if(this._bodyArrayBuffer)return Promise.resolve(function(e){for(var t=new Uint8Array(e),n=new Array(t.length),r=0;r-1?r:n),this.mode=t.mode||this.mode||null,this.signal=t.signal||this.signal,this.referrer=null,("GET"===this.method||"HEAD"===this.method)&&o)throw new TypeError("Body not allowed for GET or HEAD requests");this._initBody(o)}function w(e){var t=new FormData;return e.trim().split("&").forEach((function(e){if(e){var n=e.split("="),r=n.shift().replace(/\+/g," "),o=n.join("=").replace(/\+/g," ");t.append(decodeURIComponent(r),decodeURIComponent(o))}})),t}function E(e,t){t||(t={}),this.type="default",this.status=void 0===t.status?200:t.status,this.ok=this.status>=200&&this.status<300,this.statusText="statusText"in t?t.statusText:"OK",this.headers=new h(t.headers),this.url=t.url||"",this._initBody(e)}b.prototype.clone=function(){return new b(this,{body:this._bodyInit})},y.call(b.prototype),y.call(E.prototype),E.prototype.clone=function(){return new E(this._bodyInit,{status:this.status,statusText:this.statusText,headers:new h(this.headers),url:this.url})},E.error=function(){var e=new E(null,{status:0,statusText:""});return e.type="error",e};var x=[301,302,303,307,308];E.redirect=function(e,t){if(-1===x.indexOf(t))throw new RangeError("Invalid status code");return new E(null,{status:t,headers:{location:e}})},t.DOMException=e.DOMException;try{new t.DOMException}catch(e){t.DOMException=function(e,t){this.message=e,this.name=t;var n=Error(e);this.stack=n.stack},t.DOMException.prototype=Object.create(Error.prototype),t.DOMException.prototype.constructor=t.DOMException}function S(e,n){return new Promise((function(r,s){var i=new b(e,n);if(i.signal&&i.signal.aborted)return s(new t.DOMException("Aborted","AbortError"));var a=new XMLHttpRequest;function l(){a.abort()}a.onload=function(){var e,t,n={status:a.status,statusText:a.statusText,headers:(e=a.getAllResponseHeaders()||"",t=new h,e.replace(/\r?\n[\t ]+/g," ").split(/\r?\n/).forEach((function(e){var n=e.split(":"),r=n.shift().trim();if(r){var o=n.join(":").trim();t.append(r,o)}})),t)};n.url="responseURL"in a?a.responseURL:n.headers.get("X-Request-URL");var o="response"in a?a.response:a.responseText;r(new E(o,n))},a.onerror=function(){s(new TypeError("Network request failed"))},a.ontimeout=function(){s(new TypeError("Network request failed"))},a.onabort=function(){s(new t.DOMException("Aborted","AbortError"))},a.open(i.method,i.url,!0),"include"===i.credentials?a.withCredentials=!0:"omit"===i.credentials&&(a.withCredentials=!1),"responseType"in a&&o&&(a.responseType="blob"),i.headers.forEach((function(e,t){a.setRequestHeader(t,e)})),i.signal&&(i.signal.addEventListener("abort",l),a.onreadystatechange=function(){4===a.readyState&&i.signal.removeEventListener("abort",l)}),a.send(void 0===i._bodyInit?null:i._bodyInit)}))}S.polyfill=!0,e.fetch||(e.fetch=S,e.Headers=h,e.Request=b,e.Response=E),t.Headers=h,t.Request=b,t.Response=E,t.fetch=S,Object.defineProperty(t,"__esModule",{value:!0})}({})}("undefined"!=typeof self?self:this)},8269:function(e,t,n){var r;r=void 0!==n.g?n.g:this,e.exports=function(e){if(e.CSS&&e.CSS.escape)return e.CSS.escape;var t=function(e){if(0==arguments.length)throw new TypeError("`CSS.escape` requires an argument.");for(var t,n=String(e),r=n.length,o=-1,s="",i=n.charCodeAt(0);++o=1&&t<=31||127==t||0==o&&t>=48&&t<=57||1==o&&t>=48&&t<=57&&45==i?"\\"+t.toString(16)+" ":0==o&&1==r&&45==t||!(t>=128||45==t||95==t||t>=48&&t<=57||t>=65&&t<=90||t>=97&&t<=122)?"\\"+n.charAt(o):n.charAt(o):s+="�";return s};return e.CSS||(e.CSS={}),e.CSS.escape=t,t}(r)},27698:(e,t,n)=>{"use strict";var r=n(48764).Buffer;function o(e){return e instanceof r||e instanceof Date||e instanceof RegExp}function s(e){if(e instanceof r){var t=r.alloc?r.alloc(e.length):new r(e.length);return e.copy(t),t}if(e instanceof Date)return new Date(e.getTime());if(e instanceof RegExp)return new RegExp(e);throw new Error("Unexpected situation")}function i(e){var t=[];return e.forEach((function(e,n){"object"==typeof e&&null!==e?Array.isArray(e)?t[n]=i(e):o(e)?t[n]=s(e):t[n]=l({},e):t[n]=e})),t}function a(e,t){return"__proto__"===t?void 0:e[t]}var l=e.exports=function(){if(arguments.length<1||"object"!=typeof arguments[0])return!1;if(arguments.length<2)return arguments[0];var e,t,n=arguments[0];return Array.prototype.slice.call(arguments,1).forEach((function(r){"object"!=typeof r||null===r||Array.isArray(r)||Object.keys(r).forEach((function(c){return t=a(n,c),(e=a(r,c))===n?void 0:"object"!=typeof e||null===e?void(n[c]=e):Array.isArray(e)?void(n[c]=i(e)):o(e)?void(n[c]=s(e)):"object"!=typeof t||null===t||Array.isArray(t)?void(n[c]=l({},e)):void(n[c]=l(t,e))}))})),n}},9996:e=>{"use strict";var t=function(e){return function(e){return!!e&&"object"==typeof e}(e)&&!function(e){var t=Object.prototype.toString.call(e);return"[object RegExp]"===t||"[object Date]"===t||function(e){return e.$$typeof===n}(e)}(e)};var n="function"==typeof Symbol&&Symbol.for?Symbol.for("react.element"):60103;function r(e,t){return!1!==t.clone&&t.isMergeableObject(e)?l((n=e,Array.isArray(n)?[]:{}),e,t):e;var n}function o(e,t,n){return e.concat(t).map((function(e){return r(e,n)}))}function s(e){return Object.keys(e).concat(function(e){return Object.getOwnPropertySymbols?Object.getOwnPropertySymbols(e).filter((function(t){return Object.propertyIsEnumerable.call(e,t)})):[]}(e))}function i(e,t){try{return t in e}catch(e){return!1}}function a(e,t,n){var o={};return n.isMergeableObject(e)&&s(e).forEach((function(t){o[t]=r(e[t],n)})),s(t).forEach((function(s){(function(e,t){return i(e,t)&&!(Object.hasOwnProperty.call(e,t)&&Object.propertyIsEnumerable.call(e,t))})(e,s)||(i(e,s)&&n.isMergeableObject(t[s])?o[s]=function(e,t){if(!t.customMerge)return l;var n=t.customMerge(e);return"function"==typeof n?n:l}(s,n)(e[s],t[s],n):o[s]=r(t[s],n))})),o}function l(e,n,s){(s=s||{}).arrayMerge=s.arrayMerge||o,s.isMergeableObject=s.isMergeableObject||t,s.cloneUnlessOtherwiseSpecified=r;var i=Array.isArray(n);return i===Array.isArray(e)?i?s.arrayMerge(e,n,s):a(e,n,s):r(n,s)}l.all=function(e,t){if(!Array.isArray(e))throw new Error("first argument should be an array");return e.reduce((function(e,n){return l(e,n,t)}),{})};var c=l;e.exports=c},27856:function(e){e.exports=function(){"use strict";const{entries:e,setPrototypeOf:t,isFrozen:n,getPrototypeOf:r,getOwnPropertyDescriptor:o}=Object;let{freeze:s,seal:i,create:a}=Object,{apply:l,construct:c}="undefined"!=typeof Reflect&&Reflect;l||(l=function(e,t,n){return e.apply(t,n)}),s||(s=function(e){return e}),i||(i=function(e){return e}),c||(c=function(e,t){return new e(...t)});const u=E(Array.prototype.forEach),p=E(Array.prototype.pop),h=E(Array.prototype.push),f=E(String.prototype.toLowerCase),d=E(String.prototype.toString),m=E(String.prototype.match),g=E(String.prototype.replace),y=E(String.prototype.indexOf),v=E(String.prototype.trim),b=E(RegExp.prototype.test),w=x(TypeError);function E(e){return function(t){for(var n=arguments.length,r=new Array(n>1?n-1:0),o=1;o/gm),B=i(/\${[\w\W]*}/gm),$=i(/^data-[\-\w.\u00B7-\uFFFF]/),q=i(/^aria-[\-\w]+$/),U=i(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),z=i(/^(?:\w+script|data):/i),V=i(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g),W=i(/^html$/i);var J=Object.freeze({__proto__:null,MUSTACHE_EXPR:F,ERB_EXPR:L,TMPLIT_EXPR:B,DATA_ATTR:$,ARIA_ATTR:q,IS_ALLOWED_URI:U,IS_SCRIPT_OR_DATA:z,ATTR_WHITESPACE:V,DOCTYPE_NAME:W});const K=()=>"undefined"==typeof window?null:window,H=function(e,t){if("object"!=typeof e||"function"!=typeof e.createPolicy)return null;let n=null;const r="data-tt-policy-suffix";t&&t.hasAttribute(r)&&(n=t.getAttribute(r));const o="dompurify"+(n?"#"+n:"");try{return e.createPolicy(o,{createHTML:e=>e,createScriptURL:e=>e})}catch(e){return console.warn("TrustedTypes policy "+o+" could not be created."),null}};function G(){let t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:K();const n=e=>G(e);if(n.version="3.0.3",n.removed=[],!t||!t.document||9!==t.document.nodeType)return n.isSupported=!1,n;const r=t.document,o=r.currentScript;let{document:i}=t;const{DocumentFragment:a,HTMLTemplateElement:l,Node:c,Element:E,NodeFilter:x,NamedNodeMap:F=t.NamedNodeMap||t.MozNamedAttrMap,HTMLFormElement:L,DOMParser:B,trustedTypes:$}=t,q=E.prototype,z=j(q,"cloneNode"),V=j(q,"nextSibling"),Z=j(q,"childNodes"),Y=j(q,"parentNode");if("function"==typeof l){const e=i.createElement("template");e.content&&e.content.ownerDocument&&(i=e.content.ownerDocument)}let X,Q="";const{implementation:ee,createNodeIterator:te,createDocumentFragment:ne,getElementsByTagName:re}=i,{importNode:oe}=r;let se={};n.isSupported="function"==typeof e&&"function"==typeof Y&&ee&&void 0!==ee.createHTMLDocument;const{MUSTACHE_EXPR:ie,ERB_EXPR:ae,TMPLIT_EXPR:le,DATA_ATTR:ce,ARIA_ATTR:ue,IS_SCRIPT_OR_DATA:pe,ATTR_WHITESPACE:he}=J;let{IS_ALLOWED_URI:fe}=J,de=null;const me=S({},[...O,...k,...A,...P,...I]);let ge=null;const ye=S({},[...T,...R,...M,...D]);let ve=Object.seal(Object.create(null,{tagNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},allowCustomizedBuiltInElements:{writable:!0,configurable:!1,enumerable:!0,value:!1}})),be=null,we=null,Ee=!0,xe=!0,Se=!1,_e=!0,je=!1,Oe=!1,ke=!1,Ae=!1,Ce=!1,Pe=!1,Ne=!1,Ie=!0,Te=!1;const Re="user-content-";let Me=!0,De=!1,Fe={},Le=null;const Be=S({},["annotation-xml","audio","colgroup","desc","foreignobject","head","iframe","math","mi","mn","mo","ms","mtext","noembed","noframes","noscript","plaintext","script","style","svg","template","thead","title","video","xmp"]);let $e=null;const qe=S({},["audio","video","img","source","image","track"]);let Ue=null;const ze=S({},["alt","class","for","id","label","name","pattern","placeholder","role","summary","title","value","style","xmlns"]),Ve="http://www.w3.org/1998/Math/MathML",We="http://www.w3.org/2000/svg",Je="http://www.w3.org/1999/xhtml";let Ke=Je,He=!1,Ge=null;const Ze=S({},[Ve,We,Je],d);let Ye;const Xe=["application/xhtml+xml","text/html"],Qe="text/html";let et,tt=null;const nt=i.createElement("form"),rt=function(e){return e instanceof RegExp||e instanceof Function},ot=function(e){if(!tt||tt!==e){if(e&&"object"==typeof e||(e={}),e=_(e),Ye=Ye=-1===Xe.indexOf(e.PARSER_MEDIA_TYPE)?Qe:e.PARSER_MEDIA_TYPE,et="application/xhtml+xml"===Ye?d:f,de="ALLOWED_TAGS"in e?S({},e.ALLOWED_TAGS,et):me,ge="ALLOWED_ATTR"in e?S({},e.ALLOWED_ATTR,et):ye,Ge="ALLOWED_NAMESPACES"in e?S({},e.ALLOWED_NAMESPACES,d):Ze,Ue="ADD_URI_SAFE_ATTR"in e?S(_(ze),e.ADD_URI_SAFE_ATTR,et):ze,$e="ADD_DATA_URI_TAGS"in e?S(_(qe),e.ADD_DATA_URI_TAGS,et):qe,Le="FORBID_CONTENTS"in e?S({},e.FORBID_CONTENTS,et):Be,be="FORBID_TAGS"in e?S({},e.FORBID_TAGS,et):{},we="FORBID_ATTR"in e?S({},e.FORBID_ATTR,et):{},Fe="USE_PROFILES"in e&&e.USE_PROFILES,Ee=!1!==e.ALLOW_ARIA_ATTR,xe=!1!==e.ALLOW_DATA_ATTR,Se=e.ALLOW_UNKNOWN_PROTOCOLS||!1,_e=!1!==e.ALLOW_SELF_CLOSE_IN_ATTR,je=e.SAFE_FOR_TEMPLATES||!1,Oe=e.WHOLE_DOCUMENT||!1,Ce=e.RETURN_DOM||!1,Pe=e.RETURN_DOM_FRAGMENT||!1,Ne=e.RETURN_TRUSTED_TYPE||!1,Ae=e.FORCE_BODY||!1,Ie=!1!==e.SANITIZE_DOM,Te=e.SANITIZE_NAMED_PROPS||!1,Me=!1!==e.KEEP_CONTENT,De=e.IN_PLACE||!1,fe=e.ALLOWED_URI_REGEXP||U,Ke=e.NAMESPACE||Je,ve=e.CUSTOM_ELEMENT_HANDLING||{},e.CUSTOM_ELEMENT_HANDLING&&rt(e.CUSTOM_ELEMENT_HANDLING.tagNameCheck)&&(ve.tagNameCheck=e.CUSTOM_ELEMENT_HANDLING.tagNameCheck),e.CUSTOM_ELEMENT_HANDLING&&rt(e.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)&&(ve.attributeNameCheck=e.CUSTOM_ELEMENT_HANDLING.attributeNameCheck),e.CUSTOM_ELEMENT_HANDLING&&"boolean"==typeof e.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements&&(ve.allowCustomizedBuiltInElements=e.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements),je&&(xe=!1),Pe&&(Ce=!0),Fe&&(de=S({},[...I]),ge=[],!0===Fe.html&&(S(de,O),S(ge,T)),!0===Fe.svg&&(S(de,k),S(ge,R),S(ge,D)),!0===Fe.svgFilters&&(S(de,A),S(ge,R),S(ge,D)),!0===Fe.mathMl&&(S(de,P),S(ge,M),S(ge,D))),e.ADD_TAGS&&(de===me&&(de=_(de)),S(de,e.ADD_TAGS,et)),e.ADD_ATTR&&(ge===ye&&(ge=_(ge)),S(ge,e.ADD_ATTR,et)),e.ADD_URI_SAFE_ATTR&&S(Ue,e.ADD_URI_SAFE_ATTR,et),e.FORBID_CONTENTS&&(Le===Be&&(Le=_(Le)),S(Le,e.FORBID_CONTENTS,et)),Me&&(de["#text"]=!0),Oe&&S(de,["html","head","body"]),de.table&&(S(de,["tbody"]),delete be.tbody),e.TRUSTED_TYPES_POLICY){if("function"!=typeof e.TRUSTED_TYPES_POLICY.createHTML)throw w('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.');if("function"!=typeof e.TRUSTED_TYPES_POLICY.createScriptURL)throw w('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.');X=e.TRUSTED_TYPES_POLICY,Q=X.createHTML("")}else void 0===X&&(X=H($,o)),null!==X&&"string"==typeof Q&&(Q=X.createHTML(""));s&&s(e),tt=e}},st=S({},["mi","mo","mn","ms","mtext"]),it=S({},["foreignobject","desc","title","annotation-xml"]),at=S({},["title","style","font","a","script"]),lt=S({},k);S(lt,A),S(lt,C);const ct=S({},P);S(ct,N);const ut=function(e){let t=Y(e);t&&t.tagName||(t={namespaceURI:Ke,tagName:"template"});const n=f(e.tagName),r=f(t.tagName);return!!Ge[e.namespaceURI]&&(e.namespaceURI===We?t.namespaceURI===Je?"svg"===n:t.namespaceURI===Ve?"svg"===n&&("annotation-xml"===r||st[r]):Boolean(lt[n]):e.namespaceURI===Ve?t.namespaceURI===Je?"math"===n:t.namespaceURI===We?"math"===n&&it[r]:Boolean(ct[n]):e.namespaceURI===Je?!(t.namespaceURI===We&&!it[r])&&!(t.namespaceURI===Ve&&!st[r])&&!ct[n]&&(at[n]||!lt[n]):!("application/xhtml+xml"!==Ye||!Ge[e.namespaceURI]))},pt=function(e){h(n.removed,{element:e});try{e.parentNode.removeChild(e)}catch(t){e.remove()}},ht=function(e,t){try{h(n.removed,{attribute:t.getAttributeNode(e),from:t})}catch(e){h(n.removed,{attribute:null,from:t})}if(t.removeAttribute(e),"is"===e&&!ge[e])if(Ce||Pe)try{pt(t)}catch(e){}else try{t.setAttribute(e,"")}catch(e){}},ft=function(e){let t,n;if(Ae)e=""+e;else{const t=m(e,/^[\r\n\t ]+/);n=t&&t[0]}"application/xhtml+xml"===Ye&&Ke===Je&&(e=''+e+"");const r=X?X.createHTML(e):e;if(Ke===Je)try{t=(new B).parseFromString(r,Ye)}catch(e){}if(!t||!t.documentElement){t=ee.createDocument(Ke,"template",null);try{t.documentElement.innerHTML=He?Q:r}catch(e){}}const o=t.body||t.documentElement;return e&&n&&o.insertBefore(i.createTextNode(n),o.childNodes[0]||null),Ke===Je?re.call(t,Oe?"html":"body")[0]:Oe?t.documentElement:o},dt=function(e){return te.call(e.ownerDocument||e,e,x.SHOW_ELEMENT|x.SHOW_COMMENT|x.SHOW_TEXT,null,!1)},mt=function(e){return e instanceof L&&("string"!=typeof e.nodeName||"string"!=typeof e.textContent||"function"!=typeof e.removeChild||!(e.attributes instanceof F)||"function"!=typeof e.removeAttribute||"function"!=typeof e.setAttribute||"string"!=typeof e.namespaceURI||"function"!=typeof e.insertBefore||"function"!=typeof e.hasChildNodes)},gt=function(e){return"object"==typeof c?e instanceof c:e&&"object"==typeof e&&"number"==typeof e.nodeType&&"string"==typeof e.nodeName},yt=function(e,t,r){se[e]&&u(se[e],(e=>{e.call(n,t,r,tt)}))},vt=function(e){let t;if(yt("beforeSanitizeElements",e,null),mt(e))return pt(e),!0;const r=et(e.nodeName);if(yt("uponSanitizeElement",e,{tagName:r,allowedTags:de}),e.hasChildNodes()&&!gt(e.firstElementChild)&&(!gt(e.content)||!gt(e.content.firstElementChild))&&b(/<[/\w]/g,e.innerHTML)&&b(/<[/\w]/g,e.textContent))return pt(e),!0;if(!de[r]||be[r]){if(!be[r]&&wt(r)){if(ve.tagNameCheck instanceof RegExp&&b(ve.tagNameCheck,r))return!1;if(ve.tagNameCheck instanceof Function&&ve.tagNameCheck(r))return!1}if(Me&&!Le[r]){const t=Y(e)||e.parentNode,n=Z(e)||e.childNodes;if(n&&t)for(let r=n.length-1;r>=0;--r)t.insertBefore(z(n[r],!0),V(e))}return pt(e),!0}return e instanceof E&&!ut(e)?(pt(e),!0):"noscript"!==r&&"noembed"!==r||!b(/<\/no(script|embed)/i,e.innerHTML)?(je&&3===e.nodeType&&(t=e.textContent,t=g(t,ie," "),t=g(t,ae," "),t=g(t,le," "),e.textContent!==t&&(h(n.removed,{element:e.cloneNode()}),e.textContent=t)),yt("afterSanitizeElements",e,null),!1):(pt(e),!0)},bt=function(e,t,n){if(Ie&&("id"===t||"name"===t)&&(n in i||n in nt))return!1;if(xe&&!we[t]&&b(ce,t));else if(Ee&&b(ue,t));else if(!ge[t]||we[t]){if(!(wt(e)&&(ve.tagNameCheck instanceof RegExp&&b(ve.tagNameCheck,e)||ve.tagNameCheck instanceof Function&&ve.tagNameCheck(e))&&(ve.attributeNameCheck instanceof RegExp&&b(ve.attributeNameCheck,t)||ve.attributeNameCheck instanceof Function&&ve.attributeNameCheck(t))||"is"===t&&ve.allowCustomizedBuiltInElements&&(ve.tagNameCheck instanceof RegExp&&b(ve.tagNameCheck,n)||ve.tagNameCheck instanceof Function&&ve.tagNameCheck(n))))return!1}else if(Ue[t]);else if(b(fe,g(n,he,"")));else if("src"!==t&&"xlink:href"!==t&&"href"!==t||"script"===e||0!==y(n,"data:")||!$e[e])if(Se&&!b(pe,g(n,he,"")));else if(n)return!1;return!0},wt=function(e){return e.indexOf("-")>0},Et=function(e){let t,r,o,s;yt("beforeSanitizeAttributes",e,null);const{attributes:i}=e;if(!i)return;const a={attrName:"",attrValue:"",keepAttr:!0,allowedAttributes:ge};for(s=i.length;s--;){t=i[s];const{name:l,namespaceURI:c}=t;if(r="value"===l?t.value:v(t.value),o=et(l),a.attrName=o,a.attrValue=r,a.keepAttr=!0,a.forceKeepAttr=void 0,yt("uponSanitizeAttribute",e,a),r=a.attrValue,a.forceKeepAttr)continue;if(ht(l,e),!a.keepAttr)continue;if(!_e&&b(/\/>/i,r)){ht(l,e);continue}je&&(r=g(r,ie," "),r=g(r,ae," "),r=g(r,le," "));const u=et(e.nodeName);if(bt(u,o,r)){if(!Te||"id"!==o&&"name"!==o||(ht(l,e),r=Re+r),X&&"object"==typeof $&&"function"==typeof $.getAttributeType)if(c);else switch($.getAttributeType(u,o)){case"TrustedHTML":r=X.createHTML(r);break;case"TrustedScriptURL":r=X.createScriptURL(r)}try{c?e.setAttributeNS(c,l,r):e.setAttribute(l,r),p(n.removed)}catch(e){}}}yt("afterSanitizeAttributes",e,null)},xt=function e(t){let n;const r=dt(t);for(yt("beforeSanitizeShadowDOM",t,null);n=r.nextNode();)yt("uponSanitizeShadowNode",n,null),vt(n)||(n.content instanceof a&&e(n.content),Et(n));yt("afterSanitizeShadowDOM",t,null)};return n.sanitize=function(e){let t,o,s,i,l=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};if(He=!e,He&&(e="\x3c!--\x3e"),"string"!=typeof e&&!gt(e)){if("function"!=typeof e.toString)throw w("toString is not a function");if("string"!=typeof(e=e.toString()))throw w("dirty is not a string, aborting")}if(!n.isSupported)return e;if(ke||ot(l),n.removed=[],"string"==typeof e&&(De=!1),De){if(e.nodeName){const t=et(e.nodeName);if(!de[t]||be[t])throw w("root node is forbidden and cannot be sanitized in-place")}}else if(e instanceof c)t=ft("\x3c!----\x3e"),o=t.ownerDocument.importNode(e,!0),1===o.nodeType&&"BODY"===o.nodeName||"HTML"===o.nodeName?t=o:t.appendChild(o);else{if(!Ce&&!je&&!Oe&&-1===e.indexOf("<"))return X&&Ne?X.createHTML(e):e;if(t=ft(e),!t)return Ce?null:Ne?Q:""}t&&Ae&&pt(t.firstChild);const u=dt(De?e:t);for(;s=u.nextNode();)vt(s)||(s.content instanceof a&&xt(s.content),Et(s));if(De)return e;if(Ce){if(Pe)for(i=ne.call(t.ownerDocument);t.firstChild;)i.appendChild(t.firstChild);else i=t;return(ge.shadowroot||ge.shadowrootmod)&&(i=oe.call(r,i,!0)),i}let p=Oe?t.outerHTML:t.innerHTML;return Oe&&de["!doctype"]&&t.ownerDocument&&t.ownerDocument.doctype&&t.ownerDocument.doctype.name&&b(W,t.ownerDocument.doctype.name)&&(p="\n"+p),je&&(p=g(p,ie," "),p=g(p,ae," "),p=g(p,le," ")),X&&Ne?X.createHTML(p):p},n.setConfig=function(e){ot(e),ke=!0},n.clearConfig=function(){tt=null,ke=!1},n.isValidAttribute=function(e,t,n){tt||ot({});const r=et(e),o=et(t);return bt(r,o,n)},n.addHook=function(e,t){"function"==typeof t&&(se[e]=se[e]||[],h(se[e],t))},n.removeHook=function(e){if(se[e])return p(se[e])},n.removeHooks=function(e){se[e]&&(se[e]=[])},n.removeAllHooks=function(){se={}},n}return G()}()},69450:e=>{"use strict";class t{constructor(e,t){this.low=e,this.high=t,this.length=1+t-e}overlaps(e){return!(this.highe.high)}touches(e){return!(this.high+1e.high)}add(e){return new t(Math.min(this.low,e.low),Math.max(this.high,e.high))}subtract(e){return e.low<=this.low&&e.high>=this.high?[]:e.low>this.low&&e.highe+t.length),0)}add(e,r){var o=e=>{for(var t=0;t{for(var t=0;t{for(var n=0;n{for(var n=t.low;n<=t.high;)e.push(n),n++;return e}),[])}subranges(){return this.ranges.map((e=>({low:e.low,high:e.high,length:1+e.high-e.low})))}}e.exports=n},17187:e=>{"use strict";var t,n="object"==typeof Reflect?Reflect:null,r=n&&"function"==typeof n.apply?n.apply:function(e,t,n){return Function.prototype.apply.call(e,t,n)};t=n&&"function"==typeof n.ownKeys?n.ownKeys:Object.getOwnPropertySymbols?function(e){return Object.getOwnPropertyNames(e).concat(Object.getOwnPropertySymbols(e))}:function(e){return Object.getOwnPropertyNames(e)};var o=Number.isNaN||function(e){return e!=e};function s(){s.init.call(this)}e.exports=s,e.exports.once=function(e,t){return new Promise((function(n,r){function o(n){e.removeListener(t,s),r(n)}function s(){"function"==typeof e.removeListener&&e.removeListener("error",o),n([].slice.call(arguments))}m(e,t,s,{once:!0}),"error"!==t&&function(e,t,n){"function"==typeof e.on&&m(e,"error",t,n)}(e,o,{once:!0})}))},s.EventEmitter=s,s.prototype._events=void 0,s.prototype._eventsCount=0,s.prototype._maxListeners=void 0;var i=10;function a(e){if("function"!=typeof e)throw new TypeError('The "listener" argument must be of type Function. Received type '+typeof e)}function l(e){return void 0===e._maxListeners?s.defaultMaxListeners:e._maxListeners}function c(e,t,n,r){var o,s,i,c;if(a(n),void 0===(s=e._events)?(s=e._events=Object.create(null),e._eventsCount=0):(void 0!==s.newListener&&(e.emit("newListener",t,n.listener?n.listener:n),s=e._events),i=s[t]),void 0===i)i=s[t]=n,++e._eventsCount;else if("function"==typeof i?i=s[t]=r?[n,i]:[i,n]:r?i.unshift(n):i.push(n),(o=l(e))>0&&i.length>o&&!i.warned){i.warned=!0;var u=new Error("Possible EventEmitter memory leak detected. "+i.length+" "+String(t)+" listeners added. Use emitter.setMaxListeners() to increase limit");u.name="MaxListenersExceededWarning",u.emitter=e,u.type=t,u.count=i.length,c=u,console&&console.warn&&console.warn(c)}return e}function u(){if(!this.fired)return this.target.removeListener(this.type,this.wrapFn),this.fired=!0,0===arguments.length?this.listener.call(this.target):this.listener.apply(this.target,arguments)}function p(e,t,n){var r={fired:!1,wrapFn:void 0,target:e,type:t,listener:n},o=u.bind(r);return o.listener=n,r.wrapFn=o,o}function h(e,t,n){var r=e._events;if(void 0===r)return[];var o=r[t];return void 0===o?[]:"function"==typeof o?n?[o.listener||o]:[o]:n?function(e){for(var t=new Array(e.length),n=0;n0&&(i=t[0]),i instanceof Error)throw i;var a=new Error("Unhandled error."+(i?" ("+i.message+")":""));throw a.context=i,a}var l=s[e];if(void 0===l)return!1;if("function"==typeof l)r(l,this,t);else{var c=l.length,u=d(l,c);for(n=0;n=0;s--)if(n[s]===t||n[s].listener===t){i=n[s].listener,o=s;break}if(o<0)return this;0===o?n.shift():function(e,t){for(;t+1=0;r--)this.removeListener(e,t[r]);return this},s.prototype.listeners=function(e){return h(this,e,!0)},s.prototype.rawListeners=function(e){return h(this,e,!1)},s.listenerCount=function(e,t){return"function"==typeof e.listenerCount?e.listenerCount(t):f.call(e,t)},s.prototype.listenerCount=f,s.prototype.eventNames=function(){return this._eventsCount>0?t(this._events):[]}},21102:(e,t,n)=>{"use strict";var r=n(46291),o=s(Error);function s(e){return t.displayName=e.displayName||e.name,t;function t(t){return t&&(t=r.apply(null,arguments)),new e(t)}}e.exports=o,o.eval=s(EvalError),o.range=s(RangeError),o.reference=s(ReferenceError),o.syntax=s(SyntaxError),o.type=s(TypeError),o.uri=s(URIError),o.create=s},46291:e=>{!function(){var t;function n(e){for(var t,n,r,o,s=1,i=[].slice.call(arguments),a=0,l=e.length,c="",u=!1,p=!1,h=function(){return i[s++]},f=function(){for(var n="";/\d/.test(e[a]);)n+=e[a++],t=e[a];return n.length>0?parseInt(n):null};a{"use strict";var t=Array.prototype.slice,n=Object.prototype.toString;e.exports=function(e){var r=this;if("function"!=typeof r||"[object Function]"!==n.call(r))throw new TypeError("Function.prototype.bind called on incompatible "+r);for(var o,s=t.call(arguments,1),i=Math.max(0,r.length-s.length),a=[],l=0;l{"use strict";var r=n(17648);e.exports=Function.prototype.bind||r},40210:(e,t,n)=>{"use strict";var r,o=SyntaxError,s=Function,i=TypeError,a=function(e){try{return s('"use strict"; return ('+e+").constructor;")()}catch(e){}},l=Object.getOwnPropertyDescriptor;if(l)try{l({},"")}catch(e){l=null}var c=function(){throw new i},u=l?function(){try{return c}catch(e){try{return l(arguments,"callee").get}catch(e){return c}}}():c,p=n(41405)(),h=n(28185)(),f=Object.getPrototypeOf||(h?function(e){return e.__proto__}:null),d={},m="undefined"!=typeof Uint8Array&&f?f(Uint8Array):r,g={"%AggregateError%":"undefined"==typeof AggregateError?r:AggregateError,"%Array%":Array,"%ArrayBuffer%":"undefined"==typeof ArrayBuffer?r:ArrayBuffer,"%ArrayIteratorPrototype%":p&&f?f([][Symbol.iterator]()):r,"%AsyncFromSyncIteratorPrototype%":r,"%AsyncFunction%":d,"%AsyncGenerator%":d,"%AsyncGeneratorFunction%":d,"%AsyncIteratorPrototype%":d,"%Atomics%":"undefined"==typeof Atomics?r:Atomics,"%BigInt%":"undefined"==typeof BigInt?r:BigInt,"%BigInt64Array%":"undefined"==typeof BigInt64Array?r:BigInt64Array,"%BigUint64Array%":"undefined"==typeof BigUint64Array?r:BigUint64Array,"%Boolean%":Boolean,"%DataView%":"undefined"==typeof DataView?r:DataView,"%Date%":Date,"%decodeURI%":decodeURI,"%decodeURIComponent%":decodeURIComponent,"%encodeURI%":encodeURI,"%encodeURIComponent%":encodeURIComponent,"%Error%":Error,"%eval%":eval,"%EvalError%":EvalError,"%Float32Array%":"undefined"==typeof Float32Array?r:Float32Array,"%Float64Array%":"undefined"==typeof Float64Array?r:Float64Array,"%FinalizationRegistry%":"undefined"==typeof FinalizationRegistry?r:FinalizationRegistry,"%Function%":s,"%GeneratorFunction%":d,"%Int8Array%":"undefined"==typeof Int8Array?r:Int8Array,"%Int16Array%":"undefined"==typeof Int16Array?r:Int16Array,"%Int32Array%":"undefined"==typeof Int32Array?r:Int32Array,"%isFinite%":isFinite,"%isNaN%":isNaN,"%IteratorPrototype%":p&&f?f(f([][Symbol.iterator]())):r,"%JSON%":"object"==typeof JSON?JSON:r,"%Map%":"undefined"==typeof Map?r:Map,"%MapIteratorPrototype%":"undefined"!=typeof Map&&p&&f?f((new Map)[Symbol.iterator]()):r,"%Math%":Math,"%Number%":Number,"%Object%":Object,"%parseFloat%":parseFloat,"%parseInt%":parseInt,"%Promise%":"undefined"==typeof Promise?r:Promise,"%Proxy%":"undefined"==typeof Proxy?r:Proxy,"%RangeError%":RangeError,"%ReferenceError%":ReferenceError,"%Reflect%":"undefined"==typeof Reflect?r:Reflect,"%RegExp%":RegExp,"%Set%":"undefined"==typeof Set?r:Set,"%SetIteratorPrototype%":"undefined"!=typeof Set&&p&&f?f((new Set)[Symbol.iterator]()):r,"%SharedArrayBuffer%":"undefined"==typeof SharedArrayBuffer?r:SharedArrayBuffer,"%String%":String,"%StringIteratorPrototype%":p&&f?f(""[Symbol.iterator]()):r,"%Symbol%":p?Symbol:r,"%SyntaxError%":o,"%ThrowTypeError%":u,"%TypedArray%":m,"%TypeError%":i,"%Uint8Array%":"undefined"==typeof Uint8Array?r:Uint8Array,"%Uint8ClampedArray%":"undefined"==typeof Uint8ClampedArray?r:Uint8ClampedArray,"%Uint16Array%":"undefined"==typeof Uint16Array?r:Uint16Array,"%Uint32Array%":"undefined"==typeof Uint32Array?r:Uint32Array,"%URIError%":URIError,"%WeakMap%":"undefined"==typeof WeakMap?r:WeakMap,"%WeakRef%":"undefined"==typeof WeakRef?r:WeakRef,"%WeakSet%":"undefined"==typeof WeakSet?r:WeakSet};if(f)try{null.error}catch(e){var y=f(f(e));g["%Error.prototype%"]=y}var v=function e(t){var n;if("%AsyncFunction%"===t)n=a("async function () {}");else if("%GeneratorFunction%"===t)n=a("function* () {}");else if("%AsyncGeneratorFunction%"===t)n=a("async function* () {}");else if("%AsyncGenerator%"===t){var r=e("%AsyncGeneratorFunction%");r&&(n=r.prototype)}else if("%AsyncIteratorPrototype%"===t){var o=e("%AsyncGenerator%");o&&f&&(n=f(o.prototype))}return g[t]=n,n},b={"%ArrayBufferPrototype%":["ArrayBuffer","prototype"],"%ArrayPrototype%":["Array","prototype"],"%ArrayProto_entries%":["Array","prototype","entries"],"%ArrayProto_forEach%":["Array","prototype","forEach"],"%ArrayProto_keys%":["Array","prototype","keys"],"%ArrayProto_values%":["Array","prototype","values"],"%AsyncFunctionPrototype%":["AsyncFunction","prototype"],"%AsyncGenerator%":["AsyncGeneratorFunction","prototype"],"%AsyncGeneratorPrototype%":["AsyncGeneratorFunction","prototype","prototype"],"%BooleanPrototype%":["Boolean","prototype"],"%DataViewPrototype%":["DataView","prototype"],"%DatePrototype%":["Date","prototype"],"%ErrorPrototype%":["Error","prototype"],"%EvalErrorPrototype%":["EvalError","prototype"],"%Float32ArrayPrototype%":["Float32Array","prototype"],"%Float64ArrayPrototype%":["Float64Array","prototype"],"%FunctionPrototype%":["Function","prototype"],"%Generator%":["GeneratorFunction","prototype"],"%GeneratorPrototype%":["GeneratorFunction","prototype","prototype"],"%Int8ArrayPrototype%":["Int8Array","prototype"],"%Int16ArrayPrototype%":["Int16Array","prototype"],"%Int32ArrayPrototype%":["Int32Array","prototype"],"%JSONParse%":["JSON","parse"],"%JSONStringify%":["JSON","stringify"],"%MapPrototype%":["Map","prototype"],"%NumberPrototype%":["Number","prototype"],"%ObjectPrototype%":["Object","prototype"],"%ObjProto_toString%":["Object","prototype","toString"],"%ObjProto_valueOf%":["Object","prototype","valueOf"],"%PromisePrototype%":["Promise","prototype"],"%PromiseProto_then%":["Promise","prototype","then"],"%Promise_all%":["Promise","all"],"%Promise_reject%":["Promise","reject"],"%Promise_resolve%":["Promise","resolve"],"%RangeErrorPrototype%":["RangeError","prototype"],"%ReferenceErrorPrototype%":["ReferenceError","prototype"],"%RegExpPrototype%":["RegExp","prototype"],"%SetPrototype%":["Set","prototype"],"%SharedArrayBufferPrototype%":["SharedArrayBuffer","prototype"],"%StringPrototype%":["String","prototype"],"%SymbolPrototype%":["Symbol","prototype"],"%SyntaxErrorPrototype%":["SyntaxError","prototype"],"%TypedArrayPrototype%":["TypedArray","prototype"],"%TypeErrorPrototype%":["TypeError","prototype"],"%Uint8ArrayPrototype%":["Uint8Array","prototype"],"%Uint8ClampedArrayPrototype%":["Uint8ClampedArray","prototype"],"%Uint16ArrayPrototype%":["Uint16Array","prototype"],"%Uint32ArrayPrototype%":["Uint32Array","prototype"],"%URIErrorPrototype%":["URIError","prototype"],"%WeakMapPrototype%":["WeakMap","prototype"],"%WeakSetPrototype%":["WeakSet","prototype"]},w=n(58612),E=n(17642),x=w.call(Function.call,Array.prototype.concat),S=w.call(Function.apply,Array.prototype.splice),_=w.call(Function.call,String.prototype.replace),j=w.call(Function.call,String.prototype.slice),O=w.call(Function.call,RegExp.prototype.exec),k=/[^%.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|%$))/g,A=/\\(\\)?/g,C=function(e,t){var n,r=e;if(E(b,r)&&(r="%"+(n=b[r])[0]+"%"),E(g,r)){var s=g[r];if(s===d&&(s=v(r)),void 0===s&&!t)throw new i("intrinsic "+e+" exists, but is not available. Please file an issue!");return{alias:n,name:r,value:s}}throw new o("intrinsic "+e+" does not exist!")};e.exports=function(e,t){if("string"!=typeof e||0===e.length)throw new i("intrinsic name must be a non-empty string");if(arguments.length>1&&"boolean"!=typeof t)throw new i('"allowMissing" argument must be a boolean');if(null===O(/^%?[^%]*%?$/,e))throw new o("`%` may not be present anywhere but at the beginning and end of the intrinsic name");var n=function(e){var t=j(e,0,1),n=j(e,-1);if("%"===t&&"%"!==n)throw new o("invalid intrinsic syntax, expected closing `%`");if("%"===n&&"%"!==t)throw new o("invalid intrinsic syntax, expected opening `%`");var r=[];return _(e,k,(function(e,t,n,o){r[r.length]=n?_(o,A,"$1"):t||e})),r}(e),r=n.length>0?n[0]:"",s=C("%"+r+"%",t),a=s.name,c=s.value,u=!1,p=s.alias;p&&(r=p[0],S(n,x([0,1],p)));for(var h=1,f=!0;h=n.length){var v=l(c,d);c=(f=!!v)&&"get"in v&&!("originalValue"in v.get)?v.get:c[d]}else f=E(c,d),c=c[d];f&&!u&&(g[a]=c)}}return c}},28185:e=>{"use strict";var t={foo:{}},n=Object;e.exports=function(){return{__proto__:t}.foo===t.foo&&!({__proto__:null}instanceof n)}},41405:(e,t,n)=>{"use strict";var r="undefined"!=typeof Symbol&&Symbol,o=n(55419);e.exports=function(){return"function"==typeof r&&("function"==typeof Symbol&&("symbol"==typeof r("foo")&&("symbol"==typeof Symbol("bar")&&o())))}},55419:e=>{"use strict";e.exports=function(){if("function"!=typeof Symbol||"function"!=typeof Object.getOwnPropertySymbols)return!1;if("symbol"==typeof Symbol.iterator)return!0;var e={},t=Symbol("test"),n=Object(t);if("string"==typeof t)return!1;if("[object Symbol]"!==Object.prototype.toString.call(t))return!1;if("[object Symbol]"!==Object.prototype.toString.call(n))return!1;for(t in e[t]=42,e)return!1;if("function"==typeof Object.keys&&0!==Object.keys(e).length)return!1;if("function"==typeof Object.getOwnPropertyNames&&0!==Object.getOwnPropertyNames(e).length)return!1;var r=Object.getOwnPropertySymbols(e);if(1!==r.length||r[0]!==t)return!1;if(!Object.prototype.propertyIsEnumerable.call(e,t))return!1;if("function"==typeof Object.getOwnPropertyDescriptor){var o=Object.getOwnPropertyDescriptor(e,t);if(42!==o.value||!0!==o.enumerable)return!1}return!0}},17642:(e,t,n)=>{"use strict";var r=n(58612);e.exports=r.call(Function.call,Object.prototype.hasOwnProperty)},47802:e=>{function t(e){return e instanceof Map?e.clear=e.delete=e.set=function(){throw new Error("map is read-only")}:e instanceof Set&&(e.add=e.clear=e.delete=function(){throw new Error("set is read-only")}),Object.freeze(e),Object.getOwnPropertyNames(e).forEach((function(n){var r=e[n];"object"!=typeof r||Object.isFrozen(r)||t(r)})),e}var n=t,r=t;n.default=r;class o{constructor(e){void 0===e.data&&(e.data={}),this.data=e.data,this.isMatchIgnored=!1}ignoreMatch(){this.isMatchIgnored=!0}}function s(e){return e.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function i(e,...t){const n=Object.create(null);for(const t in e)n[t]=e[t];return t.forEach((function(e){for(const t in e)n[t]=e[t]})),n}const a=e=>!!e.kind;class l{constructor(e,t){this.buffer="",this.classPrefix=t.classPrefix,e.walk(this)}addText(e){this.buffer+=s(e)}openNode(e){if(!a(e))return;let t=e.kind;e.sublanguage||(t=`${this.classPrefix}${t}`),this.span(t)}closeNode(e){a(e)&&(this.buffer+="")}value(){return this.buffer}span(e){this.buffer+=``}}class c{constructor(){this.rootNode={children:[]},this.stack=[this.rootNode]}get top(){return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(e){this.top.children.push(e)}openNode(e){const t={kind:e,children:[]};this.add(t),this.stack.push(t)}closeNode(){if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)}walk(e){return this.constructor._walk(e,this.rootNode)}static _walk(e,t){return"string"==typeof t?e.addText(t):t.children&&(e.openNode(t),t.children.forEach((t=>this._walk(e,t))),e.closeNode(t)),e}static _collapse(e){"string"!=typeof e&&e.children&&(e.children.every((e=>"string"==typeof e))?e.children=[e.children.join("")]:e.children.forEach((e=>{c._collapse(e)})))}}class u extends c{constructor(e){super(),this.options=e}addKeyword(e,t){""!==e&&(this.openNode(t),this.addText(e),this.closeNode())}addText(e){""!==e&&this.add(e)}addSublanguage(e,t){const n=e.root;n.kind=t,n.sublanguage=!0,this.add(n)}toHTML(){return new l(this,this.options).value()}finalize(){return!0}}function p(e){return e?"string"==typeof e?e:e.source:null}const h=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./;const f="[a-zA-Z]\\w*",d="[a-zA-Z_]\\w*",m="\\b\\d+(\\.\\d+)?",g="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",y="\\b(0b[01]+)",v={begin:"\\\\[\\s\\S]",relevance:0},b={className:"string",begin:"'",end:"'",illegal:"\\n",contains:[v]},w={className:"string",begin:'"',end:'"',illegal:"\\n",contains:[v]},E={begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},x=function(e,t,n={}){const r=i({className:"comment",begin:e,end:t,contains:[]},n);return r.contains.push(E),r.contains.push({className:"doctag",begin:"(?:TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):",relevance:0}),r},S=x("//","$"),_=x("/\\*","\\*/"),j=x("#","$"),O={className:"number",begin:m,relevance:0},k={className:"number",begin:g,relevance:0},A={className:"number",begin:y,relevance:0},C={className:"number",begin:m+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",relevance:0},P={begin:/(?=\/[^/\n]*\/)/,contains:[{className:"regexp",begin:/\//,end:/\/[gimuy]*/,illegal:/\n/,contains:[v,{begin:/\[/,end:/\]/,relevance:0,contains:[v]}]}]},N={className:"title",begin:f,relevance:0},I={className:"title",begin:d,relevance:0},T={begin:"\\.\\s*"+d,relevance:0};var R=Object.freeze({__proto__:null,MATCH_NOTHING_RE:/\b\B/,IDENT_RE:f,UNDERSCORE_IDENT_RE:d,NUMBER_RE:m,C_NUMBER_RE:g,BINARY_NUMBER_RE:y,RE_STARTERS_RE:"!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",SHEBANG:(e={})=>{const t=/^#![ ]*\//;return e.binary&&(e.begin=function(...e){return e.map((e=>p(e))).join("")}(t,/.*\b/,e.binary,/\b.*/)),i({className:"meta",begin:t,end:/$/,relevance:0,"on:begin":(e,t)=>{0!==e.index&&t.ignoreMatch()}},e)},BACKSLASH_ESCAPE:v,APOS_STRING_MODE:b,QUOTE_STRING_MODE:w,PHRASAL_WORDS_MODE:E,COMMENT:x,C_LINE_COMMENT_MODE:S,C_BLOCK_COMMENT_MODE:_,HASH_COMMENT_MODE:j,NUMBER_MODE:O,C_NUMBER_MODE:k,BINARY_NUMBER_MODE:A,CSS_NUMBER_MODE:C,REGEXP_MODE:P,TITLE_MODE:N,UNDERSCORE_TITLE_MODE:I,METHOD_GUARD:T,END_SAME_AS_BEGIN:function(e){return Object.assign(e,{"on:begin":(e,t)=>{t.data._beginMatch=e[1]},"on:end":(e,t)=>{t.data._beginMatch!==e[1]&&t.ignoreMatch()}})}});function M(e,t){"."===e.input[e.index-1]&&t.ignoreMatch()}function D(e,t){t&&e.beginKeywords&&(e.begin="\\b("+e.beginKeywords.split(" ").join("|")+")(?!\\.)(?=\\b|\\s)",e.__beforeBegin=M,e.keywords=e.keywords||e.beginKeywords,delete e.beginKeywords,void 0===e.relevance&&(e.relevance=0))}function F(e,t){Array.isArray(e.illegal)&&(e.illegal=function(...e){return"("+e.map((e=>p(e))).join("|")+")"}(...e.illegal))}function L(e,t){if(e.match){if(e.begin||e.end)throw new Error("begin & end are not supported with match");e.begin=e.match,delete e.match}}function B(e,t){void 0===e.relevance&&(e.relevance=1)}const $=["of","and","for","in","not","or","if","then","parent","list","value"],q="keyword";function U(e,t,n=q){const r={};return"string"==typeof e?o(n,e.split(" ")):Array.isArray(e)?o(n,e):Object.keys(e).forEach((function(n){Object.assign(r,U(e[n],t,n))})),r;function o(e,n){t&&(n=n.map((e=>e.toLowerCase()))),n.forEach((function(t){const n=t.split("|");r[n[0]]=[e,z(n[0],n[1])]}))}}function z(e,t){return t?Number(t):function(e){return $.includes(e.toLowerCase())}(e)?0:1}function V(e,{plugins:t}){function n(t,n){return new RegExp(p(t),"m"+(e.case_insensitive?"i":"")+(n?"g":""))}class r{constructor(){this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0}addRule(e,t){t.position=this.position++,this.matchIndexes[this.matchAt]=t,this.regexes.push([t,e]),this.matchAt+=function(e){return new RegExp(e.toString()+"|").exec("").length-1}(e)+1}compile(){0===this.regexes.length&&(this.exec=()=>null);const e=this.regexes.map((e=>e[1]));this.matcherRe=n(function(e,t="|"){let n=0;return e.map((e=>{n+=1;const t=n;let r=p(e),o="";for(;r.length>0;){const e=h.exec(r);if(!e){o+=r;break}o+=r.substring(0,e.index),r=r.substring(e.index+e[0].length),"\\"===e[0][0]&&e[1]?o+="\\"+String(Number(e[1])+t):(o+=e[0],"("===e[0]&&n++)}return o})).map((e=>`(${e})`)).join(t)}(e),!0),this.lastIndex=0}exec(e){this.matcherRe.lastIndex=this.lastIndex;const t=this.matcherRe.exec(e);if(!t)return null;const n=t.findIndex(((e,t)=>t>0&&void 0!==e)),r=this.matchIndexes[n];return t.splice(0,n),Object.assign(t,r)}}class o{constructor(){this.rules=[],this.multiRegexes=[],this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(e){if(this.multiRegexes[e])return this.multiRegexes[e];const t=new r;return this.rules.slice(e).forEach((([e,n])=>t.addRule(e,n))),t.compile(),this.multiRegexes[e]=t,t}resumingScanAtSamePosition(){return 0!==this.regexIndex}considerAll(){this.regexIndex=0}addRule(e,t){this.rules.push([e,t]),"begin"===t.type&&this.count++}exec(e){const t=this.getMatcher(this.regexIndex);t.lastIndex=this.lastIndex;let n=t.exec(e);if(this.resumingScanAtSamePosition())if(n&&n.index===this.lastIndex);else{const t=this.getMatcher(0);t.lastIndex=this.lastIndex+1,n=t.exec(e)}return n&&(this.regexIndex+=n.position+1,this.regexIndex===this.count&&this.considerAll()),n}}if(e.compilerExtensions||(e.compilerExtensions=[]),e.contains&&e.contains.includes("self"))throw new Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.");return e.classNameAliases=i(e.classNameAliases||{}),function t(r,s){const a=r;if(r.isCompiled)return a;[L].forEach((e=>e(r,s))),e.compilerExtensions.forEach((e=>e(r,s))),r.__beforeBegin=null,[D,F,B].forEach((e=>e(r,s))),r.isCompiled=!0;let l=null;if("object"==typeof r.keywords&&(l=r.keywords.$pattern,delete r.keywords.$pattern),r.keywords&&(r.keywords=U(r.keywords,e.case_insensitive)),r.lexemes&&l)throw new Error("ERR: Prefer `keywords.$pattern` to `mode.lexemes`, BOTH are not allowed. (see mode reference) ");return l=l||r.lexemes||/\w+/,a.keywordPatternRe=n(l,!0),s&&(r.begin||(r.begin=/\B|\b/),a.beginRe=n(r.begin),r.endSameAsBegin&&(r.end=r.begin),r.end||r.endsWithParent||(r.end=/\B|\b/),r.end&&(a.endRe=n(r.end)),a.terminatorEnd=p(r.end)||"",r.endsWithParent&&s.terminatorEnd&&(a.terminatorEnd+=(r.end?"|":"")+s.terminatorEnd)),r.illegal&&(a.illegalRe=n(r.illegal)),r.contains||(r.contains=[]),r.contains=[].concat(...r.contains.map((function(e){return function(e){e.variants&&!e.cachedVariants&&(e.cachedVariants=e.variants.map((function(t){return i(e,{variants:null},t)})));if(e.cachedVariants)return e.cachedVariants;if(W(e))return i(e,{starts:e.starts?i(e.starts):null});if(Object.isFrozen(e))return i(e);return e}("self"===e?r:e)}))),r.contains.forEach((function(e){t(e,a)})),r.starts&&t(r.starts,s),a.matcher=function(e){const t=new o;return e.contains.forEach((e=>t.addRule(e.begin,{rule:e,type:"begin"}))),e.terminatorEnd&&t.addRule(e.terminatorEnd,{type:"end"}),e.illegal&&t.addRule(e.illegal,{type:"illegal"}),t}(a),a}(e)}function W(e){return!!e&&(e.endsWithParent||W(e.starts))}function J(e){const t={props:["language","code","autodetect"],data:function(){return{detectedLanguage:"",unknownLanguage:!1}},computed:{className(){return this.unknownLanguage?"":"hljs "+this.detectedLanguage},highlighted(){if(!this.autoDetect&&!e.getLanguage(this.language))return console.warn(`The language "${this.language}" you specified could not be found.`),this.unknownLanguage=!0,s(this.code);let t={};return this.autoDetect?(t=e.highlightAuto(this.code),this.detectedLanguage=t.language):(t=e.highlight(this.language,this.code,this.ignoreIllegals),this.detectedLanguage=this.language),t.value},autoDetect(){return!this.language||(e=this.autodetect,Boolean(e||""===e));var e},ignoreIllegals:()=>!0},render(e){return e("pre",{},[e("code",{class:this.className,domProps:{innerHTML:this.highlighted}})])}};return{Component:t,VuePlugin:{install(e){e.component("highlightjs",t)}}}}const K={"after:highlightElement":({el:e,result:t,text:n})=>{const r=G(e);if(!r.length)return;const o=document.createElement("div");o.innerHTML=t.value,t.value=function(e,t,n){let r=0,o="";const i=[];function a(){return e.length&&t.length?e[0].offset!==t[0].offset?e[0].offset"}function c(e){o+=""}function u(e){("start"===e.event?l:c)(e.node)}for(;e.length||t.length;){let t=a();if(o+=s(n.substring(r,t[0].offset)),r=t[0].offset,t===e){i.reverse().forEach(c);do{u(t.splice(0,1)[0]),t=a()}while(t===e&&t.length&&t[0].offset===r);i.reverse().forEach(l)}else"start"===t[0].event?i.push(t[0].node):i.pop(),u(t.splice(0,1)[0])}return o+s(n.substr(r))}(r,G(o),n)}};function H(e){return e.nodeName.toLowerCase()}function G(e){const t=[];return function e(n,r){for(let o=n.firstChild;o;o=o.nextSibling)3===o.nodeType?r+=o.nodeValue.length:1===o.nodeType&&(t.push({event:"start",offset:r,node:o}),r=e(o,r),H(o).match(/br|hr|img|input/)||t.push({event:"stop",offset:r,node:o}));return r}(e,0),t}const Z={},Y=e=>{console.error(e)},X=(e,...t)=>{console.log(`WARN: ${e}`,...t)},Q=(e,t)=>{Z[`${e}/${t}`]||(console.log(`Deprecated as of ${e}. ${t}`),Z[`${e}/${t}`]=!0)},ee=s,te=i,ne=Symbol("nomatch");var re=function(e){const t=Object.create(null),r=Object.create(null),s=[];let i=!0;const a=/(^(<[^>]+>|\t|)+|\n)/gm,l="Could not find the language '{}', did you forget to load/include a language module?",c={disableAutodetect:!0,name:"Plain text",contains:[]};let p={noHighlightRe:/^(no-?highlight)$/i,languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:null,__emitter:u};function h(e){return p.noHighlightRe.test(e)}function f(e,t,n,r){let o="",s="";"object"==typeof t?(o=e,n=t.ignoreIllegals,s=t.language,r=void 0):(Q("10.7.0","highlight(lang, code, ...args) has been deprecated."),Q("10.7.0","Please use highlight(code, options) instead.\nhttps://github.com/highlightjs/highlight.js/issues/2277"),s=e,o=t);const i={code:o,language:s};O("before:highlight",i);const a=i.result?i.result:d(i.language,i.code,n,r);return a.code=i.code,O("after:highlight",a),a}function d(e,n,r,a){function c(e,t){const n=E.case_insensitive?t[0].toLowerCase():t[0];return Object.prototype.hasOwnProperty.call(e.keywords,n)&&e.keywords[n]}function u(){null!=j.subLanguage?function(){if(""===A)return;let e=null;if("string"==typeof j.subLanguage){if(!t[j.subLanguage])return void k.addText(A);e=d(j.subLanguage,A,!0,O[j.subLanguage]),O[j.subLanguage]=e.top}else e=m(A,j.subLanguage.length?j.subLanguage:null);j.relevance>0&&(C+=e.relevance),k.addSublanguage(e.emitter,e.language)}():function(){if(!j.keywords)return void k.addText(A);let e=0;j.keywordPatternRe.lastIndex=0;let t=j.keywordPatternRe.exec(A),n="";for(;t;){n+=A.substring(e,t.index);const r=c(j,t);if(r){const[e,o]=r;if(k.addText(n),n="",C+=o,e.startsWith("_"))n+=t[0];else{const n=E.classNameAliases[e]||e;k.addKeyword(t[0],n)}}else n+=t[0];e=j.keywordPatternRe.lastIndex,t=j.keywordPatternRe.exec(A)}n+=A.substr(e),k.addText(n)}(),A=""}function h(e){return e.className&&k.openNode(E.classNameAliases[e.className]||e.className),j=Object.create(e,{parent:{value:j}}),j}function f(e,t,n){let r=function(e,t){const n=e&&e.exec(t);return n&&0===n.index}(e.endRe,n);if(r){if(e["on:end"]){const n=new o(e);e["on:end"](t,n),n.isMatchIgnored&&(r=!1)}if(r){for(;e.endsParent&&e.parent;)e=e.parent;return e}}if(e.endsWithParent)return f(e.parent,t,n)}function g(e){return 0===j.matcher.regexIndex?(A+=e[0],1):(I=!0,0)}function y(e){const t=e[0],n=e.rule,r=new o(n),s=[n.__beforeBegin,n["on:begin"]];for(const n of s)if(n&&(n(e,r),r.isMatchIgnored))return g(t);return n&&n.endSameAsBegin&&(n.endRe=new RegExp(t.replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&"),"m")),n.skip?A+=t:(n.excludeBegin&&(A+=t),u(),n.returnBegin||n.excludeBegin||(A=t)),h(n),n.returnBegin?0:t.length}function v(e){const t=e[0],r=n.substr(e.index),o=f(j,e,r);if(!o)return ne;const s=j;s.skip?A+=t:(s.returnEnd||s.excludeEnd||(A+=t),u(),s.excludeEnd&&(A=t));do{j.className&&k.closeNode(),j.skip||j.subLanguage||(C+=j.relevance),j=j.parent}while(j!==o.parent);return o.starts&&(o.endSameAsBegin&&(o.starts.endRe=o.endRe),h(o.starts)),s.returnEnd?0:t.length}let b={};function w(t,o){const s=o&&o[0];if(A+=t,null==s)return u(),0;if("begin"===b.type&&"end"===o.type&&b.index===o.index&&""===s){if(A+=n.slice(o.index,o.index+1),!i){const t=new Error("0 width match regex");throw t.languageName=e,t.badRule=b.rule,t}return 1}if(b=o,"begin"===o.type)return y(o);if("illegal"===o.type&&!r){const e=new Error('Illegal lexeme "'+s+'" for mode "'+(j.className||"")+'"');throw e.mode=j,e}if("end"===o.type){const e=v(o);if(e!==ne)return e}if("illegal"===o.type&&""===s)return 1;if(N>1e5&&N>3*o.index){throw new Error("potential infinite loop, way more iterations than matches")}return A+=s,s.length}const E=S(e);if(!E)throw Y(l.replace("{}",e)),new Error('Unknown language: "'+e+'"');const x=V(E,{plugins:s});let _="",j=a||x;const O={},k=new p.__emitter(p);!function(){const e=[];for(let t=j;t!==E;t=t.parent)t.className&&e.unshift(t.className);e.forEach((e=>k.openNode(e)))}();let A="",C=0,P=0,N=0,I=!1;try{for(j.matcher.considerAll();;){N++,I?I=!1:j.matcher.considerAll(),j.matcher.lastIndex=P;const e=j.matcher.exec(n);if(!e)break;const t=w(n.substring(P,e.index),e);P=e.index+t}return w(n.substr(P)),k.closeAllNodes(),k.finalize(),_=k.toHTML(),{relevance:Math.floor(C),value:_,language:e,illegal:!1,emitter:k,top:j}}catch(t){if(t.message&&t.message.includes("Illegal"))return{illegal:!0,illegalBy:{msg:t.message,context:n.slice(P-100,P+100),mode:t.mode},sofar:_,relevance:0,value:ee(n),emitter:k};if(i)return{illegal:!1,relevance:0,value:ee(n),emitter:k,language:e,top:j,errorRaised:t};throw t}}function m(e,n){n=n||p.languages||Object.keys(t);const r=function(e){const t={relevance:0,emitter:new p.__emitter(p),value:ee(e),illegal:!1,top:c};return t.emitter.addText(e),t}(e),o=n.filter(S).filter(j).map((t=>d(t,e,!1)));o.unshift(r);const s=o.sort(((e,t)=>{if(e.relevance!==t.relevance)return t.relevance-e.relevance;if(e.language&&t.language){if(S(e.language).supersetOf===t.language)return 1;if(S(t.language).supersetOf===e.language)return-1}return 0})),[i,a]=s,l=i;return l.second_best=a,l}const g={"before:highlightElement":({el:e})=>{p.useBR&&(e.innerHTML=e.innerHTML.replace(/\n/g,"").replace(//g,"\n"))},"after:highlightElement":({result:e})=>{p.useBR&&(e.value=e.value.replace(/\n/g,"
"))}},y=/^(<[^>]+>|\t)+/gm,v={"after:highlightElement":({result:e})=>{p.tabReplace&&(e.value=e.value.replace(y,(e=>e.replace(/\t/g,p.tabReplace))))}};function b(e){let t=null;const n=function(e){let t=e.className+" ";t+=e.parentNode?e.parentNode.className:"";const n=p.languageDetectRe.exec(t);if(n){const t=S(n[1]);return t||(X(l.replace("{}",n[1])),X("Falling back to no-highlight mode for this block.",e)),t?n[1]:"no-highlight"}return t.split(/\s+/).find((e=>h(e)||S(e)))}(e);if(h(n))return;O("before:highlightElement",{el:e,language:n}),t=e;const o=t.textContent,s=n?f(o,{language:n,ignoreIllegals:!0}):m(o);O("after:highlightElement",{el:e,result:s,text:o}),e.innerHTML=s.value,function(e,t,n){const o=t?r[t]:n;e.classList.add("hljs"),o&&e.classList.add(o)}(e,n,s.language),e.result={language:s.language,re:s.relevance,relavance:s.relevance},s.second_best&&(e.second_best={language:s.second_best.language,re:s.second_best.relevance,relavance:s.second_best.relevance})}const w=()=>{if(w.called)return;w.called=!0,Q("10.6.0","initHighlighting() is deprecated. Use highlightAll() instead.");document.querySelectorAll("pre code").forEach(b)};let E=!1;function x(){if("loading"===document.readyState)return void(E=!0);document.querySelectorAll("pre code").forEach(b)}function S(e){return e=(e||"").toLowerCase(),t[e]||t[r[e]]}function _(e,{languageName:t}){"string"==typeof e&&(e=[e]),e.forEach((e=>{r[e.toLowerCase()]=t}))}function j(e){const t=S(e);return t&&!t.disableAutodetect}function O(e,t){const n=e;s.forEach((function(e){e[n]&&e[n](t)}))}"undefined"!=typeof window&&window.addEventListener&&window.addEventListener("DOMContentLoaded",(function(){E&&x()}),!1),Object.assign(e,{highlight:f,highlightAuto:m,highlightAll:x,fixMarkup:function(e){return Q("10.2.0","fixMarkup will be removed entirely in v11.0"),Q("10.2.0","Please see https://github.com/highlightjs/highlight.js/issues/2534"),t=e,p.tabReplace||p.useBR?t.replace(a,(e=>"\n"===e?p.useBR?"
":e:p.tabReplace?e.replace(/\t/g,p.tabReplace):e)):t;var t},highlightElement:b,highlightBlock:function(e){return Q("10.7.0","highlightBlock will be removed entirely in v12.0"),Q("10.7.0","Please use highlightElement now."),b(e)},configure:function(e){e.useBR&&(Q("10.3.0","'useBR' will be removed entirely in v11.0"),Q("10.3.0","Please see https://github.com/highlightjs/highlight.js/issues/2559")),p=te(p,e)},initHighlighting:w,initHighlightingOnLoad:function(){Q("10.6.0","initHighlightingOnLoad() is deprecated. Use highlightAll() instead."),E=!0},registerLanguage:function(n,r){let o=null;try{o=r(e)}catch(e){if(Y("Language definition for '{}' could not be registered.".replace("{}",n)),!i)throw e;Y(e),o=c}o.name||(o.name=n),t[n]=o,o.rawDefinition=r.bind(null,e),o.aliases&&_(o.aliases,{languageName:n})},unregisterLanguage:function(e){delete t[e];for(const t of Object.keys(r))r[t]===e&&delete r[t]},listLanguages:function(){return Object.keys(t)},getLanguage:S,registerAliases:_,requireLanguage:function(e){Q("10.4.0","requireLanguage will be removed entirely in v11."),Q("10.4.0","Please see https://github.com/highlightjs/highlight.js/pull/2844");const t=S(e);if(t)return t;throw new Error("The '{}' language is required, but not loaded.".replace("{}",e))},autoDetection:j,inherit:te,addPlugin:function(e){!function(e){e["before:highlightBlock"]&&!e["before:highlightElement"]&&(e["before:highlightElement"]=t=>{e["before:highlightBlock"](Object.assign({block:t.el},t))}),e["after:highlightBlock"]&&!e["after:highlightElement"]&&(e["after:highlightElement"]=t=>{e["after:highlightBlock"](Object.assign({block:t.el},t))})}(e),s.push(e)},vuePlugin:J(e).VuePlugin}),e.debugMode=function(){i=!1},e.safeMode=function(){i=!0},e.versionString="10.7.3";for(const e in R)"object"==typeof R[e]&&n(R[e]);return Object.assign(e,R),e.addPlugin(g),e.addPlugin(K),e.addPlugin(v),e}({});e.exports=re},61519:e=>{function t(...e){return e.map((e=>{return(t=e)?"string"==typeof t?t:t.source:null;var t})).join("")}e.exports=function(e){const n={},r={begin:/\$\{/,end:/\}/,contains:["self",{begin:/:-/,contains:[n]}]};Object.assign(n,{className:"variable",variants:[{begin:t(/\$[\w\d#@][\w\d_]*/,"(?![\\w\\d])(?![$])")},r]});const o={className:"subst",begin:/\$\(/,end:/\)/,contains:[e.BACKSLASH_ESCAPE]},s={begin:/<<-?\s*(?=\w+)/,starts:{contains:[e.END_SAME_AS_BEGIN({begin:/(\w+)/,end:/(\w+)/,className:"string"})]}},i={className:"string",begin:/"/,end:/"/,contains:[e.BACKSLASH_ESCAPE,n,o]};o.contains.push(i);const a={begin:/\$\(\(/,end:/\)\)/,contains:[{begin:/\d+#[0-9a-f]+/,className:"number"},e.NUMBER_MODE,n]},l=e.SHEBANG({binary:`(${["fish","bash","zsh","sh","csh","ksh","tcsh","dash","scsh"].join("|")})`,relevance:10}),c={className:"function",begin:/\w[\w\d_]*\s*\(\s*\)\s*\{/,returnBegin:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/\w[\w\d_]*/})],relevance:0};return{name:"Bash",aliases:["sh","zsh"],keywords:{$pattern:/\b[a-z._-]+\b/,keyword:"if then else elif fi for while in do done case esac function",literal:"true false",built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp"},contains:[l,e.SHEBANG(),c,a,e.HASH_COMMENT_MODE,s,i,{className:"",begin:/\\"/},{className:"string",begin:/'/,end:/'/},n]}}},30786:e=>{function t(...e){return e.map((e=>{return(t=e)?"string"==typeof t?t:t.source:null;var t})).join("")}e.exports=function(e){const n="HTTP/(2|1\\.[01])",r={className:"attribute",begin:t("^",/[A-Za-z][A-Za-z0-9-]*/,"(?=\\:\\s)"),starts:{contains:[{className:"punctuation",begin:/: /,relevance:0,starts:{end:"$",relevance:0}}]}},o=[r,{begin:"\\n\\n",starts:{subLanguage:[],endsWithParent:!0}}];return{name:"HTTP",aliases:["https"],illegal:/\S/,contains:[{begin:"^(?="+n+" \\d{3})",end:/$/,contains:[{className:"meta",begin:n},{className:"number",begin:"\\b\\d{3}\\b"}],starts:{end:/\b\B/,illegal:/\S/,contains:o}},{begin:"(?=^[A-Z]+ (.*?) "+n+"$)",end:/$/,contains:[{className:"string",begin:" ",end:" ",excludeBegin:!0,excludeEnd:!0},{className:"meta",begin:n},{className:"keyword",begin:"[A-Z]+"}],starts:{end:/\b\B/,illegal:/\S/,contains:o}},e.inherit(r,{relevance:0})]}}},96344:e=>{const t="[A-Za-z$_][0-9A-Za-z$_]*",n=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],r=["true","false","null","undefined","NaN","Infinity"],o=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer","BigInt64Array","BigUint64Array","BigInt"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"]);function s(e){return i("(?=",e,")")}function i(...e){return e.map((e=>{return(t=e)?"string"==typeof t?t:t.source:null;var t})).join("")}e.exports=function(e){const a=t,l="<>",c="",u={begin:/<[A-Za-z0-9\\._:-]+/,end:/\/[A-Za-z0-9\\._:-]+>|\/>/,isTrulyOpeningTag:(e,t)=>{const n=e[0].length+e.index,r=e.input[n];"<"!==r?">"===r&&(((e,{after:t})=>{const n="",returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{begin:e.UNDERSCORE_IDENT_RE,relevance:0},{className:null,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:p,contains:S}]}]},{begin:/,/,relevance:0},{className:"",begin:/\s/,end:/\s*/,skip:!0},{variants:[{begin:l,end:c},{begin:u.begin,"on:begin":u.isTrulyOpeningTag,end:u.end}],subLanguage:"xml",contains:[{begin:u.begin,end:u.end,skip:!0,contains:["self"]}]}],relevance:0},{className:"function",beginKeywords:"function",end:/[{;]/,excludeEnd:!0,keywords:p,contains:["self",e.inherit(e.TITLE_MODE,{begin:a}),_],illegal:/%/},{beginKeywords:"while if switch catch for"},{className:"function",begin:e.UNDERSCORE_IDENT_RE+"\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)\\s*\\{",returnBegin:!0,contains:[_,e.inherit(e.TITLE_MODE,{begin:a})]},{variants:[{begin:"\\."+a},{begin:"\\$"+a}],relevance:0},{className:"class",beginKeywords:"class",end:/[{;=]/,excludeEnd:!0,illegal:/[:"[\]]/,contains:[{beginKeywords:"extends"},e.UNDERSCORE_TITLE_MODE]},{begin:/\b(?=constructor)/,end:/[{;]/,excludeEnd:!0,contains:[e.inherit(e.TITLE_MODE,{begin:a}),"self",_]},{begin:"(get|set)\\s+(?="+a+"\\()",end:/\{/,keywords:"get set",contains:[e.inherit(e.TITLE_MODE,{begin:a}),{begin:/\(\)/},_]},{begin:/\$[(.]/}]}}},82026:e=>{e.exports=function(e){const t={literal:"true false null"},n=[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE],r=[e.QUOTE_STRING_MODE,e.C_NUMBER_MODE],o={end:",",endsWithParent:!0,excludeEnd:!0,contains:r,keywords:t},s={begin:/\{/,end:/\}/,contains:[{className:"attr",begin:/"/,end:/"/,contains:[e.BACKSLASH_ESCAPE],illegal:"\\n"},e.inherit(o,{begin:/:/})].concat(n),illegal:"\\S"},i={begin:"\\[",end:"\\]",contains:[e.inherit(o)],illegal:"\\S"};return r.push(s,i),n.forEach((function(e){r.push(e)})),{name:"JSON",contains:r,keywords:t,illegal:"\\S"}}},66336:e=>{e.exports=function(e){const t={$pattern:/-?[A-z\.\-]+\b/,keyword:"if else foreach return do while until elseif begin for trap data dynamicparam end break throw param continue finally in switch exit filter try process catch hidden static parameter",built_in:"ac asnp cat cd CFS chdir clc clear clhy cli clp cls clv cnsn compare copy cp cpi cpp curl cvpa dbp del diff dir dnsn ebp echo|0 epal epcsv epsn erase etsn exsn fc fhx fl ft fw gal gbp gc gcb gci gcm gcs gdr gerr ghy gi gin gjb gl gm gmo gp gps gpv group gsn gsnp gsv gtz gu gv gwmi h history icm iex ihy ii ipal ipcsv ipmo ipsn irm ise iwmi iwr kill lp ls man md measure mi mount move mp mv nal ndr ni nmo npssc nsn nv ogv oh popd ps pushd pwd r rbp rcjb rcsn rd rdr ren ri rjb rm rmdir rmo rni rnp rp rsn rsnp rujb rv rvpa rwmi sajb sal saps sasv sbp sc scb select set shcm si sl sleep sls sort sp spjb spps spsv start stz sujb sv swmi tee trcm type wget where wjb write"},n={begin:"`[\\s\\S]",relevance:0},r={className:"variable",variants:[{begin:/\$\B/},{className:"keyword",begin:/\$this/},{begin:/\$[\w\d][\w\d_:]*/}]},o={className:"string",variants:[{begin:/"/,end:/"/},{begin:/@"/,end:/^"@/}],contains:[n,r,{className:"variable",begin:/\$[A-z]/,end:/[^A-z]/}]},s={className:"string",variants:[{begin:/'/,end:/'/},{begin:/@'/,end:/^'@/}]},i=e.inherit(e.COMMENT(null,null),{variants:[{begin:/#/,end:/$/},{begin:/<#/,end:/#>/}],contains:[{className:"doctag",variants:[{begin:/\.(synopsis|description|example|inputs|outputs|notes|link|component|role|functionality)/},{begin:/\.(parameter|forwardhelptargetname|forwardhelpcategory|remotehelprunspace|externalhelp)\s+\S+/}]}]}),a={className:"built_in",variants:[{begin:"(".concat("Add|Clear|Close|Copy|Enter|Exit|Find|Format|Get|Hide|Join|Lock|Move|New|Open|Optimize|Pop|Push|Redo|Remove|Rename|Reset|Resize|Search|Select|Set|Show|Skip|Split|Step|Switch|Undo|Unlock|Watch|Backup|Checkpoint|Compare|Compress|Convert|ConvertFrom|ConvertTo|Dismount|Edit|Expand|Export|Group|Import|Initialize|Limit|Merge|Mount|Out|Publish|Restore|Save|Sync|Unpublish|Update|Approve|Assert|Build|Complete|Confirm|Deny|Deploy|Disable|Enable|Install|Invoke|Register|Request|Restart|Resume|Start|Stop|Submit|Suspend|Uninstall|Unregister|Wait|Debug|Measure|Ping|Repair|Resolve|Test|Trace|Connect|Disconnect|Read|Receive|Send|Write|Block|Grant|Protect|Revoke|Unblock|Unprotect|Use|ForEach|Sort|Tee|Where",")+(-)[\\w\\d]+")}]},l={className:"class",beginKeywords:"class enum",end:/\s*[{]/,excludeEnd:!0,relevance:0,contains:[e.TITLE_MODE]},c={className:"function",begin:/function\s+/,end:/\s*\{|$/,excludeEnd:!0,returnBegin:!0,relevance:0,contains:[{begin:"function",relevance:0,className:"keyword"},{className:"title",begin:/\w[\w\d]*((-)[\w\d]+)*/,relevance:0},{begin:/\(/,end:/\)/,className:"params",relevance:0,contains:[r]}]},u={begin:/using\s/,end:/$/,returnBegin:!0,contains:[o,s,{className:"keyword",begin:/(using|assembly|command|module|namespace|type)/}]},p={variants:[{className:"operator",begin:"(".concat("-and|-as|-band|-bnot|-bor|-bxor|-casesensitive|-ccontains|-ceq|-cge|-cgt|-cle|-clike|-clt|-cmatch|-cne|-cnotcontains|-cnotlike|-cnotmatch|-contains|-creplace|-csplit|-eq|-exact|-f|-file|-ge|-gt|-icontains|-ieq|-ige|-igt|-ile|-ilike|-ilt|-imatch|-in|-ine|-inotcontains|-inotlike|-inotmatch|-ireplace|-is|-isnot|-isplit|-join|-le|-like|-lt|-match|-ne|-not|-notcontains|-notin|-notlike|-notmatch|-or|-regex|-replace|-shl|-shr|-split|-wildcard|-xor",")\\b")},{className:"literal",begin:/(-)[\w\d]+/,relevance:0}]},h={className:"function",begin:/\[.*\]\s*[\w]+[ ]??\(/,end:/$/,returnBegin:!0,relevance:0,contains:[{className:"keyword",begin:"(".concat(t.keyword.toString().replace(/\s/g,"|"),")\\b"),endsParent:!0,relevance:0},e.inherit(e.TITLE_MODE,{endsParent:!0})]},f=[h,i,n,e.NUMBER_MODE,o,s,a,r,{className:"literal",begin:/\$(null|true|false)\b/},{className:"selector-tag",begin:/@\B/,relevance:0}],d={begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0,relevance:0,contains:[].concat("self",f,{begin:"("+["string","char","byte","int","long","bool","decimal","single","double","DateTime","xml","array","hashtable","void"].join("|")+")",className:"built_in",relevance:0},{className:"type",begin:/[\.\w\d]+/,relevance:0})};return h.contains.unshift(d),{name:"PowerShell",aliases:["ps","ps1"],case_insensitive:!0,keywords:t,contains:f.concat(l,c,u,p,d)}}},42157:e=>{function t(e){return e?"string"==typeof e?e:e.source:null}function n(e){return r("(?=",e,")")}function r(...e){return e.map((e=>t(e))).join("")}function o(...e){return"("+e.map((e=>t(e))).join("|")+")"}e.exports=function(e){const t=r(/[A-Z_]/,r("(",/[A-Z0-9_.-]*:/,")?"),/[A-Z0-9_.-]*/),s={className:"symbol",begin:/&[a-z]+;|&#[0-9]+;|&#x[a-f0-9]+;/},i={begin:/\s/,contains:[{className:"meta-keyword",begin:/#?[a-z_][a-z1-9_-]+/,illegal:/\n/}]},a=e.inherit(i,{begin:/\(/,end:/\)/}),l=e.inherit(e.APOS_STRING_MODE,{className:"meta-string"}),c=e.inherit(e.QUOTE_STRING_MODE,{className:"meta-string"}),u={endsWithParent:!0,illegal:/`]+/}]}]}]};return{name:"HTML, XML",aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist","wsf","svg"],case_insensitive:!0,contains:[{className:"meta",begin://,relevance:10,contains:[i,c,l,a,{begin:/\[/,end:/\]/,contains:[{className:"meta",begin://,contains:[i,a,c,l]}]}]},e.COMMENT(//,{relevance:10}),{begin://,relevance:10},s,{className:"meta",begin:/<\?xml/,end:/\?>/,relevance:10},{className:"tag",begin:/)/,end:/>/,keywords:{name:"style"},contains:[u],starts:{end:/<\/style>/,returnEnd:!0,subLanguage:["css","xml"]}},{className:"tag",begin:/)/,end:/>/,keywords:{name:"script"},contains:[u],starts:{end:/<\/script>/,returnEnd:!0,subLanguage:["javascript","handlebars","xml"]}},{className:"tag",begin:/<>|<\/>/},{className:"tag",begin:r(//,/>/,/\s/)))),end:/\/?>/,contains:[{className:"name",begin:t,relevance:0,starts:u}]},{className:"tag",begin:r(/<\//,n(r(t,/>/))),contains:[{className:"name",begin:t,relevance:0},{begin:/>/,relevance:0,endsParent:!0}]}]}}},54587:e=>{e.exports=function(e){var t="true false yes no null",n="[\\w#;/?:@&=+$,.~*'()[\\]]+",r={className:"string",relevance:0,variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/\S+/}],contains:[e.BACKSLASH_ESCAPE,{className:"template-variable",variants:[{begin:/\{\{/,end:/\}\}/},{begin:/%\{/,end:/\}/}]}]},o=e.inherit(r,{variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/[^\s,{}[\]]+/}]}),s={className:"number",begin:"\\b[0-9]{4}(-[0-9][0-9]){0,2}([Tt \\t][0-9][0-9]?(:[0-9][0-9]){2})?(\\.[0-9]*)?([ \\t])*(Z|[-+][0-9][0-9]?(:[0-9][0-9])?)?\\b"},i={end:",",endsWithParent:!0,excludeEnd:!0,keywords:t,relevance:0},a={begin:/\{/,end:/\}/,contains:[i],illegal:"\\n",relevance:0},l={begin:"\\[",end:"\\]",contains:[i],illegal:"\\n",relevance:0},c=[{className:"attr",variants:[{begin:"\\w[\\w :\\/.-]*:(?=[ \t]|$)"},{begin:'"\\w[\\w :\\/.-]*":(?=[ \t]|$)'},{begin:"'\\w[\\w :\\/.-]*':(?=[ \t]|$)"}]},{className:"meta",begin:"^---\\s*$",relevance:10},{className:"string",begin:"[\\|>]([1-9]?[+-])?[ ]*\\n( +)[^ ][^\\n]*\\n(\\2[^\\n]+\\n?)*"},{begin:"<%[%=-]?",end:"[%-]?%>",subLanguage:"ruby",excludeBegin:!0,excludeEnd:!0,relevance:0},{className:"type",begin:"!\\w+!"+n},{className:"type",begin:"!<"+n+">"},{className:"type",begin:"!"+n},{className:"type",begin:"!!"+n},{className:"meta",begin:"&"+e.UNDERSCORE_IDENT_RE+"$"},{className:"meta",begin:"\\*"+e.UNDERSCORE_IDENT_RE+"$"},{className:"bullet",begin:"-(?=[ ]|$)",relevance:0},e.HASH_COMMENT_MODE,{beginKeywords:t,keywords:{literal:t}},s,{className:"number",begin:e.C_NUMBER_RE+"\\b",relevance:0},a,l,r],u=[...c];return u.pop(),u.push(o),i.contains=u,{name:"YAML",case_insensitive:!0,aliases:["yml"],contains:c}}},8679:(e,t,n)=>{"use strict";var r=n(59864),o={childContextTypes:!0,contextType:!0,contextTypes:!0,defaultProps:!0,displayName:!0,getDefaultProps:!0,getDerivedStateFromError:!0,getDerivedStateFromProps:!0,mixins:!0,propTypes:!0,type:!0},s={name:!0,length:!0,prototype:!0,caller:!0,callee:!0,arguments:!0,arity:!0},i={$$typeof:!0,compare:!0,defaultProps:!0,displayName:!0,propTypes:!0,type:!0},a={};function l(e){return r.isMemo(e)?i:a[e.$$typeof]||o}a[r.ForwardRef]={$$typeof:!0,render:!0,defaultProps:!0,displayName:!0,propTypes:!0},a[r.Memo]=i;var c=Object.defineProperty,u=Object.getOwnPropertyNames,p=Object.getOwnPropertySymbols,h=Object.getOwnPropertyDescriptor,f=Object.getPrototypeOf,d=Object.prototype;e.exports=function e(t,n,r){if("string"!=typeof n){if(d){var o=f(n);o&&o!==d&&e(t,o,r)}var i=u(n);p&&(i=i.concat(p(n)));for(var a=l(t),m=l(n),g=0;g{t.read=function(e,t,n,r,o){var s,i,a=8*o-r-1,l=(1<>1,u=-7,p=n?o-1:0,h=n?-1:1,f=e[t+p];for(p+=h,s=f&(1<<-u)-1,f>>=-u,u+=a;u>0;s=256*s+e[t+p],p+=h,u-=8);for(i=s&(1<<-u)-1,s>>=-u,u+=r;u>0;i=256*i+e[t+p],p+=h,u-=8);if(0===s)s=1-c;else{if(s===l)return i?NaN:1/0*(f?-1:1);i+=Math.pow(2,r),s-=c}return(f?-1:1)*i*Math.pow(2,s-r)},t.write=function(e,t,n,r,o,s){var i,a,l,c=8*s-o-1,u=(1<>1,h=23===o?Math.pow(2,-24)-Math.pow(2,-77):0,f=r?0:s-1,d=r?1:-1,m=t<0||0===t&&1/t<0?1:0;for(t=Math.abs(t),isNaN(t)||t===1/0?(a=isNaN(t)?1:0,i=u):(i=Math.floor(Math.log(t)/Math.LN2),t*(l=Math.pow(2,-i))<1&&(i--,l*=2),(t+=i+p>=1?h/l:h*Math.pow(2,1-p))*l>=2&&(i++,l/=2),i+p>=u?(a=0,i=u):i+p>=1?(a=(t*l-1)*Math.pow(2,o),i+=p):(a=t*Math.pow(2,p-1)*Math.pow(2,o),i=0));o>=8;e[n+f]=255&a,f+=d,a/=256,o-=8);for(i=i<0;e[n+f]=255&i,f+=d,i/=256,c-=8);e[n+f-d]|=128*m}},43393:function(e){e.exports=function(){"use strict";var e=Array.prototype.slice;function t(e,t){t&&(e.prototype=Object.create(t.prototype)),e.prototype.constructor=e}function n(e){return i(e)?e:K(e)}function r(e){return a(e)?e:H(e)}function o(e){return l(e)?e:G(e)}function s(e){return i(e)&&!c(e)?e:Z(e)}function i(e){return!(!e||!e[p])}function a(e){return!(!e||!e[h])}function l(e){return!(!e||!e[f])}function c(e){return a(e)||l(e)}function u(e){return!(!e||!e[d])}t(r,n),t(o,n),t(s,n),n.isIterable=i,n.isKeyed=a,n.isIndexed=l,n.isAssociative=c,n.isOrdered=u,n.Keyed=r,n.Indexed=o,n.Set=s;var p="@@__IMMUTABLE_ITERABLE__@@",h="@@__IMMUTABLE_KEYED__@@",f="@@__IMMUTABLE_INDEXED__@@",d="@@__IMMUTABLE_ORDERED__@@",m="delete",g=5,y=1<>>0;if(""+n!==t||4294967295===n)return NaN;t=n}return t<0?O(e)+t:t}function A(){return!0}function C(e,t,n){return(0===e||void 0!==n&&e<=-n)&&(void 0===t||void 0!==n&&t>=n)}function P(e,t){return I(e,t,0)}function N(e,t){return I(e,t,t)}function I(e,t,n){return void 0===e?n:e<0?Math.max(0,t+e):void 0===t?e:Math.min(t,e)}var T=0,R=1,M=2,D="function"==typeof Symbol&&Symbol.iterator,F="@@iterator",L=D||F;function B(e){this.next=e}function $(e,t,n,r){var o=0===e?t:1===e?n:[t,n];return r?r.value=o:r={value:o,done:!1},r}function q(){return{value:void 0,done:!0}}function U(e){return!!W(e)}function z(e){return e&&"function"==typeof e.next}function V(e){var t=W(e);return t&&t.call(e)}function W(e){var t=e&&(D&&e[D]||e[F]);if("function"==typeof t)return t}function J(e){return e&&"number"==typeof e.length}function K(e){return null==e?ie():i(e)?e.toSeq():ce(e)}function H(e){return null==e?ie().toKeyedSeq():i(e)?a(e)?e.toSeq():e.fromEntrySeq():ae(e)}function G(e){return null==e?ie():i(e)?a(e)?e.entrySeq():e.toIndexedSeq():le(e)}function Z(e){return(null==e?ie():i(e)?a(e)?e.entrySeq():e:le(e)).toSetSeq()}B.prototype.toString=function(){return"[Iterator]"},B.KEYS=T,B.VALUES=R,B.ENTRIES=M,B.prototype.inspect=B.prototype.toSource=function(){return this.toString()},B.prototype[L]=function(){return this},t(K,n),K.of=function(){return K(arguments)},K.prototype.toSeq=function(){return this},K.prototype.toString=function(){return this.__toString("Seq {","}")},K.prototype.cacheResult=function(){return!this._cache&&this.__iterateUncached&&(this._cache=this.entrySeq().toArray(),this.size=this._cache.length),this},K.prototype.__iterate=function(e,t){return pe(this,e,t,!0)},K.prototype.__iterator=function(e,t){return he(this,e,t,!0)},t(H,K),H.prototype.toKeyedSeq=function(){return this},t(G,K),G.of=function(){return G(arguments)},G.prototype.toIndexedSeq=function(){return this},G.prototype.toString=function(){return this.__toString("Seq [","]")},G.prototype.__iterate=function(e,t){return pe(this,e,t,!1)},G.prototype.__iterator=function(e,t){return he(this,e,t,!1)},t(Z,K),Z.of=function(){return Z(arguments)},Z.prototype.toSetSeq=function(){return this},K.isSeq=se,K.Keyed=H,K.Set=Z,K.Indexed=G;var Y,X,Q,ee="@@__IMMUTABLE_SEQ__@@";function te(e){this._array=e,this.size=e.length}function ne(e){var t=Object.keys(e);this._object=e,this._keys=t,this.size=t.length}function re(e){this._iterable=e,this.size=e.length||e.size}function oe(e){this._iterator=e,this._iteratorCache=[]}function se(e){return!(!e||!e[ee])}function ie(){return Y||(Y=new te([]))}function ae(e){var t=Array.isArray(e)?new te(e).fromEntrySeq():z(e)?new oe(e).fromEntrySeq():U(e)?new re(e).fromEntrySeq():"object"==typeof e?new ne(e):void 0;if(!t)throw new TypeError("Expected Array or iterable object of [k, v] entries, or keyed object: "+e);return t}function le(e){var t=ue(e);if(!t)throw new TypeError("Expected Array or iterable object of values: "+e);return t}function ce(e){var t=ue(e)||"object"==typeof e&&new ne(e);if(!t)throw new TypeError("Expected Array or iterable object of values, or keyed object: "+e);return t}function ue(e){return J(e)?new te(e):z(e)?new oe(e):U(e)?new re(e):void 0}function pe(e,t,n,r){var o=e._cache;if(o){for(var s=o.length-1,i=0;i<=s;i++){var a=o[n?s-i:i];if(!1===t(a[1],r?a[0]:i,e))return i+1}return i}return e.__iterateUncached(t,n)}function he(e,t,n,r){var o=e._cache;if(o){var s=o.length-1,i=0;return new B((function(){var e=o[n?s-i:i];return i++>s?q():$(t,r?e[0]:i-1,e[1])}))}return e.__iteratorUncached(t,n)}function fe(e,t){return t?de(t,e,"",{"":e}):me(e)}function de(e,t,n,r){return Array.isArray(t)?e.call(r,n,G(t).map((function(n,r){return de(e,n,r,t)}))):ge(t)?e.call(r,n,H(t).map((function(n,r){return de(e,n,r,t)}))):t}function me(e){return Array.isArray(e)?G(e).map(me).toList():ge(e)?H(e).map(me).toMap():e}function ge(e){return e&&(e.constructor===Object||void 0===e.constructor)}function ye(e,t){if(e===t||e!=e&&t!=t)return!0;if(!e||!t)return!1;if("function"==typeof e.valueOf&&"function"==typeof t.valueOf){if((e=e.valueOf())===(t=t.valueOf())||e!=e&&t!=t)return!0;if(!e||!t)return!1}return!("function"!=typeof e.equals||"function"!=typeof t.equals||!e.equals(t))}function ve(e,t){if(e===t)return!0;if(!i(t)||void 0!==e.size&&void 0!==t.size&&e.size!==t.size||void 0!==e.__hash&&void 0!==t.__hash&&e.__hash!==t.__hash||a(e)!==a(t)||l(e)!==l(t)||u(e)!==u(t))return!1;if(0===e.size&&0===t.size)return!0;var n=!c(e);if(u(e)){var r=e.entries();return t.every((function(e,t){var o=r.next().value;return o&&ye(o[1],e)&&(n||ye(o[0],t))}))&&r.next().done}var o=!1;if(void 0===e.size)if(void 0===t.size)"function"==typeof e.cacheResult&&e.cacheResult();else{o=!0;var s=e;e=t,t=s}var p=!0,h=t.__iterate((function(t,r){if(n?!e.has(t):o?!ye(t,e.get(r,b)):!ye(e.get(r,b),t))return p=!1,!1}));return p&&e.size===h}function be(e,t){if(!(this instanceof be))return new be(e,t);if(this._value=e,this.size=void 0===t?1/0:Math.max(0,t),0===this.size){if(X)return X;X=this}}function we(e,t){if(!e)throw new Error(t)}function Ee(e,t,n){if(!(this instanceof Ee))return new Ee(e,t,n);if(we(0!==n,"Cannot step a Range by 0"),e=e||0,void 0===t&&(t=1/0),n=void 0===n?1:Math.abs(n),tr?q():$(e,o,n[t?r-o++:o++])}))},t(ne,H),ne.prototype.get=function(e,t){return void 0===t||this.has(e)?this._object[e]:t},ne.prototype.has=function(e){return this._object.hasOwnProperty(e)},ne.prototype.__iterate=function(e,t){for(var n=this._object,r=this._keys,o=r.length-1,s=0;s<=o;s++){var i=r[t?o-s:s];if(!1===e(n[i],i,this))return s+1}return s},ne.prototype.__iterator=function(e,t){var n=this._object,r=this._keys,o=r.length-1,s=0;return new B((function(){var i=r[t?o-s:s];return s++>o?q():$(e,i,n[i])}))},ne.prototype[d]=!0,t(re,G),re.prototype.__iterateUncached=function(e,t){if(t)return this.cacheResult().__iterate(e,t);var n=V(this._iterable),r=0;if(z(n))for(var o;!(o=n.next()).done&&!1!==e(o.value,r++,this););return r},re.prototype.__iteratorUncached=function(e,t){if(t)return this.cacheResult().__iterator(e,t);var n=V(this._iterable);if(!z(n))return new B(q);var r=0;return new B((function(){var t=n.next();return t.done?t:$(e,r++,t.value)}))},t(oe,G),oe.prototype.__iterateUncached=function(e,t){if(t)return this.cacheResult().__iterate(e,t);for(var n,r=this._iterator,o=this._iteratorCache,s=0;s=r.length){var t=n.next();if(t.done)return t;r[o]=t.value}return $(e,o,r[o++])}))},t(be,G),be.prototype.toString=function(){return 0===this.size?"Repeat []":"Repeat [ "+this._value+" "+this.size+" times ]"},be.prototype.get=function(e,t){return this.has(e)?this._value:t},be.prototype.includes=function(e){return ye(this._value,e)},be.prototype.slice=function(e,t){var n=this.size;return C(e,t,n)?this:new be(this._value,N(t,n)-P(e,n))},be.prototype.reverse=function(){return this},be.prototype.indexOf=function(e){return ye(this._value,e)?0:-1},be.prototype.lastIndexOf=function(e){return ye(this._value,e)?this.size:-1},be.prototype.__iterate=function(e,t){for(var n=0;n=0&&t=0&&nn?q():$(e,s++,i)}))},Ee.prototype.equals=function(e){return e instanceof Ee?this._start===e._start&&this._end===e._end&&this._step===e._step:ve(this,e)},t(xe,n),t(Se,xe),t(_e,xe),t(je,xe),xe.Keyed=Se,xe.Indexed=_e,xe.Set=je;var Oe="function"==typeof Math.imul&&-2===Math.imul(4294967295,2)?Math.imul:function(e,t){var n=65535&(e|=0),r=65535&(t|=0);return n*r+((e>>>16)*r+n*(t>>>16)<<16>>>0)|0};function ke(e){return e>>>1&1073741824|3221225471&e}function Ae(e){if(!1===e||null==e)return 0;if("function"==typeof e.valueOf&&(!1===(e=e.valueOf())||null==e))return 0;if(!0===e)return 1;var t=typeof e;if("number"===t){if(e!=e||e===1/0)return 0;var n=0|e;for(n!==e&&(n^=4294967295*e);e>4294967295;)n^=e/=4294967295;return ke(n)}if("string"===t)return e.length>Be?Ce(e):Pe(e);if("function"==typeof e.hashCode)return e.hashCode();if("object"===t)return Ne(e);if("function"==typeof e.toString)return Pe(e.toString());throw new Error("Value type "+t+" cannot be hashed.")}function Ce(e){var t=Ue[e];return void 0===t&&(t=Pe(e),qe===$e&&(qe=0,Ue={}),qe++,Ue[e]=t),t}function Pe(e){for(var t=0,n=0;n0)switch(e.nodeType){case 1:return e.uniqueID;case 9:return e.documentElement&&e.documentElement.uniqueID}}var Me,De="function"==typeof WeakMap;De&&(Me=new WeakMap);var Fe=0,Le="__immutablehash__";"function"==typeof Symbol&&(Le=Symbol(Le));var Be=16,$e=255,qe=0,Ue={};function ze(e){we(e!==1/0,"Cannot perform this action with an infinite size.")}function Ve(e){return null==e?ot():We(e)&&!u(e)?e:ot().withMutations((function(t){var n=r(e);ze(n.size),n.forEach((function(e,n){return t.set(n,e)}))}))}function We(e){return!(!e||!e[Ke])}t(Ve,Se),Ve.of=function(){var t=e.call(arguments,0);return ot().withMutations((function(e){for(var n=0;n=t.length)throw new Error("Missing value for key: "+t[n]);e.set(t[n],t[n+1])}}))},Ve.prototype.toString=function(){return this.__toString("Map {","}")},Ve.prototype.get=function(e,t){return this._root?this._root.get(0,void 0,e,t):t},Ve.prototype.set=function(e,t){return st(this,e,t)},Ve.prototype.setIn=function(e,t){return this.updateIn(e,b,(function(){return t}))},Ve.prototype.remove=function(e){return st(this,e,b)},Ve.prototype.deleteIn=function(e){return this.updateIn(e,(function(){return b}))},Ve.prototype.update=function(e,t,n){return 1===arguments.length?e(this):this.updateIn([e],t,n)},Ve.prototype.updateIn=function(e,t,n){n||(n=t,t=void 0);var r=gt(this,xn(e),t,n);return r===b?void 0:r},Ve.prototype.clear=function(){return 0===this.size?this:this.__ownerID?(this.size=0,this._root=null,this.__hash=void 0,this.__altered=!0,this):ot()},Ve.prototype.merge=function(){return ht(this,void 0,arguments)},Ve.prototype.mergeWith=function(t){return ht(this,t,e.call(arguments,1))},Ve.prototype.mergeIn=function(t){var n=e.call(arguments,1);return this.updateIn(t,ot(),(function(e){return"function"==typeof e.merge?e.merge.apply(e,n):n[n.length-1]}))},Ve.prototype.mergeDeep=function(){return ht(this,ft,arguments)},Ve.prototype.mergeDeepWith=function(t){var n=e.call(arguments,1);return ht(this,dt(t),n)},Ve.prototype.mergeDeepIn=function(t){var n=e.call(arguments,1);return this.updateIn(t,ot(),(function(e){return"function"==typeof e.mergeDeep?e.mergeDeep.apply(e,n):n[n.length-1]}))},Ve.prototype.sort=function(e){return Ut(pn(this,e))},Ve.prototype.sortBy=function(e,t){return Ut(pn(this,t,e))},Ve.prototype.withMutations=function(e){var t=this.asMutable();return e(t),t.wasAltered()?t.__ensureOwner(this.__ownerID):this},Ve.prototype.asMutable=function(){return this.__ownerID?this:this.__ensureOwner(new _)},Ve.prototype.asImmutable=function(){return this.__ensureOwner()},Ve.prototype.wasAltered=function(){return this.__altered},Ve.prototype.__iterator=function(e,t){return new et(this,e,t)},Ve.prototype.__iterate=function(e,t){var n=this,r=0;return this._root&&this._root.iterate((function(t){return r++,e(t[1],t[0],n)}),t),r},Ve.prototype.__ensureOwner=function(e){return e===this.__ownerID?this:e?rt(this.size,this._root,e,this.__hash):(this.__ownerID=e,this.__altered=!1,this)},Ve.isMap=We;var Je,Ke="@@__IMMUTABLE_MAP__@@",He=Ve.prototype;function Ge(e,t){this.ownerID=e,this.entries=t}function Ze(e,t,n){this.ownerID=e,this.bitmap=t,this.nodes=n}function Ye(e,t,n){this.ownerID=e,this.count=t,this.nodes=n}function Xe(e,t,n){this.ownerID=e,this.keyHash=t,this.entries=n}function Qe(e,t,n){this.ownerID=e,this.keyHash=t,this.entry=n}function et(e,t,n){this._type=t,this._reverse=n,this._stack=e._root&&nt(e._root)}function tt(e,t){return $(e,t[0],t[1])}function nt(e,t){return{node:e,index:0,__prev:t}}function rt(e,t,n,r){var o=Object.create(He);return o.size=e,o._root=t,o.__ownerID=n,o.__hash=r,o.__altered=!1,o}function ot(){return Je||(Je=rt(0))}function st(e,t,n){var r,o;if(e._root){var s=x(w),i=x(E);if(r=it(e._root,e.__ownerID,0,void 0,t,n,s,i),!i.value)return e;o=e.size+(s.value?n===b?-1:1:0)}else{if(n===b)return e;o=1,r=new Ge(e.__ownerID,[[t,n]])}return e.__ownerID?(e.size=o,e._root=r,e.__hash=void 0,e.__altered=!0,e):r?rt(o,r):ot()}function it(e,t,n,r,o,s,i,a){return e?e.update(t,n,r,o,s,i,a):s===b?e:(S(a),S(i),new Qe(t,r,[o,s]))}function at(e){return e.constructor===Qe||e.constructor===Xe}function lt(e,t,n,r,o){if(e.keyHash===r)return new Xe(t,r,[e.entry,o]);var s,i=(0===n?e.keyHash:e.keyHash>>>n)&v,a=(0===n?r:r>>>n)&v;return new Ze(t,1<>>=1)i[a]=1&n?t[s++]:void 0;return i[r]=o,new Ye(e,s+1,i)}function ht(e,t,n){for(var o=[],s=0;s>1&1431655765))+(e>>2&858993459))+(e>>4)&252645135,e+=e>>8,127&(e+=e>>16)}function vt(e,t,n,r){var o=r?e:j(e);return o[t]=n,o}function bt(e,t,n,r){var o=e.length+1;if(r&&t+1===o)return e[t]=n,e;for(var s=new Array(o),i=0,a=0;a=Et)return ct(e,l,r,o);var h=e&&e===this.ownerID,f=h?l:j(l);return p?a?c===u-1?f.pop():f[c]=f.pop():f[c]=[r,o]:f.push([r,o]),h?(this.entries=f,this):new Ge(e,f)}},Ze.prototype.get=function(e,t,n,r){void 0===t&&(t=Ae(n));var o=1<<((0===e?t:t>>>e)&v),s=this.bitmap;return 0==(s&o)?r:this.nodes[yt(s&o-1)].get(e+g,t,n,r)},Ze.prototype.update=function(e,t,n,r,o,s,i){void 0===n&&(n=Ae(r));var a=(0===t?n:n>>>t)&v,l=1<=xt)return pt(e,h,c,a,d);if(u&&!d&&2===h.length&&at(h[1^p]))return h[1^p];if(u&&d&&1===h.length&&at(d))return d;var m=e&&e===this.ownerID,y=u?d?c:c^l:c|l,w=u?d?vt(h,p,d,m):wt(h,p,m):bt(h,p,d,m);return m?(this.bitmap=y,this.nodes=w,this):new Ze(e,y,w)},Ye.prototype.get=function(e,t,n,r){void 0===t&&(t=Ae(n));var o=(0===e?t:t>>>e)&v,s=this.nodes[o];return s?s.get(e+g,t,n,r):r},Ye.prototype.update=function(e,t,n,r,o,s,i){void 0===n&&(n=Ae(r));var a=(0===t?n:n>>>t)&v,l=o===b,c=this.nodes,u=c[a];if(l&&!u)return this;var p=it(u,e,t+g,n,r,o,s,i);if(p===u)return this;var h=this.count;if(u){if(!p&&--h0&&r=0&&e>>t&v;if(r>=this.array.length)return new At([],e);var o,s=0===r;if(t>0){var i=this.array[r];if((o=i&&i.removeBefore(e,t-g,n))===i&&s)return this}if(s&&!o)return this;var a=Ft(this,e);if(!s)for(var l=0;l>>t&v;if(o>=this.array.length)return this;if(t>0){var s=this.array[o];if((r=s&&s.removeAfter(e,t-g,n))===s&&o===this.array.length-1)return this}var i=Ft(this,e);return i.array.splice(o+1),r&&(i.array[o]=r),i};var Ct,Pt,Nt={};function It(e,t){var n=e._origin,r=e._capacity,o=qt(r),s=e._tail;return i(e._root,e._level,0);function i(e,t,n){return 0===t?a(e,n):l(e,t,n)}function a(e,i){var a=i===o?s&&s.array:e&&e.array,l=i>n?0:n-i,c=r-i;return c>y&&(c=y),function(){if(l===c)return Nt;var e=t?--c:l++;return a&&a[e]}}function l(e,o,s){var a,l=e&&e.array,c=s>n?0:n-s>>o,u=1+(r-s>>o);return u>y&&(u=y),function(){for(;;){if(a){var e=a();if(e!==Nt)return e;a=null}if(c===u)return Nt;var n=t?--u:c++;a=i(l&&l[n],o-g,s+(n<=e.size||t<0)return e.withMutations((function(e){t<0?Bt(e,t).set(0,n):Bt(e,0,t+1).set(t,n)}));t+=e._origin;var r=e._tail,o=e._root,s=x(E);return t>=qt(e._capacity)?r=Dt(r,e.__ownerID,0,t,n,s):o=Dt(o,e.__ownerID,e._level,t,n,s),s.value?e.__ownerID?(e._root=o,e._tail=r,e.__hash=void 0,e.__altered=!0,e):Tt(e._origin,e._capacity,e._level,o,r):e}function Dt(e,t,n,r,o,s){var i,a=r>>>n&v,l=e&&a0){var c=e&&e.array[a],u=Dt(c,t,n-g,r,o,s);return u===c?e:((i=Ft(e,t)).array[a]=u,i)}return l&&e.array[a]===o?e:(S(s),i=Ft(e,t),void 0===o&&a===i.array.length-1?i.array.pop():i.array[a]=o,i)}function Ft(e,t){return t&&e&&t===e.ownerID?e:new At(e?e.array.slice():[],t)}function Lt(e,t){if(t>=qt(e._capacity))return e._tail;if(t<1<0;)n=n.array[t>>>r&v],r-=g;return n}}function Bt(e,t,n){void 0!==t&&(t|=0),void 0!==n&&(n|=0);var r=e.__ownerID||new _,o=e._origin,s=e._capacity,i=o+t,a=void 0===n?s:n<0?s+n:o+n;if(i===o&&a===s)return e;if(i>=a)return e.clear();for(var l=e._level,c=e._root,u=0;i+u<0;)c=new At(c&&c.array.length?[void 0,c]:[],r),u+=1<<(l+=g);u&&(i+=u,o+=u,a+=u,s+=u);for(var p=qt(s),h=qt(a);h>=1<p?new At([],r):f;if(f&&h>p&&ig;y-=g){var b=p>>>y&v;m=m.array[b]=Ft(m.array[b],r)}m.array[p>>>g&v]=f}if(a=h)i-=h,a-=h,l=g,c=null,d=d&&d.removeBefore(r,0,i);else if(i>o||h>>l&v;if(w!==h>>>l&v)break;w&&(u+=(1<o&&(c=c.removeBefore(r,l,i-u)),c&&hs&&(s=c.size),i(l)||(c=c.map((function(e){return fe(e)}))),r.push(c)}return s>e.size&&(e=e.setSize(s)),mt(e,t,r)}function qt(e){return e>>g<=y&&i.size>=2*s.size?(r=(o=i.filter((function(e,t){return void 0!==e&&a!==t}))).toKeyedSeq().map((function(e){return e[0]})).flip().toMap(),e.__ownerID&&(r.__ownerID=o.__ownerID=e.__ownerID)):(r=s.remove(t),o=a===i.size-1?i.pop():i.set(a,void 0))}else if(l){if(n===i.get(a)[1])return e;r=s,o=i.set(a,[t,n])}else r=s.set(t,i.size),o=i.set(i.size,[t,n]);return e.__ownerID?(e.size=r.size,e._map=r,e._list=o,e.__hash=void 0,e):Vt(r,o)}function Kt(e,t){this._iter=e,this._useKeys=t,this.size=e.size}function Ht(e){this._iter=e,this.size=e.size}function Gt(e){this._iter=e,this.size=e.size}function Zt(e){this._iter=e,this.size=e.size}function Yt(e){var t=bn(e);return t._iter=e,t.size=e.size,t.flip=function(){return e},t.reverse=function(){var t=e.reverse.apply(this);return t.flip=function(){return e.reverse()},t},t.has=function(t){return e.includes(t)},t.includes=function(t){return e.has(t)},t.cacheResult=wn,t.__iterateUncached=function(t,n){var r=this;return e.__iterate((function(e,n){return!1!==t(n,e,r)}),n)},t.__iteratorUncached=function(t,n){if(t===M){var r=e.__iterator(t,n);return new B((function(){var e=r.next();if(!e.done){var t=e.value[0];e.value[0]=e.value[1],e.value[1]=t}return e}))}return e.__iterator(t===R?T:R,n)},t}function Xt(e,t,n){var r=bn(e);return r.size=e.size,r.has=function(t){return e.has(t)},r.get=function(r,o){var s=e.get(r,b);return s===b?o:t.call(n,s,r,e)},r.__iterateUncached=function(r,o){var s=this;return e.__iterate((function(e,o,i){return!1!==r(t.call(n,e,o,i),o,s)}),o)},r.__iteratorUncached=function(r,o){var s=e.__iterator(M,o);return new B((function(){var o=s.next();if(o.done)return o;var i=o.value,a=i[0];return $(r,a,t.call(n,i[1],a,e),o)}))},r}function Qt(e,t){var n=bn(e);return n._iter=e,n.size=e.size,n.reverse=function(){return e},e.flip&&(n.flip=function(){var t=Yt(e);return t.reverse=function(){return e.flip()},t}),n.get=function(n,r){return e.get(t?n:-1-n,r)},n.has=function(n){return e.has(t?n:-1-n)},n.includes=function(t){return e.includes(t)},n.cacheResult=wn,n.__iterate=function(t,n){var r=this;return e.__iterate((function(e,n){return t(e,n,r)}),!n)},n.__iterator=function(t,n){return e.__iterator(t,!n)},n}function en(e,t,n,r){var o=bn(e);return r&&(o.has=function(r){var o=e.get(r,b);return o!==b&&!!t.call(n,o,r,e)},o.get=function(r,o){var s=e.get(r,b);return s!==b&&t.call(n,s,r,e)?s:o}),o.__iterateUncached=function(o,s){var i=this,a=0;return e.__iterate((function(e,s,l){if(t.call(n,e,s,l))return a++,o(e,r?s:a-1,i)}),s),a},o.__iteratorUncached=function(o,s){var i=e.__iterator(M,s),a=0;return new B((function(){for(;;){var s=i.next();if(s.done)return s;var l=s.value,c=l[0],u=l[1];if(t.call(n,u,c,e))return $(o,r?c:a++,u,s)}}))},o}function tn(e,t,n){var r=Ve().asMutable();return e.__iterate((function(o,s){r.update(t.call(n,o,s,e),0,(function(e){return e+1}))})),r.asImmutable()}function nn(e,t,n){var r=a(e),o=(u(e)?Ut():Ve()).asMutable();e.__iterate((function(s,i){o.update(t.call(n,s,i,e),(function(e){return(e=e||[]).push(r?[i,s]:s),e}))}));var s=vn(e);return o.map((function(t){return mn(e,s(t))}))}function rn(e,t,n,r){var o=e.size;if(void 0!==t&&(t|=0),void 0!==n&&(n===1/0?n=o:n|=0),C(t,n,o))return e;var s=P(t,o),i=N(n,o);if(s!=s||i!=i)return rn(e.toSeq().cacheResult(),t,n,r);var a,l=i-s;l==l&&(a=l<0?0:l);var c=bn(e);return c.size=0===a?a:e.size&&a||void 0,!r&&se(e)&&a>=0&&(c.get=function(t,n){return(t=k(this,t))>=0&&ta)return q();var e=o.next();return r||t===R?e:$(t,l-1,t===T?void 0:e.value[1],e)}))},c}function on(e,t,n){var r=bn(e);return r.__iterateUncached=function(r,o){var s=this;if(o)return this.cacheResult().__iterate(r,o);var i=0;return e.__iterate((function(e,o,a){return t.call(n,e,o,a)&&++i&&r(e,o,s)})),i},r.__iteratorUncached=function(r,o){var s=this;if(o)return this.cacheResult().__iterator(r,o);var i=e.__iterator(M,o),a=!0;return new B((function(){if(!a)return q();var e=i.next();if(e.done)return e;var o=e.value,l=o[0],c=o[1];return t.call(n,c,l,s)?r===M?e:$(r,l,c,e):(a=!1,q())}))},r}function sn(e,t,n,r){var o=bn(e);return o.__iterateUncached=function(o,s){var i=this;if(s)return this.cacheResult().__iterate(o,s);var a=!0,l=0;return e.__iterate((function(e,s,c){if(!a||!(a=t.call(n,e,s,c)))return l++,o(e,r?s:l-1,i)})),l},o.__iteratorUncached=function(o,s){var i=this;if(s)return this.cacheResult().__iterator(o,s);var a=e.__iterator(M,s),l=!0,c=0;return new B((function(){var e,s,u;do{if((e=a.next()).done)return r||o===R?e:$(o,c++,o===T?void 0:e.value[1],e);var p=e.value;s=p[0],u=p[1],l&&(l=t.call(n,u,s,i))}while(l);return o===M?e:$(o,s,u,e)}))},o}function an(e,t){var n=a(e),o=[e].concat(t).map((function(e){return i(e)?n&&(e=r(e)):e=n?ae(e):le(Array.isArray(e)?e:[e]),e})).filter((function(e){return 0!==e.size}));if(0===o.length)return e;if(1===o.length){var s=o[0];if(s===e||n&&a(s)||l(e)&&l(s))return s}var c=new te(o);return n?c=c.toKeyedSeq():l(e)||(c=c.toSetSeq()),(c=c.flatten(!0)).size=o.reduce((function(e,t){if(void 0!==e){var n=t.size;if(void 0!==n)return e+n}}),0),c}function ln(e,t,n){var r=bn(e);return r.__iterateUncached=function(r,o){var s=0,a=!1;function l(e,c){var u=this;e.__iterate((function(e,o){return(!t||c0}function dn(e,t,r){var o=bn(e);return o.size=new te(r).map((function(e){return e.size})).min(),o.__iterate=function(e,t){for(var n,r=this.__iterator(R,t),o=0;!(n=r.next()).done&&!1!==e(n.value,o++,this););return o},o.__iteratorUncached=function(e,o){var s=r.map((function(e){return e=n(e),V(o?e.reverse():e)})),i=0,a=!1;return new B((function(){var n;return a||(n=s.map((function(e){return e.next()})),a=n.some((function(e){return e.done}))),a?q():$(e,i++,t.apply(null,n.map((function(e){return e.value}))))}))},o}function mn(e,t){return se(e)?t:e.constructor(t)}function gn(e){if(e!==Object(e))throw new TypeError("Expected [K, V] tuple: "+e)}function yn(e){return ze(e.size),O(e)}function vn(e){return a(e)?r:l(e)?o:s}function bn(e){return Object.create((a(e)?H:l(e)?G:Z).prototype)}function wn(){return this._iter.cacheResult?(this._iter.cacheResult(),this.size=this._iter.size,this):K.prototype.cacheResult.call(this)}function En(e,t){return e>t?1:e=0;n--)t={value:arguments[n],next:t};return this.__ownerID?(this.size=e,this._head=t,this.__hash=void 0,this.__altered=!0,this):Hn(e,t)},zn.prototype.pushAll=function(e){if(0===(e=o(e)).size)return this;ze(e.size);var t=this.size,n=this._head;return e.reverse().forEach((function(e){t++,n={value:e,next:n}})),this.__ownerID?(this.size=t,this._head=n,this.__hash=void 0,this.__altered=!0,this):Hn(t,n)},zn.prototype.pop=function(){return this.slice(1)},zn.prototype.unshift=function(){return this.push.apply(this,arguments)},zn.prototype.unshiftAll=function(e){return this.pushAll(e)},zn.prototype.shift=function(){return this.pop.apply(this,arguments)},zn.prototype.clear=function(){return 0===this.size?this:this.__ownerID?(this.size=0,this._head=void 0,this.__hash=void 0,this.__altered=!0,this):Gn()},zn.prototype.slice=function(e,t){if(C(e,t,this.size))return this;var n=P(e,this.size);if(N(t,this.size)!==this.size)return _e.prototype.slice.call(this,e,t);for(var r=this.size-n,o=this._head;n--;)o=o.next;return this.__ownerID?(this.size=r,this._head=o,this.__hash=void 0,this.__altered=!0,this):Hn(r,o)},zn.prototype.__ensureOwner=function(e){return e===this.__ownerID?this:e?Hn(this.size,this._head,e,this.__hash):(this.__ownerID=e,this.__altered=!1,this)},zn.prototype.__iterate=function(e,t){if(t)return this.reverse().__iterate(e);for(var n=0,r=this._head;r&&!1!==e(r.value,n++,this);)r=r.next;return n},zn.prototype.__iterator=function(e,t){if(t)return this.reverse().__iterator(e);var n=0,r=this._head;return new B((function(){if(r){var t=r.value;return r=r.next,$(e,n++,t)}return q()}))},zn.isStack=Vn;var Wn,Jn="@@__IMMUTABLE_STACK__@@",Kn=zn.prototype;function Hn(e,t,n,r){var o=Object.create(Kn);return o.size=e,o._head=t,o.__ownerID=n,o.__hash=r,o.__altered=!1,o}function Gn(){return Wn||(Wn=Hn(0))}function Zn(e,t){var n=function(n){e.prototype[n]=t[n]};return Object.keys(t).forEach(n),Object.getOwnPropertySymbols&&Object.getOwnPropertySymbols(t).forEach(n),e}Kn[Jn]=!0,Kn.withMutations=He.withMutations,Kn.asMutable=He.asMutable,Kn.asImmutable=He.asImmutable,Kn.wasAltered=He.wasAltered,n.Iterator=B,Zn(n,{toArray:function(){ze(this.size);var e=new Array(this.size||0);return this.valueSeq().__iterate((function(t,n){e[n]=t})),e},toIndexedSeq:function(){return new Ht(this)},toJS:function(){return this.toSeq().map((function(e){return e&&"function"==typeof e.toJS?e.toJS():e})).__toJS()},toJSON:function(){return this.toSeq().map((function(e){return e&&"function"==typeof e.toJSON?e.toJSON():e})).__toJS()},toKeyedSeq:function(){return new Kt(this,!0)},toMap:function(){return Ve(this.toKeyedSeq())},toObject:function(){ze(this.size);var e={};return this.__iterate((function(t,n){e[n]=t})),e},toOrderedMap:function(){return Ut(this.toKeyedSeq())},toOrderedSet:function(){return Fn(a(this)?this.valueSeq():this)},toSet:function(){return Cn(a(this)?this.valueSeq():this)},toSetSeq:function(){return new Gt(this)},toSeq:function(){return l(this)?this.toIndexedSeq():a(this)?this.toKeyedSeq():this.toSetSeq()},toStack:function(){return zn(a(this)?this.valueSeq():this)},toList:function(){return _t(a(this)?this.valueSeq():this)},toString:function(){return"[Iterable]"},__toString:function(e,t){return 0===this.size?e+t:e+" "+this.toSeq().map(this.__toStringMapper).join(", ")+" "+t},concat:function(){return mn(this,an(this,e.call(arguments,0)))},includes:function(e){return this.some((function(t){return ye(t,e)}))},entries:function(){return this.__iterator(M)},every:function(e,t){ze(this.size);var n=!0;return this.__iterate((function(r,o,s){if(!e.call(t,r,o,s))return n=!1,!1})),n},filter:function(e,t){return mn(this,en(this,e,t,!0))},find:function(e,t,n){var r=this.findEntry(e,t);return r?r[1]:n},forEach:function(e,t){return ze(this.size),this.__iterate(t?e.bind(t):e)},join:function(e){ze(this.size),e=void 0!==e?""+e:",";var t="",n=!0;return this.__iterate((function(r){n?n=!1:t+=e,t+=null!=r?r.toString():""})),t},keys:function(){return this.__iterator(T)},map:function(e,t){return mn(this,Xt(this,e,t))},reduce:function(e,t,n){var r,o;return ze(this.size),arguments.length<2?o=!0:r=t,this.__iterate((function(t,s,i){o?(o=!1,r=t):r=e.call(n,r,t,s,i)})),r},reduceRight:function(e,t,n){var r=this.toKeyedSeq().reverse();return r.reduce.apply(r,arguments)},reverse:function(){return mn(this,Qt(this,!0))},slice:function(e,t){return mn(this,rn(this,e,t,!0))},some:function(e,t){return!this.every(tr(e),t)},sort:function(e){return mn(this,pn(this,e))},values:function(){return this.__iterator(R)},butLast:function(){return this.slice(0,-1)},isEmpty:function(){return void 0!==this.size?0===this.size:!this.some((function(){return!0}))},count:function(e,t){return O(e?this.toSeq().filter(e,t):this)},countBy:function(e,t){return tn(this,e,t)},equals:function(e){return ve(this,e)},entrySeq:function(){var e=this;if(e._cache)return new te(e._cache);var t=e.toSeq().map(er).toIndexedSeq();return t.fromEntrySeq=function(){return e.toSeq()},t},filterNot:function(e,t){return this.filter(tr(e),t)},findEntry:function(e,t,n){var r=n;return this.__iterate((function(n,o,s){if(e.call(t,n,o,s))return r=[o,n],!1})),r},findKey:function(e,t){var n=this.findEntry(e,t);return n&&n[0]},findLast:function(e,t,n){return this.toKeyedSeq().reverse().find(e,t,n)},findLastEntry:function(e,t,n){return this.toKeyedSeq().reverse().findEntry(e,t,n)},findLastKey:function(e,t){return this.toKeyedSeq().reverse().findKey(e,t)},first:function(){return this.find(A)},flatMap:function(e,t){return mn(this,cn(this,e,t))},flatten:function(e){return mn(this,ln(this,e,!0))},fromEntrySeq:function(){return new Zt(this)},get:function(e,t){return this.find((function(t,n){return ye(n,e)}),void 0,t)},getIn:function(e,t){for(var n,r=this,o=xn(e);!(n=o.next()).done;){var s=n.value;if((r=r&&r.get?r.get(s,b):b)===b)return t}return r},groupBy:function(e,t){return nn(this,e,t)},has:function(e){return this.get(e,b)!==b},hasIn:function(e){return this.getIn(e,b)!==b},isSubset:function(e){return e="function"==typeof e.includes?e:n(e),this.every((function(t){return e.includes(t)}))},isSuperset:function(e){return(e="function"==typeof e.isSubset?e:n(e)).isSubset(this)},keyOf:function(e){return this.findKey((function(t){return ye(t,e)}))},keySeq:function(){return this.toSeq().map(Qn).toIndexedSeq()},last:function(){return this.toSeq().reverse().first()},lastKeyOf:function(e){return this.toKeyedSeq().reverse().keyOf(e)},max:function(e){return hn(this,e)},maxBy:function(e,t){return hn(this,t,e)},min:function(e){return hn(this,e?nr(e):sr)},minBy:function(e,t){return hn(this,t?nr(t):sr,e)},rest:function(){return this.slice(1)},skip:function(e){return this.slice(Math.max(0,e))},skipLast:function(e){return mn(this,this.toSeq().reverse().skip(e).reverse())},skipWhile:function(e,t){return mn(this,sn(this,e,t,!0))},skipUntil:function(e,t){return this.skipWhile(tr(e),t)},sortBy:function(e,t){return mn(this,pn(this,t,e))},take:function(e){return this.slice(0,Math.max(0,e))},takeLast:function(e){return mn(this,this.toSeq().reverse().take(e).reverse())},takeWhile:function(e,t){return mn(this,on(this,e,t))},takeUntil:function(e,t){return this.takeWhile(tr(e),t)},valueSeq:function(){return this.toIndexedSeq()},hashCode:function(){return this.__hash||(this.__hash=ir(this))}});var Yn=n.prototype;Yn[p]=!0,Yn[L]=Yn.values,Yn.__toJS=Yn.toArray,Yn.__toStringMapper=rr,Yn.inspect=Yn.toSource=function(){return this.toString()},Yn.chain=Yn.flatMap,Yn.contains=Yn.includes,Zn(r,{flip:function(){return mn(this,Yt(this))},mapEntries:function(e,t){var n=this,r=0;return mn(this,this.toSeq().map((function(o,s){return e.call(t,[s,o],r++,n)})).fromEntrySeq())},mapKeys:function(e,t){var n=this;return mn(this,this.toSeq().flip().map((function(r,o){return e.call(t,r,o,n)})).flip())}});var Xn=r.prototype;function Qn(e,t){return t}function er(e,t){return[t,e]}function tr(e){return function(){return!e.apply(this,arguments)}}function nr(e){return function(){return-e.apply(this,arguments)}}function rr(e){return"string"==typeof e?JSON.stringify(e):String(e)}function or(){return j(arguments)}function sr(e,t){return et?-1:0}function ir(e){if(e.size===1/0)return 0;var t=u(e),n=a(e),r=t?1:0;return ar(e.__iterate(n?t?function(e,t){r=31*r+lr(Ae(e),Ae(t))|0}:function(e,t){r=r+lr(Ae(e),Ae(t))|0}:t?function(e){r=31*r+Ae(e)|0}:function(e){r=r+Ae(e)|0}),r)}function ar(e,t){return t=Oe(t,3432918353),t=Oe(t<<15|t>>>-15,461845907),t=Oe(t<<13|t>>>-13,5),t=Oe((t=(t+3864292196|0)^e)^t>>>16,2246822507),t=ke((t=Oe(t^t>>>13,3266489909))^t>>>16)}function lr(e,t){return e^t+2654435769+(e<<6)+(e>>2)|0}return Xn[h]=!0,Xn[L]=Yn.entries,Xn.__toJS=Yn.toObject,Xn.__toStringMapper=function(e,t){return JSON.stringify(t)+": "+rr(e)},Zn(o,{toKeyedSeq:function(){return new Kt(this,!1)},filter:function(e,t){return mn(this,en(this,e,t,!1))},findIndex:function(e,t){var n=this.findEntry(e,t);return n?n[0]:-1},indexOf:function(e){var t=this.keyOf(e);return void 0===t?-1:t},lastIndexOf:function(e){var t=this.lastKeyOf(e);return void 0===t?-1:t},reverse:function(){return mn(this,Qt(this,!1))},slice:function(e,t){return mn(this,rn(this,e,t,!1))},splice:function(e,t){var n=arguments.length;if(t=Math.max(0|t,0),0===n||2===n&&!t)return this;e=P(e,e<0?this.count():this.size);var r=this.slice(0,e);return mn(this,1===n?r:r.concat(j(arguments,2),this.slice(e+t)))},findLastIndex:function(e,t){var n=this.findLastEntry(e,t);return n?n[0]:-1},first:function(){return this.get(0)},flatten:function(e){return mn(this,ln(this,e,!1))},get:function(e,t){return(e=k(this,e))<0||this.size===1/0||void 0!==this.size&&e>this.size?t:this.find((function(t,n){return n===e}),void 0,t)},has:function(e){return(e=k(this,e))>=0&&(void 0!==this.size?this.size===1/0||e{"function"==typeof Object.create?e.exports=function(e,t){t&&(e.super_=t,e.prototype=Object.create(t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}))}:e.exports=function(e,t){if(t){e.super_=t;var n=function(){};n.prototype=t.prototype,e.prototype=new n,e.prototype.constructor=e}}},35823:e=>{e.exports=function(e,t,n,r){var o=new Blob(void 0!==r?[r,e]:[e],{type:n||"application/octet-stream"});if(void 0!==window.navigator.msSaveBlob)window.navigator.msSaveBlob(o,t);else{var s=window.URL&&window.URL.createObjectURL?window.URL.createObjectURL(o):window.webkitURL.createObjectURL(o),i=document.createElement("a");i.style.display="none",i.href=s,i.setAttribute("download",t),void 0===i.download&&i.setAttribute("target","_blank"),document.body.appendChild(i),i.click(),setTimeout((function(){document.body.removeChild(i),window.URL.revokeObjectURL(s)}),200)}}},91296:(e,t,n)=>{var r=NaN,o="[object Symbol]",s=/^\s+|\s+$/g,i=/^[-+]0x[0-9a-f]+$/i,a=/^0b[01]+$/i,l=/^0o[0-7]+$/i,c=parseInt,u="object"==typeof n.g&&n.g&&n.g.Object===Object&&n.g,p="object"==typeof self&&self&&self.Object===Object&&self,h=u||p||Function("return this")(),f=Object.prototype.toString,d=Math.max,m=Math.min,g=function(){return h.Date.now()};function y(e){var t=typeof e;return!!e&&("object"==t||"function"==t)}function v(e){if("number"==typeof e)return e;if(function(e){return"symbol"==typeof e||function(e){return!!e&&"object"==typeof e}(e)&&f.call(e)==o}(e))return r;if(y(e)){var t="function"==typeof e.valueOf?e.valueOf():e;e=y(t)?t+"":t}if("string"!=typeof e)return 0===e?e:+e;e=e.replace(s,"");var n=a.test(e);return n||l.test(e)?c(e.slice(2),n?2:8):i.test(e)?r:+e}e.exports=function(e,t,n){var r,o,s,i,a,l,c=0,u=!1,p=!1,h=!0;if("function"!=typeof e)throw new TypeError("Expected a function");function f(t){var n=r,s=o;return r=o=void 0,c=t,i=e.apply(s,n)}function b(e){var n=e-l;return void 0===l||n>=t||n<0||p&&e-c>=s}function w(){var e=g();if(b(e))return E(e);a=setTimeout(w,function(e){var n=t-(e-l);return p?m(n,s-(e-c)):n}(e))}function E(e){return a=void 0,h&&r?f(e):(r=o=void 0,i)}function x(){var e=g(),n=b(e);if(r=arguments,o=this,l=e,n){if(void 0===a)return function(e){return c=e,a=setTimeout(w,t),u?f(e):i}(l);if(p)return a=setTimeout(w,t),f(l)}return void 0===a&&(a=setTimeout(w,t)),i}return t=v(t)||0,y(n)&&(u=!!n.leading,s=(p="maxWait"in n)?d(v(n.maxWait)||0,t):s,h="trailing"in n?!!n.trailing:h),x.cancel=function(){void 0!==a&&clearTimeout(a),c=0,r=l=o=a=void 0},x.flush=function(){return void 0===a?i:E(g())},x}},18552:(e,t,n)=>{var r=n(10852)(n(55639),"DataView");e.exports=r},1989:(e,t,n)=>{var r=n(51789),o=n(80401),s=n(57667),i=n(21327),a=n(81866);function l(e){var t=-1,n=null==e?0:e.length;for(this.clear();++t{var r=n(3118),o=n(9435);function s(e){this.__wrapped__=e,this.__actions__=[],this.__dir__=1,this.__filtered__=!1,this.__iteratees__=[],this.__takeCount__=4294967295,this.__views__=[]}s.prototype=r(o.prototype),s.prototype.constructor=s,e.exports=s},38407:(e,t,n)=>{var r=n(27040),o=n(14125),s=n(82117),i=n(67518),a=n(54705);function l(e){var t=-1,n=null==e?0:e.length;for(this.clear();++t{var r=n(3118),o=n(9435);function s(e,t){this.__wrapped__=e,this.__actions__=[],this.__chain__=!!t,this.__index__=0,this.__values__=void 0}s.prototype=r(o.prototype),s.prototype.constructor=s,e.exports=s},57071:(e,t,n)=>{var r=n(10852)(n(55639),"Map");e.exports=r},83369:(e,t,n)=>{var r=n(24785),o=n(11285),s=n(96e3),i=n(49916),a=n(95265);function l(e){var t=-1,n=null==e?0:e.length;for(this.clear();++t{var r=n(10852)(n(55639),"Promise");e.exports=r},58525:(e,t,n)=>{var r=n(10852)(n(55639),"Set");e.exports=r},88668:(e,t,n)=>{var r=n(83369),o=n(90619),s=n(72385);function i(e){var t=-1,n=null==e?0:e.length;for(this.__data__=new r;++t{var r=n(38407),o=n(37465),s=n(63779),i=n(67599),a=n(44758),l=n(34309);function c(e){var t=this.__data__=new r(e);this.size=t.size}c.prototype.clear=o,c.prototype.delete=s,c.prototype.get=i,c.prototype.has=a,c.prototype.set=l,e.exports=c},62705:(e,t,n)=>{var r=n(55639).Symbol;e.exports=r},11149:(e,t,n)=>{var r=n(55639).Uint8Array;e.exports=r},70577:(e,t,n)=>{var r=n(10852)(n(55639),"WeakMap");e.exports=r},96874:e=>{e.exports=function(e,t,n){switch(n.length){case 0:return e.call(t);case 1:return e.call(t,n[0]);case 2:return e.call(t,n[0],n[1]);case 3:return e.call(t,n[0],n[1],n[2])}return e.apply(t,n)}},77412:e=>{e.exports=function(e,t){for(var n=-1,r=null==e?0:e.length;++n{e.exports=function(e,t){for(var n=-1,r=null==e?0:e.length,o=0,s=[];++n{var r=n(42118);e.exports=function(e,t){return!!(null==e?0:e.length)&&r(e,t,0)>-1}},14636:(e,t,n)=>{var r=n(22545),o=n(35694),s=n(1469),i=n(44144),a=n(65776),l=n(36719),c=Object.prototype.hasOwnProperty;e.exports=function(e,t){var n=s(e),u=!n&&o(e),p=!n&&!u&&i(e),h=!n&&!u&&!p&&l(e),f=n||u||p||h,d=f?r(e.length,String):[],m=d.length;for(var g in e)!t&&!c.call(e,g)||f&&("length"==g||p&&("offset"==g||"parent"==g)||h&&("buffer"==g||"byteLength"==g||"byteOffset"==g)||a(g,m))||d.push(g);return d}},29932:e=>{e.exports=function(e,t){for(var n=-1,r=null==e?0:e.length,o=Array(r);++n{e.exports=function(e,t){for(var n=-1,r=t.length,o=e.length;++n{e.exports=function(e,t,n,r){var o=-1,s=null==e?0:e.length;for(r&&s&&(n=e[++o]);++o{e.exports=function(e,t){for(var n=-1,r=null==e?0:e.length;++n{e.exports=function(e){return e.split("")}},49029:e=>{var t=/[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g;e.exports=function(e){return e.match(t)||[]}},86556:(e,t,n)=>{var r=n(89465),o=n(77813);e.exports=function(e,t,n){(void 0!==n&&!o(e[t],n)||void 0===n&&!(t in e))&&r(e,t,n)}},34865:(e,t,n)=>{var r=n(89465),o=n(77813),s=Object.prototype.hasOwnProperty;e.exports=function(e,t,n){var i=e[t];s.call(e,t)&&o(i,n)&&(void 0!==n||t in e)||r(e,t,n)}},18470:(e,t,n)=>{var r=n(77813);e.exports=function(e,t){for(var n=e.length;n--;)if(r(e[n][0],t))return n;return-1}},44037:(e,t,n)=>{var r=n(98363),o=n(3674);e.exports=function(e,t){return e&&r(t,o(t),e)}},63886:(e,t,n)=>{var r=n(98363),o=n(81704);e.exports=function(e,t){return e&&r(t,o(t),e)}},89465:(e,t,n)=>{var r=n(38777);e.exports=function(e,t,n){"__proto__"==t&&r?r(e,t,{configurable:!0,enumerable:!0,value:n,writable:!0}):e[t]=n}},85990:(e,t,n)=>{var r=n(46384),o=n(77412),s=n(34865),i=n(44037),a=n(63886),l=n(64626),c=n(278),u=n(18805),p=n(1911),h=n(58234),f=n(46904),d=n(98882),m=n(43824),g=n(29148),y=n(38517),v=n(1469),b=n(44144),w=n(56688),E=n(13218),x=n(72928),S=n(3674),_=n(81704),j="[object Arguments]",O="[object Function]",k="[object Object]",A={};A[j]=A["[object Array]"]=A["[object ArrayBuffer]"]=A["[object DataView]"]=A["[object Boolean]"]=A["[object Date]"]=A["[object Float32Array]"]=A["[object Float64Array]"]=A["[object Int8Array]"]=A["[object Int16Array]"]=A["[object Int32Array]"]=A["[object Map]"]=A["[object Number]"]=A[k]=A["[object RegExp]"]=A["[object Set]"]=A["[object String]"]=A["[object Symbol]"]=A["[object Uint8Array]"]=A["[object Uint8ClampedArray]"]=A["[object Uint16Array]"]=A["[object Uint32Array]"]=!0,A["[object Error]"]=A[O]=A["[object WeakMap]"]=!1,e.exports=function e(t,n,C,P,N,I){var T,R=1&n,M=2&n,D=4&n;if(C&&(T=N?C(t,P,N,I):C(t)),void 0!==T)return T;if(!E(t))return t;var F=v(t);if(F){if(T=m(t),!R)return c(t,T)}else{var L=d(t),B=L==O||"[object GeneratorFunction]"==L;if(b(t))return l(t,R);if(L==k||L==j||B&&!N){if(T=M||B?{}:y(t),!R)return M?p(t,a(T,t)):u(t,i(T,t))}else{if(!A[L])return N?t:{};T=g(t,L,R)}}I||(I=new r);var $=I.get(t);if($)return $;I.set(t,T),x(t)?t.forEach((function(r){T.add(e(r,n,C,r,t,I))})):w(t)&&t.forEach((function(r,o){T.set(o,e(r,n,C,o,t,I))}));var q=F?void 0:(D?M?f:h:M?_:S)(t);return o(q||t,(function(r,o){q&&(r=t[o=r]),s(T,o,e(r,n,C,o,t,I))})),T}},3118:(e,t,n)=>{var r=n(13218),o=Object.create,s=function(){function e(){}return function(t){if(!r(t))return{};if(o)return o(t);e.prototype=t;var n=new e;return e.prototype=void 0,n}}();e.exports=s},89881:(e,t,n)=>{var r=n(47816),o=n(99291)(r);e.exports=o},41848:e=>{e.exports=function(e,t,n,r){for(var o=e.length,s=n+(r?1:-1);r?s--:++s{var r=n(62488),o=n(37285);e.exports=function e(t,n,s,i,a){var l=-1,c=t.length;for(s||(s=o),a||(a=[]);++l0&&s(u)?n>1?e(u,n-1,s,i,a):r(a,u):i||(a[a.length]=u)}return a}},28483:(e,t,n)=>{var r=n(25063)();e.exports=r},47816:(e,t,n)=>{var r=n(28483),o=n(3674);e.exports=function(e,t){return e&&r(e,t,o)}},97786:(e,t,n)=>{var r=n(71811),o=n(40327);e.exports=function(e,t){for(var n=0,s=(t=r(t,e)).length;null!=e&&n{var r=n(62488),o=n(1469);e.exports=function(e,t,n){var s=t(e);return o(e)?s:r(s,n(e))}},44239:(e,t,n)=>{var r=n(62705),o=n(89607),s=n(2333),i=r?r.toStringTag:void 0;e.exports=function(e){return null==e?void 0===e?"[object Undefined]":"[object Null]":i&&i in Object(e)?o(e):s(e)}},13:e=>{e.exports=function(e,t){return null!=e&&t in Object(e)}},42118:(e,t,n)=>{var r=n(41848),o=n(62722),s=n(42351);e.exports=function(e,t,n){return t==t?s(e,t,n):r(e,o,n)}},9454:(e,t,n)=>{var r=n(44239),o=n(37005);e.exports=function(e){return o(e)&&"[object Arguments]"==r(e)}},90939:(e,t,n)=>{var r=n(2492),o=n(37005);e.exports=function e(t,n,s,i,a){return t===n||(null==t||null==n||!o(t)&&!o(n)?t!=t&&n!=n:r(t,n,s,i,e,a))}},2492:(e,t,n)=>{var r=n(46384),o=n(67114),s=n(18351),i=n(16096),a=n(98882),l=n(1469),c=n(44144),u=n(36719),p="[object Arguments]",h="[object Array]",f="[object Object]",d=Object.prototype.hasOwnProperty;e.exports=function(e,t,n,m,g,y){var v=l(e),b=l(t),w=v?h:a(e),E=b?h:a(t),x=(w=w==p?f:w)==f,S=(E=E==p?f:E)==f,_=w==E;if(_&&c(e)){if(!c(t))return!1;v=!0,x=!1}if(_&&!x)return y||(y=new r),v||u(e)?o(e,t,n,m,g,y):s(e,t,w,n,m,g,y);if(!(1&n)){var j=x&&d.call(e,"__wrapped__"),O=S&&d.call(t,"__wrapped__");if(j||O){var k=j?e.value():e,A=O?t.value():t;return y||(y=new r),g(k,A,n,m,y)}}return!!_&&(y||(y=new r),i(e,t,n,m,g,y))}},25588:(e,t,n)=>{var r=n(98882),o=n(37005);e.exports=function(e){return o(e)&&"[object Map]"==r(e)}},2958:(e,t,n)=>{var r=n(46384),o=n(90939);e.exports=function(e,t,n,s){var i=n.length,a=i,l=!s;if(null==e)return!a;for(e=Object(e);i--;){var c=n[i];if(l&&c[2]?c[1]!==e[c[0]]:!(c[0]in e))return!1}for(;++i{e.exports=function(e){return e!=e}},28458:(e,t,n)=>{var r=n(23560),o=n(15346),s=n(13218),i=n(80346),a=/^\[object .+?Constructor\]$/,l=Function.prototype,c=Object.prototype,u=l.toString,p=c.hasOwnProperty,h=RegExp("^"+u.call(p).replace(/[\\^$.*+?()[\]{}|]/g,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$");e.exports=function(e){return!(!s(e)||o(e))&&(r(e)?h:a).test(i(e))}},29221:(e,t,n)=>{var r=n(98882),o=n(37005);e.exports=function(e){return o(e)&&"[object Set]"==r(e)}},38749:(e,t,n)=>{var r=n(44239),o=n(41780),s=n(37005),i={};i["[object Float32Array]"]=i["[object Float64Array]"]=i["[object Int8Array]"]=i["[object Int16Array]"]=i["[object Int32Array]"]=i["[object Uint8Array]"]=i["[object Uint8ClampedArray]"]=i["[object Uint16Array]"]=i["[object Uint32Array]"]=!0,i["[object Arguments]"]=i["[object Array]"]=i["[object ArrayBuffer]"]=i["[object Boolean]"]=i["[object DataView]"]=i["[object Date]"]=i["[object Error]"]=i["[object Function]"]=i["[object Map]"]=i["[object Number]"]=i["[object Object]"]=i["[object RegExp]"]=i["[object Set]"]=i["[object String]"]=i["[object WeakMap]"]=!1,e.exports=function(e){return s(e)&&o(e.length)&&!!i[r(e)]}},67206:(e,t,n)=>{var r=n(91573),o=n(16432),s=n(6557),i=n(1469),a=n(39601);e.exports=function(e){return"function"==typeof e?e:null==e?s:"object"==typeof e?i(e)?o(e[0],e[1]):r(e):a(e)}},280:(e,t,n)=>{var r=n(25726),o=n(86916),s=Object.prototype.hasOwnProperty;e.exports=function(e){if(!r(e))return o(e);var t=[];for(var n in Object(e))s.call(e,n)&&"constructor"!=n&&t.push(n);return t}},10313:(e,t,n)=>{var r=n(13218),o=n(25726),s=n(33498),i=Object.prototype.hasOwnProperty;e.exports=function(e){if(!r(e))return s(e);var t=o(e),n=[];for(var a in e)("constructor"!=a||!t&&i.call(e,a))&&n.push(a);return n}},9435:e=>{e.exports=function(){}},91573:(e,t,n)=>{var r=n(2958),o=n(1499),s=n(42634);e.exports=function(e){var t=o(e);return 1==t.length&&t[0][2]?s(t[0][0],t[0][1]):function(n){return n===e||r(n,e,t)}}},16432:(e,t,n)=>{var r=n(90939),o=n(27361),s=n(79095),i=n(15403),a=n(89162),l=n(42634),c=n(40327);e.exports=function(e,t){return i(e)&&a(t)?l(c(e),t):function(n){var i=o(n,e);return void 0===i&&i===t?s(n,e):r(t,i,3)}}},42980:(e,t,n)=>{var r=n(46384),o=n(86556),s=n(28483),i=n(59783),a=n(13218),l=n(81704),c=n(36390);e.exports=function e(t,n,u,p,h){t!==n&&s(n,(function(s,l){if(h||(h=new r),a(s))i(t,n,l,u,e,p,h);else{var f=p?p(c(t,l),s,l+"",t,n,h):void 0;void 0===f&&(f=s),o(t,l,f)}}),l)}},59783:(e,t,n)=>{var r=n(86556),o=n(64626),s=n(77133),i=n(278),a=n(38517),l=n(35694),c=n(1469),u=n(29246),p=n(44144),h=n(23560),f=n(13218),d=n(68630),m=n(36719),g=n(36390),y=n(59881);e.exports=function(e,t,n,v,b,w,E){var x=g(e,n),S=g(t,n),_=E.get(S);if(_)r(e,n,_);else{var j=w?w(x,S,n+"",e,t,E):void 0,O=void 0===j;if(O){var k=c(S),A=!k&&p(S),C=!k&&!A&&m(S);j=S,k||A||C?c(x)?j=x:u(x)?j=i(x):A?(O=!1,j=o(S,!0)):C?(O=!1,j=s(S,!0)):j=[]:d(S)||l(S)?(j=x,l(x)?j=y(x):f(x)&&!h(x)||(j=a(S))):O=!1}O&&(E.set(S,j),b(j,S,v,w,E),E.delete(S)),r(e,n,j)}}},40371:e=>{e.exports=function(e){return function(t){return null==t?void 0:t[e]}}},79152:(e,t,n)=>{var r=n(97786);e.exports=function(e){return function(t){return r(t,e)}}},18674:e=>{e.exports=function(e){return function(t){return null==e?void 0:e[t]}}},10107:e=>{e.exports=function(e,t,n,r,o){return o(e,(function(e,o,s){n=r?(r=!1,e):t(n,e,o,s)})),n}},5976:(e,t,n)=>{var r=n(6557),o=n(45357),s=n(30061);e.exports=function(e,t){return s(o(e,t,r),e+"")}},10611:(e,t,n)=>{var r=n(34865),o=n(71811),s=n(65776),i=n(13218),a=n(40327);e.exports=function(e,t,n,l){if(!i(e))return e;for(var c=-1,u=(t=o(t,e)).length,p=u-1,h=e;null!=h&&++c{var r=n(6557),o=n(89250),s=o?function(e,t){return o.set(e,t),e}:r;e.exports=s},56560:(e,t,n)=>{var r=n(75703),o=n(38777),s=n(6557),i=o?function(e,t){return o(e,"toString",{configurable:!0,enumerable:!1,value:r(t),writable:!0})}:s;e.exports=i},14259:e=>{e.exports=function(e,t,n){var r=-1,o=e.length;t<0&&(t=-t>o?0:o+t),(n=n>o?o:n)<0&&(n+=o),o=t>n?0:n-t>>>0,t>>>=0;for(var s=Array(o);++r{var r=n(89881);e.exports=function(e,t){var n;return r(e,(function(e,r,o){return!(n=t(e,r,o))})),!!n}},22545:e=>{e.exports=function(e,t){for(var n=-1,r=Array(e);++n{var r=n(62705),o=n(29932),s=n(1469),i=n(33448),a=r?r.prototype:void 0,l=a?a.toString:void 0;e.exports=function e(t){if("string"==typeof t)return t;if(s(t))return o(t,e)+"";if(i(t))return l?l.call(t):"";var n=t+"";return"0"==n&&1/t==-Infinity?"-0":n}},27561:(e,t,n)=>{var r=n(67990),o=/^\s+/;e.exports=function(e){return e?e.slice(0,r(e)+1).replace(o,""):e}},7518:e=>{e.exports=function(e){return function(t){return e(t)}}},57406:(e,t,n)=>{var r=n(71811),o=n(10928),s=n(40292),i=n(40327);e.exports=function(e,t){return t=r(t,e),null==(e=s(e,t))||delete e[i(o(t))]}},1757:e=>{e.exports=function(e,t,n){for(var r=-1,o=e.length,s=t.length,i={};++r{e.exports=function(e,t){return e.has(t)}},71811:(e,t,n)=>{var r=n(1469),o=n(15403),s=n(55514),i=n(79833);e.exports=function(e,t){return r(e)?e:o(e,t)?[e]:s(i(e))}},40180:(e,t,n)=>{var r=n(14259);e.exports=function(e,t,n){var o=e.length;return n=void 0===n?o:n,!t&&n>=o?e:r(e,t,n)}},74318:(e,t,n)=>{var r=n(11149);e.exports=function(e){var t=new e.constructor(e.byteLength);return new r(t).set(new r(e)),t}},64626:(e,t,n)=>{e=n.nmd(e);var r=n(55639),o=t&&!t.nodeType&&t,s=o&&e&&!e.nodeType&&e,i=s&&s.exports===o?r.Buffer:void 0,a=i?i.allocUnsafe:void 0;e.exports=function(e,t){if(t)return e.slice();var n=e.length,r=a?a(n):new e.constructor(n);return e.copy(r),r}},57157:(e,t,n)=>{var r=n(74318);e.exports=function(e,t){var n=t?r(e.buffer):e.buffer;return new e.constructor(n,e.byteOffset,e.byteLength)}},93147:e=>{var t=/\w*$/;e.exports=function(e){var n=new e.constructor(e.source,t.exec(e));return n.lastIndex=e.lastIndex,n}},40419:(e,t,n)=>{var r=n(62705),o=r?r.prototype:void 0,s=o?o.valueOf:void 0;e.exports=function(e){return s?Object(s.call(e)):{}}},77133:(e,t,n)=>{var r=n(74318);e.exports=function(e,t){var n=t?r(e.buffer):e.buffer;return new e.constructor(n,e.byteOffset,e.length)}},52157:e=>{var t=Math.max;e.exports=function(e,n,r,o){for(var s=-1,i=e.length,a=r.length,l=-1,c=n.length,u=t(i-a,0),p=Array(c+u),h=!o;++l{var t=Math.max;e.exports=function(e,n,r,o){for(var s=-1,i=e.length,a=-1,l=r.length,c=-1,u=n.length,p=t(i-l,0),h=Array(p+u),f=!o;++s{e.exports=function(e,t){var n=-1,r=e.length;for(t||(t=Array(r));++n{var r=n(34865),o=n(89465);e.exports=function(e,t,n,s){var i=!n;n||(n={});for(var a=-1,l=t.length;++a{var r=n(98363),o=n(99551);e.exports=function(e,t){return r(e,o(e),t)}},1911:(e,t,n)=>{var r=n(98363),o=n(51442);e.exports=function(e,t){return r(e,o(e),t)}},14429:(e,t,n)=>{var r=n(55639)["__core-js_shared__"];e.exports=r},97991:e=>{e.exports=function(e,t){for(var n=e.length,r=0;n--;)e[n]===t&&++r;return r}},21463:(e,t,n)=>{var r=n(5976),o=n(16612);e.exports=function(e){return r((function(t,n){var r=-1,s=n.length,i=s>1?n[s-1]:void 0,a=s>2?n[2]:void 0;for(i=e.length>3&&"function"==typeof i?(s--,i):void 0,a&&o(n[0],n[1],a)&&(i=s<3?void 0:i,s=1),t=Object(t);++r{var r=n(98612);e.exports=function(e,t){return function(n,o){if(null==n)return n;if(!r(n))return e(n,o);for(var s=n.length,i=t?s:-1,a=Object(n);(t?i--:++i{e.exports=function(e){return function(t,n,r){for(var o=-1,s=Object(t),i=r(t),a=i.length;a--;){var l=i[e?a:++o];if(!1===n(s[l],l,s))break}return t}}},22402:(e,t,n)=>{var r=n(71774),o=n(55639);e.exports=function(e,t,n){var s=1&t,i=r(e);return function t(){return(this&&this!==o&&this instanceof t?i:e).apply(s?n:this,arguments)}}},98805:(e,t,n)=>{var r=n(40180),o=n(62689),s=n(83140),i=n(79833);e.exports=function(e){return function(t){t=i(t);var n=o(t)?s(t):void 0,a=n?n[0]:t.charAt(0),l=n?r(n,1).join(""):t.slice(1);return a[e]()+l}}},35393:(e,t,n)=>{var r=n(62663),o=n(53816),s=n(58748),i=RegExp("['’]","g");e.exports=function(e){return function(t){return r(s(o(t).replace(i,"")),e,"")}}},71774:(e,t,n)=>{var r=n(3118),o=n(13218);e.exports=function(e){return function(){var t=arguments;switch(t.length){case 0:return new e;case 1:return new e(t[0]);case 2:return new e(t[0],t[1]);case 3:return new e(t[0],t[1],t[2]);case 4:return new e(t[0],t[1],t[2],t[3]);case 5:return new e(t[0],t[1],t[2],t[3],t[4]);case 6:return new e(t[0],t[1],t[2],t[3],t[4],t[5]);case 7:return new e(t[0],t[1],t[2],t[3],t[4],t[5],t[6])}var n=r(e.prototype),s=e.apply(n,t);return o(s)?s:n}}},46347:(e,t,n)=>{var r=n(96874),o=n(71774),s=n(86935),i=n(94487),a=n(20893),l=n(46460),c=n(55639);e.exports=function(e,t,n){var u=o(e);return function o(){for(var p=arguments.length,h=Array(p),f=p,d=a(o);f--;)h[f]=arguments[f];var m=p<3&&h[0]!==d&&h[p-1]!==d?[]:l(h,d);return(p-=m.length){var r=n(67206),o=n(98612),s=n(3674);e.exports=function(e){return function(t,n,i){var a=Object(t);if(!o(t)){var l=r(n,3);t=s(t),n=function(e){return l(a[e],e,a)}}var c=e(t,n,i);return c>-1?a[l?t[c]:c]:void 0}}},86935:(e,t,n)=>{var r=n(52157),o=n(14054),s=n(97991),i=n(71774),a=n(94487),l=n(20893),c=n(90451),u=n(46460),p=n(55639);e.exports=function e(t,n,h,f,d,m,g,y,v,b){var w=128&n,E=1&n,x=2&n,S=24&n,_=512&n,j=x?void 0:i(t);return function O(){for(var k=arguments.length,A=Array(k),C=k;C--;)A[C]=arguments[C];if(S)var P=l(O),N=s(A,P);if(f&&(A=r(A,f,d,S)),m&&(A=o(A,m,g,S)),k-=N,S&&k1&&A.reverse(),w&&v{var r=n(96874),o=n(71774),s=n(55639);e.exports=function(e,t,n,i){var a=1&t,l=o(e);return function t(){for(var o=-1,c=arguments.length,u=-1,p=i.length,h=Array(p+c),f=this&&this!==s&&this instanceof t?l:e;++u{var r=n(86528),o=n(258),s=n(69255);e.exports=function(e,t,n,i,a,l,c,u,p,h){var f=8&t;t|=f?32:64,4&(t&=~(f?64:32))||(t&=-4);var d=[e,t,a,f?l:void 0,f?c:void 0,f?void 0:l,f?void 0:c,u,p,h],m=n.apply(void 0,d);return r(e)&&o(m,d),m.placeholder=i,s(m,e,t)}},97727:(e,t,n)=>{var r=n(28045),o=n(22402),s=n(46347),i=n(86935),a=n(84375),l=n(66833),c=n(63833),u=n(258),p=n(69255),h=n(40554),f=Math.max;e.exports=function(e,t,n,d,m,g,y,v){var b=2&t;if(!b&&"function"!=typeof e)throw new TypeError("Expected a function");var w=d?d.length:0;if(w||(t&=-97,d=m=void 0),y=void 0===y?y:f(h(y),0),v=void 0===v?v:h(v),w-=m?m.length:0,64&t){var E=d,x=m;d=m=void 0}var S=b?void 0:l(e),_=[e,t,n,d,m,E,x,g,y,v];if(S&&c(_,S),e=_[0],t=_[1],n=_[2],d=_[3],m=_[4],!(v=_[9]=void 0===_[9]?b?0:e.length:f(_[9]-w,0))&&24&t&&(t&=-25),t&&1!=t)j=8==t||16==t?s(e,t,v):32!=t&&33!=t||m.length?i.apply(void 0,_):a(e,t,n,d);else var j=o(e,t,n);return p((S?r:u)(j,_),e,t)}},60696:(e,t,n)=>{var r=n(68630);e.exports=function(e){return r(e)?void 0:e}},69389:(e,t,n)=>{var r=n(18674)({À:"A",Á:"A",Â:"A",Ã:"A",Ä:"A",Å:"A",à:"a",á:"a",â:"a",ã:"a",ä:"a",å:"a",Ç:"C",ç:"c",Ð:"D",ð:"d",È:"E",É:"E",Ê:"E",Ë:"E",è:"e",é:"e",ê:"e",ë:"e",Ì:"I",Í:"I",Î:"I",Ï:"I",ì:"i",í:"i",î:"i",ï:"i",Ñ:"N",ñ:"n",Ò:"O",Ó:"O",Ô:"O",Õ:"O",Ö:"O",Ø:"O",ò:"o",ó:"o",ô:"o",õ:"o",ö:"o",ø:"o",Ù:"U",Ú:"U",Û:"U",Ü:"U",ù:"u",ú:"u",û:"u",ü:"u",Ý:"Y",ý:"y",ÿ:"y",Æ:"Ae",æ:"ae",Þ:"Th",þ:"th",ß:"ss",Ā:"A",Ă:"A",Ą:"A",ā:"a",ă:"a",ą:"a",Ć:"C",Ĉ:"C",Ċ:"C",Č:"C",ć:"c",ĉ:"c",ċ:"c",č:"c",Ď:"D",Đ:"D",ď:"d",đ:"d",Ē:"E",Ĕ:"E",Ė:"E",Ę:"E",Ě:"E",ē:"e",ĕ:"e",ė:"e",ę:"e",ě:"e",Ĝ:"G",Ğ:"G",Ġ:"G",Ģ:"G",ĝ:"g",ğ:"g",ġ:"g",ģ:"g",Ĥ:"H",Ħ:"H",ĥ:"h",ħ:"h",Ĩ:"I",Ī:"I",Ĭ:"I",Į:"I",İ:"I",ĩ:"i",ī:"i",ĭ:"i",į:"i",ı:"i",Ĵ:"J",ĵ:"j",Ķ:"K",ķ:"k",ĸ:"k",Ĺ:"L",Ļ:"L",Ľ:"L",Ŀ:"L",Ł:"L",ĺ:"l",ļ:"l",ľ:"l",ŀ:"l",ł:"l",Ń:"N",Ņ:"N",Ň:"N",Ŋ:"N",ń:"n",ņ:"n",ň:"n",ŋ:"n",Ō:"O",Ŏ:"O",Ő:"O",ō:"o",ŏ:"o",ő:"o",Ŕ:"R",Ŗ:"R",Ř:"R",ŕ:"r",ŗ:"r",ř:"r",Ś:"S",Ŝ:"S",Ş:"S",Š:"S",ś:"s",ŝ:"s",ş:"s",š:"s",Ţ:"T",Ť:"T",Ŧ:"T",ţ:"t",ť:"t",ŧ:"t",Ũ:"U",Ū:"U",Ŭ:"U",Ů:"U",Ű:"U",Ų:"U",ũ:"u",ū:"u",ŭ:"u",ů:"u",ű:"u",ų:"u",Ŵ:"W",ŵ:"w",Ŷ:"Y",ŷ:"y",Ÿ:"Y",Ź:"Z",Ż:"Z",Ž:"Z",ź:"z",ż:"z",ž:"z",IJ:"IJ",ij:"ij",Œ:"Oe",œ:"oe",ʼn:"'n",ſ:"s"});e.exports=r},38777:(e,t,n)=>{var r=n(10852),o=function(){try{var e=r(Object,"defineProperty");return e({},"",{}),e}catch(e){}}();e.exports=o},67114:(e,t,n)=>{var r=n(88668),o=n(82908),s=n(74757);e.exports=function(e,t,n,i,a,l){var c=1&n,u=e.length,p=t.length;if(u!=p&&!(c&&p>u))return!1;var h=l.get(e),f=l.get(t);if(h&&f)return h==t&&f==e;var d=-1,m=!0,g=2&n?new r:void 0;for(l.set(e,t),l.set(t,e);++d{var r=n(62705),o=n(11149),s=n(77813),i=n(67114),a=n(68776),l=n(21814),c=r?r.prototype:void 0,u=c?c.valueOf:void 0;e.exports=function(e,t,n,r,c,p,h){switch(n){case"[object DataView]":if(e.byteLength!=t.byteLength||e.byteOffset!=t.byteOffset)return!1;e=e.buffer,t=t.buffer;case"[object ArrayBuffer]":return!(e.byteLength!=t.byteLength||!p(new o(e),new o(t)));case"[object Boolean]":case"[object Date]":case"[object Number]":return s(+e,+t);case"[object Error]":return e.name==t.name&&e.message==t.message;case"[object RegExp]":case"[object String]":return e==t+"";case"[object Map]":var f=a;case"[object Set]":var d=1&r;if(f||(f=l),e.size!=t.size&&!d)return!1;var m=h.get(e);if(m)return m==t;r|=2,h.set(e,t);var g=i(f(e),f(t),r,c,p,h);return h.delete(e),g;case"[object Symbol]":if(u)return u.call(e)==u.call(t)}return!1}},16096:(e,t,n)=>{var r=n(58234),o=Object.prototype.hasOwnProperty;e.exports=function(e,t,n,s,i,a){var l=1&n,c=r(e),u=c.length;if(u!=r(t).length&&!l)return!1;for(var p=u;p--;){var h=c[p];if(!(l?h in t:o.call(t,h)))return!1}var f=a.get(e),d=a.get(t);if(f&&d)return f==t&&d==e;var m=!0;a.set(e,t),a.set(t,e);for(var g=l;++p{var r=n(85564),o=n(45357),s=n(30061);e.exports=function(e){return s(o(e,void 0,r),e+"")}},31957:(e,t,n)=>{var r="object"==typeof n.g&&n.g&&n.g.Object===Object&&n.g;e.exports=r},58234:(e,t,n)=>{var r=n(68866),o=n(99551),s=n(3674);e.exports=function(e){return r(e,s,o)}},46904:(e,t,n)=>{var r=n(68866),o=n(51442),s=n(81704);e.exports=function(e){return r(e,s,o)}},66833:(e,t,n)=>{var r=n(89250),o=n(50308),s=r?function(e){return r.get(e)}:o;e.exports=s},97658:(e,t,n)=>{var r=n(52060),o=Object.prototype.hasOwnProperty;e.exports=function(e){for(var t=e.name+"",n=r[t],s=o.call(r,t)?n.length:0;s--;){var i=n[s],a=i.func;if(null==a||a==e)return i.name}return t}},20893:e=>{e.exports=function(e){return e.placeholder}},45050:(e,t,n)=>{var r=n(37019);e.exports=function(e,t){var n=e.__data__;return r(t)?n["string"==typeof t?"string":"hash"]:n.map}},1499:(e,t,n)=>{var r=n(89162),o=n(3674);e.exports=function(e){for(var t=o(e),n=t.length;n--;){var s=t[n],i=e[s];t[n]=[s,i,r(i)]}return t}},10852:(e,t,n)=>{var r=n(28458),o=n(47801);e.exports=function(e,t){var n=o(e,t);return r(n)?n:void 0}},85924:(e,t,n)=>{var r=n(5569)(Object.getPrototypeOf,Object);e.exports=r},89607:(e,t,n)=>{var r=n(62705),o=Object.prototype,s=o.hasOwnProperty,i=o.toString,a=r?r.toStringTag:void 0;e.exports=function(e){var t=s.call(e,a),n=e[a];try{e[a]=void 0;var r=!0}catch(e){}var o=i.call(e);return r&&(t?e[a]=n:delete e[a]),o}},99551:(e,t,n)=>{var r=n(34963),o=n(70479),s=Object.prototype.propertyIsEnumerable,i=Object.getOwnPropertySymbols,a=i?function(e){return null==e?[]:(e=Object(e),r(i(e),(function(t){return s.call(e,t)})))}:o;e.exports=a},51442:(e,t,n)=>{var r=n(62488),o=n(85924),s=n(99551),i=n(70479),a=Object.getOwnPropertySymbols?function(e){for(var t=[];e;)r(t,s(e)),e=o(e);return t}:i;e.exports=a},98882:(e,t,n)=>{var r=n(18552),o=n(57071),s=n(53818),i=n(58525),a=n(70577),l=n(44239),c=n(80346),u="[object Map]",p="[object Promise]",h="[object Set]",f="[object WeakMap]",d="[object DataView]",m=c(r),g=c(o),y=c(s),v=c(i),b=c(a),w=l;(r&&w(new r(new ArrayBuffer(1)))!=d||o&&w(new o)!=u||s&&w(s.resolve())!=p||i&&w(new i)!=h||a&&w(new a)!=f)&&(w=function(e){var t=l(e),n="[object Object]"==t?e.constructor:void 0,r=n?c(n):"";if(r)switch(r){case m:return d;case g:return u;case y:return p;case v:return h;case b:return f}return t}),e.exports=w},47801:e=>{e.exports=function(e,t){return null==e?void 0:e[t]}},58775:e=>{var t=/\{\n\/\* \[wrapped with (.+)\] \*/,n=/,? & /;e.exports=function(e){var r=e.match(t);return r?r[1].split(n):[]}},222:(e,t,n)=>{var r=n(71811),o=n(35694),s=n(1469),i=n(65776),a=n(41780),l=n(40327);e.exports=function(e,t,n){for(var c=-1,u=(t=r(t,e)).length,p=!1;++c{var t=RegExp("[\\u200d\\ud800-\\udfff\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff\\ufe0e\\ufe0f]");e.exports=function(e){return t.test(e)}},93157:e=>{var t=/[a-z][A-Z]|[A-Z]{2}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/;e.exports=function(e){return t.test(e)}},51789:(e,t,n)=>{var r=n(94536);e.exports=function(){this.__data__=r?r(null):{},this.size=0}},80401:e=>{e.exports=function(e){var t=this.has(e)&&delete this.__data__[e];return this.size-=t?1:0,t}},57667:(e,t,n)=>{var r=n(94536),o=Object.prototype.hasOwnProperty;e.exports=function(e){var t=this.__data__;if(r){var n=t[e];return"__lodash_hash_undefined__"===n?void 0:n}return o.call(t,e)?t[e]:void 0}},21327:(e,t,n)=>{var r=n(94536),o=Object.prototype.hasOwnProperty;e.exports=function(e){var t=this.__data__;return r?void 0!==t[e]:o.call(t,e)}},81866:(e,t,n)=>{var r=n(94536);e.exports=function(e,t){var n=this.__data__;return this.size+=this.has(e)?0:1,n[e]=r&&void 0===t?"__lodash_hash_undefined__":t,this}},43824:e=>{var t=Object.prototype.hasOwnProperty;e.exports=function(e){var n=e.length,r=new e.constructor(n);return n&&"string"==typeof e[0]&&t.call(e,"index")&&(r.index=e.index,r.input=e.input),r}},29148:(e,t,n)=>{var r=n(74318),o=n(57157),s=n(93147),i=n(40419),a=n(77133);e.exports=function(e,t,n){var l=e.constructor;switch(t){case"[object ArrayBuffer]":return r(e);case"[object Boolean]":case"[object Date]":return new l(+e);case"[object DataView]":return o(e,n);case"[object Float32Array]":case"[object Float64Array]":case"[object Int8Array]":case"[object Int16Array]":case"[object Int32Array]":case"[object Uint8Array]":case"[object Uint8ClampedArray]":case"[object Uint16Array]":case"[object Uint32Array]":return a(e,n);case"[object Map]":case"[object Set]":return new l;case"[object Number]":case"[object String]":return new l(e);case"[object RegExp]":return s(e);case"[object Symbol]":return i(e)}}},38517:(e,t,n)=>{var r=n(3118),o=n(85924),s=n(25726);e.exports=function(e){return"function"!=typeof e.constructor||s(e)?{}:r(o(e))}},83112:e=>{var t=/\{(?:\n\/\* \[wrapped with .+\] \*\/)?\n?/;e.exports=function(e,n){var r=n.length;if(!r)return e;var o=r-1;return n[o]=(r>1?"& ":"")+n[o],n=n.join(r>2?", ":" "),e.replace(t,"{\n/* [wrapped with "+n+"] */\n")}},37285:(e,t,n)=>{var r=n(62705),o=n(35694),s=n(1469),i=r?r.isConcatSpreadable:void 0;e.exports=function(e){return s(e)||o(e)||!!(i&&e&&e[i])}},65776:e=>{var t=/^(?:0|[1-9]\d*)$/;e.exports=function(e,n){var r=typeof e;return!!(n=null==n?9007199254740991:n)&&("number"==r||"symbol"!=r&&t.test(e))&&e>-1&&e%1==0&&e{var r=n(77813),o=n(98612),s=n(65776),i=n(13218);e.exports=function(e,t,n){if(!i(n))return!1;var a=typeof t;return!!("number"==a?o(n)&&s(t,n.length):"string"==a&&t in n)&&r(n[t],e)}},15403:(e,t,n)=>{var r=n(1469),o=n(33448),s=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,i=/^\w*$/;e.exports=function(e,t){if(r(e))return!1;var n=typeof e;return!("number"!=n&&"symbol"!=n&&"boolean"!=n&&null!=e&&!o(e))||(i.test(e)||!s.test(e)||null!=t&&e in Object(t))}},37019:e=>{e.exports=function(e){var t=typeof e;return"string"==t||"number"==t||"symbol"==t||"boolean"==t?"__proto__"!==e:null===e}},86528:(e,t,n)=>{var r=n(96425),o=n(66833),s=n(97658),i=n(8111);e.exports=function(e){var t=s(e),n=i[t];if("function"!=typeof n||!(t in r.prototype))return!1;if(e===n)return!0;var a=o(n);return!!a&&e===a[0]}},15346:(e,t,n)=>{var r,o=n(14429),s=(r=/[^.]+$/.exec(o&&o.keys&&o.keys.IE_PROTO||""))?"Symbol(src)_1."+r:"";e.exports=function(e){return!!s&&s in e}},25726:e=>{var t=Object.prototype;e.exports=function(e){var n=e&&e.constructor;return e===("function"==typeof n&&n.prototype||t)}},89162:(e,t,n)=>{var r=n(13218);e.exports=function(e){return e==e&&!r(e)}},27040:e=>{e.exports=function(){this.__data__=[],this.size=0}},14125:(e,t,n)=>{var r=n(18470),o=Array.prototype.splice;e.exports=function(e){var t=this.__data__,n=r(t,e);return!(n<0)&&(n==t.length-1?t.pop():o.call(t,n,1),--this.size,!0)}},82117:(e,t,n)=>{var r=n(18470);e.exports=function(e){var t=this.__data__,n=r(t,e);return n<0?void 0:t[n][1]}},67518:(e,t,n)=>{var r=n(18470);e.exports=function(e){return r(this.__data__,e)>-1}},54705:(e,t,n)=>{var r=n(18470);e.exports=function(e,t){var n=this.__data__,o=r(n,e);return o<0?(++this.size,n.push([e,t])):n[o][1]=t,this}},24785:(e,t,n)=>{var r=n(1989),o=n(38407),s=n(57071);e.exports=function(){this.size=0,this.__data__={hash:new r,map:new(s||o),string:new r}}},11285:(e,t,n)=>{var r=n(45050);e.exports=function(e){var t=r(this,e).delete(e);return this.size-=t?1:0,t}},96e3:(e,t,n)=>{var r=n(45050);e.exports=function(e){return r(this,e).get(e)}},49916:(e,t,n)=>{var r=n(45050);e.exports=function(e){return r(this,e).has(e)}},95265:(e,t,n)=>{var r=n(45050);e.exports=function(e,t){var n=r(this,e),o=n.size;return n.set(e,t),this.size+=n.size==o?0:1,this}},68776:e=>{e.exports=function(e){var t=-1,n=Array(e.size);return e.forEach((function(e,r){n[++t]=[r,e]})),n}},42634:e=>{e.exports=function(e,t){return function(n){return null!=n&&(n[e]===t&&(void 0!==t||e in Object(n)))}}},24523:(e,t,n)=>{var r=n(88306);e.exports=function(e){var t=r(e,(function(e){return 500===n.size&&n.clear(),e})),n=t.cache;return t}},63833:(e,t,n)=>{var r=n(52157),o=n(14054),s=n(46460),i="__lodash_placeholder__",a=128,l=Math.min;e.exports=function(e,t){var n=e[1],c=t[1],u=n|c,p=u<131,h=c==a&&8==n||c==a&&256==n&&e[7].length<=t[8]||384==c&&t[7].length<=t[8]&&8==n;if(!p&&!h)return e;1&c&&(e[2]=t[2],u|=1&n?0:4);var f=t[3];if(f){var d=e[3];e[3]=d?r(d,f,t[4]):f,e[4]=d?s(e[3],i):t[4]}return(f=t[5])&&(d=e[5],e[5]=d?o(d,f,t[6]):f,e[6]=d?s(e[5],i):t[6]),(f=t[7])&&(e[7]=f),c&a&&(e[8]=null==e[8]?t[8]:l(e[8],t[8])),null==e[9]&&(e[9]=t[9]),e[0]=t[0],e[1]=u,e}},89250:(e,t,n)=>{var r=n(70577),o=r&&new r;e.exports=o},94536:(e,t,n)=>{var r=n(10852)(Object,"create");e.exports=r},86916:(e,t,n)=>{var r=n(5569)(Object.keys,Object);e.exports=r},33498:e=>{e.exports=function(e){var t=[];if(null!=e)for(var n in Object(e))t.push(n);return t}},31167:(e,t,n)=>{e=n.nmd(e);var r=n(31957),o=t&&!t.nodeType&&t,s=o&&e&&!e.nodeType&&e,i=s&&s.exports===o&&r.process,a=function(){try{var e=s&&s.require&&s.require("util").types;return e||i&&i.binding&&i.binding("util")}catch(e){}}();e.exports=a},2333:e=>{var t=Object.prototype.toString;e.exports=function(e){return t.call(e)}},5569:e=>{e.exports=function(e,t){return function(n){return e(t(n))}}},45357:(e,t,n)=>{var r=n(96874),o=Math.max;e.exports=function(e,t,n){return t=o(void 0===t?e.length-1:t,0),function(){for(var s=arguments,i=-1,a=o(s.length-t,0),l=Array(a);++i{var r=n(97786),o=n(14259);e.exports=function(e,t){return t.length<2?e:r(e,o(t,0,-1))}},52060:e=>{e.exports={}},90451:(e,t,n)=>{var r=n(278),o=n(65776),s=Math.min;e.exports=function(e,t){for(var n=e.length,i=s(t.length,n),a=r(e);i--;){var l=t[i];e[i]=o(l,n)?a[l]:void 0}return e}},46460:e=>{var t="__lodash_placeholder__";e.exports=function(e,n){for(var r=-1,o=e.length,s=0,i=[];++r{var r=n(31957),o="object"==typeof self&&self&&self.Object===Object&&self,s=r||o||Function("return this")();e.exports=s},36390:e=>{e.exports=function(e,t){if(("constructor"!==t||"function"!=typeof e[t])&&"__proto__"!=t)return e[t]}},90619:e=>{e.exports=function(e){return this.__data__.set(e,"__lodash_hash_undefined__"),this}},72385:e=>{e.exports=function(e){return this.__data__.has(e)}},258:(e,t,n)=>{var r=n(28045),o=n(21275)(r);e.exports=o},21814:e=>{e.exports=function(e){var t=-1,n=Array(e.size);return e.forEach((function(e){n[++t]=e})),n}},30061:(e,t,n)=>{var r=n(56560),o=n(21275)(r);e.exports=o},69255:(e,t,n)=>{var r=n(58775),o=n(83112),s=n(30061),i=n(87241);e.exports=function(e,t,n){var a=t+"";return s(e,o(a,i(r(a),n)))}},21275:e=>{var t=Date.now;e.exports=function(e){var n=0,r=0;return function(){var o=t(),s=16-(o-r);if(r=o,s>0){if(++n>=800)return arguments[0]}else n=0;return e.apply(void 0,arguments)}}},37465:(e,t,n)=>{var r=n(38407);e.exports=function(){this.__data__=new r,this.size=0}},63779:e=>{e.exports=function(e){var t=this.__data__,n=t.delete(e);return this.size=t.size,n}},67599:e=>{e.exports=function(e){return this.__data__.get(e)}},44758:e=>{e.exports=function(e){return this.__data__.has(e)}},34309:(e,t,n)=>{var r=n(38407),o=n(57071),s=n(83369);e.exports=function(e,t){var n=this.__data__;if(n instanceof r){var i=n.__data__;if(!o||i.length<199)return i.push([e,t]),this.size=++n.size,this;n=this.__data__=new s(i)}return n.set(e,t),this.size=n.size,this}},42351:e=>{e.exports=function(e,t,n){for(var r=n-1,o=e.length;++r{var r=n(44286),o=n(62689),s=n(676);e.exports=function(e){return o(e)?s(e):r(e)}},55514:(e,t,n)=>{var r=n(24523),o=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g,s=/\\(\\)?/g,i=r((function(e){var t=[];return 46===e.charCodeAt(0)&&t.push(""),e.replace(o,(function(e,n,r,o){t.push(r?o.replace(s,"$1"):n||e)})),t}));e.exports=i},40327:(e,t,n)=>{var r=n(33448);e.exports=function(e){if("string"==typeof e||r(e))return e;var t=e+"";return"0"==t&&1/e==-Infinity?"-0":t}},80346:e=>{var t=Function.prototype.toString;e.exports=function(e){if(null!=e){try{return t.call(e)}catch(e){}try{return e+""}catch(e){}}return""}},67990:e=>{var t=/\s/;e.exports=function(e){for(var n=e.length;n--&&t.test(e.charAt(n)););return n}},676:e=>{var t="\\ud800-\\udfff",n="["+t+"]",r="[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]",o="\\ud83c[\\udffb-\\udfff]",s="[^"+t+"]",i="(?:\\ud83c[\\udde6-\\uddff]){2}",a="[\\ud800-\\udbff][\\udc00-\\udfff]",l="(?:"+r+"|"+o+")"+"?",c="[\\ufe0e\\ufe0f]?",u=c+l+("(?:\\u200d(?:"+[s,i,a].join("|")+")"+c+l+")*"),p="(?:"+[s+r+"?",r,i,a,n].join("|")+")",h=RegExp(o+"(?="+o+")|"+p+u,"g");e.exports=function(e){return e.match(h)||[]}},2757:e=>{var t="\\ud800-\\udfff",n="\\u2700-\\u27bf",r="a-z\\xdf-\\xf6\\xf8-\\xff",o="A-Z\\xc0-\\xd6\\xd8-\\xde",s="\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000",i="["+s+"]",a="\\d+",l="["+n+"]",c="["+r+"]",u="[^"+t+s+a+n+r+o+"]",p="(?:\\ud83c[\\udde6-\\uddff]){2}",h="[\\ud800-\\udbff][\\udc00-\\udfff]",f="["+o+"]",d="(?:"+c+"|"+u+")",m="(?:"+f+"|"+u+")",g="(?:['’](?:d|ll|m|re|s|t|ve))?",y="(?:['’](?:D|LL|M|RE|S|T|VE))?",v="(?:[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]|\\ud83c[\\udffb-\\udfff])?",b="[\\ufe0e\\ufe0f]?",w=b+v+("(?:\\u200d(?:"+["[^"+t+"]",p,h].join("|")+")"+b+v+")*"),E="(?:"+[l,p,h].join("|")+")"+w,x=RegExp([f+"?"+c+"+"+g+"(?="+[i,f,"$"].join("|")+")",m+"+"+y+"(?="+[i,f+d,"$"].join("|")+")",f+"?"+d+"+"+g,f+"+"+y,"\\d*(?:1ST|2ND|3RD|(?![123])\\dTH)(?=\\b|[a-z_])","\\d*(?:1st|2nd|3rd|(?![123])\\dth)(?=\\b|[A-Z_])",a,E].join("|"),"g");e.exports=function(e){return e.match(x)||[]}},87241:(e,t,n)=>{var r=n(77412),o=n(47443),s=[["ary",128],["bind",1],["bindKey",2],["curry",8],["curryRight",16],["flip",512],["partial",32],["partialRight",64],["rearg",256]];e.exports=function(e,t){return r(s,(function(n){var r="_."+n[0];t&n[1]&&!o(e,r)&&e.push(r)})),e.sort()}},21913:(e,t,n)=>{var r=n(96425),o=n(7548),s=n(278);e.exports=function(e){if(e instanceof r)return e.clone();var t=new o(e.__wrapped__,e.__chain__);return t.__actions__=s(e.__actions__),t.__index__=e.__index__,t.__values__=e.__values__,t}},39514:(e,t,n)=>{var r=n(97727);e.exports=function(e,t,n){return t=n?void 0:t,t=e&&null==t?e.length:t,r(e,128,void 0,void 0,void 0,void 0,t)}},68929:(e,t,n)=>{var r=n(48403),o=n(35393)((function(e,t,n){return t=t.toLowerCase(),e+(n?r(t):t)}));e.exports=o},48403:(e,t,n)=>{var r=n(79833),o=n(11700);e.exports=function(e){return o(r(e).toLowerCase())}},66678:(e,t,n)=>{var r=n(85990);e.exports=function(e){return r(e,4)}},75703:e=>{e.exports=function(e){return function(){return e}}},40087:(e,t,n)=>{var r=n(97727);function o(e,t,n){var s=r(e,8,void 0,void 0,void 0,void 0,void 0,t=n?void 0:t);return s.placeholder=o.placeholder,s}o.placeholder={},e.exports=o},23279:(e,t,n)=>{var r=n(13218),o=n(7771),s=n(14841),i=Math.max,a=Math.min;e.exports=function(e,t,n){var l,c,u,p,h,f,d=0,m=!1,g=!1,y=!0;if("function"!=typeof e)throw new TypeError("Expected a function");function v(t){var n=l,r=c;return l=c=void 0,d=t,p=e.apply(r,n)}function b(e){var n=e-f;return void 0===f||n>=t||n<0||g&&e-d>=u}function w(){var e=o();if(b(e))return E(e);h=setTimeout(w,function(e){var n=t-(e-f);return g?a(n,u-(e-d)):n}(e))}function E(e){return h=void 0,y&&l?v(e):(l=c=void 0,p)}function x(){var e=o(),n=b(e);if(l=arguments,c=this,f=e,n){if(void 0===h)return function(e){return d=e,h=setTimeout(w,t),m?v(e):p}(f);if(g)return clearTimeout(h),h=setTimeout(w,t),v(f)}return void 0===h&&(h=setTimeout(w,t)),p}return t=s(t)||0,r(n)&&(m=!!n.leading,u=(g="maxWait"in n)?i(s(n.maxWait)||0,t):u,y="trailing"in n?!!n.trailing:y),x.cancel=function(){void 0!==h&&clearTimeout(h),d=0,l=f=c=h=void 0},x.flush=function(){return void 0===h?p:E(o())},x}},53816:(e,t,n)=>{var r=n(69389),o=n(79833),s=/[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g,i=RegExp("[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]","g");e.exports=function(e){return(e=o(e))&&e.replace(s,r).replace(i,"")}},77813:e=>{e.exports=function(e,t){return e===t||e!=e&&t!=t}},13311:(e,t,n)=>{var r=n(67740)(n(30998));e.exports=r},30998:(e,t,n)=>{var r=n(41848),o=n(67206),s=n(40554),i=Math.max;e.exports=function(e,t,n){var a=null==e?0:e.length;if(!a)return-1;var l=null==n?0:s(n);return l<0&&(l=i(a+l,0)),r(e,o(t,3),l)}},85564:(e,t,n)=>{var r=n(21078);e.exports=function(e){return(null==e?0:e.length)?r(e,1):[]}},84599:(e,t,n)=>{var r=n(68836),o=n(69306),s=Array.prototype.push;function i(e,t){return 2==t?function(t,n){return e(t,n)}:function(t){return e(t)}}function a(e){for(var t=e?e.length:0,n=Array(t);t--;)n[t]=e[t];return n}function l(e,t){return function(){var n=arguments.length;if(n){for(var r=Array(n);n--;)r[n]=arguments[n];var o=r[0]=t.apply(void 0,r);return e.apply(void 0,r),o}}}e.exports=function e(t,n,c,u){var p="function"==typeof n,h=n===Object(n);if(h&&(u=c,c=n,n=void 0),null==c)throw new TypeError;u||(u={});var f={cap:!("cap"in u)||u.cap,curry:!("curry"in u)||u.curry,fixed:!("fixed"in u)||u.fixed,immutable:!("immutable"in u)||u.immutable,rearg:!("rearg"in u)||u.rearg},d=p?c:o,m="curry"in u&&u.curry,g="fixed"in u&&u.fixed,y="rearg"in u&&u.rearg,v=p?c.runInContext():void 0,b=p?c:{ary:t.ary,assign:t.assign,clone:t.clone,curry:t.curry,forEach:t.forEach,isArray:t.isArray,isError:t.isError,isFunction:t.isFunction,isWeakMap:t.isWeakMap,iteratee:t.iteratee,keys:t.keys,rearg:t.rearg,toInteger:t.toInteger,toPath:t.toPath},w=b.ary,E=b.assign,x=b.clone,S=b.curry,_=b.forEach,j=b.isArray,O=b.isError,k=b.isFunction,A=b.isWeakMap,C=b.keys,P=b.rearg,N=b.toInteger,I=b.toPath,T=C(r.aryMethod),R={castArray:function(e){return function(){var t=arguments[0];return j(t)?e(a(t)):e.apply(void 0,arguments)}},iteratee:function(e){return function(){var t=arguments[1],n=e(arguments[0],t),r=n.length;return f.cap&&"number"==typeof t?(t=t>2?t-2:1,r&&r<=t?n:i(n,t)):n}},mixin:function(e){return function(t){var n=this;if(!k(n))return e(n,Object(t));var r=[];return _(C(t),(function(e){k(t[e])&&r.push([e,n.prototype[e]])})),e(n,Object(t)),_(r,(function(e){var t=e[1];k(t)?n.prototype[e[0]]=t:delete n.prototype[e[0]]})),n}},nthArg:function(e){return function(t){var n=t<0?1:N(t)+1;return S(e(t),n)}},rearg:function(e){return function(t,n){var r=n?n.length:0;return S(e(t,n),r)}},runInContext:function(n){return function(r){return e(t,n(r),u)}}};function M(e,t){if(f.cap){var n=r.iterateeRearg[e];if(n)return function(e,t){return $(e,(function(e){var n=t.length;return function(e,t){return 2==t?function(t,n){return e.apply(void 0,arguments)}:function(t){return e.apply(void 0,arguments)}}(P(i(e,n),t),n)}))}(t,n);var o=!p&&r.iterateeAry[e];if(o)return function(e,t){return $(e,(function(e){return"function"==typeof e?i(e,t):e}))}(t,o)}return t}function D(e,t,n){if(f.fixed&&(g||!r.skipFixed[e])){var o=r.methodSpread[e],i=o&&o.start;return void 0===i?w(t,n):function(e,t){return function(){for(var n=arguments.length,r=n-1,o=Array(n);n--;)o[n]=arguments[n];var i=o[t],a=o.slice(0,t);return i&&s.apply(a,i),t!=r&&s.apply(a,o.slice(t+1)),e.apply(this,a)}}(t,i)}return t}function F(e,t,n){return f.rearg&&n>1&&(y||!r.skipRearg[e])?P(t,r.methodRearg[e]||r.aryRearg[n]):t}function L(e,t){for(var n=-1,r=(t=I(t)).length,o=r-1,s=x(Object(e)),i=s;null!=i&&++n1?S(t,n):t}(0,o=M(s,o),e),!1}})),!o})),o||(o=i),o==t&&(o=m?S(o,1):function(){return t.apply(this,arguments)}),o.convert=B(s,t),o.placeholder=t.placeholder=n,o}if(!h)return q(n,c,d);var U=c,z=[];return _(T,(function(e){_(r.aryMethod[e],(function(e){var t=U[r.remap[e]||e];t&&z.push([e,q(e,t,U)])}))})),_(C(U),(function(e){var t=U[e];if("function"==typeof t){for(var n=z.length;n--;)if(z[n][0]==e)return;t.convert=B(e,t),z.push([e,t])}})),_(z,(function(e){U[e[0]]=e[1]})),U.convert=function(e){return U.runInContext.convert(e)(void 0)},U.placeholder=U,_(C(U),(function(e){_(r.realToAlias[e]||[],(function(t){U[t]=U[e]}))})),U}},68836:(e,t)=>{t.aliasToReal={each:"forEach",eachRight:"forEachRight",entries:"toPairs",entriesIn:"toPairsIn",extend:"assignIn",extendAll:"assignInAll",extendAllWith:"assignInAllWith",extendWith:"assignInWith",first:"head",conforms:"conformsTo",matches:"isMatch",property:"get",__:"placeholder",F:"stubFalse",T:"stubTrue",all:"every",allPass:"overEvery",always:"constant",any:"some",anyPass:"overSome",apply:"spread",assoc:"set",assocPath:"set",complement:"negate",compose:"flowRight",contains:"includes",dissoc:"unset",dissocPath:"unset",dropLast:"dropRight",dropLastWhile:"dropRightWhile",equals:"isEqual",identical:"eq",indexBy:"keyBy",init:"initial",invertObj:"invert",juxt:"over",omitAll:"omit",nAry:"ary",path:"get",pathEq:"matchesProperty",pathOr:"getOr",paths:"at",pickAll:"pick",pipe:"flow",pluck:"map",prop:"get",propEq:"matchesProperty",propOr:"getOr",props:"at",symmetricDifference:"xor",symmetricDifferenceBy:"xorBy",symmetricDifferenceWith:"xorWith",takeLast:"takeRight",takeLastWhile:"takeRightWhile",unapply:"rest",unnest:"flatten",useWith:"overArgs",where:"conformsTo",whereEq:"isMatch",zipObj:"zipObject"},t.aryMethod={1:["assignAll","assignInAll","attempt","castArray","ceil","create","curry","curryRight","defaultsAll","defaultsDeepAll","floor","flow","flowRight","fromPairs","invert","iteratee","memoize","method","mergeAll","methodOf","mixin","nthArg","over","overEvery","overSome","rest","reverse","round","runInContext","spread","template","trim","trimEnd","trimStart","uniqueId","words","zipAll"],2:["add","after","ary","assign","assignAllWith","assignIn","assignInAllWith","at","before","bind","bindAll","bindKey","chunk","cloneDeepWith","cloneWith","concat","conformsTo","countBy","curryN","curryRightN","debounce","defaults","defaultsDeep","defaultTo","delay","difference","divide","drop","dropRight","dropRightWhile","dropWhile","endsWith","eq","every","filter","find","findIndex","findKey","findLast","findLastIndex","findLastKey","flatMap","flatMapDeep","flattenDepth","forEach","forEachRight","forIn","forInRight","forOwn","forOwnRight","get","groupBy","gt","gte","has","hasIn","includes","indexOf","intersection","invertBy","invoke","invokeMap","isEqual","isMatch","join","keyBy","lastIndexOf","lt","lte","map","mapKeys","mapValues","matchesProperty","maxBy","meanBy","merge","mergeAllWith","minBy","multiply","nth","omit","omitBy","overArgs","pad","padEnd","padStart","parseInt","partial","partialRight","partition","pick","pickBy","propertyOf","pull","pullAll","pullAt","random","range","rangeRight","rearg","reject","remove","repeat","restFrom","result","sampleSize","some","sortBy","sortedIndex","sortedIndexOf","sortedLastIndex","sortedLastIndexOf","sortedUniqBy","split","spreadFrom","startsWith","subtract","sumBy","take","takeRight","takeRightWhile","takeWhile","tap","throttle","thru","times","trimChars","trimCharsEnd","trimCharsStart","truncate","union","uniqBy","uniqWith","unset","unzipWith","without","wrap","xor","zip","zipObject","zipObjectDeep"],3:["assignInWith","assignWith","clamp","differenceBy","differenceWith","findFrom","findIndexFrom","findLastFrom","findLastIndexFrom","getOr","includesFrom","indexOfFrom","inRange","intersectionBy","intersectionWith","invokeArgs","invokeArgsMap","isEqualWith","isMatchWith","flatMapDepth","lastIndexOfFrom","mergeWith","orderBy","padChars","padCharsEnd","padCharsStart","pullAllBy","pullAllWith","rangeStep","rangeStepRight","reduce","reduceRight","replace","set","slice","sortedIndexBy","sortedLastIndexBy","transform","unionBy","unionWith","update","xorBy","xorWith","zipWith"],4:["fill","setWith","updateWith"]},t.aryRearg={2:[1,0],3:[2,0,1],4:[3,2,0,1]},t.iterateeAry={dropRightWhile:1,dropWhile:1,every:1,filter:1,find:1,findFrom:1,findIndex:1,findIndexFrom:1,findKey:1,findLast:1,findLastFrom:1,findLastIndex:1,findLastIndexFrom:1,findLastKey:1,flatMap:1,flatMapDeep:1,flatMapDepth:1,forEach:1,forEachRight:1,forIn:1,forInRight:1,forOwn:1,forOwnRight:1,map:1,mapKeys:1,mapValues:1,partition:1,reduce:2,reduceRight:2,reject:1,remove:1,some:1,takeRightWhile:1,takeWhile:1,times:1,transform:2},t.iterateeRearg={mapKeys:[1],reduceRight:[1,0]},t.methodRearg={assignInAllWith:[1,0],assignInWith:[1,2,0],assignAllWith:[1,0],assignWith:[1,2,0],differenceBy:[1,2,0],differenceWith:[1,2,0],getOr:[2,1,0],intersectionBy:[1,2,0],intersectionWith:[1,2,0],isEqualWith:[1,2,0],isMatchWith:[2,1,0],mergeAllWith:[1,0],mergeWith:[1,2,0],padChars:[2,1,0],padCharsEnd:[2,1,0],padCharsStart:[2,1,0],pullAllBy:[2,1,0],pullAllWith:[2,1,0],rangeStep:[1,2,0],rangeStepRight:[1,2,0],setWith:[3,1,2,0],sortedIndexBy:[2,1,0],sortedLastIndexBy:[2,1,0],unionBy:[1,2,0],unionWith:[1,2,0],updateWith:[3,1,2,0],xorBy:[1,2,0],xorWith:[1,2,0],zipWith:[1,2,0]},t.methodSpread={assignAll:{start:0},assignAllWith:{start:0},assignInAll:{start:0},assignInAllWith:{start:0},defaultsAll:{start:0},defaultsDeepAll:{start:0},invokeArgs:{start:2},invokeArgsMap:{start:2},mergeAll:{start:0},mergeAllWith:{start:0},partial:{start:1},partialRight:{start:1},without:{start:1},zipAll:{start:0}},t.mutate={array:{fill:!0,pull:!0,pullAll:!0,pullAllBy:!0,pullAllWith:!0,pullAt:!0,remove:!0,reverse:!0},object:{assign:!0,assignAll:!0,assignAllWith:!0,assignIn:!0,assignInAll:!0,assignInAllWith:!0,assignInWith:!0,assignWith:!0,defaults:!0,defaultsAll:!0,defaultsDeep:!0,defaultsDeepAll:!0,merge:!0,mergeAll:!0,mergeAllWith:!0,mergeWith:!0},set:{set:!0,setWith:!0,unset:!0,update:!0,updateWith:!0}},t.realToAlias=function(){var e=Object.prototype.hasOwnProperty,n=t.aliasToReal,r={};for(var o in n){var s=n[o];e.call(r,s)?r[s].push(o):r[s]=[o]}return r}(),t.remap={assignAll:"assign",assignAllWith:"assignWith",assignInAll:"assignIn",assignInAllWith:"assignInWith",curryN:"curry",curryRightN:"curryRight",defaultsAll:"defaults",defaultsDeepAll:"defaultsDeep",findFrom:"find",findIndexFrom:"findIndex",findLastFrom:"findLast",findLastIndexFrom:"findLastIndex",getOr:"get",includesFrom:"includes",indexOfFrom:"indexOf",invokeArgs:"invoke",invokeArgsMap:"invokeMap",lastIndexOfFrom:"lastIndexOf",mergeAll:"merge",mergeAllWith:"mergeWith",padChars:"pad",padCharsEnd:"padEnd",padCharsStart:"padStart",propertyOf:"get",rangeStep:"range",rangeStepRight:"rangeRight",restFrom:"rest",spreadFrom:"spread",trimChars:"trim",trimCharsEnd:"trimEnd",trimCharsStart:"trimStart",zipAll:"zip"},t.skipFixed={castArray:!0,flow:!0,flowRight:!0,iteratee:!0,mixin:!0,rearg:!0,runInContext:!0},t.skipRearg={add:!0,assign:!0,assignIn:!0,bind:!0,bindKey:!0,concat:!0,difference:!0,divide:!0,eq:!0,gt:!0,gte:!0,isEqual:!0,lt:!0,lte:!0,matchesProperty:!0,merge:!0,multiply:!0,overArgs:!0,partial:!0,partialRight:!0,propertyOf:!0,random:!0,range:!0,rangeRight:!0,subtract:!0,zip:!0,zipObject:!0,zipObjectDeep:!0}},4269:(e,t,n)=>{e.exports={ary:n(39514),assign:n(44037),clone:n(66678),curry:n(40087),forEach:n(77412),isArray:n(1469),isError:n(64647),isFunction:n(23560),isWeakMap:n(81018),iteratee:n(72594),keys:n(280),rearg:n(4963),toInteger:n(40554),toPath:n(30084)}},72700:(e,t,n)=>{e.exports=n(28252)},92822:(e,t,n)=>{var r=n(84599),o=n(4269);e.exports=function(e,t,n){return r(o,e,t,n)}},69306:e=>{e.exports={}},28252:(e,t,n)=>{var r=n(92822)("set",n(36968));r.placeholder=n(69306),e.exports=r},27361:(e,t,n)=>{var r=n(97786);e.exports=function(e,t,n){var o=null==e?void 0:r(e,t);return void 0===o?n:o}},79095:(e,t,n)=>{var r=n(13),o=n(222);e.exports=function(e,t){return null!=e&&o(e,t,r)}},6557:e=>{e.exports=function(e){return e}},35694:(e,t,n)=>{var r=n(9454),o=n(37005),s=Object.prototype,i=s.hasOwnProperty,a=s.propertyIsEnumerable,l=r(function(){return arguments}())?r:function(e){return o(e)&&i.call(e,"callee")&&!a.call(e,"callee")};e.exports=l},1469:e=>{var t=Array.isArray;e.exports=t},98612:(e,t,n)=>{var r=n(23560),o=n(41780);e.exports=function(e){return null!=e&&o(e.length)&&!r(e)}},29246:(e,t,n)=>{var r=n(98612),o=n(37005);e.exports=function(e){return o(e)&&r(e)}},51584:(e,t,n)=>{var r=n(44239),o=n(37005);e.exports=function(e){return!0===e||!1===e||o(e)&&"[object Boolean]"==r(e)}},44144:(e,t,n)=>{e=n.nmd(e);var r=n(55639),o=n(95062),s=t&&!t.nodeType&&t,i=s&&e&&!e.nodeType&&e,a=i&&i.exports===s?r.Buffer:void 0,l=(a?a.isBuffer:void 0)||o;e.exports=l},41609:(e,t,n)=>{var r=n(280),o=n(98882),s=n(35694),i=n(1469),a=n(98612),l=n(44144),c=n(25726),u=n(36719),p=Object.prototype.hasOwnProperty;e.exports=function(e){if(null==e)return!0;if(a(e)&&(i(e)||"string"==typeof e||"function"==typeof e.splice||l(e)||u(e)||s(e)))return!e.length;var t=o(e);if("[object Map]"==t||"[object Set]"==t)return!e.size;if(c(e))return!r(e).length;for(var n in e)if(p.call(e,n))return!1;return!0}},18446:(e,t,n)=>{var r=n(90939);e.exports=function(e,t){return r(e,t)}},64647:(e,t,n)=>{var r=n(44239),o=n(37005),s=n(68630);e.exports=function(e){if(!o(e))return!1;var t=r(e);return"[object Error]"==t||"[object DOMException]"==t||"string"==typeof e.message&&"string"==typeof e.name&&!s(e)}},23560:(e,t,n)=>{var r=n(44239),o=n(13218);e.exports=function(e){if(!o(e))return!1;var t=r(e);return"[object Function]"==t||"[object GeneratorFunction]"==t||"[object AsyncFunction]"==t||"[object Proxy]"==t}},41780:e=>{e.exports=function(e){return"number"==typeof e&&e>-1&&e%1==0&&e<=9007199254740991}},56688:(e,t,n)=>{var r=n(25588),o=n(7518),s=n(31167),i=s&&s.isMap,a=i?o(i):r;e.exports=a},45220:e=>{e.exports=function(e){return null===e}},81763:(e,t,n)=>{var r=n(44239),o=n(37005);e.exports=function(e){return"number"==typeof e||o(e)&&"[object Number]"==r(e)}},13218:e=>{e.exports=function(e){var t=typeof e;return null!=e&&("object"==t||"function"==t)}},37005:e=>{e.exports=function(e){return null!=e&&"object"==typeof e}},68630:(e,t,n)=>{var r=n(44239),o=n(85924),s=n(37005),i=Function.prototype,a=Object.prototype,l=i.toString,c=a.hasOwnProperty,u=l.call(Object);e.exports=function(e){if(!s(e)||"[object Object]"!=r(e))return!1;var t=o(e);if(null===t)return!0;var n=c.call(t,"constructor")&&t.constructor;return"function"==typeof n&&n instanceof n&&l.call(n)==u}},72928:(e,t,n)=>{var r=n(29221),o=n(7518),s=n(31167),i=s&&s.isSet,a=i?o(i):r;e.exports=a},47037:(e,t,n)=>{var r=n(44239),o=n(1469),s=n(37005);e.exports=function(e){return"string"==typeof e||!o(e)&&s(e)&&"[object String]"==r(e)}},33448:(e,t,n)=>{var r=n(44239),o=n(37005);e.exports=function(e){return"symbol"==typeof e||o(e)&&"[object Symbol]"==r(e)}},36719:(e,t,n)=>{var r=n(38749),o=n(7518),s=n(31167),i=s&&s.isTypedArray,a=i?o(i):r;e.exports=a},81018:(e,t,n)=>{var r=n(98882),o=n(37005);e.exports=function(e){return o(e)&&"[object WeakMap]"==r(e)}},72594:(e,t,n)=>{var r=n(85990),o=n(67206);e.exports=function(e){return o("function"==typeof e?e:r(e,1))}},3674:(e,t,n)=>{var r=n(14636),o=n(280),s=n(98612);e.exports=function(e){return s(e)?r(e):o(e)}},81704:(e,t,n)=>{var r=n(14636),o=n(10313),s=n(98612);e.exports=function(e){return s(e)?r(e,!0):o(e)}},10928:e=>{e.exports=function(e){var t=null==e?0:e.length;return t?e[t-1]:void 0}},88306:(e,t,n)=>{var r=n(83369);function o(e,t){if("function"!=typeof e||null!=t&&"function"!=typeof t)throw new TypeError("Expected a function");var n=function(){var r=arguments,o=t?t.apply(this,r):r[0],s=n.cache;if(s.has(o))return s.get(o);var i=e.apply(this,r);return n.cache=s.set(o,i)||s,i};return n.cache=new(o.Cache||r),n}o.Cache=r,e.exports=o},82492:(e,t,n)=>{var r=n(42980),o=n(21463)((function(e,t,n){r(e,t,n)}));e.exports=o},94885:e=>{e.exports=function(e){if("function"!=typeof e)throw new TypeError("Expected a function");return function(){var t=arguments;switch(t.length){case 0:return!e.call(this);case 1:return!e.call(this,t[0]);case 2:return!e.call(this,t[0],t[1]);case 3:return!e.call(this,t[0],t[1],t[2])}return!e.apply(this,t)}}},50308:e=>{e.exports=function(){}},7771:(e,t,n)=>{var r=n(55639);e.exports=function(){return r.Date.now()}},57557:(e,t,n)=>{var r=n(29932),o=n(85990),s=n(57406),i=n(71811),a=n(98363),l=n(60696),c=n(99021),u=n(46904),p=c((function(e,t){var n={};if(null==e)return n;var c=!1;t=r(t,(function(t){return t=i(t,e),c||(c=t.length>1),t})),a(e,u(e),n),c&&(n=o(n,7,l));for(var p=t.length;p--;)s(n,t[p]);return n}));e.exports=p},39601:(e,t,n)=>{var r=n(40371),o=n(79152),s=n(15403),i=n(40327);e.exports=function(e){return s(e)?r(i(e)):o(e)}},4963:(e,t,n)=>{var r=n(97727),o=n(99021),s=o((function(e,t){return r(e,256,void 0,void 0,void 0,t)}));e.exports=s},54061:(e,t,n)=>{var r=n(62663),o=n(89881),s=n(67206),i=n(10107),a=n(1469);e.exports=function(e,t,n){var l=a(e)?r:i,c=arguments.length<3;return l(e,s(t,4),n,c,o)}},36968:(e,t,n)=>{var r=n(10611);e.exports=function(e,t,n){return null==e?e:r(e,t,n)}},59704:(e,t,n)=>{var r=n(82908),o=n(67206),s=n(5076),i=n(1469),a=n(16612);e.exports=function(e,t,n){var l=i(e)?r:s;return n&&a(e,t,n)&&(t=void 0),l(e,o(t,3))}},70479:e=>{e.exports=function(){return[]}},95062:e=>{e.exports=function(){return!1}},18601:(e,t,n)=>{var r=n(14841),o=1/0;e.exports=function(e){return e?(e=r(e))===o||e===-1/0?17976931348623157e292*(e<0?-1:1):e==e?e:0:0===e?e:0}},40554:(e,t,n)=>{var r=n(18601);e.exports=function(e){var t=r(e),n=t%1;return t==t?n?t-n:t:0}},7334:(e,t,n)=>{var r=n(79833);e.exports=function(e){return r(e).toLowerCase()}},14841:(e,t,n)=>{var r=n(27561),o=n(13218),s=n(33448),i=/^[-+]0x[0-9a-f]+$/i,a=/^0b[01]+$/i,l=/^0o[0-7]+$/i,c=parseInt;e.exports=function(e){if("number"==typeof e)return e;if(s(e))return NaN;if(o(e)){var t="function"==typeof e.valueOf?e.valueOf():e;e=o(t)?t+"":t}if("string"!=typeof e)return 0===e?e:+e;e=r(e);var n=a.test(e);return n||l.test(e)?c(e.slice(2),n?2:8):i.test(e)?NaN:+e}},30084:(e,t,n)=>{var r=n(29932),o=n(278),s=n(1469),i=n(33448),a=n(55514),l=n(40327),c=n(79833);e.exports=function(e){return s(e)?r(e,l):i(e)?[e]:o(a(c(e)))}},59881:(e,t,n)=>{var r=n(98363),o=n(81704);e.exports=function(e){return r(e,o(e))}},79833:(e,t,n)=>{var r=n(80531);e.exports=function(e){return null==e?"":r(e)}},11700:(e,t,n)=>{var r=n(98805)("toUpperCase");e.exports=r},58748:(e,t,n)=>{var r=n(49029),o=n(93157),s=n(79833),i=n(2757);e.exports=function(e,t,n){return e=s(e),void 0===(t=n?void 0:t)?o(e)?i(e):r(e):e.match(t)||[]}},8111:(e,t,n)=>{var r=n(96425),o=n(7548),s=n(9435),i=n(1469),a=n(37005),l=n(21913),c=Object.prototype.hasOwnProperty;function u(e){if(a(e)&&!i(e)&&!(e instanceof r)){if(e instanceof o)return e;if(c.call(e,"__wrapped__"))return l(e)}return new o(e)}u.prototype=s.prototype,u.prototype.constructor=u,e.exports=u},7287:(e,t,n)=>{var r=n(34865),o=n(1757);e.exports=function(e,t){return o(e||[],t||[],r)}},96470:(e,t,n)=>{"use strict";var r=n(47802),o=n(21102);t.highlight=i,t.highlightAuto=function(e,t){var n,a,l,c,u=t||{},p=u.subset||r.listLanguages(),h=u.prefix,f=p.length,d=-1;null==h&&(h=s);if("string"!=typeof e)throw o("Expected `string` for value, got `%s`",e);a={relevance:0,language:null,value:[]},n={relevance:0,language:null,value:[]};for(;++da.relevance&&(a=l),l.relevance>n.relevance&&(a=n,n=l));a.language&&(n.secondBest=a);return n},t.registerLanguage=function(e,t){r.registerLanguage(e,t)},t.listLanguages=function(){return r.listLanguages()},t.registerAlias=function(e,t){var n,o=e;t&&((o={})[e]=t);for(n in o)r.registerAliases(o[n],{languageName:n})},a.prototype.addText=function(e){var t,n,r=this.stack;if(""===e)return;t=r[r.length-1],(n=t.children[t.children.length-1])&&"text"===n.type?n.value+=e:t.children.push({type:"text",value:e})},a.prototype.addKeyword=function(e,t){this.openNode(t),this.addText(e),this.closeNode()},a.prototype.addSublanguage=function(e,t){var n=this.stack,r=n[n.length-1],o=e.rootNode.children,s=t?{type:"element",tagName:"span",properties:{className:[t]},children:o}:o;r.children=r.children.concat(s)},a.prototype.openNode=function(e){var t=this.stack,n=this.options.classPrefix+e,r=t[t.length-1],o={type:"element",tagName:"span",properties:{className:[n]},children:[]};r.children.push(o),t.push(o)},a.prototype.closeNode=function(){this.stack.pop()},a.prototype.closeAllNodes=l,a.prototype.finalize=l,a.prototype.toHTML=function(){return""};var s="hljs-";function i(e,t,n){var i,l=r.configure({}),c=(n||{}).prefix;if("string"!=typeof e)throw o("Expected `string` for name, got `%s`",e);if(!r.getLanguage(e))throw o("Unknown language: `%s` is not registered",e);if("string"!=typeof t)throw o("Expected `string` for value, got `%s`",t);if(null==c&&(c=s),r.configure({__emitter:a,classPrefix:c}),i=r.highlight(t,{language:e,ignoreIllegals:!0}),r.configure(l||{}),i.errorRaised)throw i.errorRaised;return{relevance:i.relevance,language:i.language,value:i.emitter.rootNode.children}}function a(e){this.options=e,this.rootNode={children:[]},this.stack=[this.rootNode]}function l(){}},42566:(e,t,n)=>{const r=n(94885);function o(e){return"string"==typeof e?t=>t.element===e:e.constructor&&e.extend?t=>t instanceof e:e}class s{constructor(e){this.elements=e||[]}toValue(){return this.elements.map((e=>e.toValue()))}map(e,t){return this.elements.map(e,t)}flatMap(e,t){return this.map(e,t).reduce(((e,t)=>e.concat(t)),[])}compactMap(e,t){const n=[];return this.forEach((r=>{const o=e.bind(t)(r);o&&n.push(o)})),n}filter(e,t){return e=o(e),new s(this.elements.filter(e,t))}reject(e,t){return e=o(e),new s(this.elements.filter(r(e),t))}find(e,t){return e=o(e),this.elements.find(e,t)}forEach(e,t){this.elements.forEach(e,t)}reduce(e,t){return this.elements.reduce(e,t)}includes(e){return this.elements.some((t=>t.equals(e)))}shift(){return this.elements.shift()}unshift(e){this.elements.unshift(this.refract(e))}push(e){return this.elements.push(this.refract(e)),this}add(e){this.push(e)}get(e){return this.elements[e]}getValue(e){const t=this.elements[e];if(t)return t.toValue()}get length(){return this.elements.length}get isEmpty(){return 0===this.elements.length}get first(){return this.elements[0]}}"undefined"!=typeof Symbol&&(s.prototype[Symbol.iterator]=function(){return this.elements[Symbol.iterator]()}),e.exports=s},17645:e=>{class t{constructor(e,t){this.key=e,this.value=t}clone(){const e=new t;return this.key&&(e.key=this.key.clone()),this.value&&(e.value=this.value.clone()),e}}e.exports=t},78520:(e,t,n)=>{const r=n(45220),o=n(47037),s=n(81763),i=n(51584),a=n(13218),l=n(28219),c=n(99829);class u{constructor(e){this.elementMap={},this.elementDetection=[],this.Element=c.Element,this.KeyValuePair=c.KeyValuePair,e&&e.noDefault||this.useDefault(),this._attributeElementKeys=[],this._attributeElementArrayKeys=[]}use(e){return e.namespace&&e.namespace({base:this}),e.load&&e.load({base:this}),this}useDefault(){return this.register("null",c.NullElement).register("string",c.StringElement).register("number",c.NumberElement).register("boolean",c.BooleanElement).register("array",c.ArrayElement).register("object",c.ObjectElement).register("member",c.MemberElement).register("ref",c.RefElement).register("link",c.LinkElement),this.detect(r,c.NullElement,!1).detect(o,c.StringElement,!1).detect(s,c.NumberElement,!1).detect(i,c.BooleanElement,!1).detect(Array.isArray,c.ArrayElement,!1).detect(a,c.ObjectElement,!1),this}register(e,t){return this._elements=void 0,this.elementMap[e]=t,this}unregister(e){return this._elements=void 0,delete this.elementMap[e],this}detect(e,t,n){return void 0===n||n?this.elementDetection.unshift([e,t]):this.elementDetection.push([e,t]),this}toElement(e){if(e instanceof this.Element)return e;let t;for(let n=0;n{const t=e[0].toUpperCase()+e.substr(1);this._elements[t]=this.elementMap[e]}))),this._elements}get serialiser(){return new l(this)}}l.prototype.Namespace=u,e.exports=u},87526:(e,t,n)=>{const r=n(94885),o=n(42566);class s extends o{map(e,t){return this.elements.map((n=>e.bind(t)(n.value,n.key,n)))}filter(e,t){return new s(this.elements.filter((n=>e.bind(t)(n.value,n.key,n))))}reject(e,t){return this.filter(r(e.bind(t)))}forEach(e,t){return this.elements.forEach(((n,r)=>{e.bind(t)(n.value,n.key,n,r)}))}keys(){return this.map(((e,t)=>t.toValue()))}values(){return this.map((e=>e.toValue()))}}e.exports=s},99829:(e,t,n)=>{const r=n(3079),o=n(96295),s=n(16036),i=n(91090),a=n(18866),l=n(35804),c=n(5946),u=n(76735),p=n(59964),h=n(38588),f=n(42566),d=n(87526),m=n(17645);function g(e){if(e instanceof r)return e;if("string"==typeof e)return new s(e);if("number"==typeof e)return new i(e);if("boolean"==typeof e)return new a(e);if(null===e)return new o;if(Array.isArray(e))return new l(e.map(g));if("object"==typeof e){return new u(e)}return e}r.prototype.ObjectElement=u,r.prototype.RefElement=h,r.prototype.MemberElement=c,r.prototype.refract=g,f.prototype.refract=g,e.exports={Element:r,NullElement:o,StringElement:s,NumberElement:i,BooleanElement:a,ArrayElement:l,MemberElement:c,ObjectElement:u,LinkElement:p,RefElement:h,refract:g,ArraySlice:f,ObjectSlice:d,KeyValuePair:m}},59964:(e,t,n)=>{const r=n(3079);e.exports=class extends r{constructor(e,t,n){super(e||[],t,n),this.element="link"}get relation(){return this.attributes.get("relation")}set relation(e){this.attributes.set("relation",e)}get href(){return this.attributes.get("href")}set href(e){this.attributes.set("href",e)}}},38588:(e,t,n)=>{const r=n(3079);e.exports=class extends r{constructor(e,t,n){super(e||[],t,n),this.element="ref",this.path||(this.path="element")}get path(){return this.attributes.get("path")}set path(e){this.attributes.set("path",e)}}},43500:(e,t,n)=>{const r=n(78520),o=n(99829);t.lS=r,n(17645),t.O4=o.ArraySlice,o.ObjectSlice,t.W_=o.Element,t.RP=o.StringElement,t.VL=o.NumberElement,t.hh=o.BooleanElement,t.zr=o.NullElement,t.ON=o.ArrayElement,t.Sb=o.ObjectElement,t.c6=o.MemberElement,t.tK=o.RefElement,t.EA=o.LinkElement,t.Qc=o.refract,n(28219),n(3414)},35804:(e,t,n)=>{const r=n(94885),o=n(3079),s=n(42566);class i extends o{constructor(e,t,n){super(e||[],t,n),this.element="array"}primitive(){return"array"}get(e){return this.content[e]}getValue(e){const t=this.get(e);if(t)return t.toValue()}getIndex(e){return this.content[e]}set(e,t){return this.content[e]=this.refract(t),this}remove(e){const t=this.content.splice(e,1);return t.length?t[0]:null}map(e,t){return this.content.map(e,t)}flatMap(e,t){return this.map(e,t).reduce(((e,t)=>e.concat(t)),[])}compactMap(e,t){const n=[];return this.forEach((r=>{const o=e.bind(t)(r);o&&n.push(o)})),n}filter(e,t){return new s(this.content.filter(e,t))}reject(e,t){return this.filter(r(e),t)}reduce(e,t){let n,r;void 0!==t?(n=0,r=this.refract(t)):(n=1,r="object"===this.primitive()?this.first.value:this.first);for(let t=n;t{e.bind(t)(n,this.refract(r))}))}shift(){return this.content.shift()}unshift(e){this.content.unshift(this.refract(e))}push(e){return this.content.push(this.refract(e)),this}add(e){this.push(e)}findElements(e,t){const n=t||{},r=!!n.recursive,o=void 0===n.results?[]:n.results;return this.forEach(((t,n,s)=>{r&&void 0!==t.findElements&&t.findElements(e,{results:o,recursive:r}),e(t,n,s)&&o.push(t)})),o}find(e){return new s(this.findElements(e,{recursive:!0}))}findByElement(e){return this.find((t=>t.element===e))}findByClass(e){return this.find((t=>t.classes.includes(e)))}getById(e){return this.find((t=>t.id.toValue()===e)).first}includes(e){return this.content.some((t=>t.equals(e)))}contains(e){return this.includes(e)}empty(){return new this.constructor([])}"fantasy-land/empty"(){return this.empty()}concat(e){return new this.constructor(this.content.concat(e.content))}"fantasy-land/concat"(e){return this.concat(e)}"fantasy-land/map"(e){return new this.constructor(this.map(e))}"fantasy-land/chain"(e){return this.map((t=>e(t)),this).reduce(((e,t)=>e.concat(t)),this.empty())}"fantasy-land/filter"(e){return new this.constructor(this.content.filter(e))}"fantasy-land/reduce"(e,t){return this.content.reduce(e,t)}get length(){return this.content.length}get isEmpty(){return 0===this.content.length}get first(){return this.getIndex(0)}get second(){return this.getIndex(1)}get last(){return this.getIndex(this.length-1)}}i.empty=function(){return new this},i["fantasy-land/empty"]=i.empty,"undefined"!=typeof Symbol&&(i.prototype[Symbol.iterator]=function(){return this.content[Symbol.iterator]()}),e.exports=i},18866:(e,t,n)=>{const r=n(3079);e.exports=class extends r{constructor(e,t,n){super(e,t,n),this.element="boolean"}primitive(){return"boolean"}}},3079:(e,t,n)=>{const r=n(18446),o=n(17645),s=n(42566);class i{constructor(e,t,n){t&&(this.meta=t),n&&(this.attributes=n),this.content=e}freeze(){Object.isFrozen(this)||(this._meta&&(this.meta.parent=this,this.meta.freeze()),this._attributes&&(this.attributes.parent=this,this.attributes.freeze()),this.children.forEach((e=>{e.parent=this,e.freeze()}),this),this.content&&Array.isArray(this.content)&&Object.freeze(this.content),Object.freeze(this))}primitive(){}clone(){const e=new this.constructor;return e.element=this.element,this.meta.length&&(e._meta=this.meta.clone()),this.attributes.length&&(e._attributes=this.attributes.clone()),this.content?this.content.clone?e.content=this.content.clone():Array.isArray(this.content)?e.content=this.content.map((e=>e.clone())):e.content=this.content:e.content=this.content,e}toValue(){return this.content instanceof i?this.content.toValue():this.content instanceof o?{key:this.content.key.toValue(),value:this.content.value?this.content.value.toValue():void 0}:this.content&&this.content.map?this.content.map((e=>e.toValue()),this):this.content}toRef(e){if(""===this.id.toValue())throw Error("Cannot create reference to an element that does not contain an ID");const t=new this.RefElement(this.id.toValue());return e&&(t.path=e),t}findRecursive(...e){if(arguments.length>1&&!this.isFrozen)throw new Error("Cannot find recursive with multiple element names without first freezing the element. Call `element.freeze()`");const t=e.pop();let n=new s;const r=(e,t)=>(e.push(t),e),i=(e,n)=>{n.element===t&&e.push(n);const s=n.findRecursive(t);return s&&s.reduce(r,e),n.content instanceof o&&(n.content.key&&i(e,n.content.key),n.content.value&&i(e,n.content.value)),e};return this.content&&(this.content.element&&i(n,this.content),Array.isArray(this.content)&&this.content.reduce(i,n)),e.isEmpty||(n=n.filter((t=>{let n=t.parents.map((e=>e.element));for(const t in e){const r=e[t],o=n.indexOf(r);if(-1===o)return!1;n=n.splice(0,o)}return!0}))),n}set(e){return this.content=e,this}equals(e){return r(this.toValue(),e)}getMetaProperty(e,t){if(!this.meta.hasKey(e)){if(this.isFrozen){const e=this.refract(t);return e.freeze(),e}this.meta.set(e,t)}return this.meta.get(e)}setMetaProperty(e,t){this.meta.set(e,t)}get element(){return this._storedElement||"element"}set element(e){this._storedElement=e}get content(){return this._content}set content(e){if(e instanceof i)this._content=e;else if(e instanceof s)this.content=e.elements;else if("string"==typeof e||"number"==typeof e||"boolean"==typeof e||"null"===e||null==e)this._content=e;else if(e instanceof o)this._content=e;else if(Array.isArray(e))this._content=e.map(this.refract);else{if("object"!=typeof e)throw new Error("Cannot set content to given value");this._content=Object.keys(e).map((t=>new this.MemberElement(t,e[t])))}}get meta(){if(!this._meta){if(this.isFrozen){const e=new this.ObjectElement;return e.freeze(),e}this._meta=new this.ObjectElement}return this._meta}set meta(e){e instanceof this.ObjectElement?this._meta=e:this.meta.set(e||{})}get attributes(){if(!this._attributes){if(this.isFrozen){const e=new this.ObjectElement;return e.freeze(),e}this._attributes=new this.ObjectElement}return this._attributes}set attributes(e){e instanceof this.ObjectElement?this._attributes=e:this.attributes.set(e||{})}get id(){return this.getMetaProperty("id","")}set id(e){this.setMetaProperty("id",e)}get classes(){return this.getMetaProperty("classes",[])}set classes(e){this.setMetaProperty("classes",e)}get title(){return this.getMetaProperty("title","")}set title(e){this.setMetaProperty("title",e)}get description(){return this.getMetaProperty("description","")}set description(e){this.setMetaProperty("description",e)}get links(){return this.getMetaProperty("links",[])}set links(e){this.setMetaProperty("links",e)}get isFrozen(){return Object.isFrozen(this)}get parents(){let{parent:e}=this;const t=new s;for(;e;)t.push(e),e=e.parent;return t}get children(){if(Array.isArray(this.content))return new s(this.content);if(this.content instanceof o){const e=new s([this.content.key]);return this.content.value&&e.push(this.content.value),e}return this.content instanceof i?new s([this.content]):new s}get recursiveChildren(){const e=new s;return this.children.forEach((t=>{e.push(t),t.recursiveChildren.forEach((t=>{e.push(t)}))})),e}}e.exports=i},5946:(e,t,n)=>{const r=n(17645),o=n(3079);e.exports=class extends o{constructor(e,t,n,o){super(new r,n,o),this.element="member",this.key=e,this.value=t}get key(){return this.content.key}set key(e){this.content.key=this.refract(e)}get value(){return this.content.value}set value(e){this.content.value=this.refract(e)}}},96295:(e,t,n)=>{const r=n(3079);e.exports=class extends r{constructor(e,t,n){super(e||null,t,n),this.element="null"}primitive(){return"null"}set(){return new Error("Cannot set the value of null")}}},91090:(e,t,n)=>{const r=n(3079);e.exports=class extends r{constructor(e,t,n){super(e,t,n),this.element="number"}primitive(){return"number"}}},76735:(e,t,n)=>{const r=n(94885),o=n(13218),s=n(35804),i=n(5946),a=n(87526);e.exports=class extends s{constructor(e,t,n){super(e||[],t,n),this.element="object"}primitive(){return"object"}toValue(){return this.content.reduce(((e,t)=>(e[t.key.toValue()]=t.value?t.value.toValue():void 0,e)),{})}get(e){const t=this.getMember(e);if(t)return t.value}getMember(e){if(void 0!==e)return this.content.find((t=>t.key.toValue()===e))}remove(e){let t=null;return this.content=this.content.filter((n=>n.key.toValue()!==e||(t=n,!1))),t}getKey(e){const t=this.getMember(e);if(t)return t.key}set(e,t){if(o(e))return Object.keys(e).forEach((t=>{this.set(t,e[t])})),this;const n=e,r=this.getMember(n);return r?r.value=t:this.content.push(new i(n,t)),this}keys(){return this.content.map((e=>e.key.toValue()))}values(){return this.content.map((e=>e.value.toValue()))}hasKey(e){return this.content.some((t=>t.key.equals(e)))}items(){return this.content.map((e=>[e.key.toValue(),e.value.toValue()]))}map(e,t){return this.content.map((n=>e.bind(t)(n.value,n.key,n)))}compactMap(e,t){const n=[];return this.forEach(((r,o,s)=>{const i=e.bind(t)(r,o,s);i&&n.push(i)})),n}filter(e,t){return new a(this.content).filter(e,t)}reject(e,t){return this.filter(r(e),t)}forEach(e,t){return this.content.forEach((n=>e.bind(t)(n.value,n.key,n)))}}},16036:(e,t,n)=>{const r=n(3079);e.exports=class extends r{constructor(e,t,n){super(e,t,n),this.element="string"}primitive(){return"string"}get length(){return this.content.length}}},3414:(e,t,n)=>{const r=n(28219);e.exports=class extends r{serialise(e){if(!(e instanceof this.namespace.elements.Element))throw new TypeError(`Given element \`${e}\` is not an Element instance`);let t;e._attributes&&e.attributes.get("variable")&&(t=e.attributes.get("variable"));const n={element:e.element};e._meta&&e._meta.length>0&&(n.meta=this.serialiseObject(e.meta));const r="enum"===e.element||-1!==e.attributes.keys().indexOf("enumerations");if(r){const t=this.enumSerialiseAttributes(e);t&&(n.attributes=t)}else if(e._attributes&&e._attributes.length>0){let{attributes:r}=e;r.get("metadata")&&(r=r.clone(),r.set("meta",r.get("metadata")),r.remove("metadata")),"member"===e.element&&t&&(r=r.clone(),r.remove("variable")),r.length>0&&(n.attributes=this.serialiseObject(r))}if(r)n.content=this.enumSerialiseContent(e,n);else if(this[`${e.element}SerialiseContent`])n.content=this[`${e.element}SerialiseContent`](e,n);else if(void 0!==e.content){let r;t&&e.content.key?(r=e.content.clone(),r.key.attributes.set("variable",t),r=this.serialiseContent(r)):r=this.serialiseContent(e.content),this.shouldSerialiseContent(e,r)&&(n.content=r)}else this.shouldSerialiseContent(e,e.content)&&e instanceof this.namespace.elements.Array&&(n.content=[]);return n}shouldSerialiseContent(e,t){return"parseResult"===e.element||"httpRequest"===e.element||"httpResponse"===e.element||"category"===e.element||"link"===e.element||void 0!==t&&(!Array.isArray(t)||0!==t.length)}refSerialiseContent(e,t){return delete t.attributes,{href:e.toValue(),path:e.path.toValue()}}sourceMapSerialiseContent(e){return e.toValue()}dataStructureSerialiseContent(e){return[this.serialiseContent(e.content)]}enumSerialiseAttributes(e){const t=e.attributes.clone(),n=t.remove("enumerations")||new this.namespace.elements.Array([]),r=t.get("default");let o=t.get("samples")||new this.namespace.elements.Array([]);if(r&&r.content&&(r.content.attributes&&r.content.attributes.remove("typeAttributes"),t.set("default",new this.namespace.elements.Array([r.content]))),o.forEach((e=>{e.content&&e.content.element&&e.content.attributes.remove("typeAttributes")})),e.content&&0!==n.length&&o.unshift(e.content),o=o.map((e=>e instanceof this.namespace.elements.Array?[e]:new this.namespace.elements.Array([e.content]))),o.length&&t.set("samples",o),t.length>0)return this.serialiseObject(t)}enumSerialiseContent(e){if(e._attributes){const t=e.attributes.get("enumerations");if(t&&t.length>0)return t.content.map((e=>{const t=e.clone();return t.attributes.remove("typeAttributes"),this.serialise(t)}))}if(e.content){const t=e.content.clone();return t.attributes.remove("typeAttributes"),[this.serialise(t)]}return[]}deserialise(e){if("string"==typeof e)return new this.namespace.elements.String(e);if("number"==typeof e)return new this.namespace.elements.Number(e);if("boolean"==typeof e)return new this.namespace.elements.Boolean(e);if(null===e)return new this.namespace.elements.Null;if(Array.isArray(e))return new this.namespace.elements.Array(e.map(this.deserialise,this));const t=this.namespace.getElementClass(e.element),n=new t;n.element!==e.element&&(n.element=e.element),e.meta&&this.deserialiseObject(e.meta,n.meta),e.attributes&&this.deserialiseObject(e.attributes,n.attributes);const r=this.deserialiseContent(e.content);if(void 0===r&&null!==n.content||(n.content=r),"enum"===n.element){n.content&&n.attributes.set("enumerations",n.content);let e=n.attributes.get("samples");if(n.attributes.remove("samples"),e){const r=e;e=new this.namespace.elements.Array,r.forEach((r=>{r.forEach((r=>{const o=new t(r);o.element=n.element,e.push(o)}))}));const o=e.shift();n.content=o?o.content:void 0,n.attributes.set("samples",e)}else n.content=void 0;let r=n.attributes.get("default");if(r&&r.length>0){r=r.get(0);const e=new t(r);e.element=n.element,n.attributes.set("default",e)}}else if("dataStructure"===n.element&&Array.isArray(n.content))[n.content]=n.content;else if("category"===n.element){const e=n.attributes.get("meta");e&&(n.attributes.set("metadata",e),n.attributes.remove("meta"))}else"member"===n.element&&n.key&&n.key._attributes&&n.key._attributes.getValue("variable")&&(n.attributes.set("variable",n.key.attributes.get("variable")),n.key.attributes.remove("variable"));return n}serialiseContent(e){if(e instanceof this.namespace.elements.Element)return this.serialise(e);if(e instanceof this.namespace.KeyValuePair){const t={key:this.serialise(e.key)};return e.value&&(t.value=this.serialise(e.value)),t}return e&&e.map?e.map(this.serialise,this):e}deserialiseContent(e){if(e){if(e.element)return this.deserialise(e);if(e.key){const t=new this.namespace.KeyValuePair(this.deserialise(e.key));return e.value&&(t.value=this.deserialise(e.value)),t}if(e.map)return e.map(this.deserialise,this)}return e}shouldRefract(e){return!!(e._attributes&&e.attributes.keys().length||e._meta&&e.meta.keys().length)||"enum"!==e.element&&(e.element!==e.primitive()||"member"===e.element)}convertKeyToRefract(e,t){return this.shouldRefract(t)?this.serialise(t):"enum"===t.element?this.serialiseEnum(t):"array"===t.element?t.map((t=>this.shouldRefract(t)||"default"===e?this.serialise(t):"array"===t.element||"object"===t.element||"enum"===t.element?t.children.map((e=>this.serialise(e))):t.toValue())):"object"===t.element?(t.content||[]).map(this.serialise,this):t.toValue()}serialiseEnum(e){return e.children.map((e=>this.serialise(e)))}serialiseObject(e){const t={};return e.forEach(((e,n)=>{if(e){const r=n.toValue();t[r]=this.convertKeyToRefract(r,e)}})),t}deserialiseObject(e,t){Object.keys(e).forEach((n=>{t.set(n,this.deserialise(e[n]))}))}}},28219:e=>{e.exports=class{constructor(e){this.namespace=e||new this.Namespace}serialise(e){if(!(e instanceof this.namespace.elements.Element))throw new TypeError(`Given element \`${e}\` is not an Element instance`);const t={element:e.element};e._meta&&e._meta.length>0&&(t.meta=this.serialiseObject(e.meta)),e._attributes&&e._attributes.length>0&&(t.attributes=this.serialiseObject(e.attributes));const n=this.serialiseContent(e.content);return void 0!==n&&(t.content=n),t}deserialise(e){if(!e.element)throw new Error("Given value is not an object containing an element name");const t=new(this.namespace.getElementClass(e.element));t.element!==e.element&&(t.element=e.element),e.meta&&this.deserialiseObject(e.meta,t.meta),e.attributes&&this.deserialiseObject(e.attributes,t.attributes);const n=this.deserialiseContent(e.content);return void 0===n&&null!==t.content||(t.content=n),t}serialiseContent(e){if(e instanceof this.namespace.elements.Element)return this.serialise(e);if(e instanceof this.namespace.KeyValuePair){const t={key:this.serialise(e.key)};return e.value&&(t.value=this.serialise(e.value)),t}if(e&&e.map){if(0===e.length)return;return e.map(this.serialise,this)}return e}deserialiseContent(e){if(e){if(e.element)return this.deserialise(e);if(e.key){const t=new this.namespace.KeyValuePair(this.deserialise(e.key));return e.value&&(t.value=this.deserialise(e.value)),t}if(e.map)return e.map(this.deserialise,this)}return e}serialiseObject(e){const t={};if(e.forEach(((e,n)=>{e&&(t[n.toValue()]=this.serialise(e))})),0!==Object.keys(t).length)return t}deserialiseObject(e,t){Object.keys(e).forEach((n=>{t.set(n,this.deserialise(e[n]))}))}}},27418:e=>{"use strict";var t=Object.getOwnPropertySymbols,n=Object.prototype.hasOwnProperty,r=Object.prototype.propertyIsEnumerable;e.exports=function(){try{if(!Object.assign)return!1;var e=new String("abc");if(e[5]="de","5"===Object.getOwnPropertyNames(e)[0])return!1;for(var t={},n=0;n<10;n++)t["_"+String.fromCharCode(n)]=n;if("0123456789"!==Object.getOwnPropertyNames(t).map((function(e){return t[e]})).join(""))return!1;var r={};return"abcdefghijklmnopqrst".split("").forEach((function(e){r[e]=e})),"abcdefghijklmnopqrst"===Object.keys(Object.assign({},r)).join("")}catch(e){return!1}}()?Object.assign:function(e,o){for(var s,i,a=function(e){if(null==e)throw new TypeError("Object.assign cannot be called with null or undefined");return Object(e)}(e),l=1;l{var r="function"==typeof Map&&Map.prototype,o=Object.getOwnPropertyDescriptor&&r?Object.getOwnPropertyDescriptor(Map.prototype,"size"):null,s=r&&o&&"function"==typeof o.get?o.get:null,i=r&&Map.prototype.forEach,a="function"==typeof Set&&Set.prototype,l=Object.getOwnPropertyDescriptor&&a?Object.getOwnPropertyDescriptor(Set.prototype,"size"):null,c=a&&l&&"function"==typeof l.get?l.get:null,u=a&&Set.prototype.forEach,p="function"==typeof WeakMap&&WeakMap.prototype?WeakMap.prototype.has:null,h="function"==typeof WeakSet&&WeakSet.prototype?WeakSet.prototype.has:null,f="function"==typeof WeakRef&&WeakRef.prototype?WeakRef.prototype.deref:null,d=Boolean.prototype.valueOf,m=Object.prototype.toString,g=Function.prototype.toString,y=String.prototype.match,v=String.prototype.slice,b=String.prototype.replace,w=String.prototype.toUpperCase,E=String.prototype.toLowerCase,x=RegExp.prototype.test,S=Array.prototype.concat,_=Array.prototype.join,j=Array.prototype.slice,O=Math.floor,k="function"==typeof BigInt?BigInt.prototype.valueOf:null,A=Object.getOwnPropertySymbols,C="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?Symbol.prototype.toString:null,P="function"==typeof Symbol&&"object"==typeof Symbol.iterator,N="function"==typeof Symbol&&Symbol.toStringTag&&(typeof Symbol.toStringTag===P||"symbol")?Symbol.toStringTag:null,I=Object.prototype.propertyIsEnumerable,T=("function"==typeof Reflect?Reflect.getPrototypeOf:Object.getPrototypeOf)||([].__proto__===Array.prototype?function(e){return e.__proto__}:null);function R(e,t){if(e===1/0||e===-1/0||e!=e||e&&e>-1e3&&e<1e3||x.call(/e/,t))return t;var n=/[0-9](?=(?:[0-9]{3})+(?![0-9]))/g;if("number"==typeof e){var r=e<0?-O(-e):O(e);if(r!==e){var o=String(r),s=v.call(t,o.length+1);return b.call(o,n,"$&_")+"."+b.call(b.call(s,/([0-9]{3})/g,"$&_"),/_$/,"")}}return b.call(t,n,"$&_")}var M=n(24654),D=M.custom,F=U(D)?D:null;function L(e,t,n){var r="double"===(n.quoteStyle||t)?'"':"'";return r+e+r}function B(e){return b.call(String(e),/"/g,""")}function $(e){return!("[object Array]"!==W(e)||N&&"object"==typeof e&&N in e)}function q(e){return!("[object RegExp]"!==W(e)||N&&"object"==typeof e&&N in e)}function U(e){if(P)return e&&"object"==typeof e&&e instanceof Symbol;if("symbol"==typeof e)return!0;if(!e||"object"!=typeof e||!C)return!1;try{return C.call(e),!0}catch(e){}return!1}e.exports=function e(t,n,r,o){var a=n||{};if(V(a,"quoteStyle")&&"single"!==a.quoteStyle&&"double"!==a.quoteStyle)throw new TypeError('option "quoteStyle" must be "single" or "double"');if(V(a,"maxStringLength")&&("number"==typeof a.maxStringLength?a.maxStringLength<0&&a.maxStringLength!==1/0:null!==a.maxStringLength))throw new TypeError('option "maxStringLength", if provided, must be a positive integer, Infinity, or `null`');var l=!V(a,"customInspect")||a.customInspect;if("boolean"!=typeof l&&"symbol"!==l)throw new TypeError("option \"customInspect\", if provided, must be `true`, `false`, or `'symbol'`");if(V(a,"indent")&&null!==a.indent&&"\t"!==a.indent&&!(parseInt(a.indent,10)===a.indent&&a.indent>0))throw new TypeError('option "indent" must be "\\t", an integer > 0, or `null`');if(V(a,"numericSeparator")&&"boolean"!=typeof a.numericSeparator)throw new TypeError('option "numericSeparator", if provided, must be `true` or `false`');var m=a.numericSeparator;if(void 0===t)return"undefined";if(null===t)return"null";if("boolean"==typeof t)return t?"true":"false";if("string"==typeof t)return K(t,a);if("number"==typeof t){if(0===t)return 1/0/t>0?"0":"-0";var w=String(t);return m?R(t,w):w}if("bigint"==typeof t){var x=String(t)+"n";return m?R(t,x):x}var O=void 0===a.depth?5:a.depth;if(void 0===r&&(r=0),r>=O&&O>0&&"object"==typeof t)return $(t)?"[Array]":"[Object]";var A=function(e,t){var n;if("\t"===e.indent)n="\t";else{if(!("number"==typeof e.indent&&e.indent>0))return null;n=_.call(Array(e.indent+1)," ")}return{base:n,prev:_.call(Array(t+1),n)}}(a,r);if(void 0===o)o=[];else if(J(o,t)>=0)return"[Circular]";function D(t,n,s){if(n&&(o=j.call(o)).push(n),s){var i={depth:a.depth};return V(a,"quoteStyle")&&(i.quoteStyle=a.quoteStyle),e(t,i,r+1,o)}return e(t,a,r+1,o)}if("function"==typeof t&&!q(t)){var z=function(e){if(e.name)return e.name;var t=y.call(g.call(e),/^function\s*([\w$]+)/);if(t)return t[1];return null}(t),H=Q(t,D);return"[Function"+(z?": "+z:" (anonymous)")+"]"+(H.length>0?" { "+_.call(H,", ")+" }":"")}if(U(t)){var ee=P?b.call(String(t),/^(Symbol\(.*\))_[^)]*$/,"$1"):C.call(t);return"object"!=typeof t||P?ee:G(ee)}if(function(e){if(!e||"object"!=typeof e)return!1;if("undefined"!=typeof HTMLElement&&e instanceof HTMLElement)return!0;return"string"==typeof e.nodeName&&"function"==typeof e.getAttribute}(t)){for(var te="<"+E.call(String(t.nodeName)),ne=t.attributes||[],re=0;re"}if($(t)){if(0===t.length)return"[]";var oe=Q(t,D);return A&&!function(e){for(var t=0;t=0)return!1;return!0}(oe)?"["+X(oe,A)+"]":"[ "+_.call(oe,", ")+" ]"}if(function(e){return!("[object Error]"!==W(e)||N&&"object"==typeof e&&N in e)}(t)){var se=Q(t,D);return"cause"in Error.prototype||!("cause"in t)||I.call(t,"cause")?0===se.length?"["+String(t)+"]":"{ ["+String(t)+"] "+_.call(se,", ")+" }":"{ ["+String(t)+"] "+_.call(S.call("[cause]: "+D(t.cause),se),", ")+" }"}if("object"==typeof t&&l){if(F&&"function"==typeof t[F]&&M)return M(t,{depth:O-r});if("symbol"!==l&&"function"==typeof t.inspect)return t.inspect()}if(function(e){if(!s||!e||"object"!=typeof e)return!1;try{s.call(e);try{c.call(e)}catch(e){return!0}return e instanceof Map}catch(e){}return!1}(t)){var ie=[];return i&&i.call(t,(function(e,n){ie.push(D(n,t,!0)+" => "+D(e,t))})),Y("Map",s.call(t),ie,A)}if(function(e){if(!c||!e||"object"!=typeof e)return!1;try{c.call(e);try{s.call(e)}catch(e){return!0}return e instanceof Set}catch(e){}return!1}(t)){var ae=[];return u&&u.call(t,(function(e){ae.push(D(e,t))})),Y("Set",c.call(t),ae,A)}if(function(e){if(!p||!e||"object"!=typeof e)return!1;try{p.call(e,p);try{h.call(e,h)}catch(e){return!0}return e instanceof WeakMap}catch(e){}return!1}(t))return Z("WeakMap");if(function(e){if(!h||!e||"object"!=typeof e)return!1;try{h.call(e,h);try{p.call(e,p)}catch(e){return!0}return e instanceof WeakSet}catch(e){}return!1}(t))return Z("WeakSet");if(function(e){if(!f||!e||"object"!=typeof e)return!1;try{return f.call(e),!0}catch(e){}return!1}(t))return Z("WeakRef");if(function(e){return!("[object Number]"!==W(e)||N&&"object"==typeof e&&N in e)}(t))return G(D(Number(t)));if(function(e){if(!e||"object"!=typeof e||!k)return!1;try{return k.call(e),!0}catch(e){}return!1}(t))return G(D(k.call(t)));if(function(e){return!("[object Boolean]"!==W(e)||N&&"object"==typeof e&&N in e)}(t))return G(d.call(t));if(function(e){return!("[object String]"!==W(e)||N&&"object"==typeof e&&N in e)}(t))return G(D(String(t)));if(!function(e){return!("[object Date]"!==W(e)||N&&"object"==typeof e&&N in e)}(t)&&!q(t)){var le=Q(t,D),ce=T?T(t)===Object.prototype:t instanceof Object||t.constructor===Object,ue=t instanceof Object?"":"null prototype",pe=!ce&&N&&Object(t)===t&&N in t?v.call(W(t),8,-1):ue?"Object":"",he=(ce||"function"!=typeof t.constructor?"":t.constructor.name?t.constructor.name+" ":"")+(pe||ue?"["+_.call(S.call([],pe||[],ue||[]),": ")+"] ":"");return 0===le.length?he+"{}":A?he+"{"+X(le,A)+"}":he+"{ "+_.call(le,", ")+" }"}return String(t)};var z=Object.prototype.hasOwnProperty||function(e){return e in this};function V(e,t){return z.call(e,t)}function W(e){return m.call(e)}function J(e,t){if(e.indexOf)return e.indexOf(t);for(var n=0,r=e.length;nt.maxStringLength){var n=e.length-t.maxStringLength,r="... "+n+" more character"+(n>1?"s":"");return K(v.call(e,0,t.maxStringLength),t)+r}return L(b.call(b.call(e,/(['\\])/g,"\\$1"),/[\x00-\x1f]/g,H),"single",t)}function H(e){var t=e.charCodeAt(0),n={8:"b",9:"t",10:"n",12:"f",13:"r"}[t];return n?"\\"+n:"\\x"+(t<16?"0":"")+w.call(t.toString(16))}function G(e){return"Object("+e+")"}function Z(e){return e+" { ? }"}function Y(e,t,n,r){return e+" ("+t+") {"+(r?X(n,r):_.call(n,", "))+"}"}function X(e,t){if(0===e.length)return"";var n="\n"+t.prev+t.base;return n+_.call(e,","+n)+"\n"+t.prev}function Q(e,t){var n=$(e),r=[];if(n){r.length=e.length;for(var o=0;o{var t,n,r=e.exports={};function o(){throw new Error("setTimeout has not been defined")}function s(){throw new Error("clearTimeout has not been defined")}function i(e){if(t===setTimeout)return setTimeout(e,0);if((t===o||!t)&&setTimeout)return t=setTimeout,setTimeout(e,0);try{return t(e,0)}catch(n){try{return t.call(null,e,0)}catch(n){return t.call(this,e,0)}}}!function(){try{t="function"==typeof setTimeout?setTimeout:o}catch(e){t=o}try{n="function"==typeof clearTimeout?clearTimeout:s}catch(e){n=s}}();var a,l=[],c=!1,u=-1;function p(){c&&a&&(c=!1,a.length?l=a.concat(l):u=-1,l.length&&h())}function h(){if(!c){var e=i(p);c=!0;for(var t=l.length;t;){for(a=l,l=[];++u1)for(var n=1;n{"use strict";var r=n(50414);function o(){}function s(){}s.resetWarningCache=o,e.exports=function(){function e(e,t,n,o,s,i){if(i!==r){var a=new Error("Calling PropTypes validators directly is not supported by the `prop-types` package. Use PropTypes.checkPropTypes() to call them. Read more at http://fb.me/use-check-prop-types");throw a.name="Invariant Violation",a}}function t(){return e}e.isRequired=e;var n={array:e,bigint:e,bool:e,func:e,number:e,object:e,string:e,symbol:e,any:e,arrayOf:t,element:e,elementType:e,instanceOf:t,node:e,objectOf:t,oneOf:t,oneOfType:t,shape:t,exact:t,checkPropTypes:s,resetWarningCache:o};return n.PropTypes=n,n}},45697:(e,t,n)=>{e.exports=n(92703)()},50414:e=>{"use strict";e.exports="SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED"},55798:e=>{"use strict";var t=String.prototype.replace,n=/%20/g,r="RFC1738",o="RFC3986";e.exports={default:o,formatters:{RFC1738:function(e){return t.call(e,n,"+")},RFC3986:function(e){return String(e)}},RFC1738:r,RFC3986:o}},80129:(e,t,n)=>{"use strict";var r=n(58261),o=n(55235),s=n(55798);e.exports={formats:s,parse:o,stringify:r}},55235:(e,t,n)=>{"use strict";var r=n(12769),o=Object.prototype.hasOwnProperty,s=Array.isArray,i={allowDots:!1,allowPrototypes:!1,allowSparse:!1,arrayLimit:20,charset:"utf-8",charsetSentinel:!1,comma:!1,decoder:r.decode,delimiter:"&",depth:5,ignoreQueryPrefix:!1,interpretNumericEntities:!1,parameterLimit:1e3,parseArrays:!0,plainObjects:!1,strictNullHandling:!1},a=function(e){return e.replace(/&#(\d+);/g,(function(e,t){return String.fromCharCode(parseInt(t,10))}))},l=function(e,t){return e&&"string"==typeof e&&t.comma&&e.indexOf(",")>-1?e.split(","):e},c=function(e,t,n,r){if(e){var s=n.allowDots?e.replace(/\.([^.[]+)/g,"[$1]"):e,i=/(\[[^[\]]*])/g,a=n.depth>0&&/(\[[^[\]]*])/.exec(s),c=a?s.slice(0,a.index):s,u=[];if(c){if(!n.plainObjects&&o.call(Object.prototype,c)&&!n.allowPrototypes)return;u.push(c)}for(var p=0;n.depth>0&&null!==(a=i.exec(s))&&p=0;--s){var i,a=e[s];if("[]"===a&&n.parseArrays)i=[].concat(o);else{i=n.plainObjects?Object.create(null):{};var c="["===a.charAt(0)&&"]"===a.charAt(a.length-1)?a.slice(1,-1):a,u=parseInt(c,10);n.parseArrays||""!==c?!isNaN(u)&&a!==c&&String(u)===c&&u>=0&&n.parseArrays&&u<=n.arrayLimit?(i=[])[u]=o:"__proto__"!==c&&(i[c]=o):i={0:o}}o=i}return o}(u,t,n,r)}};e.exports=function(e,t){var n=function(e){if(!e)return i;if(null!==e.decoder&&void 0!==e.decoder&&"function"!=typeof e.decoder)throw new TypeError("Decoder has to be a function.");if(void 0!==e.charset&&"utf-8"!==e.charset&&"iso-8859-1"!==e.charset)throw new TypeError("The charset option must be either utf-8, iso-8859-1, or undefined");var t=void 0===e.charset?i.charset:e.charset;return{allowDots:void 0===e.allowDots?i.allowDots:!!e.allowDots,allowPrototypes:"boolean"==typeof e.allowPrototypes?e.allowPrototypes:i.allowPrototypes,allowSparse:"boolean"==typeof e.allowSparse?e.allowSparse:i.allowSparse,arrayLimit:"number"==typeof e.arrayLimit?e.arrayLimit:i.arrayLimit,charset:t,charsetSentinel:"boolean"==typeof e.charsetSentinel?e.charsetSentinel:i.charsetSentinel,comma:"boolean"==typeof e.comma?e.comma:i.comma,decoder:"function"==typeof e.decoder?e.decoder:i.decoder,delimiter:"string"==typeof e.delimiter||r.isRegExp(e.delimiter)?e.delimiter:i.delimiter,depth:"number"==typeof e.depth||!1===e.depth?+e.depth:i.depth,ignoreQueryPrefix:!0===e.ignoreQueryPrefix,interpretNumericEntities:"boolean"==typeof e.interpretNumericEntities?e.interpretNumericEntities:i.interpretNumericEntities,parameterLimit:"number"==typeof e.parameterLimit?e.parameterLimit:i.parameterLimit,parseArrays:!1!==e.parseArrays,plainObjects:"boolean"==typeof e.plainObjects?e.plainObjects:i.plainObjects,strictNullHandling:"boolean"==typeof e.strictNullHandling?e.strictNullHandling:i.strictNullHandling}}(t);if(""===e||null==e)return n.plainObjects?Object.create(null):{};for(var u="string"==typeof e?function(e,t){var n,c={},u=t.ignoreQueryPrefix?e.replace(/^\?/,""):e,p=t.parameterLimit===1/0?void 0:t.parameterLimit,h=u.split(t.delimiter,p),f=-1,d=t.charset;if(t.charsetSentinel)for(n=0;n-1&&(g=s(g)?[g]:g),o.call(c,m)?c[m]=r.combine(c[m],g):c[m]=g}return c}(e,n):e,p=n.plainObjects?Object.create(null):{},h=Object.keys(u),f=0;f{"use strict";var r=n(37478),o=n(12769),s=n(55798),i=Object.prototype.hasOwnProperty,a={brackets:function(e){return e+"[]"},comma:"comma",indices:function(e,t){return e+"["+t+"]"},repeat:function(e){return e}},l=Array.isArray,c=String.prototype.split,u=Array.prototype.push,p=function(e,t){u.apply(e,l(t)?t:[t])},h=Date.prototype.toISOString,f=s.default,d={addQueryPrefix:!1,allowDots:!1,charset:"utf-8",charsetSentinel:!1,delimiter:"&",encode:!0,encoder:o.encode,encodeValuesOnly:!1,format:f,formatter:s.formatters[f],indices:!1,serializeDate:function(e){return h.call(e)},skipNulls:!1,strictNullHandling:!1},m={},g=function e(t,n,s,i,a,u,h,f,g,y,v,b,w,E,x,S){for(var _,j=t,O=S,k=0,A=!1;void 0!==(O=O.get(m))&&!A;){var C=O.get(t);if(k+=1,void 0!==C){if(C===k)throw new RangeError("Cyclic object value");A=!0}void 0===O.get(m)&&(k=0)}if("function"==typeof f?j=f(n,j):j instanceof Date?j=v(j):"comma"===s&&l(j)&&(j=o.maybeMap(j,(function(e){return e instanceof Date?v(e):e}))),null===j){if(a)return h&&!E?h(n,d.encoder,x,"key",b):n;j=""}if("string"==typeof(_=j)||"number"==typeof _||"boolean"==typeof _||"symbol"==typeof _||"bigint"==typeof _||o.isBuffer(j)){if(h){var P=E?n:h(n,d.encoder,x,"key",b);if("comma"===s&&E){for(var N=c.call(String(j),","),I="",T=0;T0?j.join(",")||null:void 0}];else if(l(f))R=f;else{var D=Object.keys(j);R=g?D.sort(g):D}for(var F=i&&l(j)&&1===j.length?n+"[]":n,L=0;L0?E+w:""}},12769:(e,t,n)=>{"use strict";var r=n(55798),o=Object.prototype.hasOwnProperty,s=Array.isArray,i=function(){for(var e=[],t=0;t<256;++t)e.push("%"+((t<16?"0":"")+t.toString(16)).toUpperCase());return e}(),a=function(e,t){for(var n=t&&t.plainObjects?Object.create(null):{},r=0;r1;){var t=e.pop(),n=t.obj[t.prop];if(s(n)){for(var r=[],o=0;o=48&&u<=57||u>=65&&u<=90||u>=97&&u<=122||s===r.RFC1738&&(40===u||41===u)?l+=a.charAt(c):u<128?l+=i[u]:u<2048?l+=i[192|u>>6]+i[128|63&u]:u<55296||u>=57344?l+=i[224|u>>12]+i[128|u>>6&63]+i[128|63&u]:(c+=1,u=65536+((1023&u)<<10|1023&a.charCodeAt(c)),l+=i[240|u>>18]+i[128|u>>12&63]+i[128|u>>6&63]+i[128|63&u])}return l},isBuffer:function(e){return!(!e||"object"!=typeof e)&&!!(e.constructor&&e.constructor.isBuffer&&e.constructor.isBuffer(e))},isRegExp:function(e){return"[object RegExp]"===Object.prototype.toString.call(e)},maybeMap:function(e,t){if(s(e)){for(var n=[],r=0;r{"use strict";var n=Object.prototype.hasOwnProperty;function r(e){try{return decodeURIComponent(e.replace(/\+/g," "))}catch(e){return null}}function o(e){try{return encodeURIComponent(e)}catch(e){return null}}t.stringify=function(e,t){t=t||"";var r,s,i=[];for(s in"string"!=typeof t&&(t="?"),e)if(n.call(e,s)){if((r=e[s])||null!=r&&!isNaN(r)||(r=""),s=o(s),r=o(r),null===s||null===r)continue;i.push(s+"="+r)}return i.length?t+i.join("&"):""},t.parse=function(e){for(var t,n=/([^=?#&]+)=?([^&]*)/g,o={};t=n.exec(e);){var s=r(t[1]),i=r(t[2]);null===s||null===i||s in o||(o[s]=i)}return o}},14419:(e,t,n)=>{const r=n(60697),o=n(69450),s=r.types;e.exports=class e{constructor(e,t){if(this._setDefaults(e),e instanceof RegExp)this.ignoreCase=e.ignoreCase,this.multiline=e.multiline,e=e.source;else{if("string"!=typeof e)throw new Error("Expected a regexp or string");this.ignoreCase=t&&-1!==t.indexOf("i"),this.multiline=t&&-1!==t.indexOf("m")}this.tokens=r(e)}_setDefaults(t){this.max=null!=t.max?t.max:null!=e.prototype.max?e.prototype.max:100,this.defaultRange=t.defaultRange?t.defaultRange:this.defaultRange.clone(),t.randInt&&(this.randInt=t.randInt)}gen(){return this._gen(this.tokens,[])}_gen(e,t){var n,r,o,i,a;switch(e.type){case s.ROOT:case s.GROUP:if(e.followedBy||e.notFollowedBy)return"";for(e.remember&&void 0===e.groupNumber&&(e.groupNumber=t.push(null)-1),r="",i=0,a=(n=e.options?this._randSelect(e.options):e.stack).length;i{"use strict";var r=n(34155),o=65536,s=4294967295;var i=n(89509).Buffer,a=n.g.crypto||n.g.msCrypto;a&&a.getRandomValues?e.exports=function(e,t){if(e>s)throw new RangeError("requested too many random bytes");var n=i.allocUnsafe(e);if(e>0)if(e>o)for(var l=0;l{"use strict";function r(e){return r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},r(e)}Object.defineProperty(t,"__esModule",{value:!0}),t.CopyToClipboard=void 0;var o=a(n(67294)),s=a(n(20640)),i=["text","onCopy","options","children"];function a(e){return e&&e.__esModule?e:{default:e}}function l(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function c(e){for(var t=1;t=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var s=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}function p(e,t){for(var n=0;n{"use strict";var r=n(74300).CopyToClipboard;r.CopyToClipboard=r,e.exports=r},53441:(e,t,n)=>{"use strict";function r(e){return r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},r(e)}Object.defineProperty(t,"__esModule",{value:!0}),t.DebounceInput=void 0;var o=a(n(67294)),s=a(n(91296)),i=["element","onChange","value","minLength","debounceTimeout","forceNotifyByEnter","forceNotifyOnBlur","onKeyDown","onBlur","inputRef"];function a(e){return e&&e.__esModule?e:{default:e}}function l(e,t){if(null==e)return{};var n,r,o=function(e,t){if(null==e)return{};var n,r,o={},s=Object.keys(e);for(r=0;r=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var s=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}function c(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function u(e){for(var t=1;t=r?t.notify(e):n.length>o.length&&t.notify(u(u({},e),{},{target:u(u({},e.target),{},{value:""})}))}))})),g(d(t),"onKeyDown",(function(e){"Enter"===e.key&&t.forceNotify(e);var n=t.props.onKeyDown;n&&(e.persist(),n(e))})),g(d(t),"onBlur",(function(e){t.forceNotify(e);var n=t.props.onBlur;n&&(e.persist(),n(e))})),g(d(t),"createNotifier",(function(e){if(e<0)t.notify=function(){return null};else if(0===e)t.notify=t.doNotify;else{var n=(0,s.default)((function(e){t.isDebouncing=!1,t.doNotify(e)}),e);t.notify=function(e){t.isDebouncing=!0,n(e)},t.flush=function(){return n.flush()},t.cancel=function(){t.isDebouncing=!1,n.cancel()}}})),g(d(t),"doNotify",(function(){t.props.onChange.apply(void 0,arguments)})),g(d(t),"forceNotify",(function(e){var n=t.props.debounceTimeout;if(t.isDebouncing||!(n>0)){t.cancel&&t.cancel();var r=t.state.value,o=t.props.minLength;r.length>=o?t.doNotify(e):t.doNotify(u(u({},e),{},{target:u(u({},e.target),{},{value:r})}))}})),t.isDebouncing=!1,t.state={value:void 0===e.value||null===e.value?"":e.value};var n=t.props.debounceTimeout;return t.createNotifier(n),t}return t=c,(n=[{key:"componentDidUpdate",value:function(e){if(!this.isDebouncing){var t=this.props,n=t.value,r=t.debounceTimeout,o=e.debounceTimeout,s=e.value,i=this.state.value;void 0!==n&&s!==n&&i!==n&&this.setState({value:n}),r!==o&&this.createNotifier(r)}}},{key:"componentWillUnmount",value:function(){this.flush&&this.flush()}},{key:"render",value:function(){var e,t,n=this.props,r=n.element,s=(n.onChange,n.value,n.minLength,n.debounceTimeout,n.forceNotifyByEnter),a=n.forceNotifyOnBlur,c=n.onKeyDown,p=n.onBlur,h=n.inputRef,f=l(n,i),d=this.state.value;e=s?{onKeyDown:this.onKeyDown}:c?{onKeyDown:c}:{},t=a?{onBlur:this.onBlur}:p?{onBlur:p}:{};var m=h?{ref:h}:{};return o.default.createElement(r,u(u(u(u({},f),{},{onChange:this.onChange,value:d},e),t),m))}}])&&p(t.prototype,n),r&&p(t,r),Object.defineProperty(t,"prototype",{writable:!1}),c}(o.default.PureComponent);t.DebounceInput=y,g(y,"defaultProps",{element:"input",type:"text",onKeyDown:void 0,onBlur:void 0,value:void 0,minLength:0,debounceTimeout:100,forceNotifyByEnter:!0,forceNotifyOnBlur:!0,inputRef:void 0})},775:(e,t,n)=>{"use strict";var r=n(53441).DebounceInput;r.DebounceInput=r,e.exports=r},64448:(e,t,n)=>{"use strict";var r=n(67294),o=n(27418),s=n(63840);function i(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n
")}value(){return this.buffer}span(e){this.buffer+=``}}class c{constructor(){this.rootNode={children:[]},this.stack=[this.rootNode]}get top(){return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(e){this.top.children.push(e)}openNode(e){const t={kind:e,children:[]};this.add(t),this.stack.push(t)}closeNode(){if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)}walk(e){return this.constructor._walk(e,this.rootNode)}static _walk(e,t){return"string"==typeof t?e.addText(t):t.children&&(e.openNode(t),t.children.forEach((t=>this._walk(e,t))),e.closeNode(t)),e}static _collapse(e){"string"!=typeof e&&e.children&&(e.children.every((e=>"string"==typeof e))?e.children=[e.children.join("")]:e.children.forEach((e=>{c._collapse(e)})))}}class u extends c{constructor(e){super(),this.options=e}addKeyword(e,t){""!==e&&(this.openNode(t),this.addText(e),this.closeNode())}addText(e){""!==e&&this.add(e)}addSublanguage(e,t){const n=e.root;n.kind=t,n.sublanguage=!0,this.add(n)}toHTML(){return new l(this,this.options).value()}finalize(){return!0}}function p(e){return e?"string"==typeof e?e:e.source:null}const h=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./;const f="[a-zA-Z]\\w*",d="[a-zA-Z_]\\w*",m="\\b\\d+(\\.\\d+)?",g="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",y="\\b(0b[01]+)",v={begin:"\\\\[\\s\\S]",relevance:0},b={className:"string",begin:"'",end:"'",illegal:"\\n",contains:[v]},w={className:"string",begin:'"',end:'"',illegal:"\\n",contains:[v]},E={begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},x=function(e,t,n={}){const r=i({className:"comment",begin:e,end:t,contains:[]},n);return r.contains.push(E),r.contains.push({className:"doctag",begin:"(?:TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):",relevance:0}),r},S=x("//","$"),_=x("/\\*","\\*/"),j=x("#","$"),O={className:"number",begin:m,relevance:0},k={className:"number",begin:g,relevance:0},A={className:"number",begin:y,relevance:0},C={className:"number",begin:m+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",relevance:0},P={begin:/(?=\/[^/\n]*\/)/,contains:[{className:"regexp",begin:/\//,end:/\/[gimuy]*/,illegal:/\n/,contains:[v,{begin:/\[/,end:/\]/,relevance:0,contains:[v]}]}]},N={className:"title",begin:f,relevance:0},I={className:"title",begin:d,relevance:0},T={begin:"\\.\\s*"+d,relevance:0};var R=Object.freeze({__proto__:null,MATCH_NOTHING_RE:/\b\B/,IDENT_RE:f,UNDERSCORE_IDENT_RE:d,NUMBER_RE:m,C_NUMBER_RE:g,BINARY_NUMBER_RE:y,RE_STARTERS_RE:"!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",SHEBANG:(e={})=>{const t=/^#![ ]*\//;return e.binary&&(e.begin=function(...e){return e.map((e=>p(e))).join("")}(t,/.*\b/,e.binary,/\b.*/)),i({className:"meta",begin:t,end:/$/,relevance:0,"on:begin":(e,t)=>{0!==e.index&&t.ignoreMatch()}},e)},BACKSLASH_ESCAPE:v,APOS_STRING_MODE:b,QUOTE_STRING_MODE:w,PHRASAL_WORDS_MODE:E,COMMENT:x,C_LINE_COMMENT_MODE:S,C_BLOCK_COMMENT_MODE:_,HASH_COMMENT_MODE:j,NUMBER_MODE:O,C_NUMBER_MODE:k,BINARY_NUMBER_MODE:A,CSS_NUMBER_MODE:C,REGEXP_MODE:P,TITLE_MODE:N,UNDERSCORE_TITLE_MODE:I,METHOD_GUARD:T,END_SAME_AS_BEGIN:function(e){return Object.assign(e,{"on:begin":(e,t)=>{t.data._beginMatch=e[1]},"on:end":(e,t)=>{t.data._beginMatch!==e[1]&&t.ignoreMatch()}})}});function M(e,t){"."===e.input[e.index-1]&&t.ignoreMatch()}function D(e,t){t&&e.beginKeywords&&(e.begin="\\b("+e.beginKeywords.split(" ").join("|")+")(?!\\.)(?=\\b|\\s)",e.__beforeBegin=M,e.keywords=e.keywords||e.beginKeywords,delete e.beginKeywords,void 0===e.relevance&&(e.relevance=0))}function F(e,t){Array.isArray(e.illegal)&&(e.illegal=function(...e){return"("+e.map((e=>p(e))).join("|")+")"}(...e.illegal))}function L(e,t){if(e.match){if(e.begin||e.end)throw new Error("begin & end are not supported with match");e.begin=e.match,delete e.match}}function B(e,t){void 0===e.relevance&&(e.relevance=1)}const $=["of","and","for","in","not","or","if","then","parent","list","value"],q="keyword";function U(e,t,n=q){const r={};return"string"==typeof e?o(n,e.split(" ")):Array.isArray(e)?o(n,e):Object.keys(e).forEach((function(n){Object.assign(r,U(e[n],t,n))})),r;function o(e,n){t&&(n=n.map((e=>e.toLowerCase()))),n.forEach((function(t){const n=t.split("|");r[n[0]]=[e,z(n[0],n[1])]}))}}function z(e,t){return t?Number(t):function(e){return $.includes(e.toLowerCase())}(e)?0:1}function V(e,{plugins:t}){function n(t,n){return new RegExp(p(t),"m"+(e.case_insensitive?"i":"")+(n?"g":""))}class r{constructor(){this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0}addRule(e,t){t.position=this.position++,this.matchIndexes[this.matchAt]=t,this.regexes.push([t,e]),this.matchAt+=function(e){return new RegExp(e.toString()+"|").exec("").length-1}(e)+1}compile(){0===this.regexes.length&&(this.exec=()=>null);const e=this.regexes.map((e=>e[1]));this.matcherRe=n(function(e,t="|"){let n=0;return e.map((e=>{n+=1;const t=n;let r=p(e),o="";for(;r.length>0;){const e=h.exec(r);if(!e){o+=r;break}o+=r.substring(0,e.index),r=r.substring(e.index+e[0].length),"\\"===e[0][0]&&e[1]?o+="\\"+String(Number(e[1])+t):(o+=e[0],"("===e[0]&&n++)}return o})).map((e=>`(${e})`)).join(t)}(e),!0),this.lastIndex=0}exec(e){this.matcherRe.lastIndex=this.lastIndex;const t=this.matcherRe.exec(e);if(!t)return null;const n=t.findIndex(((e,t)=>t>0&&void 0!==e)),r=this.matchIndexes[n];return t.splice(0,n),Object.assign(t,r)}}class o{constructor(){this.rules=[],this.multiRegexes=[],this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(e){if(this.multiRegexes[e])return this.multiRegexes[e];const t=new r;return this.rules.slice(e).forEach((([e,n])=>t.addRule(e,n))),t.compile(),this.multiRegexes[e]=t,t}resumingScanAtSamePosition(){return 0!==this.regexIndex}considerAll(){this.regexIndex=0}addRule(e,t){this.rules.push([e,t]),"begin"===t.type&&this.count++}exec(e){const t=this.getMatcher(this.regexIndex);t.lastIndex=this.lastIndex;let n=t.exec(e);if(this.resumingScanAtSamePosition())if(n&&n.index===this.lastIndex);else{const t=this.getMatcher(0);t.lastIndex=this.lastIndex+1,n=t.exec(e)}return n&&(this.regexIndex+=n.position+1,this.regexIndex===this.count&&this.considerAll()),n}}if(e.compilerExtensions||(e.compilerExtensions=[]),e.contains&&e.contains.includes("self"))throw new Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.");return e.classNameAliases=i(e.classNameAliases||{}),function t(r,s){const a=r;if(r.isCompiled)return a;[L].forEach((e=>e(r,s))),e.compilerExtensions.forEach((e=>e(r,s))),r.__beforeBegin=null,[D,F,B].forEach((e=>e(r,s))),r.isCompiled=!0;let l=null;if("object"==typeof r.keywords&&(l=r.keywords.$pattern,delete r.keywords.$pattern),r.keywords&&(r.keywords=U(r.keywords,e.case_insensitive)),r.lexemes&&l)throw new Error("ERR: Prefer `keywords.$pattern` to `mode.lexemes`, BOTH are not allowed. (see mode reference) ");return l=l||r.lexemes||/\w+/,a.keywordPatternRe=n(l,!0),s&&(r.begin||(r.begin=/\B|\b/),a.beginRe=n(r.begin),r.endSameAsBegin&&(r.end=r.begin),r.end||r.endsWithParent||(r.end=/\B|\b/),r.end&&(a.endRe=n(r.end)),a.terminatorEnd=p(r.end)||"",r.endsWithParent&&s.terminatorEnd&&(a.terminatorEnd+=(r.end?"|":"")+s.terminatorEnd)),r.illegal&&(a.illegalRe=n(r.illegal)),r.contains||(r.contains=[]),r.contains=[].concat(...r.contains.map((function(e){return function(e){e.variants&&!e.cachedVariants&&(e.cachedVariants=e.variants.map((function(t){return i(e,{variants:null},t)})));if(e.cachedVariants)return e.cachedVariants;if(W(e))return i(e,{starts:e.starts?i(e.starts):null});if(Object.isFrozen(e))return i(e);return e}("self"===e?r:e)}))),r.contains.forEach((function(e){t(e,a)})),r.starts&&t(r.starts,s),a.matcher=function(e){const t=new o;return e.contains.forEach((e=>t.addRule(e.begin,{rule:e,type:"begin"}))),e.terminatorEnd&&t.addRule(e.terminatorEnd,{type:"end"}),e.illegal&&t.addRule(e.illegal,{type:"illegal"}),t}(a),a}(e)}function W(e){return!!e&&(e.endsWithParent||W(e.starts))}function J(e){const t={props:["language","code","autodetect"],data:function(){return{detectedLanguage:"",unknownLanguage:!1}},computed:{className(){return this.unknownLanguage?"":"hljs "+this.detectedLanguage},highlighted(){if(!this.autoDetect&&!e.getLanguage(this.language))return console.warn(`The language "${this.language}" you specified could not be found.`),this.unknownLanguage=!0,s(this.code);let t={};return this.autoDetect?(t=e.highlightAuto(this.code),this.detectedLanguage=t.language):(t=e.highlight(this.language,this.code,this.ignoreIllegals),this.detectedLanguage=this.language),t.value},autoDetect(){return!this.language||(e=this.autodetect,Boolean(e||""===e));var e},ignoreIllegals:()=>!0},render(e){return e("pre",{},[e("code",{class:this.className,domProps:{innerHTML:this.highlighted}})])}};return{Component:t,VuePlugin:{install(e){e.component("highlightjs",t)}}}}const K={"after:highlightElement":({el:e,result:t,text:n})=>{const r=G(e);if(!r.length)return;const o=document.createElement("div");o.innerHTML=t.value,t.value=function(e,t,n){let r=0,o="";const i=[];function a(){return e.length&&t.length?e[0].offset!==t[0].offset?e[0].offset"}function c(e){o+=""}function u(e){("start"===e.event?l:c)(e.node)}for(;e.length||t.length;){let t=a();if(o+=s(n.substring(r,t[0].offset)),r=t[0].offset,t===e){i.reverse().forEach(c);do{u(t.splice(0,1)[0]),t=a()}while(t===e&&t.length&&t[0].offset===r);i.reverse().forEach(l)}else"start"===t[0].event?i.push(t[0].node):i.pop(),u(t.splice(0,1)[0])}return o+s(n.substr(r))}(r,G(o),n)}};function H(e){return e.nodeName.toLowerCase()}function G(e){const t=[];return function e(n,r){for(let o=n.firstChild;o;o=o.nextSibling)3===o.nodeType?r+=o.nodeValue.length:1===o.nodeType&&(t.push({event:"start",offset:r,node:o}),r=e(o,r),H(o).match(/br|hr|img|input/)||t.push({event:"stop",offset:r,node:o}));return r}(e,0),t}const Z={},Y=e=>{console.error(e)},X=(e,...t)=>{console.log(`WARN: ${e}`,...t)},Q=(e,t)=>{Z[`${e}/${t}`]||(console.log(`Deprecated as of ${e}. ${t}`),Z[`${e}/${t}`]=!0)},ee=s,te=i,ne=Symbol("nomatch");var re=function(e){const t=Object.create(null),r=Object.create(null),s=[];let i=!0;const a=/(^(<[^>]+>|\t|)+|\n)/gm,l="Could not find the language '{}', did you forget to load/include a language module?",c={disableAutodetect:!0,name:"Plain text",contains:[]};let p={noHighlightRe:/^(no-?highlight)$/i,languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:null,__emitter:u};function h(e){return p.noHighlightRe.test(e)}function f(e,t,n,r){let o="",s="";"object"==typeof t?(o=e,n=t.ignoreIllegals,s=t.language,r=void 0):(Q("10.7.0","highlight(lang, code, ...args) has been deprecated."),Q("10.7.0","Please use highlight(code, options) instead.\nhttps://github.com/highlightjs/highlight.js/issues/2277"),s=e,o=t);const i={code:o,language:s};O("before:highlight",i);const a=i.result?i.result:d(i.language,i.code,n,r);return a.code=i.code,O("after:highlight",a),a}function d(e,n,r,a){function c(e,t){const n=E.case_insensitive?t[0].toLowerCase():t[0];return Object.prototype.hasOwnProperty.call(e.keywords,n)&&e.keywords[n]}function u(){null!=j.subLanguage?function(){if(""===A)return;let e=null;if("string"==typeof j.subLanguage){if(!t[j.subLanguage])return void k.addText(A);e=d(j.subLanguage,A,!0,O[j.subLanguage]),O[j.subLanguage]=e.top}else e=m(A,j.subLanguage.length?j.subLanguage:null);j.relevance>0&&(C+=e.relevance),k.addSublanguage(e.emitter,e.language)}():function(){if(!j.keywords)return void k.addText(A);let e=0;j.keywordPatternRe.lastIndex=0;let t=j.keywordPatternRe.exec(A),n="";for(;t;){n+=A.substring(e,t.index);const r=c(j,t);if(r){const[e,o]=r;if(k.addText(n),n="",C+=o,e.startsWith("_"))n+=t[0];else{const n=E.classNameAliases[e]||e;k.addKeyword(t[0],n)}}else n+=t[0];e=j.keywordPatternRe.lastIndex,t=j.keywordPatternRe.exec(A)}n+=A.substr(e),k.addText(n)}(),A=""}function h(e){return e.className&&k.openNode(E.classNameAliases[e.className]||e.className),j=Object.create(e,{parent:{value:j}}),j}function f(e,t,n){let r=function(e,t){const n=e&&e.exec(t);return n&&0===n.index}(e.endRe,n);if(r){if(e["on:end"]){const n=new o(e);e["on:end"](t,n),n.isMatchIgnored&&(r=!1)}if(r){for(;e.endsParent&&e.parent;)e=e.parent;return e}}if(e.endsWithParent)return f(e.parent,t,n)}function g(e){return 0===j.matcher.regexIndex?(A+=e[0],1):(I=!0,0)}function y(e){const t=e[0],n=e.rule,r=new o(n),s=[n.__beforeBegin,n["on:begin"]];for(const n of s)if(n&&(n(e,r),r.isMatchIgnored))return g(t);return n&&n.endSameAsBegin&&(n.endRe=new RegExp(t.replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&"),"m")),n.skip?A+=t:(n.excludeBegin&&(A+=t),u(),n.returnBegin||n.excludeBegin||(A=t)),h(n),n.returnBegin?0:t.length}function v(e){const t=e[0],r=n.substr(e.index),o=f(j,e,r);if(!o)return ne;const s=j;s.skip?A+=t:(s.returnEnd||s.excludeEnd||(A+=t),u(),s.excludeEnd&&(A=t));do{j.className&&k.closeNode(),j.skip||j.subLanguage||(C+=j.relevance),j=j.parent}while(j!==o.parent);return o.starts&&(o.endSameAsBegin&&(o.starts.endRe=o.endRe),h(o.starts)),s.returnEnd?0:t.length}let b={};function w(t,o){const s=o&&o[0];if(A+=t,null==s)return u(),0;if("begin"===b.type&&"end"===o.type&&b.index===o.index&&""===s){if(A+=n.slice(o.index,o.index+1),!i){const t=new Error("0 width match regex");throw t.languageName=e,t.badRule=b.rule,t}return 1}if(b=o,"begin"===o.type)return y(o);if("illegal"===o.type&&!r){const e=new Error('Illegal lexeme "'+s+'" for mode "'+(j.className||"")+'"');throw e.mode=j,e}if("end"===o.type){const e=v(o);if(e!==ne)return e}if("illegal"===o.type&&""===s)return 1;if(N>1e5&&N>3*o.index){throw new Error("potential infinite loop, way more iterations than matches")}return A+=s,s.length}const E=S(e);if(!E)throw Y(l.replace("{}",e)),new Error('Unknown language: "'+e+'"');const x=V(E,{plugins:s});let _="",j=a||x;const O={},k=new p.__emitter(p);!function(){const e=[];for(let t=j;t!==E;t=t.parent)t.className&&e.unshift(t.className);e.forEach((e=>k.openNode(e)))}();let A="",C=0,P=0,N=0,I=!1;try{for(j.matcher.considerAll();;){N++,I?I=!1:j.matcher.considerAll(),j.matcher.lastIndex=P;const e=j.matcher.exec(n);if(!e)break;const t=w(n.substring(P,e.index),e);P=e.index+t}return w(n.substr(P)),k.closeAllNodes(),k.finalize(),_=k.toHTML(),{relevance:Math.floor(C),value:_,language:e,illegal:!1,emitter:k,top:j}}catch(t){if(t.message&&t.message.includes("Illegal"))return{illegal:!0,illegalBy:{msg:t.message,context:n.slice(P-100,P+100),mode:t.mode},sofar:_,relevance:0,value:ee(n),emitter:k};if(i)return{illegal:!1,relevance:0,value:ee(n),emitter:k,language:e,top:j,errorRaised:t};throw t}}function m(e,n){n=n||p.languages||Object.keys(t);const r=function(e){const t={relevance:0,emitter:new p.__emitter(p),value:ee(e),illegal:!1,top:c};return t.emitter.addText(e),t}(e),o=n.filter(S).filter(j).map((t=>d(t,e,!1)));o.unshift(r);const s=o.sort(((e,t)=>{if(e.relevance!==t.relevance)return t.relevance-e.relevance;if(e.language&&t.language){if(S(e.language).supersetOf===t.language)return 1;if(S(t.language).supersetOf===e.language)return-1}return 0})),[i,a]=s,l=i;return l.second_best=a,l}const g={"before:highlightElement":({el:e})=>{p.useBR&&(e.innerHTML=e.innerHTML.replace(/\n/g,"").replace(//g,"\n"))},"after:highlightElement":({result:e})=>{p.useBR&&(e.value=e.value.replace(/\n/g,"
"))}},y=/^(<[^>]+>|\t)+/gm,v={"after:highlightElement":({result:e})=>{p.tabReplace&&(e.value=e.value.replace(y,(e=>e.replace(/\t/g,p.tabReplace))))}};function b(e){let t=null;const n=function(e){let t=e.className+" ";t+=e.parentNode?e.parentNode.className:"";const n=p.languageDetectRe.exec(t);if(n){const t=S(n[1]);return t||(X(l.replace("{}",n[1])),X("Falling back to no-highlight mode for this block.",e)),t?n[1]:"no-highlight"}return t.split(/\s+/).find((e=>h(e)||S(e)))}(e);if(h(n))return;O("before:highlightElement",{el:e,language:n}),t=e;const o=t.textContent,s=n?f(o,{language:n,ignoreIllegals:!0}):m(o);O("after:highlightElement",{el:e,result:s,text:o}),e.innerHTML=s.value,function(e,t,n){const o=t?r[t]:n;e.classList.add("hljs"),o&&e.classList.add(o)}(e,n,s.language),e.result={language:s.language,re:s.relevance,relavance:s.relevance},s.second_best&&(e.second_best={language:s.second_best.language,re:s.second_best.relevance,relavance:s.second_best.relevance})}const w=()=>{if(w.called)return;w.called=!0,Q("10.6.0","initHighlighting() is deprecated. Use highlightAll() instead.");document.querySelectorAll("pre code").forEach(b)};let E=!1;function x(){if("loading"===document.readyState)return void(E=!0);document.querySelectorAll("pre code").forEach(b)}function S(e){return e=(e||"").toLowerCase(),t[e]||t[r[e]]}function _(e,{languageName:t}){"string"==typeof e&&(e=[e]),e.forEach((e=>{r[e.toLowerCase()]=t}))}function j(e){const t=S(e);return t&&!t.disableAutodetect}function O(e,t){const n=e;s.forEach((function(e){e[n]&&e[n](t)}))}"undefined"!=typeof window&&window.addEventListener&&window.addEventListener("DOMContentLoaded",(function(){E&&x()}),!1),Object.assign(e,{highlight:f,highlightAuto:m,highlightAll:x,fixMarkup:function(e){return Q("10.2.0","fixMarkup will be removed entirely in v11.0"),Q("10.2.0","Please see https://github.com/highlightjs/highlight.js/issues/2534"),t=e,p.tabReplace||p.useBR?t.replace(a,(e=>"\n"===e?p.useBR?"
":e:p.tabReplace?e.replace(/\t/g,p.tabReplace):e)):t;var t},highlightElement:b,highlightBlock:function(e){return Q("10.7.0","highlightBlock will be removed entirely in v12.0"),Q("10.7.0","Please use highlightElement now."),b(e)},configure:function(e){e.useBR&&(Q("10.3.0","'useBR' will be removed entirely in v11.0"),Q("10.3.0","Please see https://github.com/highlightjs/highlight.js/issues/2559")),p=te(p,e)},initHighlighting:w,initHighlightingOnLoad:function(){Q("10.6.0","initHighlightingOnLoad() is deprecated. Use highlightAll() instead."),E=!0},registerLanguage:function(n,r){let o=null;try{o=r(e)}catch(e){if(Y("Language definition for '{}' could not be registered.".replace("{}",n)),!i)throw e;Y(e),o=c}o.name||(o.name=n),t[n]=o,o.rawDefinition=r.bind(null,e),o.aliases&&_(o.aliases,{languageName:n})},unregisterLanguage:function(e){delete t[e];for(const t of Object.keys(r))r[t]===e&&delete r[t]},listLanguages:function(){return Object.keys(t)},getLanguage:S,registerAliases:_,requireLanguage:function(e){Q("10.4.0","requireLanguage will be removed entirely in v11."),Q("10.4.0","Please see https://github.com/highlightjs/highlight.js/pull/2844");const t=S(e);if(t)return t;throw new Error("The '{}' language is required, but not loaded.".replace("{}",e))},autoDetection:j,inherit:te,addPlugin:function(e){!function(e){e["before:highlightBlock"]&&!e["before:highlightElement"]&&(e["before:highlightElement"]=t=>{e["before:highlightBlock"](Object.assign({block:t.el},t))}),e["after:highlightBlock"]&&!e["after:highlightElement"]&&(e["after:highlightElement"]=t=>{e["after:highlightBlock"](Object.assign({block:t.el},t))})}(e),s.push(e)},vuePlugin:J(e).VuePlugin}),e.debugMode=function(){i=!1},e.safeMode=function(){i=!0},e.versionString="10.7.3";for(const e in R)"object"==typeof R[e]&&n(R[e]);return Object.assign(e,R),e.addPlugin(g),e.addPlugin(K),e.addPlugin(v),e}({});e.exports=re},61519:e=>{function t(...e){return e.map((e=>{return(t=e)?"string"==typeof t?t:t.source:null;var t})).join("")}e.exports=function(e){const n={},r={begin:/\$\{/,end:/\}/,contains:["self",{begin:/:-/,contains:[n]}]};Object.assign(n,{className:"variable",variants:[{begin:t(/\$[\w\d#@][\w\d_]*/,"(?![\\w\\d])(?![$])")},r]});const o={className:"subst",begin:/\$\(/,end:/\)/,contains:[e.BACKSLASH_ESCAPE]},s={begin:/<<-?\s*(?=\w+)/,starts:{contains:[e.END_SAME_AS_BEGIN({begin:/(\w+)/,end:/(\w+)/,className:"string"})]}},i={className:"string",begin:/"/,end:/"/,contains:[e.BACKSLASH_ESCAPE,n,o]};o.contains.push(i);const a={begin:/\$\(\(/,end:/\)\)/,contains:[{begin:/\d+#[0-9a-f]+/,className:"number"},e.NUMBER_MODE,n]},l=e.SHEBANG({binary:`(${["fish","bash","zsh","sh","csh","ksh","tcsh","dash","scsh"].join("|")})`,relevance:10}),c={className:"function",begin:/\w[\w\d_]*\s*\(\s*\)\s*\{/,returnBegin:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/\w[\w\d_]*/})],relevance:0};return{name:"Bash",aliases:["sh","zsh"],keywords:{$pattern:/\b[a-z._-]+\b/,keyword:"if then else elif fi for while in do done case esac function",literal:"true false",built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp"},contains:[l,e.SHEBANG(),c,a,e.HASH_COMMENT_MODE,s,i,{className:"",begin:/\\"/},{className:"string",begin:/'/,end:/'/},n]}}},30786:e=>{function t(...e){return e.map((e=>{return(t=e)?"string"==typeof t?t:t.source:null;var t})).join("")}e.exports=function(e){const n="HTTP/(2|1\\.[01])",r={className:"attribute",begin:t("^",/[A-Za-z][A-Za-z0-9-]*/,"(?=\\:\\s)"),starts:{contains:[{className:"punctuation",begin:/: /,relevance:0,starts:{end:"$",relevance:0}}]}},o=[r,{begin:"\\n\\n",starts:{subLanguage:[],endsWithParent:!0}}];return{name:"HTTP",aliases:["https"],illegal:/\S/,contains:[{begin:"^(?="+n+" \\d{3})",end:/$/,contains:[{className:"meta",begin:n},{className:"number",begin:"\\b\\d{3}\\b"}],starts:{end:/\b\B/,illegal:/\S/,contains:o}},{begin:"(?=^[A-Z]+ (.*?) "+n+"$)",end:/$/,contains:[{className:"string",begin:" ",end:" ",excludeBegin:!0,excludeEnd:!0},{className:"meta",begin:n},{className:"keyword",begin:"[A-Z]+"}],starts:{end:/\b\B/,illegal:/\S/,contains:o}},e.inherit(r,{relevance:0})]}}},96344:e=>{const t="[A-Za-z$_][0-9A-Za-z$_]*",n=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],r=["true","false","null","undefined","NaN","Infinity"],o=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer","BigInt64Array","BigUint64Array","BigInt"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"]);function s(e){return i("(?=",e,")")}function i(...e){return e.map((e=>{return(t=e)?"string"==typeof t?t:t.source:null;var t})).join("")}e.exports=function(e){const a=t,l="<>",c="",u={begin:/<[A-Za-z0-9\\._:-]+/,end:/\/[A-Za-z0-9\\._:-]+>|\/>/,isTrulyOpeningTag:(e,t)=>{const n=e[0].length+e.index,r=e.input[n];"<"!==r?">"===r&&(((e,{after:t})=>{const n="",returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{begin:e.UNDERSCORE_IDENT_RE,relevance:0},{className:null,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:p,contains:S}]}]},{begin:/,/,relevance:0},{className:"",begin:/\s/,end:/\s*/,skip:!0},{variants:[{begin:l,end:c},{begin:u.begin,"on:begin":u.isTrulyOpeningTag,end:u.end}],subLanguage:"xml",contains:[{begin:u.begin,end:u.end,skip:!0,contains:["self"]}]}],relevance:0},{className:"function",beginKeywords:"function",end:/[{;]/,excludeEnd:!0,keywords:p,contains:["self",e.inherit(e.TITLE_MODE,{begin:a}),_],illegal:/%/},{beginKeywords:"while if switch catch for"},{className:"function",begin:e.UNDERSCORE_IDENT_RE+"\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)\\s*\\{",returnBegin:!0,contains:[_,e.inherit(e.TITLE_MODE,{begin:a})]},{variants:[{begin:"\\."+a},{begin:"\\$"+a}],relevance:0},{className:"class",beginKeywords:"class",end:/[{;=]/,excludeEnd:!0,illegal:/[:"[\]]/,contains:[{beginKeywords:"extends"},e.UNDERSCORE_TITLE_MODE]},{begin:/\b(?=constructor)/,end:/[{;]/,excludeEnd:!0,contains:[e.inherit(e.TITLE_MODE,{begin:a}),"self",_]},{begin:"(get|set)\\s+(?="+a+"\\()",end:/\{/,keywords:"get set",contains:[e.inherit(e.TITLE_MODE,{begin:a}),{begin:/\(\)/},_]},{begin:/\$[(.]/}]}}},82026:e=>{e.exports=function(e){const t={literal:"true false null"},n=[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE],r=[e.QUOTE_STRING_MODE,e.C_NUMBER_MODE],o={end:",",endsWithParent:!0,excludeEnd:!0,contains:r,keywords:t},s={begin:/\{/,end:/\}/,contains:[{className:"attr",begin:/"/,end:/"/,contains:[e.BACKSLASH_ESCAPE],illegal:"\\n"},e.inherit(o,{begin:/:/})].concat(n),illegal:"\\S"},i={begin:"\\[",end:"\\]",contains:[e.inherit(o)],illegal:"\\S"};return r.push(s,i),n.forEach((function(e){r.push(e)})),{name:"JSON",contains:r,keywords:t,illegal:"\\S"}}},66336:e=>{e.exports=function(e){const t={$pattern:/-?[A-z\.\-]+\b/,keyword:"if else foreach return do while until elseif begin for trap data dynamicparam end break throw param continue finally in switch exit filter try process catch hidden static parameter",built_in:"ac asnp cat cd CFS chdir clc clear clhy cli clp cls clv cnsn compare copy cp cpi cpp curl cvpa dbp del diff dir dnsn ebp echo|0 epal epcsv epsn erase etsn exsn fc fhx fl ft fw gal gbp gc gcb gci gcm gcs gdr gerr ghy gi gin gjb gl gm gmo gp gps gpv group gsn gsnp gsv gtz gu gv gwmi h history icm iex ihy ii ipal ipcsv ipmo ipsn irm ise iwmi iwr kill lp ls man md measure mi mount move mp mv nal ndr ni nmo npssc nsn nv ogv oh popd ps pushd pwd r rbp rcjb rcsn rd rdr ren ri rjb rm rmdir rmo rni rnp rp rsn rsnp rujb rv rvpa rwmi sajb sal saps sasv sbp sc scb select set shcm si sl sleep sls sort sp spjb spps spsv start stz sujb sv swmi tee trcm type wget where wjb write"},n={begin:"`[\\s\\S]",relevance:0},r={className:"variable",variants:[{begin:/\$\B/},{className:"keyword",begin:/\$this/},{begin:/\$[\w\d][\w\d_:]*/}]},o={className:"string",variants:[{begin:/"/,end:/"/},{begin:/@"/,end:/^"@/}],contains:[n,r,{className:"variable",begin:/\$[A-z]/,end:/[^A-z]/}]},s={className:"string",variants:[{begin:/'/,end:/'/},{begin:/@'/,end:/^'@/}]},i=e.inherit(e.COMMENT(null,null),{variants:[{begin:/#/,end:/$/},{begin:/<#/,end:/#>/}],contains:[{className:"doctag",variants:[{begin:/\.(synopsis|description|example|inputs|outputs|notes|link|component|role|functionality)/},{begin:/\.(parameter|forwardhelptargetname|forwardhelpcategory|remotehelprunspace|externalhelp)\s+\S+/}]}]}),a={className:"built_in",variants:[{begin:"(".concat("Add|Clear|Close|Copy|Enter|Exit|Find|Format|Get|Hide|Join|Lock|Move|New|Open|Optimize|Pop|Push|Redo|Remove|Rename|Reset|Resize|Search|Select|Set|Show|Skip|Split|Step|Switch|Undo|Unlock|Watch|Backup|Checkpoint|Compare|Compress|Convert|ConvertFrom|ConvertTo|Dismount|Edit|Expand|Export|Group|Import|Initialize|Limit|Merge|Mount|Out|Publish|Restore|Save|Sync|Unpublish|Update|Approve|Assert|Build|Complete|Confirm|Deny|Deploy|Disable|Enable|Install|Invoke|Register|Request|Restart|Resume|Start|Stop|Submit|Suspend|Uninstall|Unregister|Wait|Debug|Measure|Ping|Repair|Resolve|Test|Trace|Connect|Disconnect|Read|Receive|Send|Write|Block|Grant|Protect|Revoke|Unblock|Unprotect|Use|ForEach|Sort|Tee|Where",")+(-)[\\w\\d]+")}]},l={className:"class",beginKeywords:"class enum",end:/\s*[{]/,excludeEnd:!0,relevance:0,contains:[e.TITLE_MODE]},c={className:"function",begin:/function\s+/,end:/\s*\{|$/,excludeEnd:!0,returnBegin:!0,relevance:0,contains:[{begin:"function",relevance:0,className:"keyword"},{className:"title",begin:/\w[\w\d]*((-)[\w\d]+)*/,relevance:0},{begin:/\(/,end:/\)/,className:"params",relevance:0,contains:[r]}]},u={begin:/using\s/,end:/$/,returnBegin:!0,contains:[o,s,{className:"keyword",begin:/(using|assembly|command|module|namespace|type)/}]},p={variants:[{className:"operator",begin:"(".concat("-and|-as|-band|-bnot|-bor|-bxor|-casesensitive|-ccontains|-ceq|-cge|-cgt|-cle|-clike|-clt|-cmatch|-cne|-cnotcontains|-cnotlike|-cnotmatch|-contains|-creplace|-csplit|-eq|-exact|-f|-file|-ge|-gt|-icontains|-ieq|-ige|-igt|-ile|-ilike|-ilt|-imatch|-in|-ine|-inotcontains|-inotlike|-inotmatch|-ireplace|-is|-isnot|-isplit|-join|-le|-like|-lt|-match|-ne|-not|-notcontains|-notin|-notlike|-notmatch|-or|-regex|-replace|-shl|-shr|-split|-wildcard|-xor",")\\b")},{className:"literal",begin:/(-)[\w\d]+/,relevance:0}]},h={className:"function",begin:/\[.*\]\s*[\w]+[ ]??\(/,end:/$/,returnBegin:!0,relevance:0,contains:[{className:"keyword",begin:"(".concat(t.keyword.toString().replace(/\s/g,"|"),")\\b"),endsParent:!0,relevance:0},e.inherit(e.TITLE_MODE,{endsParent:!0})]},f=[h,i,n,e.NUMBER_MODE,o,s,a,r,{className:"literal",begin:/\$(null|true|false)\b/},{className:"selector-tag",begin:/@\B/,relevance:0}],d={begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0,relevance:0,contains:[].concat("self",f,{begin:"("+["string","char","byte","int","long","bool","decimal","single","double","DateTime","xml","array","hashtable","void"].join("|")+")",className:"built_in",relevance:0},{className:"type",begin:/[\.\w\d]+/,relevance:0})};return h.contains.unshift(d),{name:"PowerShell",aliases:["ps","ps1"],case_insensitive:!0,keywords:t,contains:f.concat(l,c,u,p,d)}}},42157:e=>{function t(e){return e?"string"==typeof e?e:e.source:null}function n(e){return r("(?=",e,")")}function r(...e){return e.map((e=>t(e))).join("")}function o(...e){return"("+e.map((e=>t(e))).join("|")+")"}e.exports=function(e){const t=r(/[A-Z_]/,r("(",/[A-Z0-9_.-]*:/,")?"),/[A-Z0-9_.-]*/),s={className:"symbol",begin:/&[a-z]+;|&#[0-9]+;|&#x[a-f0-9]+;/},i={begin:/\s/,contains:[{className:"meta-keyword",begin:/#?[a-z_][a-z1-9_-]+/,illegal:/\n/}]},a=e.inherit(i,{begin:/\(/,end:/\)/}),l=e.inherit(e.APOS_STRING_MODE,{className:"meta-string"}),c=e.inherit(e.QUOTE_STRING_MODE,{className:"meta-string"}),u={endsWithParent:!0,illegal:/`]+/}]}]}]};return{name:"HTML, XML",aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist","wsf","svg"],case_insensitive:!0,contains:[{className:"meta",begin://,relevance:10,contains:[i,c,l,a,{begin:/\[/,end:/\]/,contains:[{className:"meta",begin://,contains:[i,a,c,l]}]}]},e.COMMENT(//,{relevance:10}),{begin://,relevance:10},s,{className:"meta",begin:/<\?xml/,end:/\?>/,relevance:10},{className:"tag",begin:/)/,end:/>/,keywords:{name:"style"},contains:[u],starts:{end:/<\/style>/,returnEnd:!0,subLanguage:["css","xml"]}},{className:"tag",begin:/)/,end:/>/,keywords:{name:"script"},contains:[u],starts:{end:/<\/script>/,returnEnd:!0,subLanguage:["javascript","handlebars","xml"]}},{className:"tag",begin:/<>|<\/>/},{className:"tag",begin:r(//,/>/,/\s/)))),end:/\/?>/,contains:[{className:"name",begin:t,relevance:0,starts:u}]},{className:"tag",begin:r(/<\//,n(r(t,/>/))),contains:[{className:"name",begin:t,relevance:0},{begin:/>/,relevance:0,endsParent:!0}]}]}}},54587:e=>{e.exports=function(e){var t="true false yes no null",n="[\\w#;/?:@&=+$,.~*'()[\\]]+",r={className:"string",relevance:0,variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/\S+/}],contains:[e.BACKSLASH_ESCAPE,{className:"template-variable",variants:[{begin:/\{\{/,end:/\}\}/},{begin:/%\{/,end:/\}/}]}]},o=e.inherit(r,{variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/[^\s,{}[\]]+/}]}),s={className:"number",begin:"\\b[0-9]{4}(-[0-9][0-9]){0,2}([Tt \\t][0-9][0-9]?(:[0-9][0-9]){2})?(\\.[0-9]*)?([ \\t])*(Z|[-+][0-9][0-9]?(:[0-9][0-9])?)?\\b"},i={end:",",endsWithParent:!0,excludeEnd:!0,keywords:t,relevance:0},a={begin:/\{/,end:/\}/,contains:[i],illegal:"\\n",relevance:0},l={begin:"\\[",end:"\\]",contains:[i],illegal:"\\n",relevance:0},c=[{className:"attr",variants:[{begin:"\\w[\\w :\\/.-]*:(?=[ \t]|$)"},{begin:'"\\w[\\w :\\/.-]*":(?=[ \t]|$)'},{begin:"'\\w[\\w :\\/.-]*':(?=[ \t]|$)"}]},{className:"meta",begin:"^---\\s*$",relevance:10},{className:"string",begin:"[\\|>]([1-9]?[+-])?[ ]*\\n( +)[^ ][^\\n]*\\n(\\2[^\\n]+\\n?)*"},{begin:"<%[%=-]?",end:"[%-]?%>",subLanguage:"ruby",excludeBegin:!0,excludeEnd:!0,relevance:0},{className:"type",begin:"!\\w+!"+n},{className:"type",begin:"!<"+n+">"},{className:"type",begin:"!"+n},{className:"type",begin:"!!"+n},{className:"meta",begin:"&"+e.UNDERSCORE_IDENT_RE+"$"},{className:"meta",begin:"\\*"+e.UNDERSCORE_IDENT_RE+"$"},{className:"bullet",begin:"-(?=[ ]|$)",relevance:0},e.HASH_COMMENT_MODE,{beginKeywords:t,keywords:{literal:t}},s,{className:"number",begin:e.C_NUMBER_RE+"\\b",relevance:0},a,l,r],u=[...c];return u.pop(),u.push(o),i.contains=u,{name:"YAML",case_insensitive:!0,aliases:["yml"],contains:c}}},8679:(e,t,n)=>{"use strict";var r=n(59864),o={childContextTypes:!0,contextType:!0,contextTypes:!0,defaultProps:!0,displayName:!0,getDefaultProps:!0,getDerivedStateFromError:!0,getDerivedStateFromProps:!0,mixins:!0,propTypes:!0,type:!0},s={name:!0,length:!0,prototype:!0,caller:!0,callee:!0,arguments:!0,arity:!0},i={$$typeof:!0,compare:!0,defaultProps:!0,displayName:!0,propTypes:!0,type:!0},a={};function l(e){return r.isMemo(e)?i:a[e.$$typeof]||o}a[r.ForwardRef]={$$typeof:!0,render:!0,defaultProps:!0,displayName:!0,propTypes:!0},a[r.Memo]=i;var c=Object.defineProperty,u=Object.getOwnPropertyNames,p=Object.getOwnPropertySymbols,h=Object.getOwnPropertyDescriptor,f=Object.getPrototypeOf,d=Object.prototype;e.exports=function e(t,n,r){if("string"!=typeof n){if(d){var o=f(n);o&&o!==d&&e(t,o,r)}var i=u(n);p&&(i=i.concat(p(n)));for(var a=l(t),m=l(n),g=0;g{t.read=function(e,t,n,r,o){var s,i,a=8*o-r-1,l=(1<>1,u=-7,p=n?o-1:0,h=n?-1:1,f=e[t+p];for(p+=h,s=f&(1<<-u)-1,f>>=-u,u+=a;u>0;s=256*s+e[t+p],p+=h,u-=8);for(i=s&(1<<-u)-1,s>>=-u,u+=r;u>0;i=256*i+e[t+p],p+=h,u-=8);if(0===s)s=1-c;else{if(s===l)return i?NaN:1/0*(f?-1:1);i+=Math.pow(2,r),s-=c}return(f?-1:1)*i*Math.pow(2,s-r)},t.write=function(e,t,n,r,o,s){var i,a,l,c=8*s-o-1,u=(1<>1,h=23===o?Math.pow(2,-24)-Math.pow(2,-77):0,f=r?0:s-1,d=r?1:-1,m=t<0||0===t&&1/t<0?1:0;for(t=Math.abs(t),isNaN(t)||t===1/0?(a=isNaN(t)?1:0,i=u):(i=Math.floor(Math.log(t)/Math.LN2),t*(l=Math.pow(2,-i))<1&&(i--,l*=2),(t+=i+p>=1?h/l:h*Math.pow(2,1-p))*l>=2&&(i++,l/=2),i+p>=u?(a=0,i=u):i+p>=1?(a=(t*l-1)*Math.pow(2,o),i+=p):(a=t*Math.pow(2,p-1)*Math.pow(2,o),i=0));o>=8;e[n+f]=255&a,f+=d,a/=256,o-=8);for(i=i<0;e[n+f]=255&i,f+=d,i/=256,c-=8);e[n+f-d]|=128*m}},43393:function(e){e.exports=function(){"use strict";var e=Array.prototype.slice;function t(e,t){t&&(e.prototype=Object.create(t.prototype)),e.prototype.constructor=e}function n(e){return i(e)?e:K(e)}function r(e){return a(e)?e:H(e)}function o(e){return l(e)?e:G(e)}function s(e){return i(e)&&!c(e)?e:Z(e)}function i(e){return!(!e||!e[p])}function a(e){return!(!e||!e[h])}function l(e){return!(!e||!e[f])}function c(e){return a(e)||l(e)}function u(e){return!(!e||!e[d])}t(r,n),t(o,n),t(s,n),n.isIterable=i,n.isKeyed=a,n.isIndexed=l,n.isAssociative=c,n.isOrdered=u,n.Keyed=r,n.Indexed=o,n.Set=s;var p="@@__IMMUTABLE_ITERABLE__@@",h="@@__IMMUTABLE_KEYED__@@",f="@@__IMMUTABLE_INDEXED__@@",d="@@__IMMUTABLE_ORDERED__@@",m="delete",g=5,y=1<>>0;if(""+n!==t||4294967295===n)return NaN;t=n}return t<0?O(e)+t:t}function A(){return!0}function C(e,t,n){return(0===e||void 0!==n&&e<=-n)&&(void 0===t||void 0!==n&&t>=n)}function P(e,t){return I(e,t,0)}function N(e,t){return I(e,t,t)}function I(e,t,n){return void 0===e?n:e<0?Math.max(0,t+e):void 0===t?e:Math.min(t,e)}var T=0,R=1,M=2,D="function"==typeof Symbol&&Symbol.iterator,F="@@iterator",L=D||F;function B(e){this.next=e}function $(e,t,n,r){var o=0===e?t:1===e?n:[t,n];return r?r.value=o:r={value:o,done:!1},r}function q(){return{value:void 0,done:!0}}function U(e){return!!W(e)}function z(e){return e&&"function"==typeof e.next}function V(e){var t=W(e);return t&&t.call(e)}function W(e){var t=e&&(D&&e[D]||e[F]);if("function"==typeof t)return t}function J(e){return e&&"number"==typeof e.length}function K(e){return null==e?ie():i(e)?e.toSeq():ce(e)}function H(e){return null==e?ie().toKeyedSeq():i(e)?a(e)?e.toSeq():e.fromEntrySeq():ae(e)}function G(e){return null==e?ie():i(e)?a(e)?e.entrySeq():e.toIndexedSeq():le(e)}function Z(e){return(null==e?ie():i(e)?a(e)?e.entrySeq():e:le(e)).toSetSeq()}B.prototype.toString=function(){return"[Iterator]"},B.KEYS=T,B.VALUES=R,B.ENTRIES=M,B.prototype.inspect=B.prototype.toSource=function(){return this.toString()},B.prototype[L]=function(){return this},t(K,n),K.of=function(){return K(arguments)},K.prototype.toSeq=function(){return this},K.prototype.toString=function(){return this.__toString("Seq {","}")},K.prototype.cacheResult=function(){return!this._cache&&this.__iterateUncached&&(this._cache=this.entrySeq().toArray(),this.size=this._cache.length),this},K.prototype.__iterate=function(e,t){return pe(this,e,t,!0)},K.prototype.__iterator=function(e,t){return he(this,e,t,!0)},t(H,K),H.prototype.toKeyedSeq=function(){return this},t(G,K),G.of=function(){return G(arguments)},G.prototype.toIndexedSeq=function(){return this},G.prototype.toString=function(){return this.__toString("Seq [","]")},G.prototype.__iterate=function(e,t){return pe(this,e,t,!1)},G.prototype.__iterator=function(e,t){return he(this,e,t,!1)},t(Z,K),Z.of=function(){return Z(arguments)},Z.prototype.toSetSeq=function(){return this},K.isSeq=se,K.Keyed=H,K.Set=Z,K.Indexed=G;var Y,X,Q,ee="@@__IMMUTABLE_SEQ__@@";function te(e){this._array=e,this.size=e.length}function ne(e){var t=Object.keys(e);this._object=e,this._keys=t,this.size=t.length}function re(e){this._iterable=e,this.size=e.length||e.size}function oe(e){this._iterator=e,this._iteratorCache=[]}function se(e){return!(!e||!e[ee])}function ie(){return Y||(Y=new te([]))}function ae(e){var t=Array.isArray(e)?new te(e).fromEntrySeq():z(e)?new oe(e).fromEntrySeq():U(e)?new re(e).fromEntrySeq():"object"==typeof e?new ne(e):void 0;if(!t)throw new TypeError("Expected Array or iterable object of [k, v] entries, or keyed object: "+e);return t}function le(e){var t=ue(e);if(!t)throw new TypeError("Expected Array or iterable object of values: "+e);return t}function ce(e){var t=ue(e)||"object"==typeof e&&new ne(e);if(!t)throw new TypeError("Expected Array or iterable object of values, or keyed object: "+e);return t}function ue(e){return J(e)?new te(e):z(e)?new oe(e):U(e)?new re(e):void 0}function pe(e,t,n,r){var o=e._cache;if(o){for(var s=o.length-1,i=0;i<=s;i++){var a=o[n?s-i:i];if(!1===t(a[1],r?a[0]:i,e))return i+1}return i}return e.__iterateUncached(t,n)}function he(e,t,n,r){var o=e._cache;if(o){var s=o.length-1,i=0;return new B((function(){var e=o[n?s-i:i];return i++>s?q():$(t,r?e[0]:i-1,e[1])}))}return e.__iteratorUncached(t,n)}function fe(e,t){return t?de(t,e,"",{"":e}):me(e)}function de(e,t,n,r){return Array.isArray(t)?e.call(r,n,G(t).map((function(n,r){return de(e,n,r,t)}))):ge(t)?e.call(r,n,H(t).map((function(n,r){return de(e,n,r,t)}))):t}function me(e){return Array.isArray(e)?G(e).map(me).toList():ge(e)?H(e).map(me).toMap():e}function ge(e){return e&&(e.constructor===Object||void 0===e.constructor)}function ye(e,t){if(e===t||e!=e&&t!=t)return!0;if(!e||!t)return!1;if("function"==typeof e.valueOf&&"function"==typeof t.valueOf){if((e=e.valueOf())===(t=t.valueOf())||e!=e&&t!=t)return!0;if(!e||!t)return!1}return!("function"!=typeof e.equals||"function"!=typeof t.equals||!e.equals(t))}function ve(e,t){if(e===t)return!0;if(!i(t)||void 0!==e.size&&void 0!==t.size&&e.size!==t.size||void 0!==e.__hash&&void 0!==t.__hash&&e.__hash!==t.__hash||a(e)!==a(t)||l(e)!==l(t)||u(e)!==u(t))return!1;if(0===e.size&&0===t.size)return!0;var n=!c(e);if(u(e)){var r=e.entries();return t.every((function(e,t){var o=r.next().value;return o&&ye(o[1],e)&&(n||ye(o[0],t))}))&&r.next().done}var o=!1;if(void 0===e.size)if(void 0===t.size)"function"==typeof e.cacheResult&&e.cacheResult();else{o=!0;var s=e;e=t,t=s}var p=!0,h=t.__iterate((function(t,r){if(n?!e.has(t):o?!ye(t,e.get(r,b)):!ye(e.get(r,b),t))return p=!1,!1}));return p&&e.size===h}function be(e,t){if(!(this instanceof be))return new be(e,t);if(this._value=e,this.size=void 0===t?1/0:Math.max(0,t),0===this.size){if(X)return X;X=this}}function we(e,t){if(!e)throw new Error(t)}function Ee(e,t,n){if(!(this instanceof Ee))return new Ee(e,t,n);if(we(0!==n,"Cannot step a Range by 0"),e=e||0,void 0===t&&(t=1/0),n=void 0===n?1:Math.abs(n),tr?q():$(e,o,n[t?r-o++:o++])}))},t(ne,H),ne.prototype.get=function(e,t){return void 0===t||this.has(e)?this._object[e]:t},ne.prototype.has=function(e){return this._object.hasOwnProperty(e)},ne.prototype.__iterate=function(e,t){for(var n=this._object,r=this._keys,o=r.length-1,s=0;s<=o;s++){var i=r[t?o-s:s];if(!1===e(n[i],i,this))return s+1}return s},ne.prototype.__iterator=function(e,t){var n=this._object,r=this._keys,o=r.length-1,s=0;return new B((function(){var i=r[t?o-s:s];return s++>o?q():$(e,i,n[i])}))},ne.prototype[d]=!0,t(re,G),re.prototype.__iterateUncached=function(e,t){if(t)return this.cacheResult().__iterate(e,t);var n=V(this._iterable),r=0;if(z(n))for(var o;!(o=n.next()).done&&!1!==e(o.value,r++,this););return r},re.prototype.__iteratorUncached=function(e,t){if(t)return this.cacheResult().__iterator(e,t);var n=V(this._iterable);if(!z(n))return new B(q);var r=0;return new B((function(){var t=n.next();return t.done?t:$(e,r++,t.value)}))},t(oe,G),oe.prototype.__iterateUncached=function(e,t){if(t)return this.cacheResult().__iterate(e,t);for(var n,r=this._iterator,o=this._iteratorCache,s=0;s=r.length){var t=n.next();if(t.done)return t;r[o]=t.value}return $(e,o,r[o++])}))},t(be,G),be.prototype.toString=function(){return 0===this.size?"Repeat []":"Repeat [ "+this._value+" "+this.size+" times ]"},be.prototype.get=function(e,t){return this.has(e)?this._value:t},be.prototype.includes=function(e){return ye(this._value,e)},be.prototype.slice=function(e,t){var n=this.size;return C(e,t,n)?this:new be(this._value,N(t,n)-P(e,n))},be.prototype.reverse=function(){return this},be.prototype.indexOf=function(e){return ye(this._value,e)?0:-1},be.prototype.lastIndexOf=function(e){return ye(this._value,e)?this.size:-1},be.prototype.__iterate=function(e,t){for(var n=0;n=0&&t=0&&nn?q():$(e,s++,i)}))},Ee.prototype.equals=function(e){return e instanceof Ee?this._start===e._start&&this._end===e._end&&this._step===e._step:ve(this,e)},t(xe,n),t(Se,xe),t(_e,xe),t(je,xe),xe.Keyed=Se,xe.Indexed=_e,xe.Set=je;var Oe="function"==typeof Math.imul&&-2===Math.imul(4294967295,2)?Math.imul:function(e,t){var n=65535&(e|=0),r=65535&(t|=0);return n*r+((e>>>16)*r+n*(t>>>16)<<16>>>0)|0};function ke(e){return e>>>1&1073741824|3221225471&e}function Ae(e){if(!1===e||null==e)return 0;if("function"==typeof e.valueOf&&(!1===(e=e.valueOf())||null==e))return 0;if(!0===e)return 1;var t=typeof e;if("number"===t){if(e!=e||e===1/0)return 0;var n=0|e;for(n!==e&&(n^=4294967295*e);e>4294967295;)n^=e/=4294967295;return ke(n)}if("string"===t)return e.length>Be?Ce(e):Pe(e);if("function"==typeof e.hashCode)return e.hashCode();if("object"===t)return Ne(e);if("function"==typeof e.toString)return Pe(e.toString());throw new Error("Value type "+t+" cannot be hashed.")}function Ce(e){var t=Ue[e];return void 0===t&&(t=Pe(e),qe===$e&&(qe=0,Ue={}),qe++,Ue[e]=t),t}function Pe(e){for(var t=0,n=0;n0)switch(e.nodeType){case 1:return e.uniqueID;case 9:return e.documentElement&&e.documentElement.uniqueID}}var Me,De="function"==typeof WeakMap;De&&(Me=new WeakMap);var Fe=0,Le="__immutablehash__";"function"==typeof Symbol&&(Le=Symbol(Le));var Be=16,$e=255,qe=0,Ue={};function ze(e){we(e!==1/0,"Cannot perform this action with an infinite size.")}function Ve(e){return null==e?ot():We(e)&&!u(e)?e:ot().withMutations((function(t){var n=r(e);ze(n.size),n.forEach((function(e,n){return t.set(n,e)}))}))}function We(e){return!(!e||!e[Ke])}t(Ve,Se),Ve.of=function(){var t=e.call(arguments,0);return ot().withMutations((function(e){for(var n=0;n=t.length)throw new Error("Missing value for key: "+t[n]);e.set(t[n],t[n+1])}}))},Ve.prototype.toString=function(){return this.__toString("Map {","}")},Ve.prototype.get=function(e,t){return this._root?this._root.get(0,void 0,e,t):t},Ve.prototype.set=function(e,t){return st(this,e,t)},Ve.prototype.setIn=function(e,t){return this.updateIn(e,b,(function(){return t}))},Ve.prototype.remove=function(e){return st(this,e,b)},Ve.prototype.deleteIn=function(e){return this.updateIn(e,(function(){return b}))},Ve.prototype.update=function(e,t,n){return 1===arguments.length?e(this):this.updateIn([e],t,n)},Ve.prototype.updateIn=function(e,t,n){n||(n=t,t=void 0);var r=gt(this,xn(e),t,n);return r===b?void 0:r},Ve.prototype.clear=function(){return 0===this.size?this:this.__ownerID?(this.size=0,this._root=null,this.__hash=void 0,this.__altered=!0,this):ot()},Ve.prototype.merge=function(){return ht(this,void 0,arguments)},Ve.prototype.mergeWith=function(t){return ht(this,t,e.call(arguments,1))},Ve.prototype.mergeIn=function(t){var n=e.call(arguments,1);return this.updateIn(t,ot(),(function(e){return"function"==typeof e.merge?e.merge.apply(e,n):n[n.length-1]}))},Ve.prototype.mergeDeep=function(){return ht(this,ft,arguments)},Ve.prototype.mergeDeepWith=function(t){var n=e.call(arguments,1);return ht(this,dt(t),n)},Ve.prototype.mergeDeepIn=function(t){var n=e.call(arguments,1);return this.updateIn(t,ot(),(function(e){return"function"==typeof e.mergeDeep?e.mergeDeep.apply(e,n):n[n.length-1]}))},Ve.prototype.sort=function(e){return Ut(pn(this,e))},Ve.prototype.sortBy=function(e,t){return Ut(pn(this,t,e))},Ve.prototype.withMutations=function(e){var t=this.asMutable();return e(t),t.wasAltered()?t.__ensureOwner(this.__ownerID):this},Ve.prototype.asMutable=function(){return this.__ownerID?this:this.__ensureOwner(new _)},Ve.prototype.asImmutable=function(){return this.__ensureOwner()},Ve.prototype.wasAltered=function(){return this.__altered},Ve.prototype.__iterator=function(e,t){return new et(this,e,t)},Ve.prototype.__iterate=function(e,t){var n=this,r=0;return this._root&&this._root.iterate((function(t){return r++,e(t[1],t[0],n)}),t),r},Ve.prototype.__ensureOwner=function(e){return e===this.__ownerID?this:e?rt(this.size,this._root,e,this.__hash):(this.__ownerID=e,this.__altered=!1,this)},Ve.isMap=We;var Je,Ke="@@__IMMUTABLE_MAP__@@",He=Ve.prototype;function Ge(e,t){this.ownerID=e,this.entries=t}function Ze(e,t,n){this.ownerID=e,this.bitmap=t,this.nodes=n}function Ye(e,t,n){this.ownerID=e,this.count=t,this.nodes=n}function Xe(e,t,n){this.ownerID=e,this.keyHash=t,this.entries=n}function Qe(e,t,n){this.ownerID=e,this.keyHash=t,this.entry=n}function et(e,t,n){this._type=t,this._reverse=n,this._stack=e._root&&nt(e._root)}function tt(e,t){return $(e,t[0],t[1])}function nt(e,t){return{node:e,index:0,__prev:t}}function rt(e,t,n,r){var o=Object.create(He);return o.size=e,o._root=t,o.__ownerID=n,o.__hash=r,o.__altered=!1,o}function ot(){return Je||(Je=rt(0))}function st(e,t,n){var r,o;if(e._root){var s=x(w),i=x(E);if(r=it(e._root,e.__ownerID,0,void 0,t,n,s,i),!i.value)return e;o=e.size+(s.value?n===b?-1:1:0)}else{if(n===b)return e;o=1,r=new Ge(e.__ownerID,[[t,n]])}return e.__ownerID?(e.size=o,e._root=r,e.__hash=void 0,e.__altered=!0,e):r?rt(o,r):ot()}function it(e,t,n,r,o,s,i,a){return e?e.update(t,n,r,o,s,i,a):s===b?e:(S(a),S(i),new Qe(t,r,[o,s]))}function at(e){return e.constructor===Qe||e.constructor===Xe}function lt(e,t,n,r,o){if(e.keyHash===r)return new Xe(t,r,[e.entry,o]);var s,i=(0===n?e.keyHash:e.keyHash>>>n)&v,a=(0===n?r:r>>>n)&v;return new Ze(t,1<>>=1)i[a]=1&n?t[s++]:void 0;return i[r]=o,new Ye(e,s+1,i)}function ht(e,t,n){for(var o=[],s=0;s>1&1431655765))+(e>>2&858993459))+(e>>4)&252645135,e+=e>>8,127&(e+=e>>16)}function vt(e,t,n,r){var o=r?e:j(e);return o[t]=n,o}function bt(e,t,n,r){var o=e.length+1;if(r&&t+1===o)return e[t]=n,e;for(var s=new Array(o),i=0,a=0;a=Et)return ct(e,l,r,o);var h=e&&e===this.ownerID,f=h?l:j(l);return p?a?c===u-1?f.pop():f[c]=f.pop():f[c]=[r,o]:f.push([r,o]),h?(this.entries=f,this):new Ge(e,f)}},Ze.prototype.get=function(e,t,n,r){void 0===t&&(t=Ae(n));var o=1<<((0===e?t:t>>>e)&v),s=this.bitmap;return 0==(s&o)?r:this.nodes[yt(s&o-1)].get(e+g,t,n,r)},Ze.prototype.update=function(e,t,n,r,o,s,i){void 0===n&&(n=Ae(r));var a=(0===t?n:n>>>t)&v,l=1<=xt)return pt(e,h,c,a,d);if(u&&!d&&2===h.length&&at(h[1^p]))return h[1^p];if(u&&d&&1===h.length&&at(d))return d;var m=e&&e===this.ownerID,y=u?d?c:c^l:c|l,w=u?d?vt(h,p,d,m):wt(h,p,m):bt(h,p,d,m);return m?(this.bitmap=y,this.nodes=w,this):new Ze(e,y,w)},Ye.prototype.get=function(e,t,n,r){void 0===t&&(t=Ae(n));var o=(0===e?t:t>>>e)&v,s=this.nodes[o];return s?s.get(e+g,t,n,r):r},Ye.prototype.update=function(e,t,n,r,o,s,i){void 0===n&&(n=Ae(r));var a=(0===t?n:n>>>t)&v,l=o===b,c=this.nodes,u=c[a];if(l&&!u)return this;var p=it(u,e,t+g,n,r,o,s,i);if(p===u)return this;var h=this.count;if(u){if(!p&&--h0&&r=0&&e>>t&v;if(r>=this.array.length)return new At([],e);var o,s=0===r;if(t>0){var i=this.array[r];if((o=i&&i.removeBefore(e,t-g,n))===i&&s)return this}if(s&&!o)return this;var a=Ft(this,e);if(!s)for(var l=0;l>>t&v;if(o>=this.array.length)return this;if(t>0){var s=this.array[o];if((r=s&&s.removeAfter(e,t-g,n))===s&&o===this.array.length-1)return this}var i=Ft(this,e);return i.array.splice(o+1),r&&(i.array[o]=r),i};var Ct,Pt,Nt={};function It(e,t){var n=e._origin,r=e._capacity,o=qt(r),s=e._tail;return i(e._root,e._level,0);function i(e,t,n){return 0===t?a(e,n):l(e,t,n)}function a(e,i){var a=i===o?s&&s.array:e&&e.array,l=i>n?0:n-i,c=r-i;return c>y&&(c=y),function(){if(l===c)return Nt;var e=t?--c:l++;return a&&a[e]}}function l(e,o,s){var a,l=e&&e.array,c=s>n?0:n-s>>o,u=1+(r-s>>o);return u>y&&(u=y),function(){for(;;){if(a){var e=a();if(e!==Nt)return e;a=null}if(c===u)return Nt;var n=t?--u:c++;a=i(l&&l[n],o-g,s+(n<=e.size||t<0)return e.withMutations((function(e){t<0?Bt(e,t).set(0,n):Bt(e,0,t+1).set(t,n)}));t+=e._origin;var r=e._tail,o=e._root,s=x(E);return t>=qt(e._capacity)?r=Dt(r,e.__ownerID,0,t,n,s):o=Dt(o,e.__ownerID,e._level,t,n,s),s.value?e.__ownerID?(e._root=o,e._tail=r,e.__hash=void 0,e.__altered=!0,e):Tt(e._origin,e._capacity,e._level,o,r):e}function Dt(e,t,n,r,o,s){var i,a=r>>>n&v,l=e&&a0){var c=e&&e.array[a],u=Dt(c,t,n-g,r,o,s);return u===c?e:((i=Ft(e,t)).array[a]=u,i)}return l&&e.array[a]===o?e:(S(s),i=Ft(e,t),void 0===o&&a===i.array.length-1?i.array.pop():i.array[a]=o,i)}function Ft(e,t){return t&&e&&t===e.ownerID?e:new At(e?e.array.slice():[],t)}function Lt(e,t){if(t>=qt(e._capacity))return e._tail;if(t<1<0;)n=n.array[t>>>r&v],r-=g;return n}}function Bt(e,t,n){void 0!==t&&(t|=0),void 0!==n&&(n|=0);var r=e.__ownerID||new _,o=e._origin,s=e._capacity,i=o+t,a=void 0===n?s:n<0?s+n:o+n;if(i===o&&a===s)return e;if(i>=a)return e.clear();for(var l=e._level,c=e._root,u=0;i+u<0;)c=new At(c&&c.array.length?[void 0,c]:[],r),u+=1<<(l+=g);u&&(i+=u,o+=u,a+=u,s+=u);for(var p=qt(s),h=qt(a);h>=1<p?new At([],r):f;if(f&&h>p&&ig;y-=g){var b=p>>>y&v;m=m.array[b]=Ft(m.array[b],r)}m.array[p>>>g&v]=f}if(a=h)i-=h,a-=h,l=g,c=null,d=d&&d.removeBefore(r,0,i);else if(i>o||h>>l&v;if(w!==h>>>l&v)break;w&&(u+=(1<o&&(c=c.removeBefore(r,l,i-u)),c&&hs&&(s=c.size),i(l)||(c=c.map((function(e){return fe(e)}))),r.push(c)}return s>e.size&&(e=e.setSize(s)),mt(e,t,r)}function qt(e){return e>>g<=y&&i.size>=2*s.size?(r=(o=i.filter((function(e,t){return void 0!==e&&a!==t}))).toKeyedSeq().map((function(e){return e[0]})).flip().toMap(),e.__ownerID&&(r.__ownerID=o.__ownerID=e.__ownerID)):(r=s.remove(t),o=a===i.size-1?i.pop():i.set(a,void 0))}else if(l){if(n===i.get(a)[1])return e;r=s,o=i.set(a,[t,n])}else r=s.set(t,i.size),o=i.set(i.size,[t,n]);return e.__ownerID?(e.size=r.size,e._map=r,e._list=o,e.__hash=void 0,e):Vt(r,o)}function Kt(e,t){this._iter=e,this._useKeys=t,this.size=e.size}function Ht(e){this._iter=e,this.size=e.size}function Gt(e){this._iter=e,this.size=e.size}function Zt(e){this._iter=e,this.size=e.size}function Yt(e){var t=bn(e);return t._iter=e,t.size=e.size,t.flip=function(){return e},t.reverse=function(){var t=e.reverse.apply(this);return t.flip=function(){return e.reverse()},t},t.has=function(t){return e.includes(t)},t.includes=function(t){return e.has(t)},t.cacheResult=wn,t.__iterateUncached=function(t,n){var r=this;return e.__iterate((function(e,n){return!1!==t(n,e,r)}),n)},t.__iteratorUncached=function(t,n){if(t===M){var r=e.__iterator(t,n);return new B((function(){var e=r.next();if(!e.done){var t=e.value[0];e.value[0]=e.value[1],e.value[1]=t}return e}))}return e.__iterator(t===R?T:R,n)},t}function Xt(e,t,n){var r=bn(e);return r.size=e.size,r.has=function(t){return e.has(t)},r.get=function(r,o){var s=e.get(r,b);return s===b?o:t.call(n,s,r,e)},r.__iterateUncached=function(r,o){var s=this;return e.__iterate((function(e,o,i){return!1!==r(t.call(n,e,o,i),o,s)}),o)},r.__iteratorUncached=function(r,o){var s=e.__iterator(M,o);return new B((function(){var o=s.next();if(o.done)return o;var i=o.value,a=i[0];return $(r,a,t.call(n,i[1],a,e),o)}))},r}function Qt(e,t){var n=bn(e);return n._iter=e,n.size=e.size,n.reverse=function(){return e},e.flip&&(n.flip=function(){var t=Yt(e);return t.reverse=function(){return e.flip()},t}),n.get=function(n,r){return e.get(t?n:-1-n,r)},n.has=function(n){return e.has(t?n:-1-n)},n.includes=function(t){return e.includes(t)},n.cacheResult=wn,n.__iterate=function(t,n){var r=this;return e.__iterate((function(e,n){return t(e,n,r)}),!n)},n.__iterator=function(t,n){return e.__iterator(t,!n)},n}function en(e,t,n,r){var o=bn(e);return r&&(o.has=function(r){var o=e.get(r,b);return o!==b&&!!t.call(n,o,r,e)},o.get=function(r,o){var s=e.get(r,b);return s!==b&&t.call(n,s,r,e)?s:o}),o.__iterateUncached=function(o,s){var i=this,a=0;return e.__iterate((function(e,s,l){if(t.call(n,e,s,l))return a++,o(e,r?s:a-1,i)}),s),a},o.__iteratorUncached=function(o,s){var i=e.__iterator(M,s),a=0;return new B((function(){for(;;){var s=i.next();if(s.done)return s;var l=s.value,c=l[0],u=l[1];if(t.call(n,u,c,e))return $(o,r?c:a++,u,s)}}))},o}function tn(e,t,n){var r=Ve().asMutable();return e.__iterate((function(o,s){r.update(t.call(n,o,s,e),0,(function(e){return e+1}))})),r.asImmutable()}function nn(e,t,n){var r=a(e),o=(u(e)?Ut():Ve()).asMutable();e.__iterate((function(s,i){o.update(t.call(n,s,i,e),(function(e){return(e=e||[]).push(r?[i,s]:s),e}))}));var s=vn(e);return o.map((function(t){return mn(e,s(t))}))}function rn(e,t,n,r){var o=e.size;if(void 0!==t&&(t|=0),void 0!==n&&(n===1/0?n=o:n|=0),C(t,n,o))return e;var s=P(t,o),i=N(n,o);if(s!=s||i!=i)return rn(e.toSeq().cacheResult(),t,n,r);var a,l=i-s;l==l&&(a=l<0?0:l);var c=bn(e);return c.size=0===a?a:e.size&&a||void 0,!r&&se(e)&&a>=0&&(c.get=function(t,n){return(t=k(this,t))>=0&&ta)return q();var e=o.next();return r||t===R?e:$(t,l-1,t===T?void 0:e.value[1],e)}))},c}function on(e,t,n){var r=bn(e);return r.__iterateUncached=function(r,o){var s=this;if(o)return this.cacheResult().__iterate(r,o);var i=0;return e.__iterate((function(e,o,a){return t.call(n,e,o,a)&&++i&&r(e,o,s)})),i},r.__iteratorUncached=function(r,o){var s=this;if(o)return this.cacheResult().__iterator(r,o);var i=e.__iterator(M,o),a=!0;return new B((function(){if(!a)return q();var e=i.next();if(e.done)return e;var o=e.value,l=o[0],c=o[1];return t.call(n,c,l,s)?r===M?e:$(r,l,c,e):(a=!1,q())}))},r}function sn(e,t,n,r){var o=bn(e);return o.__iterateUncached=function(o,s){var i=this;if(s)return this.cacheResult().__iterate(o,s);var a=!0,l=0;return e.__iterate((function(e,s,c){if(!a||!(a=t.call(n,e,s,c)))return l++,o(e,r?s:l-1,i)})),l},o.__iteratorUncached=function(o,s){var i=this;if(s)return this.cacheResult().__iterator(o,s);var a=e.__iterator(M,s),l=!0,c=0;return new B((function(){var e,s,u;do{if((e=a.next()).done)return r||o===R?e:$(o,c++,o===T?void 0:e.value[1],e);var p=e.value;s=p[0],u=p[1],l&&(l=t.call(n,u,s,i))}while(l);return o===M?e:$(o,s,u,e)}))},o}function an(e,t){var n=a(e),o=[e].concat(t).map((function(e){return i(e)?n&&(e=r(e)):e=n?ae(e):le(Array.isArray(e)?e:[e]),e})).filter((function(e){return 0!==e.size}));if(0===o.length)return e;if(1===o.length){var s=o[0];if(s===e||n&&a(s)||l(e)&&l(s))return s}var c=new te(o);return n?c=c.toKeyedSeq():l(e)||(c=c.toSetSeq()),(c=c.flatten(!0)).size=o.reduce((function(e,t){if(void 0!==e){var n=t.size;if(void 0!==n)return e+n}}),0),c}function ln(e,t,n){var r=bn(e);return r.__iterateUncached=function(r,o){var s=0,a=!1;function l(e,c){var u=this;e.__iterate((function(e,o){return(!t||c0}function dn(e,t,r){var o=bn(e);return o.size=new te(r).map((function(e){return e.size})).min(),o.__iterate=function(e,t){for(var n,r=this.__iterator(R,t),o=0;!(n=r.next()).done&&!1!==e(n.value,o++,this););return o},o.__iteratorUncached=function(e,o){var s=r.map((function(e){return e=n(e),V(o?e.reverse():e)})),i=0,a=!1;return new B((function(){var n;return a||(n=s.map((function(e){return e.next()})),a=n.some((function(e){return e.done}))),a?q():$(e,i++,t.apply(null,n.map((function(e){return e.value}))))}))},o}function mn(e,t){return se(e)?t:e.constructor(t)}function gn(e){if(e!==Object(e))throw new TypeError("Expected [K, V] tuple: "+e)}function yn(e){return ze(e.size),O(e)}function vn(e){return a(e)?r:l(e)?o:s}function bn(e){return Object.create((a(e)?H:l(e)?G:Z).prototype)}function wn(){return this._iter.cacheResult?(this._iter.cacheResult(),this.size=this._iter.size,this):K.prototype.cacheResult.call(this)}function En(e,t){return e>t?1:e=0;n--)t={value:arguments[n],next:t};return this.__ownerID?(this.size=e,this._head=t,this.__hash=void 0,this.__altered=!0,this):Hn(e,t)},zn.prototype.pushAll=function(e){if(0===(e=o(e)).size)return this;ze(e.size);var t=this.size,n=this._head;return e.reverse().forEach((function(e){t++,n={value:e,next:n}})),this.__ownerID?(this.size=t,this._head=n,this.__hash=void 0,this.__altered=!0,this):Hn(t,n)},zn.prototype.pop=function(){return this.slice(1)},zn.prototype.unshift=function(){return this.push.apply(this,arguments)},zn.prototype.unshiftAll=function(e){return this.pushAll(e)},zn.prototype.shift=function(){return this.pop.apply(this,arguments)},zn.prototype.clear=function(){return 0===this.size?this:this.__ownerID?(this.size=0,this._head=void 0,this.__hash=void 0,this.__altered=!0,this):Gn()},zn.prototype.slice=function(e,t){if(C(e,t,this.size))return this;var n=P(e,this.size);if(N(t,this.size)!==this.size)return _e.prototype.slice.call(this,e,t);for(var r=this.size-n,o=this._head;n--;)o=o.next;return this.__ownerID?(this.size=r,this._head=o,this.__hash=void 0,this.__altered=!0,this):Hn(r,o)},zn.prototype.__ensureOwner=function(e){return e===this.__ownerID?this:e?Hn(this.size,this._head,e,this.__hash):(this.__ownerID=e,this.__altered=!1,this)},zn.prototype.__iterate=function(e,t){if(t)return this.reverse().__iterate(e);for(var n=0,r=this._head;r&&!1!==e(r.value,n++,this);)r=r.next;return n},zn.prototype.__iterator=function(e,t){if(t)return this.reverse().__iterator(e);var n=0,r=this._head;return new B((function(){if(r){var t=r.value;return r=r.next,$(e,n++,t)}return q()}))},zn.isStack=Vn;var Wn,Jn="@@__IMMUTABLE_STACK__@@",Kn=zn.prototype;function Hn(e,t,n,r){var o=Object.create(Kn);return o.size=e,o._head=t,o.__ownerID=n,o.__hash=r,o.__altered=!1,o}function Gn(){return Wn||(Wn=Hn(0))}function Zn(e,t){var n=function(n){e.prototype[n]=t[n]};return Object.keys(t).forEach(n),Object.getOwnPropertySymbols&&Object.getOwnPropertySymbols(t).forEach(n),e}Kn[Jn]=!0,Kn.withMutations=He.withMutations,Kn.asMutable=He.asMutable,Kn.asImmutable=He.asImmutable,Kn.wasAltered=He.wasAltered,n.Iterator=B,Zn(n,{toArray:function(){ze(this.size);var e=new Array(this.size||0);return this.valueSeq().__iterate((function(t,n){e[n]=t})),e},toIndexedSeq:function(){return new Ht(this)},toJS:function(){return this.toSeq().map((function(e){return e&&"function"==typeof e.toJS?e.toJS():e})).__toJS()},toJSON:function(){return this.toSeq().map((function(e){return e&&"function"==typeof e.toJSON?e.toJSON():e})).__toJS()},toKeyedSeq:function(){return new Kt(this,!0)},toMap:function(){return Ve(this.toKeyedSeq())},toObject:function(){ze(this.size);var e={};return this.__iterate((function(t,n){e[n]=t})),e},toOrderedMap:function(){return Ut(this.toKeyedSeq())},toOrderedSet:function(){return Fn(a(this)?this.valueSeq():this)},toSet:function(){return Cn(a(this)?this.valueSeq():this)},toSetSeq:function(){return new Gt(this)},toSeq:function(){return l(this)?this.toIndexedSeq():a(this)?this.toKeyedSeq():this.toSetSeq()},toStack:function(){return zn(a(this)?this.valueSeq():this)},toList:function(){return _t(a(this)?this.valueSeq():this)},toString:function(){return"[Iterable]"},__toString:function(e,t){return 0===this.size?e+t:e+" "+this.toSeq().map(this.__toStringMapper).join(", ")+" "+t},concat:function(){return mn(this,an(this,e.call(arguments,0)))},includes:function(e){return this.some((function(t){return ye(t,e)}))},entries:function(){return this.__iterator(M)},every:function(e,t){ze(this.size);var n=!0;return this.__iterate((function(r,o,s){if(!e.call(t,r,o,s))return n=!1,!1})),n},filter:function(e,t){return mn(this,en(this,e,t,!0))},find:function(e,t,n){var r=this.findEntry(e,t);return r?r[1]:n},forEach:function(e,t){return ze(this.size),this.__iterate(t?e.bind(t):e)},join:function(e){ze(this.size),e=void 0!==e?""+e:",";var t="",n=!0;return this.__iterate((function(r){n?n=!1:t+=e,t+=null!=r?r.toString():""})),t},keys:function(){return this.__iterator(T)},map:function(e,t){return mn(this,Xt(this,e,t))},reduce:function(e,t,n){var r,o;return ze(this.size),arguments.length<2?o=!0:r=t,this.__iterate((function(t,s,i){o?(o=!1,r=t):r=e.call(n,r,t,s,i)})),r},reduceRight:function(e,t,n){var r=this.toKeyedSeq().reverse();return r.reduce.apply(r,arguments)},reverse:function(){return mn(this,Qt(this,!0))},slice:function(e,t){return mn(this,rn(this,e,t,!0))},some:function(e,t){return!this.every(tr(e),t)},sort:function(e){return mn(this,pn(this,e))},values:function(){return this.__iterator(R)},butLast:function(){return this.slice(0,-1)},isEmpty:function(){return void 0!==this.size?0===this.size:!this.some((function(){return!0}))},count:function(e,t){return O(e?this.toSeq().filter(e,t):this)},countBy:function(e,t){return tn(this,e,t)},equals:function(e){return ve(this,e)},entrySeq:function(){var e=this;if(e._cache)return new te(e._cache);var t=e.toSeq().map(er).toIndexedSeq();return t.fromEntrySeq=function(){return e.toSeq()},t},filterNot:function(e,t){return this.filter(tr(e),t)},findEntry:function(e,t,n){var r=n;return this.__iterate((function(n,o,s){if(e.call(t,n,o,s))return r=[o,n],!1})),r},findKey:function(e,t){var n=this.findEntry(e,t);return n&&n[0]},findLast:function(e,t,n){return this.toKeyedSeq().reverse().find(e,t,n)},findLastEntry:function(e,t,n){return this.toKeyedSeq().reverse().findEntry(e,t,n)},findLastKey:function(e,t){return this.toKeyedSeq().reverse().findKey(e,t)},first:function(){return this.find(A)},flatMap:function(e,t){return mn(this,cn(this,e,t))},flatten:function(e){return mn(this,ln(this,e,!0))},fromEntrySeq:function(){return new Zt(this)},get:function(e,t){return this.find((function(t,n){return ye(n,e)}),void 0,t)},getIn:function(e,t){for(var n,r=this,o=xn(e);!(n=o.next()).done;){var s=n.value;if((r=r&&r.get?r.get(s,b):b)===b)return t}return r},groupBy:function(e,t){return nn(this,e,t)},has:function(e){return this.get(e,b)!==b},hasIn:function(e){return this.getIn(e,b)!==b},isSubset:function(e){return e="function"==typeof e.includes?e:n(e),this.every((function(t){return e.includes(t)}))},isSuperset:function(e){return(e="function"==typeof e.isSubset?e:n(e)).isSubset(this)},keyOf:function(e){return this.findKey((function(t){return ye(t,e)}))},keySeq:function(){return this.toSeq().map(Qn).toIndexedSeq()},last:function(){return this.toSeq().reverse().first()},lastKeyOf:function(e){return this.toKeyedSeq().reverse().keyOf(e)},max:function(e){return hn(this,e)},maxBy:function(e,t){return hn(this,t,e)},min:function(e){return hn(this,e?nr(e):sr)},minBy:function(e,t){return hn(this,t?nr(t):sr,e)},rest:function(){return this.slice(1)},skip:function(e){return this.slice(Math.max(0,e))},skipLast:function(e){return mn(this,this.toSeq().reverse().skip(e).reverse())},skipWhile:function(e,t){return mn(this,sn(this,e,t,!0))},skipUntil:function(e,t){return this.skipWhile(tr(e),t)},sortBy:function(e,t){return mn(this,pn(this,t,e))},take:function(e){return this.slice(0,Math.max(0,e))},takeLast:function(e){return mn(this,this.toSeq().reverse().take(e).reverse())},takeWhile:function(e,t){return mn(this,on(this,e,t))},takeUntil:function(e,t){return this.takeWhile(tr(e),t)},valueSeq:function(){return this.toIndexedSeq()},hashCode:function(){return this.__hash||(this.__hash=ir(this))}});var Yn=n.prototype;Yn[p]=!0,Yn[L]=Yn.values,Yn.__toJS=Yn.toArray,Yn.__toStringMapper=rr,Yn.inspect=Yn.toSource=function(){return this.toString()},Yn.chain=Yn.flatMap,Yn.contains=Yn.includes,Zn(r,{flip:function(){return mn(this,Yt(this))},mapEntries:function(e,t){var n=this,r=0;return mn(this,this.toSeq().map((function(o,s){return e.call(t,[s,o],r++,n)})).fromEntrySeq())},mapKeys:function(e,t){var n=this;return mn(this,this.toSeq().flip().map((function(r,o){return e.call(t,r,o,n)})).flip())}});var Xn=r.prototype;function Qn(e,t){return t}function er(e,t){return[t,e]}function tr(e){return function(){return!e.apply(this,arguments)}}function nr(e){return function(){return-e.apply(this,arguments)}}function rr(e){return"string"==typeof e?JSON.stringify(e):String(e)}function or(){return j(arguments)}function sr(e,t){return et?-1:0}function ir(e){if(e.size===1/0)return 0;var t=u(e),n=a(e),r=t?1:0;return ar(e.__iterate(n?t?function(e,t){r=31*r+lr(Ae(e),Ae(t))|0}:function(e,t){r=r+lr(Ae(e),Ae(t))|0}:t?function(e){r=31*r+Ae(e)|0}:function(e){r=r+Ae(e)|0}),r)}function ar(e,t){return t=Oe(t,3432918353),t=Oe(t<<15|t>>>-15,461845907),t=Oe(t<<13|t>>>-13,5),t=Oe((t=(t+3864292196|0)^e)^t>>>16,2246822507),t=ke((t=Oe(t^t>>>13,3266489909))^t>>>16)}function lr(e,t){return e^t+2654435769+(e<<6)+(e>>2)|0}return Xn[h]=!0,Xn[L]=Yn.entries,Xn.__toJS=Yn.toObject,Xn.__toStringMapper=function(e,t){return JSON.stringify(t)+": "+rr(e)},Zn(o,{toKeyedSeq:function(){return new Kt(this,!1)},filter:function(e,t){return mn(this,en(this,e,t,!1))},findIndex:function(e,t){var n=this.findEntry(e,t);return n?n[0]:-1},indexOf:function(e){var t=this.keyOf(e);return void 0===t?-1:t},lastIndexOf:function(e){var t=this.lastKeyOf(e);return void 0===t?-1:t},reverse:function(){return mn(this,Qt(this,!1))},slice:function(e,t){return mn(this,rn(this,e,t,!1))},splice:function(e,t){var n=arguments.length;if(t=Math.max(0|t,0),0===n||2===n&&!t)return this;e=P(e,e<0?this.count():this.size);var r=this.slice(0,e);return mn(this,1===n?r:r.concat(j(arguments,2),this.slice(e+t)))},findLastIndex:function(e,t){var n=this.findLastEntry(e,t);return n?n[0]:-1},first:function(){return this.get(0)},flatten:function(e){return mn(this,ln(this,e,!1))},get:function(e,t){return(e=k(this,e))<0||this.size===1/0||void 0!==this.size&&e>this.size?t:this.find((function(t,n){return n===e}),void 0,t)},has:function(e){return(e=k(this,e))>=0&&(void 0!==this.size?this.size===1/0||e{"function"==typeof Object.create?e.exports=function(e,t){t&&(e.super_=t,e.prototype=Object.create(t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}))}:e.exports=function(e,t){if(t){e.super_=t;var n=function(){};n.prototype=t.prototype,e.prototype=new n,e.prototype.constructor=e}}},35823:e=>{e.exports=function(e,t,n,r){var o=new Blob(void 0!==r?[r,e]:[e],{type:n||"application/octet-stream"});if(void 0!==window.navigator.msSaveBlob)window.navigator.msSaveBlob(o,t);else{var s=window.URL&&window.URL.createObjectURL?window.URL.createObjectURL(o):window.webkitURL.createObjectURL(o),i=document.createElement("a");i.style.display="none",i.href=s,i.setAttribute("download",t),void 0===i.download&&i.setAttribute("target","_blank"),document.body.appendChild(i),i.click(),setTimeout((function(){document.body.removeChild(i),window.URL.revokeObjectURL(s)}),200)}}},91296:(e,t,n)=>{var r=NaN,o="[object Symbol]",s=/^\s+|\s+$/g,i=/^[-+]0x[0-9a-f]+$/i,a=/^0b[01]+$/i,l=/^0o[0-7]+$/i,c=parseInt,u="object"==typeof n.g&&n.g&&n.g.Object===Object&&n.g,p="object"==typeof self&&self&&self.Object===Object&&self,h=u||p||Function("return this")(),f=Object.prototype.toString,d=Math.max,m=Math.min,g=function(){return h.Date.now()};function y(e){var t=typeof e;return!!e&&("object"==t||"function"==t)}function v(e){if("number"==typeof e)return e;if(function(e){return"symbol"==typeof e||function(e){return!!e&&"object"==typeof e}(e)&&f.call(e)==o}(e))return r;if(y(e)){var t="function"==typeof e.valueOf?e.valueOf():e;e=y(t)?t+"":t}if("string"!=typeof e)return 0===e?e:+e;e=e.replace(s,"");var n=a.test(e);return n||l.test(e)?c(e.slice(2),n?2:8):i.test(e)?r:+e}e.exports=function(e,t,n){var r,o,s,i,a,l,c=0,u=!1,p=!1,h=!0;if("function"!=typeof e)throw new TypeError("Expected a function");function f(t){var n=r,s=o;return r=o=void 0,c=t,i=e.apply(s,n)}function b(e){var n=e-l;return void 0===l||n>=t||n<0||p&&e-c>=s}function w(){var e=g();if(b(e))return E(e);a=setTimeout(w,function(e){var n=t-(e-l);return p?m(n,s-(e-c)):n}(e))}function E(e){return a=void 0,h&&r?f(e):(r=o=void 0,i)}function x(){var e=g(),n=b(e);if(r=arguments,o=this,l=e,n){if(void 0===a)return function(e){return c=e,a=setTimeout(w,t),u?f(e):i}(l);if(p)return a=setTimeout(w,t),f(l)}return void 0===a&&(a=setTimeout(w,t)),i}return t=v(t)||0,y(n)&&(u=!!n.leading,s=(p="maxWait"in n)?d(v(n.maxWait)||0,t):s,h="trailing"in n?!!n.trailing:h),x.cancel=function(){void 0!==a&&clearTimeout(a),c=0,r=l=o=a=void 0},x.flush=function(){return void 0===a?i:E(g())},x}},18552:(e,t,n)=>{var r=n(10852)(n(55639),"DataView");e.exports=r},1989:(e,t,n)=>{var r=n(51789),o=n(80401),s=n(57667),i=n(21327),a=n(81866);function l(e){var t=-1,n=null==e?0:e.length;for(this.clear();++t{var r=n(3118),o=n(9435);function s(e){this.__wrapped__=e,this.__actions__=[],this.__dir__=1,this.__filtered__=!1,this.__iteratees__=[],this.__takeCount__=4294967295,this.__views__=[]}s.prototype=r(o.prototype),s.prototype.constructor=s,e.exports=s},38407:(e,t,n)=>{var r=n(27040),o=n(14125),s=n(82117),i=n(67518),a=n(54705);function l(e){var t=-1,n=null==e?0:e.length;for(this.clear();++t{var r=n(3118),o=n(9435);function s(e,t){this.__wrapped__=e,this.__actions__=[],this.__chain__=!!t,this.__index__=0,this.__values__=void 0}s.prototype=r(o.prototype),s.prototype.constructor=s,e.exports=s},57071:(e,t,n)=>{var r=n(10852)(n(55639),"Map");e.exports=r},83369:(e,t,n)=>{var r=n(24785),o=n(11285),s=n(96e3),i=n(49916),a=n(95265);function l(e){var t=-1,n=null==e?0:e.length;for(this.clear();++t{var r=n(10852)(n(55639),"Promise");e.exports=r},58525:(e,t,n)=>{var r=n(10852)(n(55639),"Set");e.exports=r},88668:(e,t,n)=>{var r=n(83369),o=n(90619),s=n(72385);function i(e){var t=-1,n=null==e?0:e.length;for(this.__data__=new r;++t{var r=n(38407),o=n(37465),s=n(63779),i=n(67599),a=n(44758),l=n(34309);function c(e){var t=this.__data__=new r(e);this.size=t.size}c.prototype.clear=o,c.prototype.delete=s,c.prototype.get=i,c.prototype.has=a,c.prototype.set=l,e.exports=c},62705:(e,t,n)=>{var r=n(55639).Symbol;e.exports=r},11149:(e,t,n)=>{var r=n(55639).Uint8Array;e.exports=r},70577:(e,t,n)=>{var r=n(10852)(n(55639),"WeakMap");e.exports=r},96874:e=>{e.exports=function(e,t,n){switch(n.length){case 0:return e.call(t);case 1:return e.call(t,n[0]);case 2:return e.call(t,n[0],n[1]);case 3:return e.call(t,n[0],n[1],n[2])}return e.apply(t,n)}},77412:e=>{e.exports=function(e,t){for(var n=-1,r=null==e?0:e.length;++n{e.exports=function(e,t){for(var n=-1,r=null==e?0:e.length,o=0,s=[];++n{var r=n(42118);e.exports=function(e,t){return!!(null==e?0:e.length)&&r(e,t,0)>-1}},14636:(e,t,n)=>{var r=n(22545),o=n(35694),s=n(1469),i=n(44144),a=n(65776),l=n(36719),c=Object.prototype.hasOwnProperty;e.exports=function(e,t){var n=s(e),u=!n&&o(e),p=!n&&!u&&i(e),h=!n&&!u&&!p&&l(e),f=n||u||p||h,d=f?r(e.length,String):[],m=d.length;for(var g in e)!t&&!c.call(e,g)||f&&("length"==g||p&&("offset"==g||"parent"==g)||h&&("buffer"==g||"byteLength"==g||"byteOffset"==g)||a(g,m))||d.push(g);return d}},29932:e=>{e.exports=function(e,t){for(var n=-1,r=null==e?0:e.length,o=Array(r);++n{e.exports=function(e,t){for(var n=-1,r=t.length,o=e.length;++n{e.exports=function(e,t,n,r){var o=-1,s=null==e?0:e.length;for(r&&s&&(n=e[++o]);++o{e.exports=function(e,t){for(var n=-1,r=null==e?0:e.length;++n{e.exports=function(e){return e.split("")}},49029:e=>{var t=/[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g;e.exports=function(e){return e.match(t)||[]}},86556:(e,t,n)=>{var r=n(89465),o=n(77813);e.exports=function(e,t,n){(void 0!==n&&!o(e[t],n)||void 0===n&&!(t in e))&&r(e,t,n)}},34865:(e,t,n)=>{var r=n(89465),o=n(77813),s=Object.prototype.hasOwnProperty;e.exports=function(e,t,n){var i=e[t];s.call(e,t)&&o(i,n)&&(void 0!==n||t in e)||r(e,t,n)}},18470:(e,t,n)=>{var r=n(77813);e.exports=function(e,t){for(var n=e.length;n--;)if(r(e[n][0],t))return n;return-1}},44037:(e,t,n)=>{var r=n(98363),o=n(3674);e.exports=function(e,t){return e&&r(t,o(t),e)}},63886:(e,t,n)=>{var r=n(98363),o=n(81704);e.exports=function(e,t){return e&&r(t,o(t),e)}},89465:(e,t,n)=>{var r=n(38777);e.exports=function(e,t,n){"__proto__"==t&&r?r(e,t,{configurable:!0,enumerable:!0,value:n,writable:!0}):e[t]=n}},85990:(e,t,n)=>{var r=n(46384),o=n(77412),s=n(34865),i=n(44037),a=n(63886),l=n(64626),c=n(278),u=n(18805),p=n(1911),h=n(58234),f=n(46904),d=n(98882),m=n(43824),g=n(29148),y=n(38517),v=n(1469),b=n(44144),w=n(56688),E=n(13218),x=n(72928),S=n(3674),_=n(81704),j="[object Arguments]",O="[object Function]",k="[object Object]",A={};A[j]=A["[object Array]"]=A["[object ArrayBuffer]"]=A["[object DataView]"]=A["[object Boolean]"]=A["[object Date]"]=A["[object Float32Array]"]=A["[object Float64Array]"]=A["[object Int8Array]"]=A["[object Int16Array]"]=A["[object Int32Array]"]=A["[object Map]"]=A["[object Number]"]=A[k]=A["[object RegExp]"]=A["[object Set]"]=A["[object String]"]=A["[object Symbol]"]=A["[object Uint8Array]"]=A["[object Uint8ClampedArray]"]=A["[object Uint16Array]"]=A["[object Uint32Array]"]=!0,A["[object Error]"]=A[O]=A["[object WeakMap]"]=!1,e.exports=function e(t,n,C,P,N,I){var T,R=1&n,M=2&n,D=4&n;if(C&&(T=N?C(t,P,N,I):C(t)),void 0!==T)return T;if(!E(t))return t;var F=v(t);if(F){if(T=m(t),!R)return c(t,T)}else{var L=d(t),B=L==O||"[object GeneratorFunction]"==L;if(b(t))return l(t,R);if(L==k||L==j||B&&!N){if(T=M||B?{}:y(t),!R)return M?p(t,a(T,t)):u(t,i(T,t))}else{if(!A[L])return N?t:{};T=g(t,L,R)}}I||(I=new r);var $=I.get(t);if($)return $;I.set(t,T),x(t)?t.forEach((function(r){T.add(e(r,n,C,r,t,I))})):w(t)&&t.forEach((function(r,o){T.set(o,e(r,n,C,o,t,I))}));var q=F?void 0:(D?M?f:h:M?_:S)(t);return o(q||t,(function(r,o){q&&(r=t[o=r]),s(T,o,e(r,n,C,o,t,I))})),T}},3118:(e,t,n)=>{var r=n(13218),o=Object.create,s=function(){function e(){}return function(t){if(!r(t))return{};if(o)return o(t);e.prototype=t;var n=new e;return e.prototype=void 0,n}}();e.exports=s},89881:(e,t,n)=>{var r=n(47816),o=n(99291)(r);e.exports=o},41848:e=>{e.exports=function(e,t,n,r){for(var o=e.length,s=n+(r?1:-1);r?s--:++s{var r=n(62488),o=n(37285);e.exports=function e(t,n,s,i,a){var l=-1,c=t.length;for(s||(s=o),a||(a=[]);++l0&&s(u)?n>1?e(u,n-1,s,i,a):r(a,u):i||(a[a.length]=u)}return a}},28483:(e,t,n)=>{var r=n(25063)();e.exports=r},47816:(e,t,n)=>{var r=n(28483),o=n(3674);e.exports=function(e,t){return e&&r(e,t,o)}},97786:(e,t,n)=>{var r=n(71811),o=n(40327);e.exports=function(e,t){for(var n=0,s=(t=r(t,e)).length;null!=e&&n{var r=n(62488),o=n(1469);e.exports=function(e,t,n){var s=t(e);return o(e)?s:r(s,n(e))}},44239:(e,t,n)=>{var r=n(62705),o=n(89607),s=n(2333),i=r?r.toStringTag:void 0;e.exports=function(e){return null==e?void 0===e?"[object Undefined]":"[object Null]":i&&i in Object(e)?o(e):s(e)}},13:e=>{e.exports=function(e,t){return null!=e&&t in Object(e)}},42118:(e,t,n)=>{var r=n(41848),o=n(62722),s=n(42351);e.exports=function(e,t,n){return t==t?s(e,t,n):r(e,o,n)}},9454:(e,t,n)=>{var r=n(44239),o=n(37005);e.exports=function(e){return o(e)&&"[object Arguments]"==r(e)}},90939:(e,t,n)=>{var r=n(2492),o=n(37005);e.exports=function e(t,n,s,i,a){return t===n||(null==t||null==n||!o(t)&&!o(n)?t!=t&&n!=n:r(t,n,s,i,e,a))}},2492:(e,t,n)=>{var r=n(46384),o=n(67114),s=n(18351),i=n(16096),a=n(98882),l=n(1469),c=n(44144),u=n(36719),p="[object Arguments]",h="[object Array]",f="[object Object]",d=Object.prototype.hasOwnProperty;e.exports=function(e,t,n,m,g,y){var v=l(e),b=l(t),w=v?h:a(e),E=b?h:a(t),x=(w=w==p?f:w)==f,S=(E=E==p?f:E)==f,_=w==E;if(_&&c(e)){if(!c(t))return!1;v=!0,x=!1}if(_&&!x)return y||(y=new r),v||u(e)?o(e,t,n,m,g,y):s(e,t,w,n,m,g,y);if(!(1&n)){var j=x&&d.call(e,"__wrapped__"),O=S&&d.call(t,"__wrapped__");if(j||O){var k=j?e.value():e,A=O?t.value():t;return y||(y=new r),g(k,A,n,m,y)}}return!!_&&(y||(y=new r),i(e,t,n,m,g,y))}},25588:(e,t,n)=>{var r=n(98882),o=n(37005);e.exports=function(e){return o(e)&&"[object Map]"==r(e)}},2958:(e,t,n)=>{var r=n(46384),o=n(90939);e.exports=function(e,t,n,s){var i=n.length,a=i,l=!s;if(null==e)return!a;for(e=Object(e);i--;){var c=n[i];if(l&&c[2]?c[1]!==e[c[0]]:!(c[0]in e))return!1}for(;++i{e.exports=function(e){return e!=e}},28458:(e,t,n)=>{var r=n(23560),o=n(15346),s=n(13218),i=n(80346),a=/^\[object .+?Constructor\]$/,l=Function.prototype,c=Object.prototype,u=l.toString,p=c.hasOwnProperty,h=RegExp("^"+u.call(p).replace(/[\\^$.*+?()[\]{}|]/g,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$");e.exports=function(e){return!(!s(e)||o(e))&&(r(e)?h:a).test(i(e))}},29221:(e,t,n)=>{var r=n(98882),o=n(37005);e.exports=function(e){return o(e)&&"[object Set]"==r(e)}},38749:(e,t,n)=>{var r=n(44239),o=n(41780),s=n(37005),i={};i["[object Float32Array]"]=i["[object Float64Array]"]=i["[object Int8Array]"]=i["[object Int16Array]"]=i["[object Int32Array]"]=i["[object Uint8Array]"]=i["[object Uint8ClampedArray]"]=i["[object Uint16Array]"]=i["[object Uint32Array]"]=!0,i["[object Arguments]"]=i["[object Array]"]=i["[object ArrayBuffer]"]=i["[object Boolean]"]=i["[object DataView]"]=i["[object Date]"]=i["[object Error]"]=i["[object Function]"]=i["[object Map]"]=i["[object Number]"]=i["[object Object]"]=i["[object RegExp]"]=i["[object Set]"]=i["[object String]"]=i["[object WeakMap]"]=!1,e.exports=function(e){return s(e)&&o(e.length)&&!!i[r(e)]}},67206:(e,t,n)=>{var r=n(91573),o=n(16432),s=n(6557),i=n(1469),a=n(39601);e.exports=function(e){return"function"==typeof e?e:null==e?s:"object"==typeof e?i(e)?o(e[0],e[1]):r(e):a(e)}},280:(e,t,n)=>{var r=n(25726),o=n(86916),s=Object.prototype.hasOwnProperty;e.exports=function(e){if(!r(e))return o(e);var t=[];for(var n in Object(e))s.call(e,n)&&"constructor"!=n&&t.push(n);return t}},10313:(e,t,n)=>{var r=n(13218),o=n(25726),s=n(33498),i=Object.prototype.hasOwnProperty;e.exports=function(e){if(!r(e))return s(e);var t=o(e),n=[];for(var a in e)("constructor"!=a||!t&&i.call(e,a))&&n.push(a);return n}},9435:e=>{e.exports=function(){}},91573:(e,t,n)=>{var r=n(2958),o=n(1499),s=n(42634);e.exports=function(e){var t=o(e);return 1==t.length&&t[0][2]?s(t[0][0],t[0][1]):function(n){return n===e||r(n,e,t)}}},16432:(e,t,n)=>{var r=n(90939),o=n(27361),s=n(79095),i=n(15403),a=n(89162),l=n(42634),c=n(40327);e.exports=function(e,t){return i(e)&&a(t)?l(c(e),t):function(n){var i=o(n,e);return void 0===i&&i===t?s(n,e):r(t,i,3)}}},42980:(e,t,n)=>{var r=n(46384),o=n(86556),s=n(28483),i=n(59783),a=n(13218),l=n(81704),c=n(36390);e.exports=function e(t,n,u,p,h){t!==n&&s(n,(function(s,l){if(h||(h=new r),a(s))i(t,n,l,u,e,p,h);else{var f=p?p(c(t,l),s,l+"",t,n,h):void 0;void 0===f&&(f=s),o(t,l,f)}}),l)}},59783:(e,t,n)=>{var r=n(86556),o=n(64626),s=n(77133),i=n(278),a=n(38517),l=n(35694),c=n(1469),u=n(29246),p=n(44144),h=n(23560),f=n(13218),d=n(68630),m=n(36719),g=n(36390),y=n(59881);e.exports=function(e,t,n,v,b,w,E){var x=g(e,n),S=g(t,n),_=E.get(S);if(_)r(e,n,_);else{var j=w?w(x,S,n+"",e,t,E):void 0,O=void 0===j;if(O){var k=c(S),A=!k&&p(S),C=!k&&!A&&m(S);j=S,k||A||C?c(x)?j=x:u(x)?j=i(x):A?(O=!1,j=o(S,!0)):C?(O=!1,j=s(S,!0)):j=[]:d(S)||l(S)?(j=x,l(x)?j=y(x):f(x)&&!h(x)||(j=a(S))):O=!1}O&&(E.set(S,j),b(j,S,v,w,E),E.delete(S)),r(e,n,j)}}},40371:e=>{e.exports=function(e){return function(t){return null==t?void 0:t[e]}}},79152:(e,t,n)=>{var r=n(97786);e.exports=function(e){return function(t){return r(t,e)}}},18674:e=>{e.exports=function(e){return function(t){return null==e?void 0:e[t]}}},10107:e=>{e.exports=function(e,t,n,r,o){return o(e,(function(e,o,s){n=r?(r=!1,e):t(n,e,o,s)})),n}},5976:(e,t,n)=>{var r=n(6557),o=n(45357),s=n(30061);e.exports=function(e,t){return s(o(e,t,r),e+"")}},10611:(e,t,n)=>{var r=n(34865),o=n(71811),s=n(65776),i=n(13218),a=n(40327);e.exports=function(e,t,n,l){if(!i(e))return e;for(var c=-1,u=(t=o(t,e)).length,p=u-1,h=e;null!=h&&++c{var r=n(6557),o=n(89250),s=o?function(e,t){return o.set(e,t),e}:r;e.exports=s},56560:(e,t,n)=>{var r=n(75703),o=n(38777),s=n(6557),i=o?function(e,t){return o(e,"toString",{configurable:!0,enumerable:!1,value:r(t),writable:!0})}:s;e.exports=i},14259:e=>{e.exports=function(e,t,n){var r=-1,o=e.length;t<0&&(t=-t>o?0:o+t),(n=n>o?o:n)<0&&(n+=o),o=t>n?0:n-t>>>0,t>>>=0;for(var s=Array(o);++r{var r=n(89881);e.exports=function(e,t){var n;return r(e,(function(e,r,o){return!(n=t(e,r,o))})),!!n}},22545:e=>{e.exports=function(e,t){for(var n=-1,r=Array(e);++n{var r=n(62705),o=n(29932),s=n(1469),i=n(33448),a=r?r.prototype:void 0,l=a?a.toString:void 0;e.exports=function e(t){if("string"==typeof t)return t;if(s(t))return o(t,e)+"";if(i(t))return l?l.call(t):"";var n=t+"";return"0"==n&&1/t==-Infinity?"-0":n}},27561:(e,t,n)=>{var r=n(67990),o=/^\s+/;e.exports=function(e){return e?e.slice(0,r(e)+1).replace(o,""):e}},7518:e=>{e.exports=function(e){return function(t){return e(t)}}},57406:(e,t,n)=>{var r=n(71811),o=n(10928),s=n(40292),i=n(40327);e.exports=function(e,t){return t=r(t,e),null==(e=s(e,t))||delete e[i(o(t))]}},1757:e=>{e.exports=function(e,t,n){for(var r=-1,o=e.length,s=t.length,i={};++r{e.exports=function(e,t){return e.has(t)}},71811:(e,t,n)=>{var r=n(1469),o=n(15403),s=n(55514),i=n(79833);e.exports=function(e,t){return r(e)?e:o(e,t)?[e]:s(i(e))}},40180:(e,t,n)=>{var r=n(14259);e.exports=function(e,t,n){var o=e.length;return n=void 0===n?o:n,!t&&n>=o?e:r(e,t,n)}},74318:(e,t,n)=>{var r=n(11149);e.exports=function(e){var t=new e.constructor(e.byteLength);return new r(t).set(new r(e)),t}},64626:(e,t,n)=>{e=n.nmd(e);var r=n(55639),o=t&&!t.nodeType&&t,s=o&&e&&!e.nodeType&&e,i=s&&s.exports===o?r.Buffer:void 0,a=i?i.allocUnsafe:void 0;e.exports=function(e,t){if(t)return e.slice();var n=e.length,r=a?a(n):new e.constructor(n);return e.copy(r),r}},57157:(e,t,n)=>{var r=n(74318);e.exports=function(e,t){var n=t?r(e.buffer):e.buffer;return new e.constructor(n,e.byteOffset,e.byteLength)}},93147:e=>{var t=/\w*$/;e.exports=function(e){var n=new e.constructor(e.source,t.exec(e));return n.lastIndex=e.lastIndex,n}},40419:(e,t,n)=>{var r=n(62705),o=r?r.prototype:void 0,s=o?o.valueOf:void 0;e.exports=function(e){return s?Object(s.call(e)):{}}},77133:(e,t,n)=>{var r=n(74318);e.exports=function(e,t){var n=t?r(e.buffer):e.buffer;return new e.constructor(n,e.byteOffset,e.length)}},52157:e=>{var t=Math.max;e.exports=function(e,n,r,o){for(var s=-1,i=e.length,a=r.length,l=-1,c=n.length,u=t(i-a,0),p=Array(c+u),h=!o;++l{var t=Math.max;e.exports=function(e,n,r,o){for(var s=-1,i=e.length,a=-1,l=r.length,c=-1,u=n.length,p=t(i-l,0),h=Array(p+u),f=!o;++s{e.exports=function(e,t){var n=-1,r=e.length;for(t||(t=Array(r));++n{var r=n(34865),o=n(89465);e.exports=function(e,t,n,s){var i=!n;n||(n={});for(var a=-1,l=t.length;++a{var r=n(98363),o=n(99551);e.exports=function(e,t){return r(e,o(e),t)}},1911:(e,t,n)=>{var r=n(98363),o=n(51442);e.exports=function(e,t){return r(e,o(e),t)}},14429:(e,t,n)=>{var r=n(55639)["__core-js_shared__"];e.exports=r},97991:e=>{e.exports=function(e,t){for(var n=e.length,r=0;n--;)e[n]===t&&++r;return r}},21463:(e,t,n)=>{var r=n(5976),o=n(16612);e.exports=function(e){return r((function(t,n){var r=-1,s=n.length,i=s>1?n[s-1]:void 0,a=s>2?n[2]:void 0;for(i=e.length>3&&"function"==typeof i?(s--,i):void 0,a&&o(n[0],n[1],a)&&(i=s<3?void 0:i,s=1),t=Object(t);++r{var r=n(98612);e.exports=function(e,t){return function(n,o){if(null==n)return n;if(!r(n))return e(n,o);for(var s=n.length,i=t?s:-1,a=Object(n);(t?i--:++i{e.exports=function(e){return function(t,n,r){for(var o=-1,s=Object(t),i=r(t),a=i.length;a--;){var l=i[e?a:++o];if(!1===n(s[l],l,s))break}return t}}},22402:(e,t,n)=>{var r=n(71774),o=n(55639);e.exports=function(e,t,n){var s=1&t,i=r(e);return function t(){return(this&&this!==o&&this instanceof t?i:e).apply(s?n:this,arguments)}}},98805:(e,t,n)=>{var r=n(40180),o=n(62689),s=n(83140),i=n(79833);e.exports=function(e){return function(t){t=i(t);var n=o(t)?s(t):void 0,a=n?n[0]:t.charAt(0),l=n?r(n,1).join(""):t.slice(1);return a[e]()+l}}},35393:(e,t,n)=>{var r=n(62663),o=n(53816),s=n(58748),i=RegExp("['’]","g");e.exports=function(e){return function(t){return r(s(o(t).replace(i,"")),e,"")}}},71774:(e,t,n)=>{var r=n(3118),o=n(13218);e.exports=function(e){return function(){var t=arguments;switch(t.length){case 0:return new e;case 1:return new e(t[0]);case 2:return new e(t[0],t[1]);case 3:return new e(t[0],t[1],t[2]);case 4:return new e(t[0],t[1],t[2],t[3]);case 5:return new e(t[0],t[1],t[2],t[3],t[4]);case 6:return new e(t[0],t[1],t[2],t[3],t[4],t[5]);case 7:return new e(t[0],t[1],t[2],t[3],t[4],t[5],t[6])}var n=r(e.prototype),s=e.apply(n,t);return o(s)?s:n}}},46347:(e,t,n)=>{var r=n(96874),o=n(71774),s=n(86935),i=n(94487),a=n(20893),l=n(46460),c=n(55639);e.exports=function(e,t,n){var u=o(e);return function o(){for(var p=arguments.length,h=Array(p),f=p,d=a(o);f--;)h[f]=arguments[f];var m=p<3&&h[0]!==d&&h[p-1]!==d?[]:l(h,d);return(p-=m.length){var r=n(67206),o=n(98612),s=n(3674);e.exports=function(e){return function(t,n,i){var a=Object(t);if(!o(t)){var l=r(n,3);t=s(t),n=function(e){return l(a[e],e,a)}}var c=e(t,n,i);return c>-1?a[l?t[c]:c]:void 0}}},86935:(e,t,n)=>{var r=n(52157),o=n(14054),s=n(97991),i=n(71774),a=n(94487),l=n(20893),c=n(90451),u=n(46460),p=n(55639);e.exports=function e(t,n,h,f,d,m,g,y,v,b){var w=128&n,E=1&n,x=2&n,S=24&n,_=512&n,j=x?void 0:i(t);return function O(){for(var k=arguments.length,A=Array(k),C=k;C--;)A[C]=arguments[C];if(S)var P=l(O),N=s(A,P);if(f&&(A=r(A,f,d,S)),m&&(A=o(A,m,g,S)),k-=N,S&&k1&&A.reverse(),w&&v{var r=n(96874),o=n(71774),s=n(55639);e.exports=function(e,t,n,i){var a=1&t,l=o(e);return function t(){for(var o=-1,c=arguments.length,u=-1,p=i.length,h=Array(p+c),f=this&&this!==s&&this instanceof t?l:e;++u{var r=n(86528),o=n(258),s=n(69255);e.exports=function(e,t,n,i,a,l,c,u,p,h){var f=8&t;t|=f?32:64,4&(t&=~(f?64:32))||(t&=-4);var d=[e,t,a,f?l:void 0,f?c:void 0,f?void 0:l,f?void 0:c,u,p,h],m=n.apply(void 0,d);return r(e)&&o(m,d),m.placeholder=i,s(m,e,t)}},97727:(e,t,n)=>{var r=n(28045),o=n(22402),s=n(46347),i=n(86935),a=n(84375),l=n(66833),c=n(63833),u=n(258),p=n(69255),h=n(40554),f=Math.max;e.exports=function(e,t,n,d,m,g,y,v){var b=2&t;if(!b&&"function"!=typeof e)throw new TypeError("Expected a function");var w=d?d.length:0;if(w||(t&=-97,d=m=void 0),y=void 0===y?y:f(h(y),0),v=void 0===v?v:h(v),w-=m?m.length:0,64&t){var E=d,x=m;d=m=void 0}var S=b?void 0:l(e),_=[e,t,n,d,m,E,x,g,y,v];if(S&&c(_,S),e=_[0],t=_[1],n=_[2],d=_[3],m=_[4],!(v=_[9]=void 0===_[9]?b?0:e.length:f(_[9]-w,0))&&24&t&&(t&=-25),t&&1!=t)j=8==t||16==t?s(e,t,v):32!=t&&33!=t||m.length?i.apply(void 0,_):a(e,t,n,d);else var j=o(e,t,n);return p((S?r:u)(j,_),e,t)}},60696:(e,t,n)=>{var r=n(68630);e.exports=function(e){return r(e)?void 0:e}},69389:(e,t,n)=>{var r=n(18674)({À:"A",Á:"A",Â:"A",Ã:"A",Ä:"A",Å:"A",à:"a",á:"a",â:"a",ã:"a",ä:"a",å:"a",Ç:"C",ç:"c",Ð:"D",ð:"d",È:"E",É:"E",Ê:"E",Ë:"E",è:"e",é:"e",ê:"e",ë:"e",Ì:"I",Í:"I",Î:"I",Ï:"I",ì:"i",í:"i",î:"i",ï:"i",Ñ:"N",ñ:"n",Ò:"O",Ó:"O",Ô:"O",Õ:"O",Ö:"O",Ø:"O",ò:"o",ó:"o",ô:"o",õ:"o",ö:"o",ø:"o",Ù:"U",Ú:"U",Û:"U",Ü:"U",ù:"u",ú:"u",û:"u",ü:"u",Ý:"Y",ý:"y",ÿ:"y",Æ:"Ae",æ:"ae",Þ:"Th",þ:"th",ß:"ss",Ā:"A",Ă:"A",Ą:"A",ā:"a",ă:"a",ą:"a",Ć:"C",Ĉ:"C",Ċ:"C",Č:"C",ć:"c",ĉ:"c",ċ:"c",č:"c",Ď:"D",Đ:"D",ď:"d",đ:"d",Ē:"E",Ĕ:"E",Ė:"E",Ę:"E",Ě:"E",ē:"e",ĕ:"e",ė:"e",ę:"e",ě:"e",Ĝ:"G",Ğ:"G",Ġ:"G",Ģ:"G",ĝ:"g",ğ:"g",ġ:"g",ģ:"g",Ĥ:"H",Ħ:"H",ĥ:"h",ħ:"h",Ĩ:"I",Ī:"I",Ĭ:"I",Į:"I",İ:"I",ĩ:"i",ī:"i",ĭ:"i",į:"i",ı:"i",Ĵ:"J",ĵ:"j",Ķ:"K",ķ:"k",ĸ:"k",Ĺ:"L",Ļ:"L",Ľ:"L",Ŀ:"L",Ł:"L",ĺ:"l",ļ:"l",ľ:"l",ŀ:"l",ł:"l",Ń:"N",Ņ:"N",Ň:"N",Ŋ:"N",ń:"n",ņ:"n",ň:"n",ŋ:"n",Ō:"O",Ŏ:"O",Ő:"O",ō:"o",ŏ:"o",ő:"o",Ŕ:"R",Ŗ:"R",Ř:"R",ŕ:"r",ŗ:"r",ř:"r",Ś:"S",Ŝ:"S",Ş:"S",Š:"S",ś:"s",ŝ:"s",ş:"s",š:"s",Ţ:"T",Ť:"T",Ŧ:"T",ţ:"t",ť:"t",ŧ:"t",Ũ:"U",Ū:"U",Ŭ:"U",Ů:"U",Ű:"U",Ų:"U",ũ:"u",ū:"u",ŭ:"u",ů:"u",ű:"u",ų:"u",Ŵ:"W",ŵ:"w",Ŷ:"Y",ŷ:"y",Ÿ:"Y",Ź:"Z",Ż:"Z",Ž:"Z",ź:"z",ż:"z",ž:"z",IJ:"IJ",ij:"ij",Œ:"Oe",œ:"oe",ʼn:"'n",ſ:"s"});e.exports=r},38777:(e,t,n)=>{var r=n(10852),o=function(){try{var e=r(Object,"defineProperty");return e({},"",{}),e}catch(e){}}();e.exports=o},67114:(e,t,n)=>{var r=n(88668),o=n(82908),s=n(74757);e.exports=function(e,t,n,i,a,l){var c=1&n,u=e.length,p=t.length;if(u!=p&&!(c&&p>u))return!1;var h=l.get(e),f=l.get(t);if(h&&f)return h==t&&f==e;var d=-1,m=!0,g=2&n?new r:void 0;for(l.set(e,t),l.set(t,e);++d{var r=n(62705),o=n(11149),s=n(77813),i=n(67114),a=n(68776),l=n(21814),c=r?r.prototype:void 0,u=c?c.valueOf:void 0;e.exports=function(e,t,n,r,c,p,h){switch(n){case"[object DataView]":if(e.byteLength!=t.byteLength||e.byteOffset!=t.byteOffset)return!1;e=e.buffer,t=t.buffer;case"[object ArrayBuffer]":return!(e.byteLength!=t.byteLength||!p(new o(e),new o(t)));case"[object Boolean]":case"[object Date]":case"[object Number]":return s(+e,+t);case"[object Error]":return e.name==t.name&&e.message==t.message;case"[object RegExp]":case"[object String]":return e==t+"";case"[object Map]":var f=a;case"[object Set]":var d=1&r;if(f||(f=l),e.size!=t.size&&!d)return!1;var m=h.get(e);if(m)return m==t;r|=2,h.set(e,t);var g=i(f(e),f(t),r,c,p,h);return h.delete(e),g;case"[object Symbol]":if(u)return u.call(e)==u.call(t)}return!1}},16096:(e,t,n)=>{var r=n(58234),o=Object.prototype.hasOwnProperty;e.exports=function(e,t,n,s,i,a){var l=1&n,c=r(e),u=c.length;if(u!=r(t).length&&!l)return!1;for(var p=u;p--;){var h=c[p];if(!(l?h in t:o.call(t,h)))return!1}var f=a.get(e),d=a.get(t);if(f&&d)return f==t&&d==e;var m=!0;a.set(e,t),a.set(t,e);for(var g=l;++p{var r=n(85564),o=n(45357),s=n(30061);e.exports=function(e){return s(o(e,void 0,r),e+"")}},31957:(e,t,n)=>{var r="object"==typeof n.g&&n.g&&n.g.Object===Object&&n.g;e.exports=r},58234:(e,t,n)=>{var r=n(68866),o=n(99551),s=n(3674);e.exports=function(e){return r(e,s,o)}},46904:(e,t,n)=>{var r=n(68866),o=n(51442),s=n(81704);e.exports=function(e){return r(e,s,o)}},66833:(e,t,n)=>{var r=n(89250),o=n(50308),s=r?function(e){return r.get(e)}:o;e.exports=s},97658:(e,t,n)=>{var r=n(52060),o=Object.prototype.hasOwnProperty;e.exports=function(e){for(var t=e.name+"",n=r[t],s=o.call(r,t)?n.length:0;s--;){var i=n[s],a=i.func;if(null==a||a==e)return i.name}return t}},20893:e=>{e.exports=function(e){return e.placeholder}},45050:(e,t,n)=>{var r=n(37019);e.exports=function(e,t){var n=e.__data__;return r(t)?n["string"==typeof t?"string":"hash"]:n.map}},1499:(e,t,n)=>{var r=n(89162),o=n(3674);e.exports=function(e){for(var t=o(e),n=t.length;n--;){var s=t[n],i=e[s];t[n]=[s,i,r(i)]}return t}},10852:(e,t,n)=>{var r=n(28458),o=n(47801);e.exports=function(e,t){var n=o(e,t);return r(n)?n:void 0}},85924:(e,t,n)=>{var r=n(5569)(Object.getPrototypeOf,Object);e.exports=r},89607:(e,t,n)=>{var r=n(62705),o=Object.prototype,s=o.hasOwnProperty,i=o.toString,a=r?r.toStringTag:void 0;e.exports=function(e){var t=s.call(e,a),n=e[a];try{e[a]=void 0;var r=!0}catch(e){}var o=i.call(e);return r&&(t?e[a]=n:delete e[a]),o}},99551:(e,t,n)=>{var r=n(34963),o=n(70479),s=Object.prototype.propertyIsEnumerable,i=Object.getOwnPropertySymbols,a=i?function(e){return null==e?[]:(e=Object(e),r(i(e),(function(t){return s.call(e,t)})))}:o;e.exports=a},51442:(e,t,n)=>{var r=n(62488),o=n(85924),s=n(99551),i=n(70479),a=Object.getOwnPropertySymbols?function(e){for(var t=[];e;)r(t,s(e)),e=o(e);return t}:i;e.exports=a},98882:(e,t,n)=>{var r=n(18552),o=n(57071),s=n(53818),i=n(58525),a=n(70577),l=n(44239),c=n(80346),u="[object Map]",p="[object Promise]",h="[object Set]",f="[object WeakMap]",d="[object DataView]",m=c(r),g=c(o),y=c(s),v=c(i),b=c(a),w=l;(r&&w(new r(new ArrayBuffer(1)))!=d||o&&w(new o)!=u||s&&w(s.resolve())!=p||i&&w(new i)!=h||a&&w(new a)!=f)&&(w=function(e){var t=l(e),n="[object Object]"==t?e.constructor:void 0,r=n?c(n):"";if(r)switch(r){case m:return d;case g:return u;case y:return p;case v:return h;case b:return f}return t}),e.exports=w},47801:e=>{e.exports=function(e,t){return null==e?void 0:e[t]}},58775:e=>{var t=/\{\n\/\* \[wrapped with (.+)\] \*/,n=/,? & /;e.exports=function(e){var r=e.match(t);return r?r[1].split(n):[]}},222:(e,t,n)=>{var r=n(71811),o=n(35694),s=n(1469),i=n(65776),a=n(41780),l=n(40327);e.exports=function(e,t,n){for(var c=-1,u=(t=r(t,e)).length,p=!1;++c{var t=RegExp("[\\u200d\\ud800-\\udfff\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff\\ufe0e\\ufe0f]");e.exports=function(e){return t.test(e)}},93157:e=>{var t=/[a-z][A-Z]|[A-Z]{2}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/;e.exports=function(e){return t.test(e)}},51789:(e,t,n)=>{var r=n(94536);e.exports=function(){this.__data__=r?r(null):{},this.size=0}},80401:e=>{e.exports=function(e){var t=this.has(e)&&delete this.__data__[e];return this.size-=t?1:0,t}},57667:(e,t,n)=>{var r=n(94536),o=Object.prototype.hasOwnProperty;e.exports=function(e){var t=this.__data__;if(r){var n=t[e];return"__lodash_hash_undefined__"===n?void 0:n}return o.call(t,e)?t[e]:void 0}},21327:(e,t,n)=>{var r=n(94536),o=Object.prototype.hasOwnProperty;e.exports=function(e){var t=this.__data__;return r?void 0!==t[e]:o.call(t,e)}},81866:(e,t,n)=>{var r=n(94536);e.exports=function(e,t){var n=this.__data__;return this.size+=this.has(e)?0:1,n[e]=r&&void 0===t?"__lodash_hash_undefined__":t,this}},43824:e=>{var t=Object.prototype.hasOwnProperty;e.exports=function(e){var n=e.length,r=new e.constructor(n);return n&&"string"==typeof e[0]&&t.call(e,"index")&&(r.index=e.index,r.input=e.input),r}},29148:(e,t,n)=>{var r=n(74318),o=n(57157),s=n(93147),i=n(40419),a=n(77133);e.exports=function(e,t,n){var l=e.constructor;switch(t){case"[object ArrayBuffer]":return r(e);case"[object Boolean]":case"[object Date]":return new l(+e);case"[object DataView]":return o(e,n);case"[object Float32Array]":case"[object Float64Array]":case"[object Int8Array]":case"[object Int16Array]":case"[object Int32Array]":case"[object Uint8Array]":case"[object Uint8ClampedArray]":case"[object Uint16Array]":case"[object Uint32Array]":return a(e,n);case"[object Map]":case"[object Set]":return new l;case"[object Number]":case"[object String]":return new l(e);case"[object RegExp]":return s(e);case"[object Symbol]":return i(e)}}},38517:(e,t,n)=>{var r=n(3118),o=n(85924),s=n(25726);e.exports=function(e){return"function"!=typeof e.constructor||s(e)?{}:r(o(e))}},83112:e=>{var t=/\{(?:\n\/\* \[wrapped with .+\] \*\/)?\n?/;e.exports=function(e,n){var r=n.length;if(!r)return e;var o=r-1;return n[o]=(r>1?"& ":"")+n[o],n=n.join(r>2?", ":" "),e.replace(t,"{\n/* [wrapped with "+n+"] */\n")}},37285:(e,t,n)=>{var r=n(62705),o=n(35694),s=n(1469),i=r?r.isConcatSpreadable:void 0;e.exports=function(e){return s(e)||o(e)||!!(i&&e&&e[i])}},65776:e=>{var t=/^(?:0|[1-9]\d*)$/;e.exports=function(e,n){var r=typeof e;return!!(n=null==n?9007199254740991:n)&&("number"==r||"symbol"!=r&&t.test(e))&&e>-1&&e%1==0&&e{var r=n(77813),o=n(98612),s=n(65776),i=n(13218);e.exports=function(e,t,n){if(!i(n))return!1;var a=typeof t;return!!("number"==a?o(n)&&s(t,n.length):"string"==a&&t in n)&&r(n[t],e)}},15403:(e,t,n)=>{var r=n(1469),o=n(33448),s=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,i=/^\w*$/;e.exports=function(e,t){if(r(e))return!1;var n=typeof e;return!("number"!=n&&"symbol"!=n&&"boolean"!=n&&null!=e&&!o(e))||(i.test(e)||!s.test(e)||null!=t&&e in Object(t))}},37019:e=>{e.exports=function(e){var t=typeof e;return"string"==t||"number"==t||"symbol"==t||"boolean"==t?"__proto__"!==e:null===e}},86528:(e,t,n)=>{var r=n(96425),o=n(66833),s=n(97658),i=n(8111);e.exports=function(e){var t=s(e),n=i[t];if("function"!=typeof n||!(t in r.prototype))return!1;if(e===n)return!0;var a=o(n);return!!a&&e===a[0]}},15346:(e,t,n)=>{var r,o=n(14429),s=(r=/[^.]+$/.exec(o&&o.keys&&o.keys.IE_PROTO||""))?"Symbol(src)_1."+r:"";e.exports=function(e){return!!s&&s in e}},25726:e=>{var t=Object.prototype;e.exports=function(e){var n=e&&e.constructor;return e===("function"==typeof n&&n.prototype||t)}},89162:(e,t,n)=>{var r=n(13218);e.exports=function(e){return e==e&&!r(e)}},27040:e=>{e.exports=function(){this.__data__=[],this.size=0}},14125:(e,t,n)=>{var r=n(18470),o=Array.prototype.splice;e.exports=function(e){var t=this.__data__,n=r(t,e);return!(n<0)&&(n==t.length-1?t.pop():o.call(t,n,1),--this.size,!0)}},82117:(e,t,n)=>{var r=n(18470);e.exports=function(e){var t=this.__data__,n=r(t,e);return n<0?void 0:t[n][1]}},67518:(e,t,n)=>{var r=n(18470);e.exports=function(e){return r(this.__data__,e)>-1}},54705:(e,t,n)=>{var r=n(18470);e.exports=function(e,t){var n=this.__data__,o=r(n,e);return o<0?(++this.size,n.push([e,t])):n[o][1]=t,this}},24785:(e,t,n)=>{var r=n(1989),o=n(38407),s=n(57071);e.exports=function(){this.size=0,this.__data__={hash:new r,map:new(s||o),string:new r}}},11285:(e,t,n)=>{var r=n(45050);e.exports=function(e){var t=r(this,e).delete(e);return this.size-=t?1:0,t}},96e3:(e,t,n)=>{var r=n(45050);e.exports=function(e){return r(this,e).get(e)}},49916:(e,t,n)=>{var r=n(45050);e.exports=function(e){return r(this,e).has(e)}},95265:(e,t,n)=>{var r=n(45050);e.exports=function(e,t){var n=r(this,e),o=n.size;return n.set(e,t),this.size+=n.size==o?0:1,this}},68776:e=>{e.exports=function(e){var t=-1,n=Array(e.size);return e.forEach((function(e,r){n[++t]=[r,e]})),n}},42634:e=>{e.exports=function(e,t){return function(n){return null!=n&&(n[e]===t&&(void 0!==t||e in Object(n)))}}},24523:(e,t,n)=>{var r=n(88306);e.exports=function(e){var t=r(e,(function(e){return 500===n.size&&n.clear(),e})),n=t.cache;return t}},63833:(e,t,n)=>{var r=n(52157),o=n(14054),s=n(46460),i="__lodash_placeholder__",a=128,l=Math.min;e.exports=function(e,t){var n=e[1],c=t[1],u=n|c,p=u<131,h=c==a&&8==n||c==a&&256==n&&e[7].length<=t[8]||384==c&&t[7].length<=t[8]&&8==n;if(!p&&!h)return e;1&c&&(e[2]=t[2],u|=1&n?0:4);var f=t[3];if(f){var d=e[3];e[3]=d?r(d,f,t[4]):f,e[4]=d?s(e[3],i):t[4]}return(f=t[5])&&(d=e[5],e[5]=d?o(d,f,t[6]):f,e[6]=d?s(e[5],i):t[6]),(f=t[7])&&(e[7]=f),c&a&&(e[8]=null==e[8]?t[8]:l(e[8],t[8])),null==e[9]&&(e[9]=t[9]),e[0]=t[0],e[1]=u,e}},89250:(e,t,n)=>{var r=n(70577),o=r&&new r;e.exports=o},94536:(e,t,n)=>{var r=n(10852)(Object,"create");e.exports=r},86916:(e,t,n)=>{var r=n(5569)(Object.keys,Object);e.exports=r},33498:e=>{e.exports=function(e){var t=[];if(null!=e)for(var n in Object(e))t.push(n);return t}},31167:(e,t,n)=>{e=n.nmd(e);var r=n(31957),o=t&&!t.nodeType&&t,s=o&&e&&!e.nodeType&&e,i=s&&s.exports===o&&r.process,a=function(){try{var e=s&&s.require&&s.require("util").types;return e||i&&i.binding&&i.binding("util")}catch(e){}}();e.exports=a},2333:e=>{var t=Object.prototype.toString;e.exports=function(e){return t.call(e)}},5569:e=>{e.exports=function(e,t){return function(n){return e(t(n))}}},45357:(e,t,n)=>{var r=n(96874),o=Math.max;e.exports=function(e,t,n){return t=o(void 0===t?e.length-1:t,0),function(){for(var s=arguments,i=-1,a=o(s.length-t,0),l=Array(a);++i{var r=n(97786),o=n(14259);e.exports=function(e,t){return t.length<2?e:r(e,o(t,0,-1))}},52060:e=>{e.exports={}},90451:(e,t,n)=>{var r=n(278),o=n(65776),s=Math.min;e.exports=function(e,t){for(var n=e.length,i=s(t.length,n),a=r(e);i--;){var l=t[i];e[i]=o(l,n)?a[l]:void 0}return e}},46460:e=>{var t="__lodash_placeholder__";e.exports=function(e,n){for(var r=-1,o=e.length,s=0,i=[];++r{var r=n(31957),o="object"==typeof self&&self&&self.Object===Object&&self,s=r||o||Function("return this")();e.exports=s},36390:e=>{e.exports=function(e,t){if(("constructor"!==t||"function"!=typeof e[t])&&"__proto__"!=t)return e[t]}},90619:e=>{e.exports=function(e){return this.__data__.set(e,"__lodash_hash_undefined__"),this}},72385:e=>{e.exports=function(e){return this.__data__.has(e)}},258:(e,t,n)=>{var r=n(28045),o=n(21275)(r);e.exports=o},21814:e=>{e.exports=function(e){var t=-1,n=Array(e.size);return e.forEach((function(e){n[++t]=e})),n}},30061:(e,t,n)=>{var r=n(56560),o=n(21275)(r);e.exports=o},69255:(e,t,n)=>{var r=n(58775),o=n(83112),s=n(30061),i=n(87241);e.exports=function(e,t,n){var a=t+"";return s(e,o(a,i(r(a),n)))}},21275:e=>{var t=Date.now;e.exports=function(e){var n=0,r=0;return function(){var o=t(),s=16-(o-r);if(r=o,s>0){if(++n>=800)return arguments[0]}else n=0;return e.apply(void 0,arguments)}}},37465:(e,t,n)=>{var r=n(38407);e.exports=function(){this.__data__=new r,this.size=0}},63779:e=>{e.exports=function(e){var t=this.__data__,n=t.delete(e);return this.size=t.size,n}},67599:e=>{e.exports=function(e){return this.__data__.get(e)}},44758:e=>{e.exports=function(e){return this.__data__.has(e)}},34309:(e,t,n)=>{var r=n(38407),o=n(57071),s=n(83369);e.exports=function(e,t){var n=this.__data__;if(n instanceof r){var i=n.__data__;if(!o||i.length<199)return i.push([e,t]),this.size=++n.size,this;n=this.__data__=new s(i)}return n.set(e,t),this.size=n.size,this}},42351:e=>{e.exports=function(e,t,n){for(var r=n-1,o=e.length;++r{var r=n(44286),o=n(62689),s=n(676);e.exports=function(e){return o(e)?s(e):r(e)}},55514:(e,t,n)=>{var r=n(24523),o=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g,s=/\\(\\)?/g,i=r((function(e){var t=[];return 46===e.charCodeAt(0)&&t.push(""),e.replace(o,(function(e,n,r,o){t.push(r?o.replace(s,"$1"):n||e)})),t}));e.exports=i},40327:(e,t,n)=>{var r=n(33448);e.exports=function(e){if("string"==typeof e||r(e))return e;var t=e+"";return"0"==t&&1/e==-Infinity?"-0":t}},80346:e=>{var t=Function.prototype.toString;e.exports=function(e){if(null!=e){try{return t.call(e)}catch(e){}try{return e+""}catch(e){}}return""}},67990:e=>{var t=/\s/;e.exports=function(e){for(var n=e.length;n--&&t.test(e.charAt(n)););return n}},676:e=>{var t="\\ud800-\\udfff",n="["+t+"]",r="[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]",o="\\ud83c[\\udffb-\\udfff]",s="[^"+t+"]",i="(?:\\ud83c[\\udde6-\\uddff]){2}",a="[\\ud800-\\udbff][\\udc00-\\udfff]",l="(?:"+r+"|"+o+")"+"?",c="[\\ufe0e\\ufe0f]?",u=c+l+("(?:\\u200d(?:"+[s,i,a].join("|")+")"+c+l+")*"),p="(?:"+[s+r+"?",r,i,a,n].join("|")+")",h=RegExp(o+"(?="+o+")|"+p+u,"g");e.exports=function(e){return e.match(h)||[]}},2757:e=>{var t="\\ud800-\\udfff",n="\\u2700-\\u27bf",r="a-z\\xdf-\\xf6\\xf8-\\xff",o="A-Z\\xc0-\\xd6\\xd8-\\xde",s="\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000",i="["+s+"]",a="\\d+",l="["+n+"]",c="["+r+"]",u="[^"+t+s+a+n+r+o+"]",p="(?:\\ud83c[\\udde6-\\uddff]){2}",h="[\\ud800-\\udbff][\\udc00-\\udfff]",f="["+o+"]",d="(?:"+c+"|"+u+")",m="(?:"+f+"|"+u+")",g="(?:['’](?:d|ll|m|re|s|t|ve))?",y="(?:['’](?:D|LL|M|RE|S|T|VE))?",v="(?:[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]|\\ud83c[\\udffb-\\udfff])?",b="[\\ufe0e\\ufe0f]?",w=b+v+("(?:\\u200d(?:"+["[^"+t+"]",p,h].join("|")+")"+b+v+")*"),E="(?:"+[l,p,h].join("|")+")"+w,x=RegExp([f+"?"+c+"+"+g+"(?="+[i,f,"$"].join("|")+")",m+"+"+y+"(?="+[i,f+d,"$"].join("|")+")",f+"?"+d+"+"+g,f+"+"+y,"\\d*(?:1ST|2ND|3RD|(?![123])\\dTH)(?=\\b|[a-z_])","\\d*(?:1st|2nd|3rd|(?![123])\\dth)(?=\\b|[A-Z_])",a,E].join("|"),"g");e.exports=function(e){return e.match(x)||[]}},87241:(e,t,n)=>{var r=n(77412),o=n(47443),s=[["ary",128],["bind",1],["bindKey",2],["curry",8],["curryRight",16],["flip",512],["partial",32],["partialRight",64],["rearg",256]];e.exports=function(e,t){return r(s,(function(n){var r="_."+n[0];t&n[1]&&!o(e,r)&&e.push(r)})),e.sort()}},21913:(e,t,n)=>{var r=n(96425),o=n(7548),s=n(278);e.exports=function(e){if(e instanceof r)return e.clone();var t=new o(e.__wrapped__,e.__chain__);return t.__actions__=s(e.__actions__),t.__index__=e.__index__,t.__values__=e.__values__,t}},39514:(e,t,n)=>{var r=n(97727);e.exports=function(e,t,n){return t=n?void 0:t,t=e&&null==t?e.length:t,r(e,128,void 0,void 0,void 0,void 0,t)}},68929:(e,t,n)=>{var r=n(48403),o=n(35393)((function(e,t,n){return t=t.toLowerCase(),e+(n?r(t):t)}));e.exports=o},48403:(e,t,n)=>{var r=n(79833),o=n(11700);e.exports=function(e){return o(r(e).toLowerCase())}},66678:(e,t,n)=>{var r=n(85990);e.exports=function(e){return r(e,4)}},75703:e=>{e.exports=function(e){return function(){return e}}},40087:(e,t,n)=>{var r=n(97727);function o(e,t,n){var s=r(e,8,void 0,void 0,void 0,void 0,void 0,t=n?void 0:t);return s.placeholder=o.placeholder,s}o.placeholder={},e.exports=o},23279:(e,t,n)=>{var r=n(13218),o=n(7771),s=n(14841),i=Math.max,a=Math.min;e.exports=function(e,t,n){var l,c,u,p,h,f,d=0,m=!1,g=!1,y=!0;if("function"!=typeof e)throw new TypeError("Expected a function");function v(t){var n=l,r=c;return l=c=void 0,d=t,p=e.apply(r,n)}function b(e){var n=e-f;return void 0===f||n>=t||n<0||g&&e-d>=u}function w(){var e=o();if(b(e))return E(e);h=setTimeout(w,function(e){var n=t-(e-f);return g?a(n,u-(e-d)):n}(e))}function E(e){return h=void 0,y&&l?v(e):(l=c=void 0,p)}function x(){var e=o(),n=b(e);if(l=arguments,c=this,f=e,n){if(void 0===h)return function(e){return d=e,h=setTimeout(w,t),m?v(e):p}(f);if(g)return clearTimeout(h),h=setTimeout(w,t),v(f)}return void 0===h&&(h=setTimeout(w,t)),p}return t=s(t)||0,r(n)&&(m=!!n.leading,u=(g="maxWait"in n)?i(s(n.maxWait)||0,t):u,y="trailing"in n?!!n.trailing:y),x.cancel=function(){void 0!==h&&clearTimeout(h),d=0,l=f=c=h=void 0},x.flush=function(){return void 0===h?p:E(o())},x}},53816:(e,t,n)=>{var r=n(69389),o=n(79833),s=/[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g,i=RegExp("[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]","g");e.exports=function(e){return(e=o(e))&&e.replace(s,r).replace(i,"")}},77813:e=>{e.exports=function(e,t){return e===t||e!=e&&t!=t}},13311:(e,t,n)=>{var r=n(67740)(n(30998));e.exports=r},30998:(e,t,n)=>{var r=n(41848),o=n(67206),s=n(40554),i=Math.max;e.exports=function(e,t,n){var a=null==e?0:e.length;if(!a)return-1;var l=null==n?0:s(n);return l<0&&(l=i(a+l,0)),r(e,o(t,3),l)}},85564:(e,t,n)=>{var r=n(21078);e.exports=function(e){return(null==e?0:e.length)?r(e,1):[]}},84599:(e,t,n)=>{var r=n(68836),o=n(69306),s=Array.prototype.push;function i(e,t){return 2==t?function(t,n){return e(t,n)}:function(t){return e(t)}}function a(e){for(var t=e?e.length:0,n=Array(t);t--;)n[t]=e[t];return n}function l(e,t){return function(){var n=arguments.length;if(n){for(var r=Array(n);n--;)r[n]=arguments[n];var o=r[0]=t.apply(void 0,r);return e.apply(void 0,r),o}}}e.exports=function e(t,n,c,u){var p="function"==typeof n,h=n===Object(n);if(h&&(u=c,c=n,n=void 0),null==c)throw new TypeError;u||(u={});var f={cap:!("cap"in u)||u.cap,curry:!("curry"in u)||u.curry,fixed:!("fixed"in u)||u.fixed,immutable:!("immutable"in u)||u.immutable,rearg:!("rearg"in u)||u.rearg},d=p?c:o,m="curry"in u&&u.curry,g="fixed"in u&&u.fixed,y="rearg"in u&&u.rearg,v=p?c.runInContext():void 0,b=p?c:{ary:t.ary,assign:t.assign,clone:t.clone,curry:t.curry,forEach:t.forEach,isArray:t.isArray,isError:t.isError,isFunction:t.isFunction,isWeakMap:t.isWeakMap,iteratee:t.iteratee,keys:t.keys,rearg:t.rearg,toInteger:t.toInteger,toPath:t.toPath},w=b.ary,E=b.assign,x=b.clone,S=b.curry,_=b.forEach,j=b.isArray,O=b.isError,k=b.isFunction,A=b.isWeakMap,C=b.keys,P=b.rearg,N=b.toInteger,I=b.toPath,T=C(r.aryMethod),R={castArray:function(e){return function(){var t=arguments[0];return j(t)?e(a(t)):e.apply(void 0,arguments)}},iteratee:function(e){return function(){var t=arguments[1],n=e(arguments[0],t),r=n.length;return f.cap&&"number"==typeof t?(t=t>2?t-2:1,r&&r<=t?n:i(n,t)):n}},mixin:function(e){return function(t){var n=this;if(!k(n))return e(n,Object(t));var r=[];return _(C(t),(function(e){k(t[e])&&r.push([e,n.prototype[e]])})),e(n,Object(t)),_(r,(function(e){var t=e[1];k(t)?n.prototype[e[0]]=t:delete n.prototype[e[0]]})),n}},nthArg:function(e){return function(t){var n=t<0?1:N(t)+1;return S(e(t),n)}},rearg:function(e){return function(t,n){var r=n?n.length:0;return S(e(t,n),r)}},runInContext:function(n){return function(r){return e(t,n(r),u)}}};function M(e,t){if(f.cap){var n=r.iterateeRearg[e];if(n)return function(e,t){return $(e,(function(e){var n=t.length;return function(e,t){return 2==t?function(t,n){return e.apply(void 0,arguments)}:function(t){return e.apply(void 0,arguments)}}(P(i(e,n),t),n)}))}(t,n);var o=!p&&r.iterateeAry[e];if(o)return function(e,t){return $(e,(function(e){return"function"==typeof e?i(e,t):e}))}(t,o)}return t}function D(e,t,n){if(f.fixed&&(g||!r.skipFixed[e])){var o=r.methodSpread[e],i=o&&o.start;return void 0===i?w(t,n):function(e,t){return function(){for(var n=arguments.length,r=n-1,o=Array(n);n--;)o[n]=arguments[n];var i=o[t],a=o.slice(0,t);return i&&s.apply(a,i),t!=r&&s.apply(a,o.slice(t+1)),e.apply(this,a)}}(t,i)}return t}function F(e,t,n){return f.rearg&&n>1&&(y||!r.skipRearg[e])?P(t,r.methodRearg[e]||r.aryRearg[n]):t}function L(e,t){for(var n=-1,r=(t=I(t)).length,o=r-1,s=x(Object(e)),i=s;null!=i&&++n1?S(t,n):t}(0,o=M(s,o),e),!1}})),!o})),o||(o=i),o==t&&(o=m?S(o,1):function(){return t.apply(this,arguments)}),o.convert=B(s,t),o.placeholder=t.placeholder=n,o}if(!h)return q(n,c,d);var U=c,z=[];return _(T,(function(e){_(r.aryMethod[e],(function(e){var t=U[r.remap[e]||e];t&&z.push([e,q(e,t,U)])}))})),_(C(U),(function(e){var t=U[e];if("function"==typeof t){for(var n=z.length;n--;)if(z[n][0]==e)return;t.convert=B(e,t),z.push([e,t])}})),_(z,(function(e){U[e[0]]=e[1]})),U.convert=function(e){return U.runInContext.convert(e)(void 0)},U.placeholder=U,_(C(U),(function(e){_(r.realToAlias[e]||[],(function(t){U[t]=U[e]}))})),U}},68836:(e,t)=>{t.aliasToReal={each:"forEach",eachRight:"forEachRight",entries:"toPairs",entriesIn:"toPairsIn",extend:"assignIn",extendAll:"assignInAll",extendAllWith:"assignInAllWith",extendWith:"assignInWith",first:"head",conforms:"conformsTo",matches:"isMatch",property:"get",__:"placeholder",F:"stubFalse",T:"stubTrue",all:"every",allPass:"overEvery",always:"constant",any:"some",anyPass:"overSome",apply:"spread",assoc:"set",assocPath:"set",complement:"negate",compose:"flowRight",contains:"includes",dissoc:"unset",dissocPath:"unset",dropLast:"dropRight",dropLastWhile:"dropRightWhile",equals:"isEqual",identical:"eq",indexBy:"keyBy",init:"initial",invertObj:"invert",juxt:"over",omitAll:"omit",nAry:"ary",path:"get",pathEq:"matchesProperty",pathOr:"getOr",paths:"at",pickAll:"pick",pipe:"flow",pluck:"map",prop:"get",propEq:"matchesProperty",propOr:"getOr",props:"at",symmetricDifference:"xor",symmetricDifferenceBy:"xorBy",symmetricDifferenceWith:"xorWith",takeLast:"takeRight",takeLastWhile:"takeRightWhile",unapply:"rest",unnest:"flatten",useWith:"overArgs",where:"conformsTo",whereEq:"isMatch",zipObj:"zipObject"},t.aryMethod={1:["assignAll","assignInAll","attempt","castArray","ceil","create","curry","curryRight","defaultsAll","defaultsDeepAll","floor","flow","flowRight","fromPairs","invert","iteratee","memoize","method","mergeAll","methodOf","mixin","nthArg","over","overEvery","overSome","rest","reverse","round","runInContext","spread","template","trim","trimEnd","trimStart","uniqueId","words","zipAll"],2:["add","after","ary","assign","assignAllWith","assignIn","assignInAllWith","at","before","bind","bindAll","bindKey","chunk","cloneDeepWith","cloneWith","concat","conformsTo","countBy","curryN","curryRightN","debounce","defaults","defaultsDeep","defaultTo","delay","difference","divide","drop","dropRight","dropRightWhile","dropWhile","endsWith","eq","every","filter","find","findIndex","findKey","findLast","findLastIndex","findLastKey","flatMap","flatMapDeep","flattenDepth","forEach","forEachRight","forIn","forInRight","forOwn","forOwnRight","get","groupBy","gt","gte","has","hasIn","includes","indexOf","intersection","invertBy","invoke","invokeMap","isEqual","isMatch","join","keyBy","lastIndexOf","lt","lte","map","mapKeys","mapValues","matchesProperty","maxBy","meanBy","merge","mergeAllWith","minBy","multiply","nth","omit","omitBy","overArgs","pad","padEnd","padStart","parseInt","partial","partialRight","partition","pick","pickBy","propertyOf","pull","pullAll","pullAt","random","range","rangeRight","rearg","reject","remove","repeat","restFrom","result","sampleSize","some","sortBy","sortedIndex","sortedIndexOf","sortedLastIndex","sortedLastIndexOf","sortedUniqBy","split","spreadFrom","startsWith","subtract","sumBy","take","takeRight","takeRightWhile","takeWhile","tap","throttle","thru","times","trimChars","trimCharsEnd","trimCharsStart","truncate","union","uniqBy","uniqWith","unset","unzipWith","without","wrap","xor","zip","zipObject","zipObjectDeep"],3:["assignInWith","assignWith","clamp","differenceBy","differenceWith","findFrom","findIndexFrom","findLastFrom","findLastIndexFrom","getOr","includesFrom","indexOfFrom","inRange","intersectionBy","intersectionWith","invokeArgs","invokeArgsMap","isEqualWith","isMatchWith","flatMapDepth","lastIndexOfFrom","mergeWith","orderBy","padChars","padCharsEnd","padCharsStart","pullAllBy","pullAllWith","rangeStep","rangeStepRight","reduce","reduceRight","replace","set","slice","sortedIndexBy","sortedLastIndexBy","transform","unionBy","unionWith","update","xorBy","xorWith","zipWith"],4:["fill","setWith","updateWith"]},t.aryRearg={2:[1,0],3:[2,0,1],4:[3,2,0,1]},t.iterateeAry={dropRightWhile:1,dropWhile:1,every:1,filter:1,find:1,findFrom:1,findIndex:1,findIndexFrom:1,findKey:1,findLast:1,findLastFrom:1,findLastIndex:1,findLastIndexFrom:1,findLastKey:1,flatMap:1,flatMapDeep:1,flatMapDepth:1,forEach:1,forEachRight:1,forIn:1,forInRight:1,forOwn:1,forOwnRight:1,map:1,mapKeys:1,mapValues:1,partition:1,reduce:2,reduceRight:2,reject:1,remove:1,some:1,takeRightWhile:1,takeWhile:1,times:1,transform:2},t.iterateeRearg={mapKeys:[1],reduceRight:[1,0]},t.methodRearg={assignInAllWith:[1,0],assignInWith:[1,2,0],assignAllWith:[1,0],assignWith:[1,2,0],differenceBy:[1,2,0],differenceWith:[1,2,0],getOr:[2,1,0],intersectionBy:[1,2,0],intersectionWith:[1,2,0],isEqualWith:[1,2,0],isMatchWith:[2,1,0],mergeAllWith:[1,0],mergeWith:[1,2,0],padChars:[2,1,0],padCharsEnd:[2,1,0],padCharsStart:[2,1,0],pullAllBy:[2,1,0],pullAllWith:[2,1,0],rangeStep:[1,2,0],rangeStepRight:[1,2,0],setWith:[3,1,2,0],sortedIndexBy:[2,1,0],sortedLastIndexBy:[2,1,0],unionBy:[1,2,0],unionWith:[1,2,0],updateWith:[3,1,2,0],xorBy:[1,2,0],xorWith:[1,2,0],zipWith:[1,2,0]},t.methodSpread={assignAll:{start:0},assignAllWith:{start:0},assignInAll:{start:0},assignInAllWith:{start:0},defaultsAll:{start:0},defaultsDeepAll:{start:0},invokeArgs:{start:2},invokeArgsMap:{start:2},mergeAll:{start:0},mergeAllWith:{start:0},partial:{start:1},partialRight:{start:1},without:{start:1},zipAll:{start:0}},t.mutate={array:{fill:!0,pull:!0,pullAll:!0,pullAllBy:!0,pullAllWith:!0,pullAt:!0,remove:!0,reverse:!0},object:{assign:!0,assignAll:!0,assignAllWith:!0,assignIn:!0,assignInAll:!0,assignInAllWith:!0,assignInWith:!0,assignWith:!0,defaults:!0,defaultsAll:!0,defaultsDeep:!0,defaultsDeepAll:!0,merge:!0,mergeAll:!0,mergeAllWith:!0,mergeWith:!0},set:{set:!0,setWith:!0,unset:!0,update:!0,updateWith:!0}},t.realToAlias=function(){var e=Object.prototype.hasOwnProperty,n=t.aliasToReal,r={};for(var o in n){var s=n[o];e.call(r,s)?r[s].push(o):r[s]=[o]}return r}(),t.remap={assignAll:"assign",assignAllWith:"assignWith",assignInAll:"assignIn",assignInAllWith:"assignInWith",curryN:"curry",curryRightN:"curryRight",defaultsAll:"defaults",defaultsDeepAll:"defaultsDeep",findFrom:"find",findIndexFrom:"findIndex",findLastFrom:"findLast",findLastIndexFrom:"findLastIndex",getOr:"get",includesFrom:"includes",indexOfFrom:"indexOf",invokeArgs:"invoke",invokeArgsMap:"invokeMap",lastIndexOfFrom:"lastIndexOf",mergeAll:"merge",mergeAllWith:"mergeWith",padChars:"pad",padCharsEnd:"padEnd",padCharsStart:"padStart",propertyOf:"get",rangeStep:"range",rangeStepRight:"rangeRight",restFrom:"rest",spreadFrom:"spread",trimChars:"trim",trimCharsEnd:"trimEnd",trimCharsStart:"trimStart",zipAll:"zip"},t.skipFixed={castArray:!0,flow:!0,flowRight:!0,iteratee:!0,mixin:!0,rearg:!0,runInContext:!0},t.skipRearg={add:!0,assign:!0,assignIn:!0,bind:!0,bindKey:!0,concat:!0,difference:!0,divide:!0,eq:!0,gt:!0,gte:!0,isEqual:!0,lt:!0,lte:!0,matchesProperty:!0,merge:!0,multiply:!0,overArgs:!0,partial:!0,partialRight:!0,propertyOf:!0,random:!0,range:!0,rangeRight:!0,subtract:!0,zip:!0,zipObject:!0,zipObjectDeep:!0}},4269:(e,t,n)=>{e.exports={ary:n(39514),assign:n(44037),clone:n(66678),curry:n(40087),forEach:n(77412),isArray:n(1469),isError:n(64647),isFunction:n(23560),isWeakMap:n(81018),iteratee:n(72594),keys:n(280),rearg:n(4963),toInteger:n(40554),toPath:n(30084)}},72700:(e,t,n)=>{e.exports=n(28252)},92822:(e,t,n)=>{var r=n(84599),o=n(4269);e.exports=function(e,t,n){return r(o,e,t,n)}},69306:e=>{e.exports={}},28252:(e,t,n)=>{var r=n(92822)("set",n(36968));r.placeholder=n(69306),e.exports=r},27361:(e,t,n)=>{var r=n(97786);e.exports=function(e,t,n){var o=null==e?void 0:r(e,t);return void 0===o?n:o}},79095:(e,t,n)=>{var r=n(13),o=n(222);e.exports=function(e,t){return null!=e&&o(e,t,r)}},6557:e=>{e.exports=function(e){return e}},35694:(e,t,n)=>{var r=n(9454),o=n(37005),s=Object.prototype,i=s.hasOwnProperty,a=s.propertyIsEnumerable,l=r(function(){return arguments}())?r:function(e){return o(e)&&i.call(e,"callee")&&!a.call(e,"callee")};e.exports=l},1469:e=>{var t=Array.isArray;e.exports=t},98612:(e,t,n)=>{var r=n(23560),o=n(41780);e.exports=function(e){return null!=e&&o(e.length)&&!r(e)}},29246:(e,t,n)=>{var r=n(98612),o=n(37005);e.exports=function(e){return o(e)&&r(e)}},51584:(e,t,n)=>{var r=n(44239),o=n(37005);e.exports=function(e){return!0===e||!1===e||o(e)&&"[object Boolean]"==r(e)}},44144:(e,t,n)=>{e=n.nmd(e);var r=n(55639),o=n(95062),s=t&&!t.nodeType&&t,i=s&&e&&!e.nodeType&&e,a=i&&i.exports===s?r.Buffer:void 0,l=(a?a.isBuffer:void 0)||o;e.exports=l},41609:(e,t,n)=>{var r=n(280),o=n(98882),s=n(35694),i=n(1469),a=n(98612),l=n(44144),c=n(25726),u=n(36719),p=Object.prototype.hasOwnProperty;e.exports=function(e){if(null==e)return!0;if(a(e)&&(i(e)||"string"==typeof e||"function"==typeof e.splice||l(e)||u(e)||s(e)))return!e.length;var t=o(e);if("[object Map]"==t||"[object Set]"==t)return!e.size;if(c(e))return!r(e).length;for(var n in e)if(p.call(e,n))return!1;return!0}},18446:(e,t,n)=>{var r=n(90939);e.exports=function(e,t){return r(e,t)}},64647:(e,t,n)=>{var r=n(44239),o=n(37005),s=n(68630);e.exports=function(e){if(!o(e))return!1;var t=r(e);return"[object Error]"==t||"[object DOMException]"==t||"string"==typeof e.message&&"string"==typeof e.name&&!s(e)}},23560:(e,t,n)=>{var r=n(44239),o=n(13218);e.exports=function(e){if(!o(e))return!1;var t=r(e);return"[object Function]"==t||"[object GeneratorFunction]"==t||"[object AsyncFunction]"==t||"[object Proxy]"==t}},41780:e=>{e.exports=function(e){return"number"==typeof e&&e>-1&&e%1==0&&e<=9007199254740991}},56688:(e,t,n)=>{var r=n(25588),o=n(7518),s=n(31167),i=s&&s.isMap,a=i?o(i):r;e.exports=a},45220:e=>{e.exports=function(e){return null===e}},81763:(e,t,n)=>{var r=n(44239),o=n(37005);e.exports=function(e){return"number"==typeof e||o(e)&&"[object Number]"==r(e)}},13218:e=>{e.exports=function(e){var t=typeof e;return null!=e&&("object"==t||"function"==t)}},37005:e=>{e.exports=function(e){return null!=e&&"object"==typeof e}},68630:(e,t,n)=>{var r=n(44239),o=n(85924),s=n(37005),i=Function.prototype,a=Object.prototype,l=i.toString,c=a.hasOwnProperty,u=l.call(Object);e.exports=function(e){if(!s(e)||"[object Object]"!=r(e))return!1;var t=o(e);if(null===t)return!0;var n=c.call(t,"constructor")&&t.constructor;return"function"==typeof n&&n instanceof n&&l.call(n)==u}},72928:(e,t,n)=>{var r=n(29221),o=n(7518),s=n(31167),i=s&&s.isSet,a=i?o(i):r;e.exports=a},47037:(e,t,n)=>{var r=n(44239),o=n(1469),s=n(37005);e.exports=function(e){return"string"==typeof e||!o(e)&&s(e)&&"[object String]"==r(e)}},33448:(e,t,n)=>{var r=n(44239),o=n(37005);e.exports=function(e){return"symbol"==typeof e||o(e)&&"[object Symbol]"==r(e)}},36719:(e,t,n)=>{var r=n(38749),o=n(7518),s=n(31167),i=s&&s.isTypedArray,a=i?o(i):r;e.exports=a},81018:(e,t,n)=>{var r=n(98882),o=n(37005);e.exports=function(e){return o(e)&&"[object WeakMap]"==r(e)}},72594:(e,t,n)=>{var r=n(85990),o=n(67206);e.exports=function(e){return o("function"==typeof e?e:r(e,1))}},3674:(e,t,n)=>{var r=n(14636),o=n(280),s=n(98612);e.exports=function(e){return s(e)?r(e):o(e)}},81704:(e,t,n)=>{var r=n(14636),o=n(10313),s=n(98612);e.exports=function(e){return s(e)?r(e,!0):o(e)}},10928:e=>{e.exports=function(e){var t=null==e?0:e.length;return t?e[t-1]:void 0}},88306:(e,t,n)=>{var r=n(83369);function o(e,t){if("function"!=typeof e||null!=t&&"function"!=typeof t)throw new TypeError("Expected a function");var n=function(){var r=arguments,o=t?t.apply(this,r):r[0],s=n.cache;if(s.has(o))return s.get(o);var i=e.apply(this,r);return n.cache=s.set(o,i)||s,i};return n.cache=new(o.Cache||r),n}o.Cache=r,e.exports=o},82492:(e,t,n)=>{var r=n(42980),o=n(21463)((function(e,t,n){r(e,t,n)}));e.exports=o},94885:e=>{e.exports=function(e){if("function"!=typeof e)throw new TypeError("Expected a function");return function(){var t=arguments;switch(t.length){case 0:return!e.call(this);case 1:return!e.call(this,t[0]);case 2:return!e.call(this,t[0],t[1]);case 3:return!e.call(this,t[0],t[1],t[2])}return!e.apply(this,t)}}},50308:e=>{e.exports=function(){}},7771:(e,t,n)=>{var r=n(55639);e.exports=function(){return r.Date.now()}},57557:(e,t,n)=>{var r=n(29932),o=n(85990),s=n(57406),i=n(71811),a=n(98363),l=n(60696),c=n(99021),u=n(46904),p=c((function(e,t){var n={};if(null==e)return n;var c=!1;t=r(t,(function(t){return t=i(t,e),c||(c=t.length>1),t})),a(e,u(e),n),c&&(n=o(n,7,l));for(var p=t.length;p--;)s(n,t[p]);return n}));e.exports=p},39601:(e,t,n)=>{var r=n(40371),o=n(79152),s=n(15403),i=n(40327);e.exports=function(e){return s(e)?r(i(e)):o(e)}},4963:(e,t,n)=>{var r=n(97727),o=n(99021),s=o((function(e,t){return r(e,256,void 0,void 0,void 0,t)}));e.exports=s},54061:(e,t,n)=>{var r=n(62663),o=n(89881),s=n(67206),i=n(10107),a=n(1469);e.exports=function(e,t,n){var l=a(e)?r:i,c=arguments.length<3;return l(e,s(t,4),n,c,o)}},36968:(e,t,n)=>{var r=n(10611);e.exports=function(e,t,n){return null==e?e:r(e,t,n)}},59704:(e,t,n)=>{var r=n(82908),o=n(67206),s=n(5076),i=n(1469),a=n(16612);e.exports=function(e,t,n){var l=i(e)?r:s;return n&&a(e,t,n)&&(t=void 0),l(e,o(t,3))}},70479:e=>{e.exports=function(){return[]}},95062:e=>{e.exports=function(){return!1}},18601:(e,t,n)=>{var r=n(14841),o=1/0;e.exports=function(e){return e?(e=r(e))===o||e===-1/0?17976931348623157e292*(e<0?-1:1):e==e?e:0:0===e?e:0}},40554:(e,t,n)=>{var r=n(18601);e.exports=function(e){var t=r(e),n=t%1;return t==t?n?t-n:t:0}},7334:(e,t,n)=>{var r=n(79833);e.exports=function(e){return r(e).toLowerCase()}},14841:(e,t,n)=>{var r=n(27561),o=n(13218),s=n(33448),i=/^[-+]0x[0-9a-f]+$/i,a=/^0b[01]+$/i,l=/^0o[0-7]+$/i,c=parseInt;e.exports=function(e){if("number"==typeof e)return e;if(s(e))return NaN;if(o(e)){var t="function"==typeof e.valueOf?e.valueOf():e;e=o(t)?t+"":t}if("string"!=typeof e)return 0===e?e:+e;e=r(e);var n=a.test(e);return n||l.test(e)?c(e.slice(2),n?2:8):i.test(e)?NaN:+e}},30084:(e,t,n)=>{var r=n(29932),o=n(278),s=n(1469),i=n(33448),a=n(55514),l=n(40327),c=n(79833);e.exports=function(e){return s(e)?r(e,l):i(e)?[e]:o(a(c(e)))}},59881:(e,t,n)=>{var r=n(98363),o=n(81704);e.exports=function(e){return r(e,o(e))}},79833:(e,t,n)=>{var r=n(80531);e.exports=function(e){return null==e?"":r(e)}},11700:(e,t,n)=>{var r=n(98805)("toUpperCase");e.exports=r},58748:(e,t,n)=>{var r=n(49029),o=n(93157),s=n(79833),i=n(2757);e.exports=function(e,t,n){return e=s(e),void 0===(t=n?void 0:t)?o(e)?i(e):r(e):e.match(t)||[]}},8111:(e,t,n)=>{var r=n(96425),o=n(7548),s=n(9435),i=n(1469),a=n(37005),l=n(21913),c=Object.prototype.hasOwnProperty;function u(e){if(a(e)&&!i(e)&&!(e instanceof r)){if(e instanceof o)return e;if(c.call(e,"__wrapped__"))return l(e)}return new o(e)}u.prototype=s.prototype,u.prototype.constructor=u,e.exports=u},7287:(e,t,n)=>{var r=n(34865),o=n(1757);e.exports=function(e,t){return o(e||[],t||[],r)}},96470:(e,t,n)=>{"use strict";var r=n(47802),o=n(21102);t.highlight=i,t.highlightAuto=function(e,t){var n,a,l,c,u=t||{},p=u.subset||r.listLanguages(),h=u.prefix,f=p.length,d=-1;null==h&&(h=s);if("string"!=typeof e)throw o("Expected `string` for value, got `%s`",e);a={relevance:0,language:null,value:[]},n={relevance:0,language:null,value:[]};for(;++da.relevance&&(a=l),l.relevance>n.relevance&&(a=n,n=l));a.language&&(n.secondBest=a);return n},t.registerLanguage=function(e,t){r.registerLanguage(e,t)},t.listLanguages=function(){return r.listLanguages()},t.registerAlias=function(e,t){var n,o=e;t&&((o={})[e]=t);for(n in o)r.registerAliases(o[n],{languageName:n})},a.prototype.addText=function(e){var t,n,r=this.stack;if(""===e)return;t=r[r.length-1],(n=t.children[t.children.length-1])&&"text"===n.type?n.value+=e:t.children.push({type:"text",value:e})},a.prototype.addKeyword=function(e,t){this.openNode(t),this.addText(e),this.closeNode()},a.prototype.addSublanguage=function(e,t){var n=this.stack,r=n[n.length-1],o=e.rootNode.children,s=t?{type:"element",tagName:"span",properties:{className:[t]},children:o}:o;r.children=r.children.concat(s)},a.prototype.openNode=function(e){var t=this.stack,n=this.options.classPrefix+e,r=t[t.length-1],o={type:"element",tagName:"span",properties:{className:[n]},children:[]};r.children.push(o),t.push(o)},a.prototype.closeNode=function(){this.stack.pop()},a.prototype.closeAllNodes=l,a.prototype.finalize=l,a.prototype.toHTML=function(){return""};var s="hljs-";function i(e,t,n){var i,l=r.configure({}),c=(n||{}).prefix;if("string"!=typeof e)throw o("Expected `string` for name, got `%s`",e);if(!r.getLanguage(e))throw o("Unknown language: `%s` is not registered",e);if("string"!=typeof t)throw o("Expected `string` for value, got `%s`",t);if(null==c&&(c=s),r.configure({__emitter:a,classPrefix:c}),i=r.highlight(t,{language:e,ignoreIllegals:!0}),r.configure(l||{}),i.errorRaised)throw i.errorRaised;return{relevance:i.relevance,language:i.language,value:i.emitter.rootNode.children}}function a(e){this.options=e,this.rootNode={children:[]},this.stack=[this.rootNode]}function l(){}},42566:(e,t,n)=>{const r=n(94885);function o(e){return"string"==typeof e?t=>t.element===e:e.constructor&&e.extend?t=>t instanceof e:e}class s{constructor(e){this.elements=e||[]}toValue(){return this.elements.map((e=>e.toValue()))}map(e,t){return this.elements.map(e,t)}flatMap(e,t){return this.map(e,t).reduce(((e,t)=>e.concat(t)),[])}compactMap(e,t){const n=[];return this.forEach((r=>{const o=e.bind(t)(r);o&&n.push(o)})),n}filter(e,t){return e=o(e),new s(this.elements.filter(e,t))}reject(e,t){return e=o(e),new s(this.elements.filter(r(e),t))}find(e,t){return e=o(e),this.elements.find(e,t)}forEach(e,t){this.elements.forEach(e,t)}reduce(e,t){return this.elements.reduce(e,t)}includes(e){return this.elements.some((t=>t.equals(e)))}shift(){return this.elements.shift()}unshift(e){this.elements.unshift(this.refract(e))}push(e){return this.elements.push(this.refract(e)),this}add(e){this.push(e)}get(e){return this.elements[e]}getValue(e){const t=this.elements[e];if(t)return t.toValue()}get length(){return this.elements.length}get isEmpty(){return 0===this.elements.length}get first(){return this.elements[0]}}"undefined"!=typeof Symbol&&(s.prototype[Symbol.iterator]=function(){return this.elements[Symbol.iterator]()}),e.exports=s},17645:e=>{class t{constructor(e,t){this.key=e,this.value=t}clone(){const e=new t;return this.key&&(e.key=this.key.clone()),this.value&&(e.value=this.value.clone()),e}}e.exports=t},78520:(e,t,n)=>{const r=n(45220),o=n(47037),s=n(81763),i=n(51584),a=n(13218),l=n(28219),c=n(99829);class u{constructor(e){this.elementMap={},this.elementDetection=[],this.Element=c.Element,this.KeyValuePair=c.KeyValuePair,e&&e.noDefault||this.useDefault(),this._attributeElementKeys=[],this._attributeElementArrayKeys=[]}use(e){return e.namespace&&e.namespace({base:this}),e.load&&e.load({base:this}),this}useDefault(){return this.register("null",c.NullElement).register("string",c.StringElement).register("number",c.NumberElement).register("boolean",c.BooleanElement).register("array",c.ArrayElement).register("object",c.ObjectElement).register("member",c.MemberElement).register("ref",c.RefElement).register("link",c.LinkElement),this.detect(r,c.NullElement,!1).detect(o,c.StringElement,!1).detect(s,c.NumberElement,!1).detect(i,c.BooleanElement,!1).detect(Array.isArray,c.ArrayElement,!1).detect(a,c.ObjectElement,!1),this}register(e,t){return this._elements=void 0,this.elementMap[e]=t,this}unregister(e){return this._elements=void 0,delete this.elementMap[e],this}detect(e,t,n){return void 0===n||n?this.elementDetection.unshift([e,t]):this.elementDetection.push([e,t]),this}toElement(e){if(e instanceof this.Element)return e;let t;for(let n=0;n{const t=e[0].toUpperCase()+e.substr(1);this._elements[t]=this.elementMap[e]}))),this._elements}get serialiser(){return new l(this)}}l.prototype.Namespace=u,e.exports=u},87526:(e,t,n)=>{const r=n(94885),o=n(42566);class s extends o{map(e,t){return this.elements.map((n=>e.bind(t)(n.value,n.key,n)))}filter(e,t){return new s(this.elements.filter((n=>e.bind(t)(n.value,n.key,n))))}reject(e,t){return this.filter(r(e.bind(t)))}forEach(e,t){return this.elements.forEach(((n,r)=>{e.bind(t)(n.value,n.key,n,r)}))}keys(){return this.map(((e,t)=>t.toValue()))}values(){return this.map((e=>e.toValue()))}}e.exports=s},99829:(e,t,n)=>{const r=n(3079),o=n(96295),s=n(16036),i=n(91090),a=n(18866),l=n(35804),c=n(5946),u=n(76735),p=n(59964),h=n(38588),f=n(42566),d=n(87526),m=n(17645);function g(e){if(e instanceof r)return e;if("string"==typeof e)return new s(e);if("number"==typeof e)return new i(e);if("boolean"==typeof e)return new a(e);if(null===e)return new o;if(Array.isArray(e))return new l(e.map(g));if("object"==typeof e){return new u(e)}return e}r.prototype.ObjectElement=u,r.prototype.RefElement=h,r.prototype.MemberElement=c,r.prototype.refract=g,f.prototype.refract=g,e.exports={Element:r,NullElement:o,StringElement:s,NumberElement:i,BooleanElement:a,ArrayElement:l,MemberElement:c,ObjectElement:u,LinkElement:p,RefElement:h,refract:g,ArraySlice:f,ObjectSlice:d,KeyValuePair:m}},59964:(e,t,n)=>{const r=n(3079);e.exports=class extends r{constructor(e,t,n){super(e||[],t,n),this.element="link"}get relation(){return this.attributes.get("relation")}set relation(e){this.attributes.set("relation",e)}get href(){return this.attributes.get("href")}set href(e){this.attributes.set("href",e)}}},38588:(e,t,n)=>{const r=n(3079);e.exports=class extends r{constructor(e,t,n){super(e||[],t,n),this.element="ref",this.path||(this.path="element")}get path(){return this.attributes.get("path")}set path(e){this.attributes.set("path",e)}}},43500:(e,t,n)=>{const r=n(78520),o=n(99829);t.lS=r,n(17645),t.O4=o.ArraySlice,o.ObjectSlice,t.W_=o.Element,t.RP=o.StringElement,t.VL=o.NumberElement,t.hh=o.BooleanElement,t.zr=o.NullElement,t.ON=o.ArrayElement,t.Sb=o.ObjectElement,t.c6=o.MemberElement,t.tK=o.RefElement,t.EA=o.LinkElement,t.Qc=o.refract,n(28219),n(3414)},35804:(e,t,n)=>{const r=n(94885),o=n(3079),s=n(42566);class i extends o{constructor(e,t,n){super(e||[],t,n),this.element="array"}primitive(){return"array"}get(e){return this.content[e]}getValue(e){const t=this.get(e);if(t)return t.toValue()}getIndex(e){return this.content[e]}set(e,t){return this.content[e]=this.refract(t),this}remove(e){const t=this.content.splice(e,1);return t.length?t[0]:null}map(e,t){return this.content.map(e,t)}flatMap(e,t){return this.map(e,t).reduce(((e,t)=>e.concat(t)),[])}compactMap(e,t){const n=[];return this.forEach((r=>{const o=e.bind(t)(r);o&&n.push(o)})),n}filter(e,t){return new s(this.content.filter(e,t))}reject(e,t){return this.filter(r(e),t)}reduce(e,t){let n,r;void 0!==t?(n=0,r=this.refract(t)):(n=1,r="object"===this.primitive()?this.first.value:this.first);for(let t=n;t{e.bind(t)(n,this.refract(r))}))}shift(){return this.content.shift()}unshift(e){this.content.unshift(this.refract(e))}push(e){return this.content.push(this.refract(e)),this}add(e){this.push(e)}findElements(e,t){const n=t||{},r=!!n.recursive,o=void 0===n.results?[]:n.results;return this.forEach(((t,n,s)=>{r&&void 0!==t.findElements&&t.findElements(e,{results:o,recursive:r}),e(t,n,s)&&o.push(t)})),o}find(e){return new s(this.findElements(e,{recursive:!0}))}findByElement(e){return this.find((t=>t.element===e))}findByClass(e){return this.find((t=>t.classes.includes(e)))}getById(e){return this.find((t=>t.id.toValue()===e)).first}includes(e){return this.content.some((t=>t.equals(e)))}contains(e){return this.includes(e)}empty(){return new this.constructor([])}"fantasy-land/empty"(){return this.empty()}concat(e){return new this.constructor(this.content.concat(e.content))}"fantasy-land/concat"(e){return this.concat(e)}"fantasy-land/map"(e){return new this.constructor(this.map(e))}"fantasy-land/chain"(e){return this.map((t=>e(t)),this).reduce(((e,t)=>e.concat(t)),this.empty())}"fantasy-land/filter"(e){return new this.constructor(this.content.filter(e))}"fantasy-land/reduce"(e,t){return this.content.reduce(e,t)}get length(){return this.content.length}get isEmpty(){return 0===this.content.length}get first(){return this.getIndex(0)}get second(){return this.getIndex(1)}get last(){return this.getIndex(this.length-1)}}i.empty=function(){return new this},i["fantasy-land/empty"]=i.empty,"undefined"!=typeof Symbol&&(i.prototype[Symbol.iterator]=function(){return this.content[Symbol.iterator]()}),e.exports=i},18866:(e,t,n)=>{const r=n(3079);e.exports=class extends r{constructor(e,t,n){super(e,t,n),this.element="boolean"}primitive(){return"boolean"}}},3079:(e,t,n)=>{const r=n(18446),o=n(17645),s=n(42566);class i{constructor(e,t,n){t&&(this.meta=t),n&&(this.attributes=n),this.content=e}freeze(){Object.isFrozen(this)||(this._meta&&(this.meta.parent=this,this.meta.freeze()),this._attributes&&(this.attributes.parent=this,this.attributes.freeze()),this.children.forEach((e=>{e.parent=this,e.freeze()}),this),this.content&&Array.isArray(this.content)&&Object.freeze(this.content),Object.freeze(this))}primitive(){}clone(){const e=new this.constructor;return e.element=this.element,this.meta.length&&(e._meta=this.meta.clone()),this.attributes.length&&(e._attributes=this.attributes.clone()),this.content?this.content.clone?e.content=this.content.clone():Array.isArray(this.content)?e.content=this.content.map((e=>e.clone())):e.content=this.content:e.content=this.content,e}toValue(){return this.content instanceof i?this.content.toValue():this.content instanceof o?{key:this.content.key.toValue(),value:this.content.value?this.content.value.toValue():void 0}:this.content&&this.content.map?this.content.map((e=>e.toValue()),this):this.content}toRef(e){if(""===this.id.toValue())throw Error("Cannot create reference to an element that does not contain an ID");const t=new this.RefElement(this.id.toValue());return e&&(t.path=e),t}findRecursive(...e){if(arguments.length>1&&!this.isFrozen)throw new Error("Cannot find recursive with multiple element names without first freezing the element. Call `element.freeze()`");const t=e.pop();let n=new s;const r=(e,t)=>(e.push(t),e),i=(e,n)=>{n.element===t&&e.push(n);const s=n.findRecursive(t);return s&&s.reduce(r,e),n.content instanceof o&&(n.content.key&&i(e,n.content.key),n.content.value&&i(e,n.content.value)),e};return this.content&&(this.content.element&&i(n,this.content),Array.isArray(this.content)&&this.content.reduce(i,n)),e.isEmpty||(n=n.filter((t=>{let n=t.parents.map((e=>e.element));for(const t in e){const r=e[t],o=n.indexOf(r);if(-1===o)return!1;n=n.splice(0,o)}return!0}))),n}set(e){return this.content=e,this}equals(e){return r(this.toValue(),e)}getMetaProperty(e,t){if(!this.meta.hasKey(e)){if(this.isFrozen){const e=this.refract(t);return e.freeze(),e}this.meta.set(e,t)}return this.meta.get(e)}setMetaProperty(e,t){this.meta.set(e,t)}get element(){return this._storedElement||"element"}set element(e){this._storedElement=e}get content(){return this._content}set content(e){if(e instanceof i)this._content=e;else if(e instanceof s)this.content=e.elements;else if("string"==typeof e||"number"==typeof e||"boolean"==typeof e||"null"===e||null==e)this._content=e;else if(e instanceof o)this._content=e;else if(Array.isArray(e))this._content=e.map(this.refract);else{if("object"!=typeof e)throw new Error("Cannot set content to given value");this._content=Object.keys(e).map((t=>new this.MemberElement(t,e[t])))}}get meta(){if(!this._meta){if(this.isFrozen){const e=new this.ObjectElement;return e.freeze(),e}this._meta=new this.ObjectElement}return this._meta}set meta(e){e instanceof this.ObjectElement?this._meta=e:this.meta.set(e||{})}get attributes(){if(!this._attributes){if(this.isFrozen){const e=new this.ObjectElement;return e.freeze(),e}this._attributes=new this.ObjectElement}return this._attributes}set attributes(e){e instanceof this.ObjectElement?this._attributes=e:this.attributes.set(e||{})}get id(){return this.getMetaProperty("id","")}set id(e){this.setMetaProperty("id",e)}get classes(){return this.getMetaProperty("classes",[])}set classes(e){this.setMetaProperty("classes",e)}get title(){return this.getMetaProperty("title","")}set title(e){this.setMetaProperty("title",e)}get description(){return this.getMetaProperty("description","")}set description(e){this.setMetaProperty("description",e)}get links(){return this.getMetaProperty("links",[])}set links(e){this.setMetaProperty("links",e)}get isFrozen(){return Object.isFrozen(this)}get parents(){let{parent:e}=this;const t=new s;for(;e;)t.push(e),e=e.parent;return t}get children(){if(Array.isArray(this.content))return new s(this.content);if(this.content instanceof o){const e=new s([this.content.key]);return this.content.value&&e.push(this.content.value),e}return this.content instanceof i?new s([this.content]):new s}get recursiveChildren(){const e=new s;return this.children.forEach((t=>{e.push(t),t.recursiveChildren.forEach((t=>{e.push(t)}))})),e}}e.exports=i},5946:(e,t,n)=>{const r=n(17645),o=n(3079);e.exports=class extends o{constructor(e,t,n,o){super(new r,n,o),this.element="member",this.key=e,this.value=t}get key(){return this.content.key}set key(e){this.content.key=this.refract(e)}get value(){return this.content.value}set value(e){this.content.value=this.refract(e)}}},96295:(e,t,n)=>{const r=n(3079);e.exports=class extends r{constructor(e,t,n){super(e||null,t,n),this.element="null"}primitive(){return"null"}set(){return new Error("Cannot set the value of null")}}},91090:(e,t,n)=>{const r=n(3079);e.exports=class extends r{constructor(e,t,n){super(e,t,n),this.element="number"}primitive(){return"number"}}},76735:(e,t,n)=>{const r=n(94885),o=n(13218),s=n(35804),i=n(5946),a=n(87526);e.exports=class extends s{constructor(e,t,n){super(e||[],t,n),this.element="object"}primitive(){return"object"}toValue(){return this.content.reduce(((e,t)=>(e[t.key.toValue()]=t.value?t.value.toValue():void 0,e)),{})}get(e){const t=this.getMember(e);if(t)return t.value}getMember(e){if(void 0!==e)return this.content.find((t=>t.key.toValue()===e))}remove(e){let t=null;return this.content=this.content.filter((n=>n.key.toValue()!==e||(t=n,!1))),t}getKey(e){const t=this.getMember(e);if(t)return t.key}set(e,t){if(o(e))return Object.keys(e).forEach((t=>{this.set(t,e[t])})),this;const n=e,r=this.getMember(n);return r?r.value=t:this.content.push(new i(n,t)),this}keys(){return this.content.map((e=>e.key.toValue()))}values(){return this.content.map((e=>e.value.toValue()))}hasKey(e){return this.content.some((t=>t.key.equals(e)))}items(){return this.content.map((e=>[e.key.toValue(),e.value.toValue()]))}map(e,t){return this.content.map((n=>e.bind(t)(n.value,n.key,n)))}compactMap(e,t){const n=[];return this.forEach(((r,o,s)=>{const i=e.bind(t)(r,o,s);i&&n.push(i)})),n}filter(e,t){return new a(this.content).filter(e,t)}reject(e,t){return this.filter(r(e),t)}forEach(e,t){return this.content.forEach((n=>e.bind(t)(n.value,n.key,n)))}}},16036:(e,t,n)=>{const r=n(3079);e.exports=class extends r{constructor(e,t,n){super(e,t,n),this.element="string"}primitive(){return"string"}get length(){return this.content.length}}},3414:(e,t,n)=>{const r=n(28219);e.exports=class extends r{serialise(e){if(!(e instanceof this.namespace.elements.Element))throw new TypeError(`Given element \`${e}\` is not an Element instance`);let t;e._attributes&&e.attributes.get("variable")&&(t=e.attributes.get("variable"));const n={element:e.element};e._meta&&e._meta.length>0&&(n.meta=this.serialiseObject(e.meta));const r="enum"===e.element||-1!==e.attributes.keys().indexOf("enumerations");if(r){const t=this.enumSerialiseAttributes(e);t&&(n.attributes=t)}else if(e._attributes&&e._attributes.length>0){let{attributes:r}=e;r.get("metadata")&&(r=r.clone(),r.set("meta",r.get("metadata")),r.remove("metadata")),"member"===e.element&&t&&(r=r.clone(),r.remove("variable")),r.length>0&&(n.attributes=this.serialiseObject(r))}if(r)n.content=this.enumSerialiseContent(e,n);else if(this[`${e.element}SerialiseContent`])n.content=this[`${e.element}SerialiseContent`](e,n);else if(void 0!==e.content){let r;t&&e.content.key?(r=e.content.clone(),r.key.attributes.set("variable",t),r=this.serialiseContent(r)):r=this.serialiseContent(e.content),this.shouldSerialiseContent(e,r)&&(n.content=r)}else this.shouldSerialiseContent(e,e.content)&&e instanceof this.namespace.elements.Array&&(n.content=[]);return n}shouldSerialiseContent(e,t){return"parseResult"===e.element||"httpRequest"===e.element||"httpResponse"===e.element||"category"===e.element||"link"===e.element||void 0!==t&&(!Array.isArray(t)||0!==t.length)}refSerialiseContent(e,t){return delete t.attributes,{href:e.toValue(),path:e.path.toValue()}}sourceMapSerialiseContent(e){return e.toValue()}dataStructureSerialiseContent(e){return[this.serialiseContent(e.content)]}enumSerialiseAttributes(e){const t=e.attributes.clone(),n=t.remove("enumerations")||new this.namespace.elements.Array([]),r=t.get("default");let o=t.get("samples")||new this.namespace.elements.Array([]);if(r&&r.content&&(r.content.attributes&&r.content.attributes.remove("typeAttributes"),t.set("default",new this.namespace.elements.Array([r.content]))),o.forEach((e=>{e.content&&e.content.element&&e.content.attributes.remove("typeAttributes")})),e.content&&0!==n.length&&o.unshift(e.content),o=o.map((e=>e instanceof this.namespace.elements.Array?[e]:new this.namespace.elements.Array([e.content]))),o.length&&t.set("samples",o),t.length>0)return this.serialiseObject(t)}enumSerialiseContent(e){if(e._attributes){const t=e.attributes.get("enumerations");if(t&&t.length>0)return t.content.map((e=>{const t=e.clone();return t.attributes.remove("typeAttributes"),this.serialise(t)}))}if(e.content){const t=e.content.clone();return t.attributes.remove("typeAttributes"),[this.serialise(t)]}return[]}deserialise(e){if("string"==typeof e)return new this.namespace.elements.String(e);if("number"==typeof e)return new this.namespace.elements.Number(e);if("boolean"==typeof e)return new this.namespace.elements.Boolean(e);if(null===e)return new this.namespace.elements.Null;if(Array.isArray(e))return new this.namespace.elements.Array(e.map(this.deserialise,this));const t=this.namespace.getElementClass(e.element),n=new t;n.element!==e.element&&(n.element=e.element),e.meta&&this.deserialiseObject(e.meta,n.meta),e.attributes&&this.deserialiseObject(e.attributes,n.attributes);const r=this.deserialiseContent(e.content);if(void 0===r&&null!==n.content||(n.content=r),"enum"===n.element){n.content&&n.attributes.set("enumerations",n.content);let e=n.attributes.get("samples");if(n.attributes.remove("samples"),e){const r=e;e=new this.namespace.elements.Array,r.forEach((r=>{r.forEach((r=>{const o=new t(r);o.element=n.element,e.push(o)}))}));const o=e.shift();n.content=o?o.content:void 0,n.attributes.set("samples",e)}else n.content=void 0;let r=n.attributes.get("default");if(r&&r.length>0){r=r.get(0);const e=new t(r);e.element=n.element,n.attributes.set("default",e)}}else if("dataStructure"===n.element&&Array.isArray(n.content))[n.content]=n.content;else if("category"===n.element){const e=n.attributes.get("meta");e&&(n.attributes.set("metadata",e),n.attributes.remove("meta"))}else"member"===n.element&&n.key&&n.key._attributes&&n.key._attributes.getValue("variable")&&(n.attributes.set("variable",n.key.attributes.get("variable")),n.key.attributes.remove("variable"));return n}serialiseContent(e){if(e instanceof this.namespace.elements.Element)return this.serialise(e);if(e instanceof this.namespace.KeyValuePair){const t={key:this.serialise(e.key)};return e.value&&(t.value=this.serialise(e.value)),t}return e&&e.map?e.map(this.serialise,this):e}deserialiseContent(e){if(e){if(e.element)return this.deserialise(e);if(e.key){const t=new this.namespace.KeyValuePair(this.deserialise(e.key));return e.value&&(t.value=this.deserialise(e.value)),t}if(e.map)return e.map(this.deserialise,this)}return e}shouldRefract(e){return!!(e._attributes&&e.attributes.keys().length||e._meta&&e.meta.keys().length)||"enum"!==e.element&&(e.element!==e.primitive()||"member"===e.element)}convertKeyToRefract(e,t){return this.shouldRefract(t)?this.serialise(t):"enum"===t.element?this.serialiseEnum(t):"array"===t.element?t.map((t=>this.shouldRefract(t)||"default"===e?this.serialise(t):"array"===t.element||"object"===t.element||"enum"===t.element?t.children.map((e=>this.serialise(e))):t.toValue())):"object"===t.element?(t.content||[]).map(this.serialise,this):t.toValue()}serialiseEnum(e){return e.children.map((e=>this.serialise(e)))}serialiseObject(e){const t={};return e.forEach(((e,n)=>{if(e){const r=n.toValue();t[r]=this.convertKeyToRefract(r,e)}})),t}deserialiseObject(e,t){Object.keys(e).forEach((n=>{t.set(n,this.deserialise(e[n]))}))}}},28219:e=>{e.exports=class{constructor(e){this.namespace=e||new this.Namespace}serialise(e){if(!(e instanceof this.namespace.elements.Element))throw new TypeError(`Given element \`${e}\` is not an Element instance`);const t={element:e.element};e._meta&&e._meta.length>0&&(t.meta=this.serialiseObject(e.meta)),e._attributes&&e._attributes.length>0&&(t.attributes=this.serialiseObject(e.attributes));const n=this.serialiseContent(e.content);return void 0!==n&&(t.content=n),t}deserialise(e){if(!e.element)throw new Error("Given value is not an object containing an element name");const t=new(this.namespace.getElementClass(e.element));t.element!==e.element&&(t.element=e.element),e.meta&&this.deserialiseObject(e.meta,t.meta),e.attributes&&this.deserialiseObject(e.attributes,t.attributes);const n=this.deserialiseContent(e.content);return void 0===n&&null!==t.content||(t.content=n),t}serialiseContent(e){if(e instanceof this.namespace.elements.Element)return this.serialise(e);if(e instanceof this.namespace.KeyValuePair){const t={key:this.serialise(e.key)};return e.value&&(t.value=this.serialise(e.value)),t}if(e&&e.map){if(0===e.length)return;return e.map(this.serialise,this)}return e}deserialiseContent(e){if(e){if(e.element)return this.deserialise(e);if(e.key){const t=new this.namespace.KeyValuePair(this.deserialise(e.key));return e.value&&(t.value=this.deserialise(e.value)),t}if(e.map)return e.map(this.deserialise,this)}return e}serialiseObject(e){const t={};if(e.forEach(((e,n)=>{e&&(t[n.toValue()]=this.serialise(e))})),0!==Object.keys(t).length)return t}deserialiseObject(e,t){Object.keys(e).forEach((n=>{t.set(n,this.deserialise(e[n]))}))}}},27418:e=>{"use strict";var t=Object.getOwnPropertySymbols,n=Object.prototype.hasOwnProperty,r=Object.prototype.propertyIsEnumerable;e.exports=function(){try{if(!Object.assign)return!1;var e=new String("abc");if(e[5]="de","5"===Object.getOwnPropertyNames(e)[0])return!1;for(var t={},n=0;n<10;n++)t["_"+String.fromCharCode(n)]=n;if("0123456789"!==Object.getOwnPropertyNames(t).map((function(e){return t[e]})).join(""))return!1;var r={};return"abcdefghijklmnopqrst".split("").forEach((function(e){r[e]=e})),"abcdefghijklmnopqrst"===Object.keys(Object.assign({},r)).join("")}catch(e){return!1}}()?Object.assign:function(e,o){for(var s,i,a=function(e){if(null==e)throw new TypeError("Object.assign cannot be called with null or undefined");return Object(e)}(e),l=1;l{var r="function"==typeof Map&&Map.prototype,o=Object.getOwnPropertyDescriptor&&r?Object.getOwnPropertyDescriptor(Map.prototype,"size"):null,s=r&&o&&"function"==typeof o.get?o.get:null,i=r&&Map.prototype.forEach,a="function"==typeof Set&&Set.prototype,l=Object.getOwnPropertyDescriptor&&a?Object.getOwnPropertyDescriptor(Set.prototype,"size"):null,c=a&&l&&"function"==typeof l.get?l.get:null,u=a&&Set.prototype.forEach,p="function"==typeof WeakMap&&WeakMap.prototype?WeakMap.prototype.has:null,h="function"==typeof WeakSet&&WeakSet.prototype?WeakSet.prototype.has:null,f="function"==typeof WeakRef&&WeakRef.prototype?WeakRef.prototype.deref:null,d=Boolean.prototype.valueOf,m=Object.prototype.toString,g=Function.prototype.toString,y=String.prototype.match,v=String.prototype.slice,b=String.prototype.replace,w=String.prototype.toUpperCase,E=String.prototype.toLowerCase,x=RegExp.prototype.test,S=Array.prototype.concat,_=Array.prototype.join,j=Array.prototype.slice,O=Math.floor,k="function"==typeof BigInt?BigInt.prototype.valueOf:null,A=Object.getOwnPropertySymbols,C="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?Symbol.prototype.toString:null,P="function"==typeof Symbol&&"object"==typeof Symbol.iterator,N="function"==typeof Symbol&&Symbol.toStringTag&&(typeof Symbol.toStringTag===P||"symbol")?Symbol.toStringTag:null,I=Object.prototype.propertyIsEnumerable,T=("function"==typeof Reflect?Reflect.getPrototypeOf:Object.getPrototypeOf)||([].__proto__===Array.prototype?function(e){return e.__proto__}:null);function R(e,t){if(e===1/0||e===-1/0||e!=e||e&&e>-1e3&&e<1e3||x.call(/e/,t))return t;var n=/[0-9](?=(?:[0-9]{3})+(?![0-9]))/g;if("number"==typeof e){var r=e<0?-O(-e):O(e);if(r!==e){var o=String(r),s=v.call(t,o.length+1);return b.call(o,n,"$&_")+"."+b.call(b.call(s,/([0-9]{3})/g,"$&_"),/_$/,"")}}return b.call(t,n,"$&_")}var M=n(24654),D=M.custom,F=U(D)?D:null;function L(e,t,n){var r="double"===(n.quoteStyle||t)?'"':"'";return r+e+r}function B(e){return b.call(String(e),/"/g,""")}function $(e){return!("[object Array]"!==W(e)||N&&"object"==typeof e&&N in e)}function q(e){return!("[object RegExp]"!==W(e)||N&&"object"==typeof e&&N in e)}function U(e){if(P)return e&&"object"==typeof e&&e instanceof Symbol;if("symbol"==typeof e)return!0;if(!e||"object"!=typeof e||!C)return!1;try{return C.call(e),!0}catch(e){}return!1}e.exports=function e(t,n,r,o){var a=n||{};if(V(a,"quoteStyle")&&"single"!==a.quoteStyle&&"double"!==a.quoteStyle)throw new TypeError('option "quoteStyle" must be "single" or "double"');if(V(a,"maxStringLength")&&("number"==typeof a.maxStringLength?a.maxStringLength<0&&a.maxStringLength!==1/0:null!==a.maxStringLength))throw new TypeError('option "maxStringLength", if provided, must be a positive integer, Infinity, or `null`');var l=!V(a,"customInspect")||a.customInspect;if("boolean"!=typeof l&&"symbol"!==l)throw new TypeError("option \"customInspect\", if provided, must be `true`, `false`, or `'symbol'`");if(V(a,"indent")&&null!==a.indent&&"\t"!==a.indent&&!(parseInt(a.indent,10)===a.indent&&a.indent>0))throw new TypeError('option "indent" must be "\\t", an integer > 0, or `null`');if(V(a,"numericSeparator")&&"boolean"!=typeof a.numericSeparator)throw new TypeError('option "numericSeparator", if provided, must be `true` or `false`');var m=a.numericSeparator;if(void 0===t)return"undefined";if(null===t)return"null";if("boolean"==typeof t)return t?"true":"false";if("string"==typeof t)return K(t,a);if("number"==typeof t){if(0===t)return 1/0/t>0?"0":"-0";var w=String(t);return m?R(t,w):w}if("bigint"==typeof t){var x=String(t)+"n";return m?R(t,x):x}var O=void 0===a.depth?5:a.depth;if(void 0===r&&(r=0),r>=O&&O>0&&"object"==typeof t)return $(t)?"[Array]":"[Object]";var A=function(e,t){var n;if("\t"===e.indent)n="\t";else{if(!("number"==typeof e.indent&&e.indent>0))return null;n=_.call(Array(e.indent+1)," ")}return{base:n,prev:_.call(Array(t+1),n)}}(a,r);if(void 0===o)o=[];else if(J(o,t)>=0)return"[Circular]";function D(t,n,s){if(n&&(o=j.call(o)).push(n),s){var i={depth:a.depth};return V(a,"quoteStyle")&&(i.quoteStyle=a.quoteStyle),e(t,i,r+1,o)}return e(t,a,r+1,o)}if("function"==typeof t&&!q(t)){var z=function(e){if(e.name)return e.name;var t=y.call(g.call(e),/^function\s*([\w$]+)/);if(t)return t[1];return null}(t),H=Q(t,D);return"[Function"+(z?": "+z:" (anonymous)")+"]"+(H.length>0?" { "+_.call(H,", ")+" }":"")}if(U(t)){var ee=P?b.call(String(t),/^(Symbol\(.*\))_[^)]*$/,"$1"):C.call(t);return"object"!=typeof t||P?ee:G(ee)}if(function(e){if(!e||"object"!=typeof e)return!1;if("undefined"!=typeof HTMLElement&&e instanceof HTMLElement)return!0;return"string"==typeof e.nodeName&&"function"==typeof e.getAttribute}(t)){for(var te="<"+E.call(String(t.nodeName)),ne=t.attributes||[],re=0;re"}if($(t)){if(0===t.length)return"[]";var oe=Q(t,D);return A&&!function(e){for(var t=0;t=0)return!1;return!0}(oe)?"["+X(oe,A)+"]":"[ "+_.call(oe,", ")+" ]"}if(function(e){return!("[object Error]"!==W(e)||N&&"object"==typeof e&&N in e)}(t)){var se=Q(t,D);return"cause"in Error.prototype||!("cause"in t)||I.call(t,"cause")?0===se.length?"["+String(t)+"]":"{ ["+String(t)+"] "+_.call(se,", ")+" }":"{ ["+String(t)+"] "+_.call(S.call("[cause]: "+D(t.cause),se),", ")+" }"}if("object"==typeof t&&l){if(F&&"function"==typeof t[F]&&M)return M(t,{depth:O-r});if("symbol"!==l&&"function"==typeof t.inspect)return t.inspect()}if(function(e){if(!s||!e||"object"!=typeof e)return!1;try{s.call(e);try{c.call(e)}catch(e){return!0}return e instanceof Map}catch(e){}return!1}(t)){var ie=[];return i&&i.call(t,(function(e,n){ie.push(D(n,t,!0)+" => "+D(e,t))})),Y("Map",s.call(t),ie,A)}if(function(e){if(!c||!e||"object"!=typeof e)return!1;try{c.call(e);try{s.call(e)}catch(e){return!0}return e instanceof Set}catch(e){}return!1}(t)){var ae=[];return u&&u.call(t,(function(e){ae.push(D(e,t))})),Y("Set",c.call(t),ae,A)}if(function(e){if(!p||!e||"object"!=typeof e)return!1;try{p.call(e,p);try{h.call(e,h)}catch(e){return!0}return e instanceof WeakMap}catch(e){}return!1}(t))return Z("WeakMap");if(function(e){if(!h||!e||"object"!=typeof e)return!1;try{h.call(e,h);try{p.call(e,p)}catch(e){return!0}return e instanceof WeakSet}catch(e){}return!1}(t))return Z("WeakSet");if(function(e){if(!f||!e||"object"!=typeof e)return!1;try{return f.call(e),!0}catch(e){}return!1}(t))return Z("WeakRef");if(function(e){return!("[object Number]"!==W(e)||N&&"object"==typeof e&&N in e)}(t))return G(D(Number(t)));if(function(e){if(!e||"object"!=typeof e||!k)return!1;try{return k.call(e),!0}catch(e){}return!1}(t))return G(D(k.call(t)));if(function(e){return!("[object Boolean]"!==W(e)||N&&"object"==typeof e&&N in e)}(t))return G(d.call(t));if(function(e){return!("[object String]"!==W(e)||N&&"object"==typeof e&&N in e)}(t))return G(D(String(t)));if(!function(e){return!("[object Date]"!==W(e)||N&&"object"==typeof e&&N in e)}(t)&&!q(t)){var le=Q(t,D),ce=T?T(t)===Object.prototype:t instanceof Object||t.constructor===Object,ue=t instanceof Object?"":"null prototype",pe=!ce&&N&&Object(t)===t&&N in t?v.call(W(t),8,-1):ue?"Object":"",he=(ce||"function"!=typeof t.constructor?"":t.constructor.name?t.constructor.name+" ":"")+(pe||ue?"["+_.call(S.call([],pe||[],ue||[]),": ")+"] ":"");return 0===le.length?he+"{}":A?he+"{"+X(le,A)+"}":he+"{ "+_.call(le,", ")+" }"}return String(t)};var z=Object.prototype.hasOwnProperty||function(e){return e in this};function V(e,t){return z.call(e,t)}function W(e){return m.call(e)}function J(e,t){if(e.indexOf)return e.indexOf(t);for(var n=0,r=e.length;nt.maxStringLength){var n=e.length-t.maxStringLength,r="... "+n+" more character"+(n>1?"s":"");return K(v.call(e,0,t.maxStringLength),t)+r}return L(b.call(b.call(e,/(['\\])/g,"\\$1"),/[\x00-\x1f]/g,H),"single",t)}function H(e){var t=e.charCodeAt(0),n={8:"b",9:"t",10:"n",12:"f",13:"r"}[t];return n?"\\"+n:"\\x"+(t<16?"0":"")+w.call(t.toString(16))}function G(e){return"Object("+e+")"}function Z(e){return e+" { ? }"}function Y(e,t,n,r){return e+" ("+t+") {"+(r?X(n,r):_.call(n,", "))+"}"}function X(e,t){if(0===e.length)return"";var n="\n"+t.prev+t.base;return n+_.call(e,","+n)+"\n"+t.prev}function Q(e,t){var n=$(e),r=[];if(n){r.length=e.length;for(var o=0;o{var t,n,r=e.exports={};function o(){throw new Error("setTimeout has not been defined")}function s(){throw new Error("clearTimeout has not been defined")}function i(e){if(t===setTimeout)return setTimeout(e,0);if((t===o||!t)&&setTimeout)return t=setTimeout,setTimeout(e,0);try{return t(e,0)}catch(n){try{return t.call(null,e,0)}catch(n){return t.call(this,e,0)}}}!function(){try{t="function"==typeof setTimeout?setTimeout:o}catch(e){t=o}try{n="function"==typeof clearTimeout?clearTimeout:s}catch(e){n=s}}();var a,l=[],c=!1,u=-1;function p(){c&&a&&(c=!1,a.length?l=a.concat(l):u=-1,l.length&&h())}function h(){if(!c){var e=i(p);c=!0;for(var t=l.length;t;){for(a=l,l=[];++u1)for(var n=1;n{"use strict";var r=n(50414);function o(){}function s(){}s.resetWarningCache=o,e.exports=function(){function e(e,t,n,o,s,i){if(i!==r){var a=new Error("Calling PropTypes validators directly is not supported by the `prop-types` package. Use PropTypes.checkPropTypes() to call them. Read more at http://fb.me/use-check-prop-types");throw a.name="Invariant Violation",a}}function t(){return e}e.isRequired=e;var n={array:e,bigint:e,bool:e,func:e,number:e,object:e,string:e,symbol:e,any:e,arrayOf:t,element:e,elementType:e,instanceOf:t,node:e,objectOf:t,oneOf:t,oneOfType:t,shape:t,exact:t,checkPropTypes:s,resetWarningCache:o};return n.PropTypes=n,n}},45697:(e,t,n)=>{e.exports=n(92703)()},50414:e=>{"use strict";e.exports="SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED"},55798:e=>{"use strict";var t=String.prototype.replace,n=/%20/g,r="RFC1738",o="RFC3986";e.exports={default:o,formatters:{RFC1738:function(e){return t.call(e,n,"+")},RFC3986:function(e){return String(e)}},RFC1738:r,RFC3986:o}},80129:(e,t,n)=>{"use strict";var r=n(58261),o=n(55235),s=n(55798);e.exports={formats:s,parse:o,stringify:r}},55235:(e,t,n)=>{"use strict";var r=n(12769),o=Object.prototype.hasOwnProperty,s=Array.isArray,i={allowDots:!1,allowPrototypes:!1,allowSparse:!1,arrayLimit:20,charset:"utf-8",charsetSentinel:!1,comma:!1,decoder:r.decode,delimiter:"&",depth:5,ignoreQueryPrefix:!1,interpretNumericEntities:!1,parameterLimit:1e3,parseArrays:!0,plainObjects:!1,strictNullHandling:!1},a=function(e){return e.replace(/&#(\d+);/g,(function(e,t){return String.fromCharCode(parseInt(t,10))}))},l=function(e,t){return e&&"string"==typeof e&&t.comma&&e.indexOf(",")>-1?e.split(","):e},c=function(e,t,n,r){if(e){var s=n.allowDots?e.replace(/\.([^.[]+)/g,"[$1]"):e,i=/(\[[^[\]]*])/g,a=n.depth>0&&/(\[[^[\]]*])/.exec(s),c=a?s.slice(0,a.index):s,u=[];if(c){if(!n.plainObjects&&o.call(Object.prototype,c)&&!n.allowPrototypes)return;u.push(c)}for(var p=0;n.depth>0&&null!==(a=i.exec(s))&&p=0;--s){var i,a=e[s];if("[]"===a&&n.parseArrays)i=[].concat(o);else{i=n.plainObjects?Object.create(null):{};var c="["===a.charAt(0)&&"]"===a.charAt(a.length-1)?a.slice(1,-1):a,u=parseInt(c,10);n.parseArrays||""!==c?!isNaN(u)&&a!==c&&String(u)===c&&u>=0&&n.parseArrays&&u<=n.arrayLimit?(i=[])[u]=o:"__proto__"!==c&&(i[c]=o):i={0:o}}o=i}return o}(u,t,n,r)}};e.exports=function(e,t){var n=function(e){if(!e)return i;if(null!==e.decoder&&void 0!==e.decoder&&"function"!=typeof e.decoder)throw new TypeError("Decoder has to be a function.");if(void 0!==e.charset&&"utf-8"!==e.charset&&"iso-8859-1"!==e.charset)throw new TypeError("The charset option must be either utf-8, iso-8859-1, or undefined");var t=void 0===e.charset?i.charset:e.charset;return{allowDots:void 0===e.allowDots?i.allowDots:!!e.allowDots,allowPrototypes:"boolean"==typeof e.allowPrototypes?e.allowPrototypes:i.allowPrototypes,allowSparse:"boolean"==typeof e.allowSparse?e.allowSparse:i.allowSparse,arrayLimit:"number"==typeof e.arrayLimit?e.arrayLimit:i.arrayLimit,charset:t,charsetSentinel:"boolean"==typeof e.charsetSentinel?e.charsetSentinel:i.charsetSentinel,comma:"boolean"==typeof e.comma?e.comma:i.comma,decoder:"function"==typeof e.decoder?e.decoder:i.decoder,delimiter:"string"==typeof e.delimiter||r.isRegExp(e.delimiter)?e.delimiter:i.delimiter,depth:"number"==typeof e.depth||!1===e.depth?+e.depth:i.depth,ignoreQueryPrefix:!0===e.ignoreQueryPrefix,interpretNumericEntities:"boolean"==typeof e.interpretNumericEntities?e.interpretNumericEntities:i.interpretNumericEntities,parameterLimit:"number"==typeof e.parameterLimit?e.parameterLimit:i.parameterLimit,parseArrays:!1!==e.parseArrays,plainObjects:"boolean"==typeof e.plainObjects?e.plainObjects:i.plainObjects,strictNullHandling:"boolean"==typeof e.strictNullHandling?e.strictNullHandling:i.strictNullHandling}}(t);if(""===e||null==e)return n.plainObjects?Object.create(null):{};for(var u="string"==typeof e?function(e,t){var n,c={},u=t.ignoreQueryPrefix?e.replace(/^\?/,""):e,p=t.parameterLimit===1/0?void 0:t.parameterLimit,h=u.split(t.delimiter,p),f=-1,d=t.charset;if(t.charsetSentinel)for(n=0;n-1&&(g=s(g)?[g]:g),o.call(c,m)?c[m]=r.combine(c[m],g):c[m]=g}return c}(e,n):e,p=n.plainObjects?Object.create(null):{},h=Object.keys(u),f=0;f{"use strict";var r=n(37478),o=n(12769),s=n(55798),i=Object.prototype.hasOwnProperty,a={brackets:function(e){return e+"[]"},comma:"comma",indices:function(e,t){return e+"["+t+"]"},repeat:function(e){return e}},l=Array.isArray,c=String.prototype.split,u=Array.prototype.push,p=function(e,t){u.apply(e,l(t)?t:[t])},h=Date.prototype.toISOString,f=s.default,d={addQueryPrefix:!1,allowDots:!1,charset:"utf-8",charsetSentinel:!1,delimiter:"&",encode:!0,encoder:o.encode,encodeValuesOnly:!1,format:f,formatter:s.formatters[f],indices:!1,serializeDate:function(e){return h.call(e)},skipNulls:!1,strictNullHandling:!1},m={},g=function e(t,n,s,i,a,u,h,f,g,y,v,b,w,E,x,S){for(var _,j=t,O=S,k=0,A=!1;void 0!==(O=O.get(m))&&!A;){var C=O.get(t);if(k+=1,void 0!==C){if(C===k)throw new RangeError("Cyclic object value");A=!0}void 0===O.get(m)&&(k=0)}if("function"==typeof f?j=f(n,j):j instanceof Date?j=v(j):"comma"===s&&l(j)&&(j=o.maybeMap(j,(function(e){return e instanceof Date?v(e):e}))),null===j){if(a)return h&&!E?h(n,d.encoder,x,"key",b):n;j=""}if("string"==typeof(_=j)||"number"==typeof _||"boolean"==typeof _||"symbol"==typeof _||"bigint"==typeof _||o.isBuffer(j)){if(h){var P=E?n:h(n,d.encoder,x,"key",b);if("comma"===s&&E){for(var N=c.call(String(j),","),I="",T=0;T0?j.join(",")||null:void 0}];else if(l(f))R=f;else{var D=Object.keys(j);R=g?D.sort(g):D}for(var F=i&&l(j)&&1===j.length?n+"[]":n,L=0;L0?E+w:""}},12769:(e,t,n)=>{"use strict";var r=n(55798),o=Object.prototype.hasOwnProperty,s=Array.isArray,i=function(){for(var e=[],t=0;t<256;++t)e.push("%"+((t<16?"0":"")+t.toString(16)).toUpperCase());return e}(),a=function(e,t){for(var n=t&&t.plainObjects?Object.create(null):{},r=0;r1;){var t=e.pop(),n=t.obj[t.prop];if(s(n)){for(var r=[],o=0;o=48&&u<=57||u>=65&&u<=90||u>=97&&u<=122||s===r.RFC1738&&(40===u||41===u)?l+=a.charAt(c):u<128?l+=i[u]:u<2048?l+=i[192|u>>6]+i[128|63&u]:u<55296||u>=57344?l+=i[224|u>>12]+i[128|u>>6&63]+i[128|63&u]:(c+=1,u=65536+((1023&u)<<10|1023&a.charCodeAt(c)),l+=i[240|u>>18]+i[128|u>>12&63]+i[128|u>>6&63]+i[128|63&u])}return l},isBuffer:function(e){return!(!e||"object"!=typeof e)&&!!(e.constructor&&e.constructor.isBuffer&&e.constructor.isBuffer(e))},isRegExp:function(e){return"[object RegExp]"===Object.prototype.toString.call(e)},maybeMap:function(e,t){if(s(e)){for(var n=[],r=0;r{"use strict";var n=Object.prototype.hasOwnProperty;function r(e){try{return decodeURIComponent(e.replace(/\+/g," "))}catch(e){return null}}function o(e){try{return encodeURIComponent(e)}catch(e){return null}}t.stringify=function(e,t){t=t||"";var r,s,i=[];for(s in"string"!=typeof t&&(t="?"),e)if(n.call(e,s)){if((r=e[s])||null!=r&&!isNaN(r)||(r=""),s=o(s),r=o(r),null===s||null===r)continue;i.push(s+"="+r)}return i.length?t+i.join("&"):""},t.parse=function(e){for(var t,n=/([^=?#&]+)=?([^&]*)/g,o={};t=n.exec(e);){var s=r(t[1]),i=r(t[2]);null===s||null===i||s in o||(o[s]=i)}return o}},14419:(e,t,n)=>{const r=n(60697),o=n(69450),s=r.types;e.exports=class e{constructor(e,t){if(this._setDefaults(e),e instanceof RegExp)this.ignoreCase=e.ignoreCase,this.multiline=e.multiline,e=e.source;else{if("string"!=typeof e)throw new Error("Expected a regexp or string");this.ignoreCase=t&&-1!==t.indexOf("i"),this.multiline=t&&-1!==t.indexOf("m")}this.tokens=r(e)}_setDefaults(t){this.max=null!=t.max?t.max:null!=e.prototype.max?e.prototype.max:100,this.defaultRange=t.defaultRange?t.defaultRange:this.defaultRange.clone(),t.randInt&&(this.randInt=t.randInt)}gen(){return this._gen(this.tokens,[])}_gen(e,t){var n,r,o,i,a;switch(e.type){case s.ROOT:case s.GROUP:if(e.followedBy||e.notFollowedBy)return"";for(e.remember&&void 0===e.groupNumber&&(e.groupNumber=t.push(null)-1),r="",i=0,a=(n=e.options?this._randSelect(e.options):e.stack).length;i{"use strict";var r=n(34155),o=65536,s=4294967295;var i=n(89509).Buffer,a=n.g.crypto||n.g.msCrypto;a&&a.getRandomValues?e.exports=function(e,t){if(e>s)throw new RangeError("requested too many random bytes");var n=i.allocUnsafe(e);if(e>0)if(e>o)for(var l=0;l{"use strict";function r(e){return r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},r(e)}Object.defineProperty(t,"__esModule",{value:!0}),t.CopyToClipboard=void 0;var o=a(n(67294)),s=a(n(20640)),i=["text","onCopy","options","children"];function a(e){return e&&e.__esModule?e:{default:e}}function l(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function c(e){for(var t=1;t=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var s=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}function p(e,t){for(var n=0;n{"use strict";var r=n(74300).CopyToClipboard;r.CopyToClipboard=r,e.exports=r},53441:(e,t,n)=>{"use strict";function r(e){return r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},r(e)}Object.defineProperty(t,"__esModule",{value:!0}),t.DebounceInput=void 0;var o=a(n(67294)),s=a(n(91296)),i=["element","onChange","value","minLength","debounceTimeout","forceNotifyByEnter","forceNotifyOnBlur","onKeyDown","onBlur","inputRef"];function a(e){return e&&e.__esModule?e:{default:e}}function l(e,t){if(null==e)return{};var n,r,o=function(e,t){if(null==e)return{};var n,r,o={},s=Object.keys(e);for(r=0;r=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var s=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}function c(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function u(e){for(var t=1;t=r?t.notify(e):n.length>o.length&&t.notify(u(u({},e),{},{target:u(u({},e.target),{},{value:""})}))}))})),g(d(t),"onKeyDown",(function(e){"Enter"===e.key&&t.forceNotify(e);var n=t.props.onKeyDown;n&&(e.persist(),n(e))})),g(d(t),"onBlur",(function(e){t.forceNotify(e);var n=t.props.onBlur;n&&(e.persist(),n(e))})),g(d(t),"createNotifier",(function(e){if(e<0)t.notify=function(){return null};else if(0===e)t.notify=t.doNotify;else{var n=(0,s.default)((function(e){t.isDebouncing=!1,t.doNotify(e)}),e);t.notify=function(e){t.isDebouncing=!0,n(e)},t.flush=function(){return n.flush()},t.cancel=function(){t.isDebouncing=!1,n.cancel()}}})),g(d(t),"doNotify",(function(){t.props.onChange.apply(void 0,arguments)})),g(d(t),"forceNotify",(function(e){var n=t.props.debounceTimeout;if(t.isDebouncing||!(n>0)){t.cancel&&t.cancel();var r=t.state.value,o=t.props.minLength;r.length>=o?t.doNotify(e):t.doNotify(u(u({},e),{},{target:u(u({},e.target),{},{value:r})}))}})),t.isDebouncing=!1,t.state={value:void 0===e.value||null===e.value?"":e.value};var n=t.props.debounceTimeout;return t.createNotifier(n),t}return t=c,(n=[{key:"componentDidUpdate",value:function(e){if(!this.isDebouncing){var t=this.props,n=t.value,r=t.debounceTimeout,o=e.debounceTimeout,s=e.value,i=this.state.value;void 0!==n&&s!==n&&i!==n&&this.setState({value:n}),r!==o&&this.createNotifier(r)}}},{key:"componentWillUnmount",value:function(){this.flush&&this.flush()}},{key:"render",value:function(){var e,t,n=this.props,r=n.element,s=(n.onChange,n.value,n.minLength,n.debounceTimeout,n.forceNotifyByEnter),a=n.forceNotifyOnBlur,c=n.onKeyDown,p=n.onBlur,h=n.inputRef,f=l(n,i),d=this.state.value;e=s?{onKeyDown:this.onKeyDown}:c?{onKeyDown:c}:{},t=a?{onBlur:this.onBlur}:p?{onBlur:p}:{};var m=h?{ref:h}:{};return o.default.createElement(r,u(u(u(u({},f),{},{onChange:this.onChange,value:d},e),t),m))}}])&&p(t.prototype,n),r&&p(t,r),Object.defineProperty(t,"prototype",{writable:!1}),c}(o.default.PureComponent);t.DebounceInput=y,g(y,"defaultProps",{element:"input",type:"text",onKeyDown:void 0,onBlur:void 0,value:void 0,minLength:0,debounceTimeout:100,forceNotifyByEnter:!0,forceNotifyOnBlur:!0,inputRef:void 0})},775:(e,t,n)=>{"use strict";var r=n(53441).DebounceInput;r.DebounceInput=r,e.exports=r},64448:(e,t,n)=>{"use strict";var r=n(67294),o=n(27418),s=n(63840);function i(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n