From 8cceee4084c116dedc731da050d49549a8f71a1d Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Sat, 20 Aug 2022 00:20:56 +0000 Subject: [PATCH 01/85] [skip ci] Updated translations via Crowdin --- options/locale/locale_es-ES.ini | 14 +++--- options/locale/locale_fi-FI.ini | 89 +++++++++++++++++++++++++++++++-- options/locale/locale_fr-FR.ini | 1 + 3 files changed, 94 insertions(+), 10 deletions(-) diff --git a/options/locale/locale_es-ES.ini b/options/locale/locale_es-ES.ini index ea1ce54749..f7343b7f44 100644 --- a/options/locale/locale_es-ES.ini +++ b/options/locale/locale_es-ES.ini @@ -36,9 +36,9 @@ passcode=Código de acceso webauthn_insert_key=Introduzca su clave de seguridad webauthn_sign_in=Presione el botón en su clave de seguridad. Si su clave de seguridad no tiene ningún botón, vuelva a insertarla. -webauthn_press_button=Por favor, preione el botón en su clave de seguridad… +webauthn_press_button=Por favor, presione el botón de su llave de seguridad… webauthn_use_twofa=Utilice un código de doble factor desde su teléfono móvil -webauthn_error=No se pudo leer la clave de seguridad. +webauthn_error=No se pudo leer su llave de seguridad. webauthn_unsupported_browser=Su navegador no soporta actualmente WebAuthn. webauthn_error_unknown=Ha ocurrido un error desconocido. Por favor, inténtelo de nuevo. webauthn_error_insecure=WebAuthn sólo soporta conexiones seguras. Para probar sobre HTTP, puede utilizar el origen "localhost" o "127.0.0.1" @@ -533,7 +533,7 @@ twofa=Autenticación de doble factor account_link=Cuentas vinculadas organization=Organizaciones uid=UUID -webauthn=Claves Seguridades +webauthn=Llaves de Seguridad public_profile=Perfil público biography_placeholder=Cuéntenos un poco más sobre usted @@ -764,7 +764,7 @@ twofa_disable_note=Puede deshabilitar la autenticación de doble factor si lo ne twofa_disable_desc=Deshabilitar la autenticación de doble factor hará su cuenta menos segura. ¿Continuar? regenerate_scratch_token_desc=Si extravió su código de respaldo, o ya lo usó para iniciar sesión, puede restablecerlo aquí. twofa_disabled=La autenticación de doble factor ha sido deshabilitada. -scan_this_image=Analiza esta imagen con la aplicación de autenticación: +scan_this_image=Escanee esta imagen con su aplicación de autenticación: or_enter_secret=O introduzca el secreto: %s then_enter_passcode=E introduzca el código de acceso mostrado en la aplicación: passcode_invalid=El código de acceso es incorrecto. Vuelva a intentarlo. @@ -775,7 +775,7 @@ webauthn_desc=Las claves de seguridad son dispositivos hardware que contienen cl webauthn_register_key=Añadir clave de seguridad webauthn_nickname=Apodo webauthn_delete_key=Eliminar clave de seguridad -webauthn_delete_key_desc=Si elimina una clave de seguridad no podrá utilizarla para registrarte con ella. ¿Continuar? +webauthn_delete_key_desc=Si elimina una llave de seguridad ya no podrá utilizarla para iniciar sesión con ella. ¿Continuar? manage_account_links=Administrar cuentas vinculadas manage_account_links_desc=Estas cuentas externas están vinculadas a su cuenta de Gitea. @@ -1235,9 +1235,9 @@ issues.new_label_placeholder=Nombre etiqueta issues.new_label_desc_placeholder=Descripción issues.create_label=Crear etiqueta issues.label_templates.title=Carga un conjunto predefinido de etiquetas -issues.label_templates.info=No hay etiquetas existentes todavía. Crea una etiqueta con "Nueva Etiqueta" o use la etiqueta predefinida: +issues.label_templates.info=Todavía no existen etiquetas. Cree una etiqueta con "Nueva Etiqueta" o use un conjunto predefinido de etiquetas: issues.label_templates.helper=Seleccionar un conjunto de etiquetas -issues.label_templates.use=Utilice la etiqueta +issues.label_templates.use=Usar este conjunto de etiquetas issues.label_templates.fail_to_load_file=Error al cargar el archivo de plantilla de etiqueta '%s': %v issues.add_label=añadió la etiqueta %s %s issues.add_labels=añadió las etiquetas %s %s diff --git a/options/locale/locale_fi-FI.ini b/options/locale/locale_fi-FI.ini index 7c0cc31097..27f183e8d6 100644 --- a/options/locale/locale_fi-FI.ini +++ b/options/locale/locale_fi-FI.ini @@ -34,9 +34,12 @@ twofa_scratch=Kaksivaiheinen kertakäyttöinen koodi passcode=Tunnuskoodi webauthn_insert_key=Aseta turva-avaimesi +webauthn_sign_in=Paina turva-avaimesi painiketta. Jos turva-avaimessasi ei ole painiketta, irroita se ja aseta uudelleen. webauthn_press_button=Paina turva-avaimesi painiketta… +webauthn_use_twofa=Käytä kaksivaihesta vahvistusta puhelimestasi webauthn_error=Turva-avainta ei voitu lukea. webauthn_unsupported_browser=Selaimesi ei tällä hetkellä tue WebAuthnia. +webauthn_error_unknown=Tuntematon virhe. Yritä uudelleen. webauthn_error_insecure=WebAuthn tukee vain suojattuja yhteyksiä. Testaukseen HTTP:n yli, voit käyttää osoitetta "localhost" tai "127.0.0.1" webauthn_error_unable_to_process=Palvelin ei pystynyt toteuttamaan kutsua. webauthn_error_duplicated=Turva-avainta ei ole sallittu tässä pyynnössä. Varmista, ettei avainta ole jo rekisteröity. @@ -210,6 +213,7 @@ internal_token_failed=Sisäisen pääsymerkin luonti epäonnistui: %v save_config_failed=Asetusten tallentaminen epäonnistui: %v install_success=Tervetuloa! Kiitos kun valitsit Gitean. Pidä hauskaa! default_keep_email_private=Piilota sähköpostiosoitteet oletuksena +default_keep_email_private_popup=Piilota oletusarvoisesti uusien käyttäjätilien sähköpostiosoitteet. default_enable_timetracking=Ota ajan seuranta oletusarvoisesti käyttöön default_enable_timetracking_popup=Ota käyttöön uusien repojen aikaseuranta oletusarvoisesti. no_reply_address=Piilotettu sähköpostin verkkotunnus @@ -342,6 +346,7 @@ register_success=Rekisteröinti onnistui issue.x_mentioned_you=@%s mainitsi sinut: issue.action.push_1=@%[1]s työnsi %[3]d commitin kohteeseen %[2]s issue.action.push_n=@%[1]s työnsi %[3]d committia kohteeseen %[2]s +issue.action.reject=@%[1]s pyysi muutoksia tässä vetopyynnössä. release.title=Otsikko: %s release.note=Huomautus: @@ -489,6 +494,7 @@ comment_type_group_project=Projekti saved_successfully=Asetuksesi tallennettiin onnistuneesti. privacy=Yksityisyys keep_activity_private=Piilota toiminta profiilisivulta +keep_activity_private_popup=Tekee toiminnon näkyvän vain sinulle ja ylläpitäjille lookup_avatar_by_mail=Hae profiilikuva sähköpostin perusteella federated_avatar_lookup=Ulkopuolinen profiilikuvan haku @@ -601,6 +607,8 @@ generate_token=Luo pääsymerkki generate_token_success=Uusi pääsymerkkisi on nyt luotu. Kopioi se nyt, koska sitä ei näytetä enää uudelleen. delete_token=Poista access_token_deletion=Poista pääsymerkki +access_token_deletion_cancel_action=Peruuta +access_token_deletion_confirm_action=Poista edit_oauth2_application=Muokkaa OAuth2 sovellusta remove_oauth2_application=Poista OAuth2 sovellus @@ -647,6 +655,7 @@ visibility.public=Julkinen visibility.public_tooltip=Näkyvissä kaikille käyttäjille visibility.limited=Rajattu visibility.private=Yksityinen +visibility.private_tooltip=Näkyy vain organisaation jäsenille [repo] new_repo_helper=Repo sisältää kaikki projektitiedostot, mukaan lukien revisiohistorian. Onko sinulla repo jo muualla? Voit siirtää repon. @@ -714,7 +723,10 @@ migrate.github_token_desc=Voit laittaa yhden tai useamman pääsymerkin pilkulla migrate.permission_denied=Sinun ei sallita tuovan paikallisia repoja. migrate.failed=Siirto epäonnistui: %v migrate.migrate_items_options=Pääsymerkki vaaditaan lisäkohteiden siirtämiseen +migrate.migrating=Tuodaan kohteesta %s ... +migrate.migrating_failed=Tuonti kohteesta %s epäonnistui. migrate.migrating_failed.error=Virhe: %s +migrate.migrating_git=Tuodaan Git-tietoja mirror_from=peilaus alkaen forked_from=forkattu lähteestä @@ -722,7 +734,7 @@ unwatch=Lopeta tarkkailu watch=Tarkkaile unstar=Poista tähti star=Tähti -download_archive=Lataa varasto +download_archive=Lataa repo no_desc=Ei kuvausta quick_guide=Pikaopas @@ -790,6 +802,7 @@ editor.upload_files_to_dir=Lataa tiedostot kohteeseen '%s' editor.require_signed_commit=Haara vaatii vahvistetun commitin commits.commits=Commitit +commits.nothing_to_compare=Nämä haarat vastaavat toisiaan. commits.find=Haku commits.search_all=Kaikki haarat commits.author=Tekijä @@ -826,11 +839,14 @@ projects.open=Avaa projects.close=Sulje issues.desc=Ongelmien, tehtävien ja merkkipaalujen hallinta. +issues.filter_assignees=Suodata käyttäjiä issues.filter_milestones=Suodata merkkipaalu issues.new=Uusi ongelma issues.new.labels=Tunnisteet +issues.new.add_labels_title=Aseta tunniste issues.new.no_label=Ei tunnistetta issues.new.clear_labels=Tyhjennä tunnisteet +issues.new.no_items=Ei kohteita issues.new.milestone=Merkkipaalu issues.new.add_milestone_title=Aseta merkkipaalu issues.new.no_milestone=Ei merkkipaalua @@ -838,6 +854,7 @@ issues.new.clear_milestone=Tyhjennä merkkipaalu issues.new.open_milestone=Avoimet merkkipaalut issues.new.closed_milestone=Suljetut merkkipaalut issues.new.assignees=Käsittelijä +issues.new.add_assignees_title=Osoita käyttäjille issues.new.clear_assignees=Tyhjennä käsittelijä issues.new.no_assignees=Ei käsittelijää issues.choose.blank=Oletus @@ -849,8 +866,12 @@ issues.new_label_desc_placeholder=Kuvaus issues.create_label=Luo tunniste issues.label_templates.helper=Valitse tunnistejoukko issues.add_milestone_at=`lisäsi tämän merkkipaaluun %s %s` +issues.change_milestone_at=`vaihtoi merkkipaalun %s merkkipaaluun %s %s` +issues.remove_milestone_at=`poisti tämän %s merkkipaalusta %s` +issues.remove_project_at=`poisti tämän %s projektista %s` issues.deleted_milestone=`(poistettu)` issues.self_assign_at=`itse otti tämän käsittelyyn %s` +issues.change_title_at=`muutti otsikon %s otsikoksi %s %s` issues.delete_branch_at=`poisti haaran %s %s` issues.filter_label=Tunniste issues.filter_label_exclude=`Käytä alt + klikkaus/rivinvaihto poissulkeaksesi tunnisteita` @@ -892,6 +913,7 @@ issues.commented_at=`kommentoi %s` issues.delete_comment_confirm=Haluatko varmasti poistaa tämän kommentin? issues.context.copy_link=Kopioi linkki issues.context.quote_reply=Vastaa lainaamalla +issues.context.reference_issue=Viittaa uudesa ongelmassa issues.context.edit=Muokkaa issues.context.delete=Poista issues.no_content=Sisältöä ei vielä ole. @@ -936,9 +958,11 @@ issues.lock.reason=Lukitsemisen syy issues.lock.title=Lukitse keskustelu tästä ongelmasta. issues.unlock.title=Avaa keskustelu tästä ongelmasta. issues.tracker=Ajan seuranta +issues.start_tracking_short=Aloita ajanotto issues.start_tracking=Aloita ajan seuranta issues.start_tracking_history=`aloitti työskentelyn %s` issues.tracker_auto_close=Ajan seuranta pysähtyy automaattisesti kun tämä ongelma on suljettu +issues.stop_tracking=Pysäytä ajanotto issues.stop_tracking_history=`lopetti työskentelyn %s` issues.add_time=Lisää aika käsin issues.add_time_short=Lisää aika @@ -949,30 +973,44 @@ issues.add_time_minutes=Minuuttia issues.add_time_sum_to_small=Aikaa ei syötetty. issues.time_spent_from_all_authors=`Käytetty kokonaisaika: %s` issues.due_date=Määräpäivä +issues.push_commit_1=lisäsi %d commitin %s +issues.push_commits_n=lisäsi %d committia %s issues.due_date_form=vvvv-kk-pp issues.due_date_form_edit=Muokkaa issues.due_date_form_remove=Poista issues.due_date_not_set=Määräpäivää ei asetettu. issues.due_date_overdue=Myöhässä issues.dependency.title=Riippuvuudet +issues.dependency.issue_no_dependencies=Riippuvuuksia ei asetettu. +issues.dependency.pr_no_dependencies=Riippuvuuksia ei asetettu. issues.dependency.add=Lisää riippuvuus… issues.dependency.cancel=Peru issues.dependency.remove=Poista issues.dependency.remove_info=Poistä tämä riippuvuus issues.review.self.approval=Et voi hyväksyä omia vetopyyntöjä. +issues.review.self.rejection=Et voi pyytää muutoksia omaan vetopyyntöön. issues.review.approve=hyväksyi nämä muutokset %s issues.review.left_comment=jätti kommentin issues.review.pending=Odottaa +issues.review.pending.tooltip=Tämä kommentti ei tällä hetkellä näy muille käyttäjille. Lähettääksesi odottavat kommentit, valitse '%s' -> '%s/%s/%s' sivun yläreunassa. +issues.review.show_resolved=Näytä ratkaisu +issues.review.hide_resolved=Piilota ratkaisu issues.reference_issue.body=Kuvaus issues.content_history.deleted=poistettu issues.content_history.edited=muokattu issues.content_history.created=luotu -pulls.new=Uusi pull pyyntö +pulls.new=Uusi vetopyyntö +pulls.compare_changes=Uusi vetopyyntö +pulls.has_viewed_file=Katsottu +pulls.viewed_files_label=%[1]d / %[2]d tiedostoa katsottu +pulls.compare_compare=vedä kohteesta pulls.filter_branch=Suodata branch pulls.no_results=Tuloksia ei löytynyt. -pulls.nothing_to_compare=Nämä haarat ovat samanlaisia. Ei ole tarvetta luoda vetopyyntöä. +pulls.nothing_to_compare=Nämä haarat vastaavat toisiaan. Ei ole tarvetta luoda vetopyyntöä. +pulls.nothing_to_compare_and_allow_empty_pr=Nämä haarat vastaavat toisiaan. Vetopyyntö tulee olemaan tyhjä. +pulls.has_pull_request=`Vetopyyntö haarojen välillä on jo olemassa: %[2]s#%[3]d` pulls.create=Luo Pull-pyyntö pulls.title_desc=haluaa yhdistää %[1]d committia lähteestä %[2]s kohteeseen %[3]s pulls.merged_title_desc=yhdistetty %[1]d committia lähteestä %[2]s kohteeseen %[3]s %[4]s @@ -981,6 +1019,9 @@ pulls.tab_commits=Commitit pulls.tab_files=Muuttuneet tiedostot pulls.merged=Yhdistetty pulls.has_merged=Vetopyyntö on yhdistetty. +pulls.title_wip_desc=`Aloita otsikko sanalla %s estääksesi vetopyynnön yhdistämisen vahingossa.` +pulls.add_prefix=Lisää %s etuliite +pulls.remove_prefix=Poista %s etuliite pulls.can_auto_merge_desc=Tämä pull-pyyntö voidaan yhdistää automaattisesti. @@ -1113,18 +1154,37 @@ settings.githook_edit_desc=Jos koukku ei ole käytössä, esitellään esimerkki settings.githook_name=Koukun nimi settings.githook_content=Koukun sisältö settings.update_githook=Päivitys koukku +settings.payload_url=Kohde URL settings.http_method=HTTP-menetelmä settings.secret=Salaus settings.slack_username=Käyttäjätunnus settings.slack_icon_url=Kuvakkeen URL settings.discord_username=Käyttäjätunnus +settings.event_desc=Triggeröi: +settings.event_send_everything=Kaikki tapahtumat +settings.event_choose=Mukautetut tapahtumat… +settings.event_header_repository=Repon tapahtumat settings.event_create=Luo +settings.event_create_desc=Haara tai tagi luotu. settings.event_delete=Poista +settings.event_delete_desc=Haara tai tagi poistettu. settings.event_release_desc=Julkaisu julkaistu, päivitetty tai poistettu varastosta. settings.event_push=Työnnä +settings.event_push_desc=Git push repoon. settings.event_repository=Repo +settings.event_repository_desc=Repo luotu tai poistettu. +settings.event_header_issue=Ongelmien tapahtumat +settings.event_issues_desc=Ongelma avattu, suljettu, avattu uudelleen tai muokattu. +settings.event_issue_assign=Ongelma määritetty +settings.event_issue_assign_desc=Ongelma osoitettu tai osoitus poistettu. +settings.event_issue_label_desc=Ongelman tunnisteet päivitetty tai tyhjennetty. +settings.event_issue_milestone_desc=Ongelma merkkipaaluteettu tai merkkipaalu-osoitus poistettu. settings.event_issue_comment_desc=Ongelman kommentti luotu, muokattu tai poistettu. +settings.event_header_pull_request=Vetopyyntöjen tapahtumat settings.event_pull_request=Vetopyyntö +settings.event_package_desc=Paketti on luotu tai poistettu repossa. +settings.active_helper=Tiedot käynnistetyistä tapahtumista lähetetään tähän webkoukun URL-osoitteeseen. +settings.add_hook_success=Uusi webkoukku on lisätty. settings.update_webhook=Päivitä webkoukku settings.delete_webhook=Poista webkoukku settings.recent_deliveries=Viimeisimmät toimitukset @@ -1132,6 +1192,7 @@ settings.hook_type=Koukkutyyppi settings.slack_token=Pääsymerkki settings.slack_domain=Verkkotunnus settings.slack_channel=Kanava +settings.add_web_hook_desc=Integroi %s repoon. settings.web_hook_name_gitea=Gitea settings.web_hook_name_gogs=Gogs settings.web_hook_name_slack=Slack @@ -1175,13 +1236,21 @@ settings.no_protected_branch=Suojattuja haaroja ei ole. settings.edit_protected_branch=Muokkaa settings.protected_branch_required_approvals_min=Vaadittavat hyväksynnät ei voi olla negatiivinen. settings.tags=Tagit +settings.tags.protection=Tagien suojaaminen settings.tags.protection.pattern=Tagin kuvio +settings.tags.protection.allowed=Sallitut +settings.tags.protection.allowed.users=Sallitut käyttäjät +settings.tags.protection.allowed.teams=Sallitut tiimit +settings.tags.protection.allowed.noone=Ei kukaan +settings.tags.protection.create=Suojaa tagi +settings.tags.protection.none=Suojattuja tageja ei ole. settings.tags.protection.pattern.description=Voit käyttää yhtä nimeä tai glob-kuviota tai säännöllistä lauseketta, joka täsmää useisiin tageihin. Lue lisää suojatut tagit oppaasta. settings.bot_token=Botti pääsymerkki settings.matrix.homeserver_url=Kotipalvelimen URL settings.matrix.access_token=Pääsymerkki settings.archive.button=Arkistoi repo settings.archive.header=Arkistoi tämä repo +settings.archive.tagsettings_unavailable=Tagien asetukset eivät ole saatavilla, jos repo on arkistoitu. settings.lfs=LFS settings.lfs_filelist=LFS-tiedostot tallennettu tähän repoon settings.lfs_no_lfs_files=LFS-tiedostoja ei ole tallennettu tähän repoon. @@ -1224,10 +1293,15 @@ diff.view_file=Näytä tiedosto diff.file_image_width=Leveys diff.file_image_height=Korkeus diff.file_byte_size=Koko +diff.comment.markdown_info=Muotoilu markdownilla tuettu. +diff.comment.add_single_comment=Lisää yksittäinen kommentti diff.comment.add_review_comment=Lisää kommentti +diff.comment.start_review=Aloita tarkistus diff.comment.reply=Vastaa +diff.review.header=Lähetä arvio diff.review.comment=Kommentoi diff.review.approve=Hyväksy +diff.review.reject=Pyydä muutoksia release.releases=Julkaisut release.tags=Tagit @@ -1251,6 +1325,9 @@ release.publish=Julkaise versio release.save_draft=Tallenna luonnos release.edit_release=Päivitä julkaisu release.delete_release=Poista julkaisu +release.delete_tag=Poista tagi +release.deletion_tag_desc=Poistetaanko tämä tagi reposta? Repon sisältö ja historia pysyvät muuttumattomina. Jatketaanko? +release.deletion_tag_success=Tagi on poistettu. release.tag_name_invalid=Tagin nimi ei ole kelvollinen. release.downloads=Lataukset @@ -1304,7 +1381,9 @@ settings.visibility.private_shortname=Yksityinen settings.update_settings=Päivitä asetukset settings.delete=Poista organisaatio settings.delete_account=Poista tämä organisaatio +settings.delete_prompt=Organisaatio poistetaan pysyvästi, ja tätä EI VOI peruuttaa myöhemmin! settings.confirm_delete_account=Vahvista poisto +settings.hooks_desc=Lisää webkoukkuja, jotka suoritetaan kaikissa repoissa tässä organisaatiossa. members.membership_visibility=Jäsenyyden näkyvyys: @@ -1538,6 +1617,7 @@ config.show_registration_button=Näytä rekisteröidy painike config.disable_key_size_check=Poista käytöstä avaimen vähimmäiskoko tarkistus config.enable_captcha=Ota CAPTCHA käyttöön config.active_code_lives=Aktiivinen koodi elämät ennen vanhenemista +config.default_keep_email_private=Piilota sähköpostiosoitteet oletuksena config.default_visibility_organization=Uuden organisaation oletusnäkyvyys config.webhook_config=Webkoukku asetukset @@ -1592,6 +1672,7 @@ monitor.queues=Jonot monitor.queue=Jono: %s monitor.queue.name=Nimi monitor.queue.type=Tyyppi +monitor.queue.pool.addworkers.desc=Lisää käsittelijöitä tähän pooliin aikakatkaisulla tai ilman. Jos asetat aikakatkaisun, nämä käsittelijät poistetaan poolista kun aikakatkaisu on päättynyt. @@ -1612,7 +1693,9 @@ create_repo=luotu repo %s rename_repo=uudelleennimetty repo %[1]s nimelle %[3]s transfer_repo=siirretty repo %s kohteeseen %s push_tag=työnsi tagin %[3]s kohteeseen %[4]s +delete_tag=poisti tagin %[2]s kohteesta %[3]s compare_commits_general=Vertaa committeja +create_branch=loi haaran %[3]s repossa %[4]s [tool] ago=%s sitten diff --git a/options/locale/locale_fr-FR.ini b/options/locale/locale_fr-FR.ini index 18eb44b16a..3417fdbef9 100644 --- a/options/locale/locale_fr-FR.ini +++ b/options/locale/locale_fr-FR.ini @@ -1458,6 +1458,7 @@ pulls.required_status_check_missing=Certains contrôles requis sont manquants. pulls.required_status_check_administrator=En tant qu'administrateur, vous pouvez toujours fusionner cette requête de pull. pulls.blocked_by_approvals=Cette demande d'ajout n'a pas assez d'approbation. %d sur %d approbations accordées. pulls.blocked_by_rejection=Cette demande de fusion a des modifications demandées par un réviseur officiel. +pulls.blocked_by_official_review_requests=Cette demande d'ajout a des demandes de revue officielles. pulls.blocked_by_outdated_branch=Cette demande d'ajout est bloquée car elle est obsolète. pulls.blocked_by_changed_protected_files_1=Cette demande d'ajout est bloquée car elle modifie un fichier protégé : pulls.blocked_by_changed_protected_files_n=Cette Pull Request est bloquée car elle modifie les fichiers protégés : From 3d52edc7a43ca429b7921b96c3663c32179881ab Mon Sep 17 00:00:00 2001 From: Gusted Date: Sat, 20 Aug 2022 16:47:04 +0200 Subject: [PATCH 02/85] Don't open new page for ext wiki on same repository (#20725) - When the external wiki has been set to a file on the repository, don't open the page on a tab. - Resolves #20657 --- modules/templates/helper.go | 1 + templates/repo/header.tmpl | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/templates/helper.go b/modules/templates/helper.go index 602afec413..410857f37e 100644 --- a/modules/templates/helper.go +++ b/modules/templates/helper.go @@ -453,6 +453,7 @@ func NewFuncMap() []template.FuncMap { } return items }, + "HasPrefix": strings.HasPrefix, }} } diff --git a/templates/repo/header.tmpl b/templates/repo/header.tmpl index e6ed76bb60..a08196f264 100644 --- a/templates/repo/header.tmpl +++ b/templates/repo/header.tmpl @@ -206,7 +206,7 @@ {{end}} {{if or (.Permission.CanRead $.UnitTypeWiki) (.Permission.CanRead $.UnitTypeExternalWiki)}} - + {{svg "octicon-book"}} {{.locale.Tr "repo.wiki"}} {{end}} From cb37c6ba5cc3c8113a7252e16b386eb887346185 Mon Sep 17 00:00:00 2001 From: JonRB <4564448+eeyrjmr@users.noreply.github.com> Date: Sat, 20 Aug 2022 22:09:41 +0100 Subject: [PATCH 03/85] call builtinUnused() if internal SSH is disabled (#20877) The graceful manager waits for 4 listeners to be created or to be told that they are not needed. If it is not told about them it will indefinitely and timeout. This leads to SVC hosts not being told of being in the readyState but on Unix would lead to the termination of the process. There was an unfortunate regression in #20299 which missed this subtly and in the case whereby SSH is disabled the `builtinUnused()` is not called. This PR adds a call to `builtinUnused()` when not using the builtin ssh to allow `createServerWaitGroup.Done()` to be called. In addition it was noted that the if/else clauses for timeout informing of the SVC host were in the wrong order. These have been swapped. Fix #20609 --- modules/graceful/manager_windows.go | 4 ++-- modules/ssh/init.go | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/graceful/manager_windows.go b/modules/graceful/manager_windows.go index e7e619f53f..10c1d67b97 100644 --- a/modules/graceful/manager_windows.go +++ b/modules/graceful/manager_windows.go @@ -114,9 +114,9 @@ func (g *Manager) start() { // Execute makes Manager implement svc.Handler func (g *Manager) Execute(args []string, changes <-chan svc.ChangeRequest, status chan<- svc.Status) (svcSpecificEC bool, exitCode uint32) { if setting.StartupTimeout > 0 { - status <- svc.Status{State: svc.StartPending} - } else { status <- svc.Status{State: svc.StartPending, WaitHint: uint32(setting.StartupTimeout / time.Millisecond)} + } else { + status <- svc.Status{State: svc.StartPending} } log.Trace("Awaiting server start-up") diff --git a/modules/ssh/init.go b/modules/ssh/init.go index f6332bb18b..72cb6df7a4 100644 --- a/modules/ssh/init.go +++ b/modules/ssh/init.go @@ -18,6 +18,7 @@ import ( func Init() error { if setting.SSH.Disabled { + builtinUnused() return nil } From 0ee96da052edd3fe25b66d2c9fd5fd3d01171091 Mon Sep 17 00:00:00 2001 From: JonRB <4564448+eeyrjmr@users.noreply.github.com> Date: Sun, 21 Aug 2022 00:20:58 +0000 Subject: [PATCH 04/85] [skip ci] Updated translations via Crowdin --- options/locale/locale_pt-BR.ini | 2 ++ 1 file changed, 2 insertions(+) diff --git a/options/locale/locale_pt-BR.ini b/options/locale/locale_pt-BR.ini index b5c81702dc..255e657608 100644 --- a/options/locale/locale_pt-BR.ini +++ b/options/locale/locale_pt-BR.ini @@ -1797,6 +1797,7 @@ settings.tracker_issue_style=Formato de número do issue tracker externo settings.tracker_issue_style.numeric=Numérico settings.tracker_issue_style.alphanumeric=Alfanumérico settings.tracker_issue_style.regexp=Expressão Regular +settings.tracker_issue_style.regexp_pattern=Padrão de expressão regular settings.tracker_url_format_desc=Use os espaços reservados {user}, {repo} e {index} para o nome de usuário, nome do repositório e o índice de problemas. settings.enable_timetracker=Habilitar Cronômetro settings.allow_only_contributors_to_track_time=Permitir que apenas os colaboradores acompanhem o contador de tempo @@ -2771,6 +2772,7 @@ config.mailer_config=Configuração de Envio de E-mail config.mailer_enabled=Habilitado config.mailer_name=Nome config.mailer_protocol=Protocolo +config.mailer_smtp_addr=Addr SMTP config.mailer_smtp_port=Porta SMTP config.mailer_user=Usuário config.mailer_use_sendmail=Usar o Sendmail From 11bae504846293806fec27f3271e0d3aba8f0f93 Mon Sep 17 00:00:00 2001 From: zeripath Date: Sun, 21 Aug 2022 07:50:15 +0100 Subject: [PATCH 05/85] Pad GPG Key ID with preceding zeroes (#20878) --- models/asymkey/gpg_key.go | 9 +++++++++ routers/api/v1/user/gpg_key.go | 7 +++++++ templates/repo/commit_page.tmpl | 8 ++++---- templates/user/settings/keys_gpg.tmpl | 8 ++++---- 4 files changed, 24 insertions(+), 8 deletions(-) diff --git a/models/asymkey/gpg_key.go b/models/asymkey/gpg_key.go index 2b99972379..21554d2150 100644 --- a/models/asymkey/gpg_key.go +++ b/models/asymkey/gpg_key.go @@ -63,6 +63,15 @@ func (key *GPGKey) AfterLoad(session *xorm.Session) { } } +// PaddedKeyID show KeyID padded to 16 characters +func (key *GPGKey) PaddedKeyID() string { + if len(key.KeyID) > 15 { + return key.KeyID + } + zeros := "0000000000000000" + return zeros[0:16-len(key.KeyID)] + key.KeyID +} + // ListGPGKeys returns a list of public keys belongs to given user. func ListGPGKeys(ctx context.Context, uid int64, listOptions db.ListOptions) ([]*GPGKey, error) { sess := db.GetEngine(ctx).Table(&GPGKey{}).Where("owner_id=? AND primary_key_id=''", uid) diff --git a/routers/api/v1/user/gpg_key.go b/routers/api/v1/user/gpg_key.go index b211a24a0e..b87cf0041e 100644 --- a/routers/api/v1/user/gpg_key.go +++ b/routers/api/v1/user/gpg_key.go @@ -7,6 +7,7 @@ package user import ( "fmt" "net/http" + "strings" asymkey_model "code.gitea.io/gitea/models/asymkey" "code.gitea.io/gitea/models/db" @@ -177,6 +178,12 @@ func VerifyUserGPGKey(ctx *context.APIContext) { token := asymkey_model.VerificationToken(ctx.Doer, 1) lastToken := asymkey_model.VerificationToken(ctx.Doer, 0) + form.KeyID = strings.TrimLeft(form.KeyID, "0") + if form.KeyID == "" { + ctx.NotFound() + return + } + _, err := asymkey_model.VerifyGPGKey(ctx.Doer.ID, form.KeyID, token, form.Signature) if err != nil && asymkey_model.IsErrGPGInvalidTokenSignature(err) { _, err = asymkey_model.VerifyGPGKey(ctx.Doer.ID, form.KeyID, lastToken, form.Signature) diff --git a/templates/repo/commit_page.tmpl b/templates/repo/commit_page.tmpl index 4c4f6f5bc8..9e3fadee0c 100644 --- a/templates/repo/commit_page.tmpl +++ b/templates/repo/commit_page.tmpl @@ -222,7 +222,7 @@ {{.Verification.SigningSSHKey.Fingerprint}} {{else}} {{.locale.Tr "repo.commits.gpg_key_id"}}: - {{.Verification.SigningKey.KeyID}} + {{.Verification.SigningKey.PaddedKeyID}} {{end}} {{else}} {{svg "octicon-shield-lock" 16 "mr-3"}} @@ -231,7 +231,7 @@ {{.Verification.SigningSSHKey.Fingerprint}} {{else}} {{.locale.Tr "repo.commits.gpg_key_id"}}: - {{.Verification.SigningKey.KeyID}} + {{.Verification.SigningKey.PaddedKeyID}} {{end}} {{end}} {{else if .Verification.Warning}} @@ -241,14 +241,14 @@ {{.Verification.SigningSSHKey.Fingerprint}} {{else}} {{.locale.Tr "repo.commits.gpg_key_id"}}: - {{.Verification.SigningKey.KeyID}} + {{.Verification.SigningKey.PaddedKeyID}} {{end}} {{else}} {{if .Verification.SigningKey}} {{if ne .Verification.SigningKey.KeyID ""}} {{svg "octicon-shield" 16 "mr-3"}} {{.locale.Tr "repo.commits.gpg_key_id"}}: - {{.Verification.SigningKey.KeyID}} + {{.Verification.SigningKey.PaddedKeyID}} {{end}} {{end}} {{if .Verification.SigningSSHKey}} diff --git a/templates/user/settings/keys_gpg.tmpl b/templates/user/settings/keys_gpg.tmpl index fbb0da5302..5883508179 100644 --- a/templates/user/settings/keys_gpg.tmpl +++ b/templates/user/settings/keys_gpg.tmpl @@ -22,7 +22,7 @@

{{.locale.Tr "settings.gpg_token_help"}}

-

{{$.locale.Tr "settings.gpg_token_code" .TokenToSign .KeyID}}

+

{{$.locale.Tr "settings.gpg_token_code" .TokenToSign .PaddedKeyID}}

@@ -64,8 +64,8 @@ {{svg "octicon-mail"}} {{$.locale.Tr "settings.gpg_key_matched_identities"}} {{range .Emails}}{{.Email}} {{end}} {{end}}
- {{$.locale.Tr "settings.key_id"}}: {{.KeyID}} - {{$.locale.Tr "settings.subkeys"}}: {{range .SubsKey}} {{.KeyID}} {{end}} + {{$.locale.Tr "settings.key_id"}}: {{.PaddedKeyID}} + {{$.locale.Tr "settings.subkeys"}}: {{range .SubsKey}} {{.PaddedKeyID}} {{end}}
{{$.locale.Tr "settings.add_on"}} {{.AddedUnix.FormatShort}} @@ -87,7 +87,7 @@

{{$.locale.Tr "settings.gpg_token_help"}}

-

{{$.locale.Tr "settings.gpg_token_code" $.TokenToSign .KeyID}}

+

{{$.locale.Tr "settings.gpg_token_code" $.TokenToSign .PaddedKeyID}}


From 6784a707d101ef31a9ef3a46468c28e1d9700ac8 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Sun, 21 Aug 2022 14:50:27 +0800 Subject: [PATCH 06/85] Fix graceful doc (#20883) --- modules/graceful/manager.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/modules/graceful/manager.go b/modules/graceful/manager.go index 8766cfca0e..21f019fb56 100644 --- a/modules/graceful/manager.go +++ b/modules/graceful/manager.go @@ -24,11 +24,12 @@ const ( stateTerminate ) -// There are three places that could inherit sockets: +// There are some places that could inherit sockets: // // * HTTP or HTTPS main listener +// * HTTP or HTTPS install listener // * HTTP redirection fallback -// * SSH +// * Builtin SSH listener // // If you add an additional place you must increment this number // and add a function to call manager.InformCleanup if it's not going to be used @@ -305,8 +306,9 @@ func (g *Manager) setState(st state) { g.state = st } -// InformCleanup tells the cleanup wait group that we have either taken a listener -// or will not be taking a listener +// InformCleanup tells the cleanup wait group that we have either taken a listener or will not be taking a listener. +// At the moment the total number of servers (numberOfServersToCreate) are pre-defined as a const before global init, +// so this function MUST be called if a server is not used. func (g *Manager) InformCleanup() { g.createServerWaitGroup.Done() } From 6d3181406d87503dbd15e4a7c764c8963f13977f Mon Sep 17 00:00:00 2001 From: zeripath Date: Sun, 21 Aug 2022 14:28:15 +0100 Subject: [PATCH 07/85] Double check CloneURL is acceptable (#20869) Some Migration Downloaders provide re-writing of CloneURLs that may point to unallowed urls. Recheck after the CloneURL is rewritten. Signed-off-by: Andrew Thornton --- services/migrations/dump.go | 8 ++++++-- services/migrations/gitea_uploader_test.go | 2 +- services/migrations/migrate.go | 19 +++++++++++++++++-- 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/services/migrations/dump.go b/services/migrations/dump.go index a9ec459519..f040c6c663 100644 --- a/services/migrations/dump.go +++ b/services/migrations/dump.go @@ -560,6 +560,10 @@ func (g *RepositoryDumper) Finish() error { // DumpRepository dump repository according MigrateOptions to a local directory func DumpRepository(ctx context.Context, baseDir, ownerName string, opts base.MigrateOptions) error { + doer, err := user_model.GetAdminUser() + if err != nil { + return err + } downloader, err := newDownloader(ctx, ownerName, opts) if err != nil { return err @@ -569,7 +573,7 @@ func DumpRepository(ctx context.Context, baseDir, ownerName string, opts base.Mi return err } - if err := migrateRepository(downloader, uploader, opts, nil); err != nil { + if err := migrateRepository(doer, downloader, uploader, opts, nil); err != nil { if err1 := uploader.Rollback(); err1 != nil { log.Error("rollback failed: %v", err1) } @@ -641,7 +645,7 @@ func RestoreRepository(ctx context.Context, baseDir, ownerName, repoName string, return err } - if err = migrateRepository(downloader, uploader, migrateOpts, nil); err != nil { + if err = migrateRepository(doer, downloader, uploader, migrateOpts, nil); err != nil { if err1 := uploader.Rollback(); err1 != nil { log.Error("rollback failed: %v", err1) } diff --git a/services/migrations/gitea_uploader_test.go b/services/migrations/gitea_uploader_test.go index 9fb3d6ffd6..1f1c4bde6f 100644 --- a/services/migrations/gitea_uploader_test.go +++ b/services/migrations/gitea_uploader_test.go @@ -46,7 +46,7 @@ func TestGiteaUploadRepo(t *testing.T) { uploader = NewGiteaLocalUploader(graceful.GetManager().HammerContext(), user, user.Name, repoName) ) - err := migrateRepository(downloader, uploader, base.MigrateOptions{ + err := migrateRepository(user, downloader, uploader, base.MigrateOptions{ CloneAddr: "https://github.com/go-xorm/builder", RepoName: repoName, AuthUsername: "", diff --git a/services/migrations/migrate.go b/services/migrations/migrate.go index 9460c66dbc..a5715072c5 100644 --- a/services/migrations/migrate.go +++ b/services/migrations/migrate.go @@ -128,7 +128,7 @@ func MigrateRepository(ctx context.Context, doer *user_model.User, ownerName str uploader := NewGiteaLocalUploader(ctx, doer, ownerName, opts.RepoName) uploader.gitServiceType = opts.GitServiceType - if err := migrateRepository(downloader, uploader, opts, messenger); err != nil { + if err := migrateRepository(doer, downloader, uploader, opts, messenger); err != nil { if err1 := uploader.Rollback(); err1 != nil { log.Error("rollback failed: %v", err1) } @@ -177,7 +177,7 @@ func newDownloader(ctx context.Context, ownerName string, opts base.MigrateOptio // migrateRepository will download information and then upload it to Uploader, this is a simple // process for small repository. For a big repository, save all the data to disk // before upload is better -func migrateRepository(downloader base.Downloader, uploader base.Uploader, opts base.MigrateOptions, messenger base.Messenger) error { +func migrateRepository(doer *user_model.User, downloader base.Downloader, uploader base.Uploader, opts base.MigrateOptions, messenger base.Messenger) error { if messenger == nil { messenger = base.NilMessenger } @@ -198,6 +198,21 @@ func migrateRepository(downloader base.Downloader, uploader base.Uploader, opts return err } + // If the downloader is not a RepositoryRestorer then we need to recheck the CloneURL + if _, ok := downloader.(*RepositoryRestorer); !ok { + // Now the clone URL can be rewritten by the downloader so we must recheck + if err := IsMigrateURLAllowed(repo.CloneURL, doer); err != nil { + return err + } + + // And so can the original URL too so again we must recheck + if repo.OriginalURL != "" { + if err := IsMigrateURLAllowed(repo.OriginalURL, doer); err != nil { + return err + } + } + } + log.Trace("migrating git data from %s", repo.CloneURL) messenger("repo.migrate.migrating_git") if err = uploader.CreateRepo(repo, opts); err != nil { From 0b4c166e8a90beeb1e71ee2fc16b3a240517c82d Mon Sep 17 00:00:00 2001 From: Gusted Date: Sun, 21 Aug 2022 18:24:05 +0200 Subject: [PATCH 08/85] Fix SQL Query for `SearchTeam` (#20844) - Currently the function takes in the `UserID` option, but isn't being used within the SQL query. This patch fixes that by checking that only teams are being returned that the user belongs to. Fix #20829 Co-authored-by: delvh --- integrations/api_team_test.go | 2 +- integrations/api_user_orgs_test.go | 22 ++++++++++++++++++ integrations/org_test.go | 9 ++++---- models/fixtures/org_user.yml | 6 +++++ models/fixtures/user.yml | 2 +- models/organization/team.go | 37 ++++++++++++++++++++---------- routers/web/org/teams.go | 2 +- 7 files changed, 61 insertions(+), 19 deletions(-) diff --git a/integrations/api_team_test.go b/integrations/api_team_test.go index 82824d610b..9ea7a6f787 100644 --- a/integrations/api_team_test.go +++ b/integrations/api_team_test.go @@ -223,7 +223,7 @@ func TestAPITeamSearch(t *testing.T) { defer prepareTestEnv(t)() user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) + org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 17}) var results TeamSearchResults diff --git a/integrations/api_user_orgs_test.go b/integrations/api_user_orgs_test.go index 9f3487cb7f..1555b53390 100644 --- a/integrations/api_user_orgs_test.go +++ b/integrations/api_user_orgs_test.go @@ -26,8 +26,19 @@ func TestUserOrgs(t *testing.T) { orgs := getUserOrgs(t, adminUsername, normalUsername) user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user3"}) + user17 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user17"}) assert.Equal(t, []*api.Organization{ + { + ID: 17, + UserName: user17.Name, + FullName: user17.FullName, + AvatarURL: user17.AvatarLink(), + Description: "", + Website: "", + Location: "", + Visibility: "public", + }, { ID: 3, UserName: user3.Name, @@ -82,8 +93,19 @@ func TestMyOrgs(t *testing.T) { var orgs []*api.Organization DecodeJSON(t, resp, &orgs) user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user3"}) + user17 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user17"}) assert.Equal(t, []*api.Organization{ + { + ID: 17, + UserName: user17.Name, + FullName: user17.FullName, + AvatarURL: user17.AvatarLink(), + Description: "", + Website: "", + Location: "", + Visibility: "public", + }, { ID: 3, UserName: user3.Name, diff --git a/integrations/org_test.go b/integrations/org_test.go index e4677f58de..9fb1175d7a 100644 --- a/integrations/org_test.go +++ b/integrations/org_test.go @@ -197,8 +197,8 @@ func TestOrgRestrictedUser(t *testing.T) { func TestTeamSearch(t *testing.T) { defer prepareTestEnv(t)() - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 15}) + org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 17}) var results TeamSearchResults @@ -209,8 +209,9 @@ func TestTeamSearch(t *testing.T) { resp := session.MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &results) assert.NotEmpty(t, results.Data) - assert.Len(t, results.Data, 1) - assert.Equal(t, "test_team", results.Data[0].Name) + assert.Len(t, results.Data, 2) + assert.Equal(t, "review_team", results.Data[0].Name) + assert.Equal(t, "test_team", results.Data[1].Name) // no access if not organization member user5 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}) diff --git a/models/fixtures/org_user.yml b/models/fixtures/org_user.yml index a0bc4b9b43..e06d94cfcd 100644 --- a/models/fixtures/org_user.yml +++ b/models/fixtures/org_user.yml @@ -63,3 +63,9 @@ uid: 29 org_id: 17 is_public: true + +- + id: 12 + uid: 2 + org_id: 17 + is_public: true diff --git a/models/fixtures/user.yml b/models/fixtures/user.yml index 67ba869c76..87405bfd26 100644 --- a/models/fixtures/user.yml +++ b/models/fixtures/user.yml @@ -309,7 +309,7 @@ avatar_email: user17@example.com num_repos: 2 is_active: true - num_members: 3 + num_members: 4 num_teams: 3 - diff --git a/models/organization/team.go b/models/organization/team.go index 0b53c84d67..6787b9e0fa 100644 --- a/models/organization/team.go +++ b/models/organization/team.go @@ -96,16 +96,7 @@ type SearchTeamOptions struct { IncludeDesc bool } -// SearchTeam search for teams. Caller is responsible to check permissions. -func SearchTeam(opts *SearchTeamOptions) ([]*Team, int64, error) { - if opts.Page <= 0 { - opts.Page = 1 - } - if opts.PageSize == 0 { - // Default limit - opts.PageSize = 10 - } - +func (opts *SearchTeamOptions) toCond() builder.Cond { cond := builder.NewCond() if len(opts.Keyword) > 0 { @@ -117,10 +108,28 @@ func SearchTeam(opts *SearchTeamOptions) ([]*Team, int64, error) { cond = cond.And(keywordCond) } - cond = cond.And(builder.Eq{"org_id": opts.OrgID}) + if opts.OrgID > 0 { + cond = cond.And(builder.Eq{"`team`.org_id": opts.OrgID}) + } + if opts.UserID > 0 { + cond = cond.And(builder.Eq{"team_user.uid": opts.UserID}) + } + + return cond +} + +// SearchTeam search for teams. Caller is responsible to check permissions. +func SearchTeam(opts *SearchTeamOptions) ([]*Team, int64, error) { sess := db.GetEngine(db.DefaultContext) + opts.SetDefaultValues() + cond := opts.toCond() + + if opts.UserID > 0 { + sess = sess.Join("INNER", "team_user", "team_user.team_id = team.id") + } + count, err := sess. Where(cond). Count(new(Team)) @@ -128,7 +137,10 @@ func SearchTeam(opts *SearchTeamOptions) ([]*Team, int64, error) { return nil, 0, err } - sess = sess.Where(cond) + if opts.UserID > 0 { + sess = sess.Join("INNER", "team_user", "team_user.team_id = team.id") + } + if opts.PageSize == -1 { opts.PageSize = int(count) } else { @@ -137,6 +149,7 @@ func SearchTeam(opts *SearchTeamOptions) ([]*Team, int64, error) { teams := make([]*Team, 0, opts.PageSize) if err = sess. + Where(cond). OrderBy("lower_name"). Find(&teams); err != nil { return nil, 0, err diff --git a/routers/web/org/teams.go b/routers/web/org/teams.go index 9ee66a1a3e..a3c3acb4f4 100644 --- a/routers/web/org/teams.go +++ b/routers/web/org/teams.go @@ -339,7 +339,7 @@ func SearchTeam(ctx *context.Context) { } opts := &organization.SearchTeamOptions{ - UserID: ctx.Doer.ID, + // UserID is not set because the router already requires the doer to be an org admin. Thus, we don't need to restrict to teams that the user belongs in Keyword: ctx.FormTrim("q"), OrgID: ctx.Org.Organization.ID, IncludeDesc: ctx.FormString("include_desc") == "" || ctx.FormBool("include_desc"), From 943753f560fab8bb01946618b16c694bc2032827 Mon Sep 17 00:00:00 2001 From: zeripath Date: Sun, 21 Aug 2022 19:20:43 +0100 Subject: [PATCH 09/85] Support Proxy protocol (#12527) This PR adds functionality to allow Gitea to sit behind an HAProxy and HAProxy protocolled connections directly. Fix #7508 Signed-off-by: Andrew Thornton --- cmd/web.go | 28 +- cmd/web_acme.go | 4 +- cmd/web_graceful.go | 8 +- cmd/web_https.go | 10 +- custom/conf/app.example.ini | 22 +- .../doc/advanced/config-cheat-sheet.en-us.md | 8 + modules/graceful/server.go | 48 +- modules/graceful/server_http.go | 8 +- modules/private/internal.go | 28 +- modules/proxyprotocol/conn.go | 506 ++++++++++++++++++ modules/proxyprotocol/errors.go | 45 ++ modules/proxyprotocol/listener.go | 47 ++ modules/proxyprotocol/util.go | 15 + modules/setting/setting.go | 82 +-- modules/ssh/ssh_graceful.go | 2 +- 15 files changed, 787 insertions(+), 74 deletions(-) create mode 100644 modules/proxyprotocol/conn.go create mode 100644 modules/proxyprotocol/errors.go create mode 100644 modules/proxyprotocol/listener.go create mode 100644 modules/proxyprotocol/util.go diff --git a/cmd/web.go b/cmd/web.go index 3bc61b0443..43f106f780 100644 --- a/cmd/web.go +++ b/cmd/web.go @@ -76,7 +76,7 @@ func runHTTPRedirector() { http.Redirect(w, r, target, http.StatusTemporaryRedirect) }) - err := runHTTP("tcp", source, "HTTP Redirector", handler) + err := runHTTP("tcp", source, "HTTP Redirector", handler, setting.RedirectorUseProxyProtocol) if err != nil { log.Fatal("Failed to start port redirection: %v", err) } @@ -231,40 +231,38 @@ func listen(m http.Handler, handleRedirector bool) error { if handleRedirector { NoHTTPRedirector() } - err = runHTTP("tcp", listenAddr, "Web", m) + err = runHTTP("tcp", listenAddr, "Web", m, setting.UseProxyProtocol) case setting.HTTPS: if setting.EnableAcme { err = runACME(listenAddr, m) break - } else { - if handleRedirector { - if setting.RedirectOtherPort { - go runHTTPRedirector() - } else { - NoHTTPRedirector() - } - } - err = runHTTPS("tcp", listenAddr, "Web", setting.CertFile, setting.KeyFile, m) } + if handleRedirector { + if setting.RedirectOtherPort { + go runHTTPRedirector() + } else { + NoHTTPRedirector() + } + } + err = runHTTPS("tcp", listenAddr, "Web", setting.CertFile, setting.KeyFile, m, setting.UseProxyProtocol, setting.ProxyProtocolTLSBridging) case setting.FCGI: if handleRedirector { NoHTTPRedirector() } - err = runFCGI("tcp", listenAddr, "FCGI Web", m) + err = runFCGI("tcp", listenAddr, "FCGI Web", m, setting.UseProxyProtocol) case setting.HTTPUnix: if handleRedirector { NoHTTPRedirector() } - err = runHTTP("unix", listenAddr, "Web", m) + err = runHTTP("unix", listenAddr, "Web", m, setting.UseProxyProtocol) case setting.FCGIUnix: if handleRedirector { NoHTTPRedirector() } - err = runFCGI("unix", listenAddr, "Web", m) + err = runFCGI("unix", listenAddr, "Web", m, setting.UseProxyProtocol) default: log.Fatal("Invalid protocol: %s", setting.Protocol) } - if err != nil { log.Critical("Failed to start server: %v", err) } diff --git a/cmd/web_acme.go b/cmd/web_acme.go index 57b400dae6..d8e550b321 100644 --- a/cmd/web_acme.go +++ b/cmd/web_acme.go @@ -113,14 +113,14 @@ func runACME(listenAddr string, m http.Handler) error { log.Info("Running Let's Encrypt handler on %s", setting.HTTPAddr+":"+setting.PortToRedirect) // all traffic coming into HTTP will be redirect to HTTPS automatically (LE HTTP-01 validation happens here) - err := runHTTP("tcp", setting.HTTPAddr+":"+setting.PortToRedirect, "Let's Encrypt HTTP Challenge", myACME.HTTPChallengeHandler(http.HandlerFunc(runLetsEncryptFallbackHandler))) + err := runHTTP("tcp", setting.HTTPAddr+":"+setting.PortToRedirect, "Let's Encrypt HTTP Challenge", myACME.HTTPChallengeHandler(http.HandlerFunc(runLetsEncryptFallbackHandler)), setting.RedirectorUseProxyProtocol) if err != nil { log.Fatal("Failed to start the Let's Encrypt handler on port %s: %v", setting.PortToRedirect, err) } }() } - return runHTTPSWithTLSConfig("tcp", listenAddr, "Web", tlsConfig, m) + return runHTTPSWithTLSConfig("tcp", listenAddr, "Web", tlsConfig, m, setting.UseProxyProtocol, setting.ProxyProtocolTLSBridging) } func runLetsEncryptFallbackHandler(w http.ResponseWriter, r *http.Request) { diff --git a/cmd/web_graceful.go b/cmd/web_graceful.go index 1618208c55..ba88cc59c2 100644 --- a/cmd/web_graceful.go +++ b/cmd/web_graceful.go @@ -15,8 +15,8 @@ import ( "code.gitea.io/gitea/modules/setting" ) -func runHTTP(network, listenAddr, name string, m http.Handler) error { - return graceful.HTTPListenAndServe(network, listenAddr, name, m) +func runHTTP(network, listenAddr, name string, m http.Handler, useProxyProtocol bool) error { + return graceful.HTTPListenAndServe(network, listenAddr, name, m, useProxyProtocol) } // NoHTTPRedirector tells our cleanup routine that we will not be using a fallback http redirector @@ -36,7 +36,7 @@ func NoInstallListener() { graceful.GetManager().InformCleanup() } -func runFCGI(network, listenAddr, name string, m http.Handler) error { +func runFCGI(network, listenAddr, name string, m http.Handler, useProxyProtocol bool) error { // This needs to handle stdin as fcgi point fcgiServer := graceful.NewServer(network, listenAddr, name) @@ -47,7 +47,7 @@ func runFCGI(network, listenAddr, name string, m http.Handler) error { } m.ServeHTTP(resp, req) })) - }) + }, useProxyProtocol) if err != nil { log.Fatal("Failed to start FCGI main server: %v", err) } diff --git a/cmd/web_https.go b/cmd/web_https.go index b0910ca040..aac11517a6 100644 --- a/cmd/web_https.go +++ b/cmd/web_https.go @@ -129,14 +129,14 @@ var ( defaultCiphersChaChaFirst = append(defaultCiphersChaCha, defaultCiphersAES...) ) -// runHTTPs listens on the provided network address and then calls +// runHTTPS listens on the provided network address and then calls // Serve to handle requests on incoming TLS connections. // // Filenames containing a certificate and matching private key for the server must // be provided. If the certificate is signed by a certificate authority, the // certFile should be the concatenation of the server's certificate followed by the // CA's certificate. -func runHTTPS(network, listenAddr, name, certFile, keyFile string, m http.Handler) error { +func runHTTPS(network, listenAddr, name, certFile, keyFile string, m http.Handler, useProxyProtocol, proxyProtocolTLSBridging bool) error { tlsConfig := &tls.Config{} if tlsConfig.NextProtos == nil { tlsConfig.NextProtos = []string{"h2", "http/1.1"} @@ -184,9 +184,9 @@ func runHTTPS(network, listenAddr, name, certFile, keyFile string, m http.Handle return err } - return graceful.HTTPListenAndServeTLSConfig(network, listenAddr, name, tlsConfig, m) + return graceful.HTTPListenAndServeTLSConfig(network, listenAddr, name, tlsConfig, m, useProxyProtocol, proxyProtocolTLSBridging) } -func runHTTPSWithTLSConfig(network, listenAddr, name string, tlsConfig *tls.Config, m http.Handler) error { - return graceful.HTTPListenAndServeTLSConfig(network, listenAddr, name, tlsConfig, m) +func runHTTPSWithTLSConfig(network, listenAddr, name string, tlsConfig *tls.Config, m http.Handler, useProxyProtocol, proxyProtocolTLSBridging bool) error { + return graceful.HTTPListenAndServeTLSConfig(network, listenAddr, name, tlsConfig, m, useProxyProtocol, proxyProtocolTLSBridging) } diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 5e612fb428..0949c3d399 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -29,6 +29,18 @@ RUN_MODE = ; prod ;; The protocol the server listens on. One of 'http', 'https', 'unix' or 'fcgi'. Defaults to 'http' ;PROTOCOL = http ;; +;; Expect PROXY protocol headers on connections +;USE_PROXY_PROTOCOL = false +;; +;; Use PROXY protocol in TLS Bridging mode +;PROXY_PROTOCOL_TLS_BRIDGING = false +;; +; Timeout to wait for PROXY protocol header (set to 0 to have no timeout) +;PROXY_PROTOCOL_HEADER_TIMEOUT=5s +;; +; Accept PROXY protocol headers with UNKNOWN type +;PROXY_PROTOCOL_ACCEPT_UNKNOWN=false +;; ;; Set the domain for the server ;DOMAIN = localhost ;; @@ -51,6 +63,8 @@ RUN_MODE = ; prod ;REDIRECT_OTHER_PORT = false ;PORT_TO_REDIRECT = 80 ;; +;; expect PROXY protocol header on connections to https redirector. +;REDIRECTOR_USE_PROXY_PROTOCOL = %(USE_PROXY_PROTOCOL) ;; Minimum and maximum supported TLS versions ;SSL_MIN_VERSION=TLSv1.2 ;SSL_MAX_VERSION= @@ -76,13 +90,19 @@ RUN_MODE = ; prod ;; Do not set this variable if PROTOCOL is set to 'unix'. ;LOCAL_ROOT_URL = %(PROTOCOL)s://%(HTTP_ADDR)s:%(HTTP_PORT)s/ ;; +;; When making local connections pass the PROXY protocol header. +;LOCAL_USE_PROXY_PROTOCOL = %(USE_PROXY_PROTOCOL) +;; ;; Disable SSH feature when not available ;DISABLE_SSH = false ;; ;; Whether to use the builtin SSH server or not. ;START_SSH_SERVER = false ;; -;; Username to use for the builtin SSH server. +;; Expect PROXY protocol header on connections to the built-in SSH server +;SSH_SERVER_USE_PROXY_PROTOCOL = false +;; +;; Username to use for the builtin SSH server. If blank, then it is the value of RUN_USER. ;BUILTIN_SSH_SERVER_USER = %(RUN_USER)s ;; ;; Domain name to be exposed in clone URL diff --git a/docs/content/doc/advanced/config-cheat-sheet.en-us.md b/docs/content/doc/advanced/config-cheat-sheet.en-us.md index 37f887d4aa..8ce8641365 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md +++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md @@ -238,6 +238,10 @@ The following configuration set `Content-Type: application/vnd.android.package-a ## Server (`server`) - `PROTOCOL`: **http**: \[http, https, fcgi, http+unix, fcgi+unix\] +- `USE_PROXY_PROTOCOL`: **false**: Expect PROXY protocol headers on connections +- `PROXY_PROTOCOL_TLS_BRIDGING`: **false**: When protocol is https, expect PROXY protocol headers after TLS negotiation. +- `PROXY_PROTOCOL_HEADER_TIMEOUT`: **5s**: Timeout to wait for PROXY protocol header (set to 0 to have no timeout) +- `PROXY_PROTOCOL_ACCEPT_UNKNOWN`: **false**: Accept PROXY protocol headers with Unknown type. - `DOMAIN`: **localhost**: Domain name of this server. - `ROOT_URL`: **%(PROTOCOL)s://%(DOMAIN)s:%(HTTP\_PORT)s/**: Overwrite the automatically generated public URL. @@ -262,12 +266,15 @@ The following configuration set `Content-Type: application/vnd.android.package-a most cases you do not need to change the default value. Alter it only if your SSH server node is not the same as HTTP node. Do not set this variable if `PROTOCOL` is set to `http+unix`. +- `LOCAL_USE_PROXY_PROTOCOL`: **%(USE_PROXY_PROTOCOL)**: When making local connections pass the PROXY protocol header. + This should be set to false if the local connection will go through the proxy. - `PER_WRITE_TIMEOUT`: **30s**: Timeout for any write to the connection. (Set to -1 to disable all timeouts.) - `PER_WRITE_PER_KB_TIMEOUT`: **10s**: Timeout per Kb written to connections. - `DISABLE_SSH`: **false**: Disable SSH feature when it's not available. - `START_SSH_SERVER`: **false**: When enabled, use the built-in SSH server. +- `SSH_SERVER_USE_PROXY_PROTOCOL`: **false**: Expect PROXY protocol header on connections to the built-in SSH Server. - `BUILTIN_SSH_SERVER_USER`: **%(RUN_USER)s**: Username to use for the built-in SSH Server. - `SSH_USER`: **%(BUILTIN_SSH_SERVER_USER)**: SSH username displayed in clone URLs. This is only for people who configure the SSH server themselves; in most cases, you want to leave this blank and modify the `BUILTIN_SSH_SERVER_USER`. - `SSH_DOMAIN`: **%(DOMAIN)s**: Domain name of this server, used for displayed clone URL. @@ -313,6 +320,7 @@ The following configuration set `Content-Type: application/vnd.android.package-a - `LFS_LOCKS_PAGING_NUM`: **50**: Maximum number of LFS Locks returned per page. - `REDIRECT_OTHER_PORT`: **false**: If true and `PROTOCOL` is https, allows redirecting http requests on `PORT_TO_REDIRECT` to the https port Gitea listens on. +- `REDIRECTOR_USE_PROXY_PROTOCOL`: **%(USE_PROXY_PROTOCOL)**: expect PROXY protocol header on connections to https redirector. - `PORT_TO_REDIRECT`: **80**: Port for the http redirection service to listen on. Used when `REDIRECT_OTHER_PORT` is true. - `SSL_MIN_VERSION`: **TLSv1.2**: Set the minimum version of ssl support. - `SSL_MAX_VERSION`: **\**: Set the maximum version of ssl support. diff --git a/modules/graceful/server.go b/modules/graceful/server.go index 159a9879df..30a460a943 100644 --- a/modules/graceful/server.go +++ b/modules/graceful/server.go @@ -16,6 +16,7 @@ import ( "time" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/proxyprotocol" "code.gitea.io/gitea/modules/setting" ) @@ -79,16 +80,27 @@ func NewServer(network, address, name string) *Server { // ListenAndServe listens on the provided network address and then calls Serve // to handle requests on incoming connections. -func (srv *Server) ListenAndServe(serve ServeFunction) error { +func (srv *Server) ListenAndServe(serve ServeFunction, useProxyProtocol bool) error { go srv.awaitShutdown() - l, err := GetListener(srv.network, srv.address) + listener, err := GetListener(srv.network, srv.address) if err != nil { log.Error("Unable to GetListener: %v", err) return err } - srv.listener = newWrappedListener(l, srv) + // we need to wrap the listener to take account of our lifecycle + listener = newWrappedListener(listener, srv) + + // Now we need to take account of ProxyProtocol settings... + if useProxyProtocol { + listener = &proxyprotocol.Listener{ + Listener: listener, + ProxyHeaderTimeout: setting.ProxyProtocolHeaderTimeout, + AcceptUnknown: setting.ProxyProtocolAcceptUnknown, + } + } + srv.listener = listener srv.BeforeBegin(srv.network, srv.address) @@ -97,22 +109,44 @@ func (srv *Server) ListenAndServe(serve ServeFunction) error { // ListenAndServeTLSConfig listens on the provided network address and then calls // Serve to handle requests on incoming TLS connections. -func (srv *Server) ListenAndServeTLSConfig(tlsConfig *tls.Config, serve ServeFunction) error { +func (srv *Server) ListenAndServeTLSConfig(tlsConfig *tls.Config, serve ServeFunction, useProxyProtocol, proxyProtocolTLSBridging bool) error { go srv.awaitShutdown() if tlsConfig.MinVersion == 0 { tlsConfig.MinVersion = tls.VersionTLS12 } - l, err := GetListener(srv.network, srv.address) + listener, err := GetListener(srv.network, srv.address) if err != nil { log.Error("Unable to get Listener: %v", err) return err } - wl := newWrappedListener(l, srv) - srv.listener = tls.NewListener(wl, tlsConfig) + // we need to wrap the listener to take account of our lifecycle + listener = newWrappedListener(listener, srv) + // Now we need to take account of ProxyProtocol settings... If we're not bridging then we expect that the proxy will forward the connection to us + if useProxyProtocol && !proxyProtocolTLSBridging { + listener = &proxyprotocol.Listener{ + Listener: listener, + ProxyHeaderTimeout: setting.ProxyProtocolHeaderTimeout, + AcceptUnknown: setting.ProxyProtocolAcceptUnknown, + } + } + + // Now handle the tls protocol + listener = tls.NewListener(listener, tlsConfig) + + // Now if we're bridging then we need the proxy to tell us who we're bridging for... + if useProxyProtocol && proxyProtocolTLSBridging { + listener = &proxyprotocol.Listener{ + Listener: listener, + ProxyHeaderTimeout: setting.ProxyProtocolHeaderTimeout, + AcceptUnknown: setting.ProxyProtocolAcceptUnknown, + } + } + + srv.listener = listener srv.BeforeBegin(srv.network, srv.address) return srv.Serve(serve) diff --git a/modules/graceful/server_http.go b/modules/graceful/server_http.go index f7b22ceb5e..8ab2bdf41f 100644 --- a/modules/graceful/server_http.go +++ b/modules/graceful/server_http.go @@ -28,14 +28,14 @@ func newHTTPServer(network, address, name string, handler http.Handler) (*Server // HTTPListenAndServe listens on the provided network address and then calls Serve // to handle requests on incoming connections. -func HTTPListenAndServe(network, address, name string, handler http.Handler) error { +func HTTPListenAndServe(network, address, name string, handler http.Handler, useProxyProtocol bool) error { server, lHandler := newHTTPServer(network, address, name, handler) - return server.ListenAndServe(lHandler) + return server.ListenAndServe(lHandler, useProxyProtocol) } // HTTPListenAndServeTLSConfig listens on the provided network address and then calls Serve // to handle requests on incoming connections. -func HTTPListenAndServeTLSConfig(network, address, name string, tlsConfig *tls.Config, handler http.Handler) error { +func HTTPListenAndServeTLSConfig(network, address, name string, tlsConfig *tls.Config, handler http.Handler, useProxyProtocol, proxyProtocolTLSBridging bool) error { server, lHandler := newHTTPServer(network, address, name, handler) - return server.ListenAndServeTLSConfig(tlsConfig, lHandler) + return server.ListenAndServeTLSConfig(tlsConfig, lHandler, useProxyProtocol, proxyProtocolTLSBridging) } diff --git a/modules/private/internal.go b/modules/private/internal.go index a77a990627..2ea516ba80 100644 --- a/modules/private/internal.go +++ b/modules/private/internal.go @@ -14,6 +14,7 @@ import ( "code.gitea.io/gitea/modules/httplib" "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/proxyprotocol" "code.gitea.io/gitea/modules/setting" ) @@ -50,7 +51,32 @@ func newInternalRequest(ctx context.Context, url, method string) *httplib.Reques req.SetTransport(&http.Transport{ DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) { var d net.Dialer - return d.DialContext(ctx, "unix", setting.HTTPAddr) + conn, err := d.DialContext(ctx, "unix", setting.HTTPAddr) + if err != nil { + return conn, err + } + if setting.LocalUseProxyProtocol { + if err = proxyprotocol.WriteLocalHeader(conn); err != nil { + _ = conn.Close() + return nil, err + } + } + return conn, err + }, + }) + } else if setting.LocalUseProxyProtocol { + req.SetTransport(&http.Transport{ + DialContext: func(ctx context.Context, network, address string) (net.Conn, error) { + var d net.Dialer + conn, err := d.DialContext(ctx, network, address) + if err != nil { + return conn, err + } + if err = proxyprotocol.WriteLocalHeader(conn); err != nil { + _ = conn.Close() + return nil, err + } + return conn, err }, }) } diff --git a/modules/proxyprotocol/conn.go b/modules/proxyprotocol/conn.go new file mode 100644 index 0000000000..10333b204d --- /dev/null +++ b/modules/proxyprotocol/conn.go @@ -0,0 +1,506 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package proxyprotocol + +import ( + "bufio" + "bytes" + "encoding/binary" + "io" + "net" + "strconv" + "strings" + "sync" + "time" + + "code.gitea.io/gitea/modules/log" +) + +var ( + // v1Prefix is the string we look for at the start of a connection + // to check if this connection is using the proxy protocol + v1Prefix = []byte("PROXY ") + v1PrefixLen = len(v1Prefix) + v2Prefix = []byte("\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A") + v2PrefixLen = len(v2Prefix) +) + +// Conn is used to wrap and underlying connection which is speaking the +// Proxy Protocol. RemoteAddr() will return the address of the client +// instead of the proxy address. +type Conn struct { + bufReader *bufio.Reader + conn net.Conn + localAddr net.Addr + remoteAddr net.Addr + once sync.Once + proxyHeaderTimeout time.Duration + acceptUnknown bool +} + +// NewConn is used to wrap a net.Conn speaking the proxy protocol into +// a proxyprotocol.Conn +func NewConn(conn net.Conn, timeout time.Duration) *Conn { + pConn := &Conn{ + bufReader: bufio.NewReader(conn), + conn: conn, + proxyHeaderTimeout: timeout, + } + return pConn +} + +// Read reads data from the connection. +// It will initially read the proxy protocol header. +// If there is an error parsing the header, it is returned and the socket is closed. +func (p *Conn) Read(b []byte) (int, error) { + if err := p.readProxyHeaderOnce(); err != nil { + return 0, err + } + return p.bufReader.Read(b) +} + +// ReadFrom reads data from a provided reader and copies it to the connection. +func (p *Conn) ReadFrom(r io.Reader) (int64, error) { + if err := p.readProxyHeaderOnce(); err != nil { + return 0, err + } + if rf, ok := p.conn.(io.ReaderFrom); ok { + return rf.ReadFrom(r) + } + return io.Copy(p.conn, r) +} + +// WriteTo reads data from the connection and writes it to the writer. +// It will initially read the proxy protocol header. +// If there is an error parsing the header, it is returned and the socket is closed. +func (p *Conn) WriteTo(w io.Writer) (int64, error) { + if err := p.readProxyHeaderOnce(); err != nil { + return 0, err + } + return p.bufReader.WriteTo(w) +} + +// Write writes data to the connection. +// Write can be made to time out and return an error after a fixed +// time limit; see SetDeadline and SetWriteDeadline. +func (p *Conn) Write(b []byte) (int, error) { + if err := p.readProxyHeaderOnce(); err != nil { + return 0, err + } + return p.conn.Write(b) +} + +// Close closes the connection. +// Any blocked Read or Write operations will be unblocked and return errors. +func (p *Conn) Close() error { + return p.conn.Close() +} + +// LocalAddr returns the local network address. +func (p *Conn) LocalAddr() net.Addr { + _ = p.readProxyHeaderOnce() + if p.localAddr != nil { + return p.localAddr + } + return p.conn.LocalAddr() +} + +// RemoteAddr returns the address of the client if the proxy +// protocol is being used, otherwise just returns the address of +// the socket peer. If there is an error parsing the header, the +// address of the client is not returned, and the socket is closed. +// One implication of this is that the call could block if the +// client is slow. Using a Deadline is recommended if this is called +// before Read() +func (p *Conn) RemoteAddr() net.Addr { + _ = p.readProxyHeaderOnce() + if p.remoteAddr != nil { + return p.remoteAddr + } + return p.conn.RemoteAddr() +} + +// SetDeadline sets the read and write deadlines associated +// with the connection. It is equivalent to calling both +// SetReadDeadline and SetWriteDeadline. +// +// A deadline is an absolute time after which I/O operations +// fail instead of blocking. The deadline applies to all future +// and pending I/O, not just the immediately following call to +// Read or Write. After a deadline has been exceeded, the +// connection can be refreshed by setting a deadline in the future. +// +// If the deadline is exceeded a call to Read or Write or to other +// I/O methods will return an error that wraps os.ErrDeadlineExceeded. +// This can be tested using errors.Is(err, os.ErrDeadlineExceeded). +// The error's Timeout method will return true, but note that there +// are other possible errors for which the Timeout method will +// return true even if the deadline has not been exceeded. +// +// An idle timeout can be implemented by repeatedly extending +// the deadline after successful Read or Write calls. +// +// A zero value for t means I/O operations will not time out. +func (p *Conn) SetDeadline(t time.Time) error { + return p.conn.SetDeadline(t) +} + +// SetReadDeadline sets the deadline for future Read calls +// and any currently-blocked Read call. +// A zero value for t means Read will not time out. +func (p *Conn) SetReadDeadline(t time.Time) error { + return p.conn.SetReadDeadline(t) +} + +// SetWriteDeadline sets the deadline for future Write calls +// and any currently-blocked Write call. +// Even if write times out, it may return n > 0, indicating that +// some of the data was successfully written. +// A zero value for t means Write will not time out. +func (p *Conn) SetWriteDeadline(t time.Time) error { + return p.conn.SetWriteDeadline(t) +} + +// readProxyHeaderOnce will ensure that the proxy header has been read +func (p *Conn) readProxyHeaderOnce() (err error) { + p.once.Do(func() { + if err = p.readProxyHeader(); err != nil && err != io.EOF { + log.Error("Failed to read proxy prefix: %v", err) + p.Close() + p.bufReader = bufio.NewReader(p.conn) + } + }) + return err +} + +func (p *Conn) readProxyHeader() error { + if p.proxyHeaderTimeout != 0 { + readDeadLine := time.Now().Add(p.proxyHeaderTimeout) + _ = p.conn.SetReadDeadline(readDeadLine) + defer func() { + _ = p.conn.SetReadDeadline(time.Time{}) + }() + } + + inp, err := p.bufReader.Peek(v1PrefixLen) + if err != nil { + return err + } + + if bytes.Equal(inp, v1Prefix) { + return p.readV1ProxyHeader() + } + + inp, err = p.bufReader.Peek(v2PrefixLen) + if err != nil { + return err + } + if bytes.Equal(inp, v2Prefix) { + return p.readV2ProxyHeader() + } + + return &ErrBadHeader{inp} +} + +func (p *Conn) readV2ProxyHeader() error { + // The binary header format starts with a constant 12 bytes block containing the + // protocol signature : + // + // \x0D \x0A \x0D \x0A \x00 \x0D \x0A \x51 \x55 \x49 \x54 \x0A + // + // Note that this block contains a null byte at the 5th position, so it must not + // be handled as a null-terminated string. + + if _, err := p.bufReader.Discard(v2PrefixLen); err != nil { + // This shouldn't happen as we have already asserted that there should be enough in the buffer + return err + } + + // The next byte (the 13th one) is the protocol version and command. + version, err := p.bufReader.ReadByte() + if err != nil { + return err + } + + // The 14th byte contains the transport protocol and address family.otocol. + familyByte, err := p.bufReader.ReadByte() + if err != nil { + return err + } + + // The 15th and 16th bytes is the address length in bytes in network endian order. + var addressLen uint16 + if err := binary.Read(p.bufReader, binary.BigEndian, &addressLen); err != nil { + return err + } + + // Now handle the version byte: (14th byte). + // The highest four bits contains the version. As of this specification, it must + // always be sent as \x2 and the receiver must only accept this value. + if version>>4 != 0x2 { + return &ErrBadHeader{append(v2Prefix, version, familyByte, uint8(addressLen>>8), uint8(addressLen&0xff))} + } + + // The lowest four bits represents the command : + switch version & 0xf { + case 0x0: + // - \x0 : LOCAL : the connection was established on purpose by the proxy + // without being relayed. The connection endpoints are the sender and the + // receiver. Such connections exist when the proxy sends health-checks to the + // server. The receiver must accept this connection as valid and must use the + // real connection endpoints and discard the protocol block including the + // family which is ignored. + + // We therefore ignore the 14th, 15th and 16th bytes + p.remoteAddr = p.conn.LocalAddr() + p.localAddr = p.conn.RemoteAddr() + return nil + case 0x1: + // - \x1 : PROXY : the connection was established on behalf of another node, + // and reflects the original connection endpoints. The receiver must then use + // the information provided in the protocol block to get original the address. + default: + // - other values are unassigned and must not be emitted by senders. Receivers + // must drop connections presenting unexpected values here. + return &ErrBadHeader{append(v2Prefix, version, familyByte, uint8(addressLen>>8), uint8(addressLen&0xff))} + } + + // Now handle the familyByte byte: (15th byte). + // The highest 4 bits contain the address family, the lowest 4 bits contain the protocol + + // The address family maps to the original socket family without necessarily + // matching the values internally used by the system. It may be one of : + // + // - 0x0 : AF_UNSPEC : the connection is forwarded for an unknown, unspecified + // or unsupported protocol. The sender should use this family when sending + // LOCAL commands or when dealing with unsupported protocol families. The + // receiver is free to accept the connection anyway and use the real endpoint + // addresses or to reject it. The receiver should ignore address information. + // + // - 0x1 : AF_INET : the forwarded connection uses the AF_INET address family + // (IPv4). The addresses are exactly 4 bytes each in network byte order, + // followed by transport protocol information (typically ports). + // + // - 0x2 : AF_INET6 : the forwarded connection uses the AF_INET6 address family + // (IPv6). The addresses are exactly 16 bytes each in network byte order, + // followed by transport protocol information (typically ports). + // + // - 0x3 : AF_UNIX : the forwarded connection uses the AF_UNIX address family + // (UNIX). The addresses are exactly 108 bytes each. + // + // - other values are unspecified and must not be emitted in version 2 of this + // protocol and must be rejected as invalid by receivers. + + // The transport protocol is specified in the lowest 4 bits of the 14th byte : + // + // - 0x0 : UNSPEC : the connection is forwarded for an unknown, unspecified + // or unsupported protocol. The sender should use this family when sending + // LOCAL commands or when dealing with unsupported protocol families. The + // receiver is free to accept the connection anyway and use the real endpoint + // addresses or to reject it. The receiver should ignore address information. + // + // - 0x1 : STREAM : the forwarded connection uses a SOCK_STREAM protocol (eg: + // TCP or UNIX_STREAM). When used with AF_INET/AF_INET6 (TCP), the addresses + // are followed by the source and destination ports represented on 2 bytes + // each in network byte order. + // + // - 0x2 : DGRAM : the forwarded connection uses a SOCK_DGRAM protocol (eg: + // UDP or UNIX_DGRAM). When used with AF_INET/AF_INET6 (UDP), the addresses + // are followed by the source and destination ports represented on 2 bytes + // each in network byte order. + // + // - other values are unspecified and must not be emitted in version 2 of this + // protocol and must be rejected as invalid by receivers. + + if familyByte>>4 == 0x0 || familyByte&0xf == 0x0 { + // - hi 0x0 : AF_UNSPEC : the connection is forwarded for an unknown address type + // or + // - lo 0x0 : UNSPEC : the connection is forwarded for an unspecified protocol + if !p.acceptUnknown { + p.conn.Close() + return &ErrBadHeader{append(v2Prefix, version, familyByte, uint8(addressLen>>8), uint8(addressLen&0xff))} + } + p.remoteAddr = p.conn.LocalAddr() + p.localAddr = p.conn.RemoteAddr() + _, err = p.bufReader.Discard(int(addressLen)) + return err + } + + // other address or protocol + if (familyByte>>4) > 0x3 || (familyByte&0xf) > 0x2 { + return &ErrBadHeader{append(v2Prefix, version, familyByte, uint8(addressLen>>8), uint8(addressLen&0xff))} + } + + // Handle AF_UNIX addresses + if familyByte>>4 == 0x3 { + // - \x31 : UNIX stream : the forwarded connection uses SOCK_STREAM over the + // AF_UNIX protocol family. Address length is 2*108 = 216 bytes. + // - \x32 : UNIX datagram : the forwarded connection uses SOCK_DGRAM over the + // AF_UNIX protocol family. Address length is 2*108 = 216 bytes. + if addressLen != 216 { + return &ErrBadHeader{append(v2Prefix, version, familyByte, uint8(addressLen>>8), uint8(addressLen&0xff))} + } + remoteName := make([]byte, 108) + localName := make([]byte, 108) + if _, err := p.bufReader.Read(remoteName); err != nil { + return err + } + if _, err := p.bufReader.Read(localName); err != nil { + return err + } + protocol := "unix" + if familyByte&0xf == 2 { + protocol = "unixgram" + } + + p.remoteAddr = &net.UnixAddr{ + Name: string(remoteName), + Net: protocol, + } + p.localAddr = &net.UnixAddr{ + Name: string(localName), + Net: protocol, + } + return nil + } + + var remoteIP []byte + var localIP []byte + var remotePort uint16 + var localPort uint16 + + if familyByte>>4 == 0x1 { + // AF_INET + // - \x11 : TCP over IPv4 : the forwarded connection uses TCP over the AF_INET + // protocol family. Address length is 2*4 + 2*2 = 12 bytes. + // - \x12 : UDP over IPv4 : the forwarded connection uses UDP over the AF_INET + // protocol family. Address length is 2*4 + 2*2 = 12 bytes. + if addressLen != 12 { + return &ErrBadHeader{append(v2Prefix, version, familyByte, uint8(addressLen>>8), uint8(addressLen&0xff))} + } + + remoteIP = make([]byte, 4) + localIP = make([]byte, 4) + } else { + // AF_INET6 + // - \x21 : TCP over IPv6 : the forwarded connection uses TCP over the AF_INET6 + // protocol family. Address length is 2*16 + 2*2 = 36 bytes. + // - \x22 : UDP over IPv6 : the forwarded connection uses UDP over the AF_INET6 + // protocol family. Address length is 2*16 + 2*2 = 36 bytes. + if addressLen != 36 { + return &ErrBadHeader{append(v2Prefix, version, familyByte, uint8(addressLen>>8), uint8(addressLen&0xff))} + } + + remoteIP = make([]byte, 16) + localIP = make([]byte, 16) + } + + if _, err := p.bufReader.Read(remoteIP); err != nil { + return err + } + if _, err := p.bufReader.Read(localIP); err != nil { + return err + } + if err := binary.Read(p.bufReader, binary.BigEndian, &remotePort); err != nil { + return err + } + if err := binary.Read(p.bufReader, binary.BigEndian, &localPort); err != nil { + return err + } + + if familyByte&0xf == 1 { + p.remoteAddr = &net.TCPAddr{ + IP: remoteIP, + Port: int(remotePort), + } + p.localAddr = &net.TCPAddr{ + IP: localIP, + Port: int(localPort), + } + } else { + p.remoteAddr = &net.UDPAddr{ + IP: remoteIP, + Port: int(remotePort), + } + p.localAddr = &net.UDPAddr{ + IP: localIP, + Port: int(localPort), + } + } + return nil +} + +func (p *Conn) readV1ProxyHeader() error { + // Read until a newline + header, err := p.bufReader.ReadString('\n') + if err != nil { + p.conn.Close() + return err + } + + if header[len(header)-2] != '\r' { + return &ErrBadHeader{[]byte(header)} + } + + // Strip the carriage return and new line + header = header[:len(header)-2] + + // Split on spaces, should be (PROXY ) + parts := strings.Split(header, " ") + if len(parts) < 2 { + p.conn.Close() + return &ErrBadHeader{[]byte(header)} + } + + // Verify the type is known + switch parts[1] { + case "UNKNOWN": + if !p.acceptUnknown || len(parts) != 2 { + p.conn.Close() + return &ErrBadHeader{[]byte(header)} + } + p.remoteAddr = p.conn.LocalAddr() + p.localAddr = p.conn.RemoteAddr() + return nil + case "TCP4": + case "TCP6": + default: + p.conn.Close() + return &ErrBadAddressType{parts[1]} + } + + if len(parts) != 6 { + p.conn.Close() + return &ErrBadHeader{[]byte(header)} + } + + // Parse out the remote address + ip := net.ParseIP(parts[2]) + if ip == nil { + p.conn.Close() + return &ErrBadRemote{parts[2], parts[4]} + } + port, err := strconv.Atoi(parts[4]) + if err != nil { + p.conn.Close() + return &ErrBadRemote{parts[2], parts[4]} + } + p.remoteAddr = &net.TCPAddr{IP: ip, Port: port} + + // Parse out the destination address + ip = net.ParseIP(parts[3]) + if ip == nil { + p.conn.Close() + return &ErrBadLocal{parts[3], parts[5]} + } + port, err = strconv.Atoi(parts[5]) + if err != nil { + p.conn.Close() + return &ErrBadLocal{parts[3], parts[5]} + } + p.localAddr = &net.TCPAddr{IP: ip, Port: port} + + return nil +} diff --git a/modules/proxyprotocol/errors.go b/modules/proxyprotocol/errors.go new file mode 100644 index 0000000000..2acf9d84b0 --- /dev/null +++ b/modules/proxyprotocol/errors.go @@ -0,0 +1,45 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package proxyprotocol + +import "fmt" + +// ErrBadHeader is an error demonstrating a bad proxy header +type ErrBadHeader struct { + Header []byte +} + +func (e *ErrBadHeader) Error() string { + return fmt.Sprintf("Unexpected proxy header: %v", e.Header) +} + +// ErrBadAddressType is an error demonstrating a bad proxy header with bad Address type +type ErrBadAddressType struct { + Address string +} + +func (e *ErrBadAddressType) Error() string { + return fmt.Sprintf("Unexpected proxy header address type: %s", e.Address) +} + +// ErrBadRemote is an error demonstrating a bad proxy header with bad Remote +type ErrBadRemote struct { + IP string + Port string +} + +func (e *ErrBadRemote) Error() string { + return fmt.Sprintf("Unexpected proxy header remote IP and port: %s %s", e.IP, e.Port) +} + +// ErrBadLocal is an error demonstrating a bad proxy header with bad Local +type ErrBadLocal struct { + IP string + Port string +} + +func (e *ErrBadLocal) Error() string { + return fmt.Sprintf("Unexpected proxy header local IP and port: %s %s", e.IP, e.Port) +} diff --git a/modules/proxyprotocol/listener.go b/modules/proxyprotocol/listener.go new file mode 100644 index 0000000000..64d9b323e5 --- /dev/null +++ b/modules/proxyprotocol/listener.go @@ -0,0 +1,47 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package proxyprotocol + +import ( + "net" + "time" +) + +// Listener is used to wrap an underlying listener, +// whose connections may be using the HAProxy Proxy Protocol (version 1 or 2). +// If the connection is using the protocol, the RemoteAddr() will return +// the correct client address. +// +// Optionally define ProxyHeaderTimeout to set a maximum time to +// receive the Proxy Protocol Header. Zero means no timeout. +type Listener struct { + Listener net.Listener + ProxyHeaderTimeout time.Duration + AcceptUnknown bool // allow PROXY UNKNOWN +} + +// Accept implements the Accept method in the Listener interface +// it waits for the next call and returns a wrapped Conn. +func (p *Listener) Accept() (net.Conn, error) { + // Get the underlying connection + conn, err := p.Listener.Accept() + if err != nil { + return nil, err + } + + newConn := NewConn(conn, p.ProxyHeaderTimeout) + newConn.acceptUnknown = p.AcceptUnknown + return newConn, nil +} + +// Close closes the underlying listener. +func (p *Listener) Close() error { + return p.Listener.Close() +} + +// Addr returns the underlying listener's network address. +func (p *Listener) Addr() net.Addr { + return p.Listener.Addr() +} diff --git a/modules/proxyprotocol/util.go b/modules/proxyprotocol/util.go new file mode 100644 index 0000000000..b12771b686 --- /dev/null +++ b/modules/proxyprotocol/util.go @@ -0,0 +1,15 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package proxyprotocol + +import "io" + +var localHeader = append(v2Prefix, '\x20', '\x00', '\x00', '\x00', '\x00') + +// WriteLocalHeader will write the ProxyProtocol Header for a local connection to the provided writer +func WriteLocalHeader(w io.Writer) error { + _, err := w.Write(localHeader) + return err +} diff --git a/modules/setting/setting.go b/modules/setting/setting.go index 3ab25fef6e..931b6523ea 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -94,45 +94,52 @@ var ( LocalURL string // Server settings - Protocol Scheme - Domain string - HTTPAddr string - HTTPPort string - RedirectOtherPort bool - PortToRedirect string - OfflineMode bool - CertFile string - KeyFile string - StaticRootPath string - StaticCacheTime time.Duration - EnableGzip bool - LandingPageURL LandingPage - LandingPageCustom string - UnixSocketPermission uint32 - EnablePprof bool - PprofDataPath string - EnableAcme bool - AcmeTOS bool - AcmeLiveDirectory string - AcmeEmail string - AcmeURL string - AcmeCARoot string - SSLMinimumVersion string - SSLMaximumVersion string - SSLCurvePreferences []string - SSLCipherSuites []string - GracefulRestartable bool - GracefulHammerTime time.Duration - StartupTimeout time.Duration - PerWriteTimeout = 30 * time.Second - PerWritePerKbTimeout = 10 * time.Second - StaticURLPrefix string - AbsoluteAssetURL string + Protocol Scheme + UseProxyProtocol bool // `ini:"USE_PROXY_PROTOCOL"` + ProxyProtocolTLSBridging bool //`ini:"PROXY_PROTOCOL_TLS_BRIDGING"` + ProxyProtocolHeaderTimeout time.Duration + ProxyProtocolAcceptUnknown bool + Domain string + HTTPAddr string + HTTPPort string + LocalUseProxyProtocol bool + RedirectOtherPort bool + RedirectorUseProxyProtocol bool + PortToRedirect string + OfflineMode bool + CertFile string + KeyFile string + StaticRootPath string + StaticCacheTime time.Duration + EnableGzip bool + LandingPageURL LandingPage + LandingPageCustom string + UnixSocketPermission uint32 + EnablePprof bool + PprofDataPath string + EnableAcme bool + AcmeTOS bool + AcmeLiveDirectory string + AcmeEmail string + AcmeURL string + AcmeCARoot string + SSLMinimumVersion string + SSLMaximumVersion string + SSLCurvePreferences []string + SSLCipherSuites []string + GracefulRestartable bool + GracefulHammerTime time.Duration + StartupTimeout time.Duration + PerWriteTimeout = 30 * time.Second + PerWritePerKbTimeout = 10 * time.Second + StaticURLPrefix string + AbsoluteAssetURL string SSH = struct { Disabled bool `ini:"DISABLE_SSH"` StartBuiltinServer bool `ini:"START_SSH_SERVER"` BuiltinServerUser string `ini:"BUILTIN_SSH_SERVER_USER"` + UseProxyProtocol bool `ini:"SSH_SERVER_USE_PROXY_PROTOCOL"` Domain string `ini:"SSH_DOMAIN"` Port int `ini:"SSH_PORT"` User string `ini:"SSH_USER"` @@ -717,6 +724,10 @@ func loadFromConf(allowEmpty bool, extraConfig string) { HTTPAddr = filepath.Join(AppWorkPath, HTTPAddr) } } + UseProxyProtocol = sec.Key("USE_PROXY_PROTOCOL").MustBool(false) + ProxyProtocolTLSBridging = sec.Key("PROXY_PROTOCOL_TLS_BRIDGING").MustBool(false) + ProxyProtocolHeaderTimeout = sec.Key("PROXY_PROTOCOL_HEADER_TIMEOUT").MustDuration(5 * time.Second) + ProxyProtocolAcceptUnknown = sec.Key("PROXY_PROTOCOL_ACCEPT_UNKNOWN").MustBool(false) GracefulRestartable = sec.Key("ALLOW_GRACEFUL_RESTARTS").MustBool(true) GracefulHammerTime = sec.Key("GRACEFUL_HAMMER_TIME").MustDuration(60 * time.Second) StartupTimeout = sec.Key("STARTUP_TIMEOUT").MustDuration(0 * time.Second) @@ -770,8 +781,10 @@ func loadFromConf(allowEmpty bool, extraConfig string) { } LocalURL = sec.Key("LOCAL_ROOT_URL").MustString(defaultLocalURL) LocalURL = strings.TrimRight(LocalURL, "/") + "/" + LocalUseProxyProtocol = sec.Key("LOCAL_USE_PROXY_PROTOCOL").MustBool(UseProxyProtocol) RedirectOtherPort = sec.Key("REDIRECT_OTHER_PORT").MustBool(false) PortToRedirect = sec.Key("PORT_TO_REDIRECT").MustString("80") + RedirectorUseProxyProtocol = sec.Key("REDIRECTOR_USE_PROXY_PROTOCOL").MustBool(UseProxyProtocol) OfflineMode = sec.Key("OFFLINE_MODE").MustBool() DisableRouterLog = sec.Key("DISABLE_ROUTER_LOG").MustBool() if len(StaticRootPath) == 0 { @@ -836,6 +849,7 @@ func loadFromConf(allowEmpty bool, extraConfig string) { SSH.KeygenPath = sec.Key("SSH_KEYGEN_PATH").MustString("ssh-keygen") SSH.Port = sec.Key("SSH_PORT").MustInt(22) SSH.ListenPort = sec.Key("SSH_LISTEN_PORT").MustInt(SSH.Port) + SSH.UseProxyProtocol = sec.Key("SSH_SERVER_USE_PROXY_PROTOCOL").MustBool(false) // When disable SSH, start builtin server value is ignored. if SSH.Disabled { diff --git a/modules/ssh/ssh_graceful.go b/modules/ssh/ssh_graceful.go index 9b91baf09e..166ea0b982 100644 --- a/modules/ssh/ssh_graceful.go +++ b/modules/ssh/ssh_graceful.go @@ -17,7 +17,7 @@ func listen(server *ssh.Server) { gracefulServer.PerWriteTimeout = setting.SSH.PerWriteTimeout gracefulServer.PerWritePerKbTimeout = setting.SSH.PerWritePerKbTimeout - err := gracefulServer.ListenAndServe(server.Serve) + err := gracefulServer.ListenAndServe(server.Serve, setting.SSH.UseProxyProtocol) if err != nil { select { case <-graceful.GetManager().IsShutdown(): From 36dfe544f49b26a77f7ee87d30b5464f1df1a6a7 Mon Sep 17 00:00:00 2001 From: Gary Wang Date: Mon, 22 Aug 2022 02:23:50 +0800 Subject: [PATCH 10/85] Fix mirror address setting not working (#20850) This patch fixes the issue that the mirror address field is ignored from the repo setting form. --- routers/web/repo/setting.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/routers/web/repo/setting.go b/routers/web/repo/setting.go index a59824cecd..2a04dc06a3 100644 --- a/routers/web/repo/setting.go +++ b/routers/web/repo/setting.go @@ -228,14 +228,17 @@ func SettingsPost(ctx *context.Context) { form.MirrorPassword, _ = u.User.Password() } - err = migrations.IsMigrateURLAllowed(u.String(), ctx.Doer) + address, err := forms.ParseRemoteAddr(form.MirrorAddress, form.MirrorUsername, form.MirrorPassword) + if err == nil { + err = migrations.IsMigrateURLAllowed(address, ctx.Doer) + } if err != nil { ctx.Data["Err_MirrorAddress"] = true handleSettingRemoteAddrError(ctx, err, form) return } - if err := mirror_service.UpdateAddress(ctx, ctx.Repo.Mirror, u.String()); err != nil { + if err := mirror_service.UpdateAddress(ctx, ctx.Repo.Mirror, address); err != nil { ctx.ServerError("UpdateAddress", err) return } From 585e80a7facd961c7c472ca7cfd9b62fc6711eee Mon Sep 17 00:00:00 2001 From: Gary Wang Date: Mon, 22 Aug 2022 00:20:55 +0000 Subject: [PATCH 11/85] [skip ci] Updated translations via Crowdin --- options/locale/locale_ja-JP.ini | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/options/locale/locale_ja-JP.ini b/options/locale/locale_ja-JP.ini index efb41ddd84..04e5d25436 100644 --- a/options/locale/locale_ja-JP.ini +++ b/options/locale/locale_ja-JP.ini @@ -1035,6 +1035,13 @@ file_view_rendered=レンダリング表示 file_view_raw=Rawデータを見る file_permalink=パーマリンク file_too_large=このファイルは大きすぎるため、表示できません。 +invisible_runes_header=`このファイルには不可視のUnicode文字が含まれています!` +invisible_runes_description=`このファイルには不可視Unicode文字が含まれており、下に見えているものとは異なる処理が行われる可能性があります。 あなたのユースケースが意図的かつ正当な場合はこの警告を無視して構いません。 不可視文字を表示するにはエスケープボタンを使用します。` +ambiguous_runes_header=`このファイルには曖昧(ambiguous)なUnicode文字が含まれています!` +ambiguous_runes_description=`このファイルには曖昧(ambiguous)なUnicode文字が含まれており、あなたが使用しているロケールにおいて他の文字と混同する可能性があります。 あなたのユースケースが意図的かつ正当な場合はこの警告を無視して構いません。 それらの文字をハイライトするにはエスケープボタンを使用します。` +invisible_runes_line=`この行には不可視のUnicode文字があります` +ambiguous_runes_line=`この行には曖昧(ambiguous)なUnicode文字があります` +ambiguous_character=`%[1]c [U+%04[1]X] は %[2]c [U+%04[2]X] と混同するおそれがあります` escape_control_characters=エスケープ unescape_control_characters=エスケープ解除 @@ -2888,6 +2895,7 @@ monitor.queue.nopool.title=ワーカープールはありません monitor.queue.nopool.desc=このキューは他のキューをラップし、これ自体にはワーカープールがありません。 monitor.queue.wrapped.desc=wrappedキューは、すぐに開始されないキューをラップし、入ってきたリクエストをチャンネルにバッファリングします。 これ自体にはワーカープールがありません。 monitor.queue.persistable-channel.desc=persistable-channelキューは二つのキューをラップします。 一つはchannelキューで、自分のワーカープールを持ちます。もう一つはlevelキューで、前回のシャットダウンからリクエストを引き継ぐためのものです。 これ自体にはワーカープールがありません。 +monitor.queue.flush=掃き出しワーカー monitor.queue.pool.timeout=タイムアウト monitor.queue.pool.addworkers.title=ワーカーの追加 monitor.queue.pool.addworkers.submit=ワーカーを追加 From 7854c447d920334e941f43d00622592a0869ef0d Mon Sep 17 00:00:00 2001 From: techknowlogick Date: Sun, 21 Aug 2022 22:14:13 -0400 Subject: [PATCH 12/85] update current stable version --- docs/config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/config.yaml b/docs/config.yaml index d9544bd301..92e033a907 100644 --- a/docs/config.yaml +++ b/docs/config.yaml @@ -18,7 +18,7 @@ params: description: Git with a cup of tea author: The Gitea Authors website: https://docs.gitea.io - version: 1.16.9 + version: 1.17.1 minGoVersion: 1.18 goVersion: 1.19 minNodeVersion: 14 From 353a5331d520a7299d922b670f6ccee0df8a345e Mon Sep 17 00:00:00 2001 From: Gusted Date: Mon, 22 Aug 2022 06:33:01 +0200 Subject: [PATCH 13/85] Remove deprecated licenses (#20222) --- build/generate-licenses.go | 8 +++++ options/license/AGPL-1.0 | 48 ---------------------------- options/license/BSD-2-Clause-FreeBSD | 27 ---------------- options/license/BSD-2-Clause-NetBSD | 24 -------------- options/license/Verbatim-man-pages | 21 ------------ 5 files changed, 8 insertions(+), 120 deletions(-) delete mode 100644 options/license/AGPL-1.0 delete mode 100644 options/license/BSD-2-Clause-FreeBSD delete mode 100644 options/license/BSD-2-Clause-NetBSD delete mode 100644 options/license/Verbatim-man-pages diff --git a/build/generate-licenses.go b/build/generate-licenses.go index 02b41a229a..9a111bc811 100644 --- a/build/generate-licenses.go +++ b/build/generate-licenses.go @@ -39,6 +39,14 @@ func main() { defer util.Remove(file.Name()) + if err := os.RemoveAll(destination); err != nil { + log.Fatalf("Cannot clean destination folder: %v", err) + } + + if err := os.MkdirAll(destination, 0o755); err != nil { + log.Fatalf("Cannot create destination: %v", err) + } + req, err := http.NewRequest("GET", url, nil) if err != nil { log.Fatalf("Failed to download archive. %s", err) diff --git a/options/license/AGPL-1.0 b/options/license/AGPL-1.0 deleted file mode 100644 index 3c7a40e177..0000000000 --- a/options/license/AGPL-1.0 +++ /dev/null @@ -1,48 +0,0 @@ -AFFERO GENERAL PUBLIC LICENSE -Version 1, March 2002 Copyright © 2002 Affero Inc. -510 Third Street - Suite 225, San Francisco, CA 94107, USA -This license is a modified version of the GNU General Public License copyright (C) 1989, 1991 Free Software Foundation, Inc. made with their permission. Section 2(d) has been added to cover use of software over a computer network. -Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. -Preamble -The licenses for most software are designed to take away your freedom to share and change it. By contrast, the Affero General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This Public License applies to most of Affero's software and to any other program whose authors commit to using it. (Some other Affero software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. -When we speak of free software, we are referring to freedom, not price. This General Public License is designed to make sure that you have the freedom to distribute copies of free software (and charge for this service 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 make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. -For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. 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. -We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. -Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. -Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. -The precise terms and conditions for copying, distribution and modification follow. -TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this Affero General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". - Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. - 1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. - You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. - 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: - a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. - b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. - c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) - d) If the Program as you received it is intended to interact with users through a computer network and if, in the version you received, any user interacting with the Program was given the opportunity to request transmission to that user of the Program's complete source code, you must not remove that facility from your modified version of the Program or work based on the Program, and must offer an equivalent opportunity for all users interacting with your Program through a computer network to request immediate transmission by HTTP of the complete source code of your modified version or other derivative work. - These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. - Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. - In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. - 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: - a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, - b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, - c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) - The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. - If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. - 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. - 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. - 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. - 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), 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 distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. - If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. - It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. - This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. - 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. - 9. Affero Inc. may publish revised and/or new versions of the Affero 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 a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by Affero, Inc. If the Program does not specify a version number of this License, you may choose any version ever published by Affero, Inc. - You may also choose to redistribute modified versions of this program under any version of the Free Software Foundation's GNU General Public License version 3 or higher, so long as that version of the GNU GPL includes terms and conditions substantially equivalent to those of this license. - 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by Affero, Inc., write to us; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. - NO WARRANTY - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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. - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE 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. diff --git a/options/license/BSD-2-Clause-FreeBSD b/options/license/BSD-2-Clause-FreeBSD deleted file mode 100644 index 004ec94575..0000000000 --- a/options/license/BSD-2-Clause-FreeBSD +++ /dev/null @@ -1,27 +0,0 @@ -The FreeBSD Copyright Copyright 1992-2012 The FreeBSD Project. All rights -reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation -and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE FREEBSD PROJECT ``AS IS'' AND ANY EXPRESS -OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -NO EVENT SHALL THE FREEBSD PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY -OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE -OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF -ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -The views and conclusions contained in the software and documentation are -those of the authors and should not be interpreted as representing official -policies, either expressed or implied, of the FreeBSD Project. diff --git a/options/license/BSD-2-Clause-NetBSD b/options/license/BSD-2-Clause-NetBSD deleted file mode 100644 index a842566867..0000000000 --- a/options/license/BSD-2-Clause-NetBSD +++ /dev/null @@ -1,24 +0,0 @@ -Copyright (c) 2008 The NetBSD Foundation, Inc. All rights reserved. - -This code is derived from software contributed to The NetBSD Foundation by - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation -and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS -``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED -TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE -LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE -USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/options/license/Verbatim-man-pages b/options/license/Verbatim-man-pages deleted file mode 100644 index 8a10d8e684..0000000000 --- a/options/license/Verbatim-man-pages +++ /dev/null @@ -1,21 +0,0 @@ -Copyright (c) 0000, Obelix the Gaul . - -Permission is granted to make and distribute verbatim copies of this -manual provided the copyright notice and this permission notice are -preserved on all copies. - -Permission is granted to copy and distribute modified versions of -this manual under the conditions for verbatim copying, provided that -the entire resulting derived work is distributed under the terms of -a permission notice identical to this one. - -Since the Linux kernel and libraries are constantly changing, this -manual page may be incorrect or out-of-date. The author(s) assume -no responsibility for errors or omissions, or for damages resulting -from the use of the information contained herein. The author(s) may -not have taken the same level of care in the production of this -manual, which is licensed free of charge, as they might when working -professionally. - -Formatted or processed versions of this manual, if unaccompanied by -the source, must acknowledge the copyright and authors of this work. From aedfc923addcb7aa013f96474e942951e3fda74c Mon Sep 17 00:00:00 2001 From: CodeDoctor <20452814+CodeDoctorDE@users.noreply.github.com> Date: Mon, 22 Aug 2022 14:51:48 +0200 Subject: [PATCH 14/85] Improve single repo action for issue and pull requests (#20730) Related to #20650. This will fix the behavior of the single repo action for pull requests and disables the button for other screens that don't have a single repo action currently. --- routers/web/user/home.go | 2 ++ templates/user/dashboard/issues.tmpl | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/routers/web/user/home.go b/routers/web/user/home.go index f28a684054..c4aa749ce2 100644 --- a/routers/web/user/home.go +++ b/routers/web/user/home.go @@ -301,6 +301,7 @@ func Pulls(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("pull_requests") ctx.Data["PageIsPulls"] = true + ctx.Data["SingleRepoAction"] = "pull" buildIssueOverview(ctx, unit.TypePullRequests) } @@ -314,6 +315,7 @@ func Issues(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("issues") ctx.Data["PageIsIssues"] = true + ctx.Data["SingleRepoAction"] = "issue" buildIssueOverview(ctx, unit.TypeIssues) } diff --git a/templates/user/dashboard/issues.tmpl b/templates/user/dashboard/issues.tmpl index b6dc002154..666a7bdb2a 100644 --- a/templates/user/dashboard/issues.tmpl +++ b/templates/user/dashboard/issues.tmpl @@ -100,7 +100,11 @@
{{if .SingleRepoLink}} - {{.locale.Tr "repo.issues.new"}} + {{if eq .SingleRepoAction "issue"}} + {{.locale.Tr "repo.issues.new"}} + {{else if eq .SingleRepoAction "pull"}} + {{.locale.Tr "repo.pulls.new"}} + {{end}} {{end}} From 9d6a2034bcf0f62dadb51cfe1471632eaa52e36c Mon Sep 17 00:00:00 2001 From: zeripath Date: Mon, 22 Aug 2022 14:32:28 +0100 Subject: [PATCH 15/85] Increase Content field size of gpg_key and public_key to MEDIUMTEXT (#20896) Unfortunately some keys are too big to fix within the 65535 limit of TEXT on MySQL this causes issues with these large keys. Therefore increase these fields to MEDIUMTEXT. Fix #20894 Signed-off-by: Andrew Thornton --- models/asymkey/gpg_key.go | 2 +- models/asymkey/ssh_key.go | 2 +- models/migrations/migrations.go | 7 ++++++- models/migrations/v224.go | 2 +- models/migrations/v225.go | 29 +++++++++++++++++++++++++++++ 5 files changed, 38 insertions(+), 4 deletions(-) create mode 100644 models/migrations/v225.go diff --git a/models/asymkey/gpg_key.go b/models/asymkey/gpg_key.go index 21554d2150..78dc453e0d 100644 --- a/models/asymkey/gpg_key.go +++ b/models/asymkey/gpg_key.go @@ -33,7 +33,7 @@ type GPGKey struct { OwnerID int64 `xorm:"INDEX NOT NULL"` KeyID string `xorm:"INDEX CHAR(16) NOT NULL"` PrimaryKeyID string `xorm:"CHAR(16)"` - Content string `xorm:"TEXT NOT NULL"` + Content string `xorm:"MEDIUMTEXT NOT NULL"` CreatedUnix timeutil.TimeStamp `xorm:"created"` ExpiredUnix timeutil.TimeStamp AddedUnix timeutil.TimeStamp diff --git a/models/asymkey/ssh_key.go b/models/asymkey/ssh_key.go index 107a29e985..9f95bb5baf 100644 --- a/models/asymkey/ssh_key.go +++ b/models/asymkey/ssh_key.go @@ -41,7 +41,7 @@ type PublicKey struct { OwnerID int64 `xorm:"INDEX NOT NULL"` Name string `xorm:"NOT NULL"` Fingerprint string `xorm:"INDEX NOT NULL"` - Content string `xorm:"TEXT NOT NULL"` + Content string `xorm:"MEDIUMTEXT NOT NULL"` Mode perm.AccessMode `xorm:"NOT NULL DEFAULT 2"` Type KeyType `xorm:"NOT NULL DEFAULT 1"` LoginSourceID int64 `xorm:"NOT NULL DEFAULT 0"` diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 30c4ad250c..28ffc99886 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -406,8 +406,13 @@ var migrations = []Migration{ NewMigration("Drop old CredentialID column", dropOldCredentialIDColumn), // v223 -> v224 NewMigration("Rename CredentialIDBytes column to CredentialID", renameCredentialIDBytes), + + // Gitea 1.17.0 ends at v224 + // v224 -> v225 - NewMigration("Add badges to users", creatUserBadgesTable), + NewMigration("Add badges to users", createUserBadgesTable), + // v225 -> v226 + NewMigration("Alter gpg_key/public_key content TEXT fields to MEDIUMTEXT", alterPublicGPGKeyContentFieldsToMediumText), } // GetCurrentDBVersion returns the current db version diff --git a/models/migrations/v224.go b/models/migrations/v224.go index d684d538df..2ed161ef4d 100644 --- a/models/migrations/v224.go +++ b/models/migrations/v224.go @@ -8,7 +8,7 @@ import ( "xorm.io/xorm" ) -func creatUserBadgesTable(x *xorm.Engine) error { +func createUserBadgesTable(x *xorm.Engine) error { type Badge struct { ID int64 `xorm:"pk autoincr"` Description string diff --git a/models/migrations/v225.go b/models/migrations/v225.go new file mode 100644 index 0000000000..6dd460eb68 --- /dev/null +++ b/models/migrations/v225.go @@ -0,0 +1,29 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package migrations + +import ( + "code.gitea.io/gitea/modules/setting" + + "xorm.io/xorm" +) + +func alterPublicGPGKeyContentFieldsToMediumText(x *xorm.Engine) error { + sess := x.NewSession() + defer sess.Close() + if err := sess.Begin(); err != nil { + return err + } + + if setting.Database.UseMySQL { + if _, err := sess.Exec("ALTER TABLE `gpg_key` CHANGE `content` `content` MEDIUMTEXT"); err != nil { + return err + } + if _, err := sess.Exec("ALTER TABLE `public_key` CHANGE `content` `content` MEDIUMTEXT"); err != nil { + return err + } + } + return sess.Commit() +} From 502f7526d0588894d179731a0a130d9448588e21 Mon Sep 17 00:00:00 2001 From: Enrico Date: Mon, 22 Aug 2022 20:39:59 +0200 Subject: [PATCH 16/85] Fix typo in backup documentation (pgdump->pg_dump) (#20913) This PR fixes a small typo in the backup documentation: `pgdump` command is wrong, the correct name for the backup software in PostgreSQL is `pg_dump` --- docs/content/doc/usage/backup-and-restore.en-us.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/doc/usage/backup-and-restore.en-us.md b/docs/content/doc/usage/backup-and-restore.en-us.md index c9c41c413c..cae097082e 100644 --- a/docs/content/doc/usage/backup-and-restore.en-us.md +++ b/docs/content/doc/usage/backup-and-restore.en-us.md @@ -62,7 +62,7 @@ The SQL dump created by `gitea dump` uses XORM and Gitea admins may prefer to us # mysql mysqldump -u$USER -p$PASS --database $DATABASE > gitea-db.sql # postgres -pgdump -U $USER $DATABASE > gitea-db.sql +pg_dump -U $USER $DATABASE > gitea-db.sql ``` ### Using Docker (`dump`) From fa8fc5d3a71fcceb543c7a4a4f2c7f28c103db16 Mon Sep 17 00:00:00 2001 From: John Olheiser Date: Mon, 22 Aug 2022 21:02:16 -0500 Subject: [PATCH 17/85] Changelog 1.17.1 (#20833) (#20919) * Changelog 1.17.1 (#20833) * Changelog 1.17.1 ## [1.17.1](https://github.com/go-gitea/gitea/releases/tag/1.17.1) - 2022-08-17 * SECURITY * Correctly escape within tribute.js (#20831) (#20832) * FEATURES * Add support for NuGet API keys (#20721) (#20734) * ENHANCEMENTS * Display project in issue list (#20583) * Add disable download source configuration (#20548) (#20579) * BUGFIXES * Use the total issue count for UI (#20785) (#20827) * Add proxy host into allow list (#20798) (#20819) * Add missing translation for queue flush workers (#20791) (#20792) * Improve comment header for mobile (#20781) (#20789) * Fix git.Init for doctor sub-command (#20782) (#20783) * Check webhooks slice length before calling xorm (#20642) (#20768) * Remove manual rollback for failed generated repositories (#20639) (#20762) * Use correct field name in npm template (#20675) (#20760) * Keep download count on Container tag overwrite (#20728) (#20735) * Fix v220 migration to be compatible for MSSQL 2008 r2 (#20702) (#20707) * Use request timeout for git service rpc (#20689) (#20693) * Send correct NuGet status codes (#20647) (#20677) * Use correct context to get package content (#20673) (#20676) * Fix the JS error "EventSource is not defined" caused by some non-standard browsers (#20584) (#20663) * Add default commit messages to PR for squash merge (#20618) (#20645) * Fix package upload for files >32mb (#20622) (#20635) * Fix the new-line copy-paste for rendered code (#20612) * Clean up and fix clone button script (#20415 & #20600) (#20599) * Fix default merge style (#20564) (#20565) * Add repository condition for issue count (#20454) (#20496) * MISC * Make branch icon stand out more (#20726) (#20774) * Fix loading button with invalid form (#20754) (#20759) * Add username check to doctor (#20140) (#20671) * Enable Wire 2 for Internal SSH Server (#20616) (#20617) * Fix SecToTime edge-cases (#20610) (#20611) Signed-off-by: Andrew Thornton * Apply suggestions from code review Co-authored-by: John Olheiser * Update CHANGELOG.md Co-authored-by: delvh * Update CHANGELOG.md * Update CHANGELOG.md * update changelog * Update CHANGELOG.md Co-authored-by: John Olheiser Signed-off-by: Andrew Thornton Co-authored-by: techknowlogick Co-authored-by: John Olheiser Co-authored-by: delvh Co-authored-by: Lauris BH Co-authored-by: Lunny Xiao Co-authored-by: techknowlogick * update changelog Signed-off-by: Andrew Thornton Co-authored-by: zeripath Co-authored-by: techknowlogick Co-authored-by: delvh Co-authored-by: Lauris BH Co-authored-by: Lunny Xiao Co-authored-by: techknowlogick --- CHANGELOG.md | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 50e8394a99..1f7fbac1bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,45 @@ This changelog goes through all the changes that have been made in each release without substantial changes to our git log; to see the highlights of what has been added to each release, please refer to the [blog](https://blog.gitea.io). +## [1.17.1](https://github.com/go-gitea/gitea/releases/tag/1.17.1) - 2022-08-17 + +* SECURITY + * Correctly escape within tribute.js (#20831) (#20832) +* ENHANCEMENTS + * Add support for NuGet API keys (#20721) (#20734) + * Display project in issue list (#20583) + * Add disable download source configuration (#20548) (#20579) + * Add username check to doctor (#20140) (#20671) + * Enable Wire 2 for Internal SSH Server (#20616) (#20617) +* BUGFIXES + * Use the total issue count for UI (#20785) (#20827) + * Add proxy host into allow list (#20798) (#20819) + * Add missing translation for queue flush workers (#20791) (#20792) + * Improve comment header for mobile (#20781) (#20789) + * Fix git.Init for doctor sub-command (#20782) (#20783) + * Check webhooks slice length before calling xorm (#20642) (#20768) + * Remove manual rollback for failed generated repositories (#20639) (#20762) + * Use correct field name in npm template (#20675) (#20760) + * Keep download count on Container tag overwrite (#20728) (#20735) + * Fix v220 migration to be compatible for MSSQL 2008 r2 (#20702) (#20707) + * Use request timeout for git service rpc (#20689) (#20693) + * Send correct NuGet status codes (#20647) (#20677) + * Use correct context to get package content (#20673) (#20676) + * Fix the JS error "EventSource is not defined" caused by some non-standard browsers (#20584) (#20663) + * Add default commit messages to PR for squash merge (#20618) (#20645) + * Fix package upload for files >32mb (#20622) (#20635) + * Fix the new-line copy-paste for rendered code (#20612) + * Clean up and fix clone button script (#20415 & #20600) (#20599) + * Fix default merge style (#20564) (#20565) + * Add repository condition for issue count (#20454) (#20496) + * Make branch icon stand out more (#20726) (#20774) + * Fix loading button with invalid form (#20754) (#20759) + * Fix SecToTime edge-cases (#20610) (#20611) + * Executable check always returns true for windows (#20637) (#20835) + * Check issue labels slice length before calling xorm Insert (#20655) (#20836) + * Fix owners cannot create organization repos bug (#20841) (#20854) + * Prevent 500 is head repo does not have PullRequest unit in IsUserAllowedToUpdate (#20839) (#20848) + ## [1.17.0](https://github.com/go-gitea/gitea/releases/tag/v1.17.0) - 2022-07-30 * BREAKING From 0a9ed54abbe8b6837dcb22a35b744c11f410421f Mon Sep 17 00:00:00 2001 From: oliverpool <3864879+oliverpool@users.noreply.github.com> Date: Tue, 23 Aug 2022 08:52:35 +0200 Subject: [PATCH 18/85] refactor webhook *EditPost (#20780) --- routers/web/repo/webhook.go | 671 +++++++++++------------------------- routers/web/web.go | 6 +- 2 files changed, 196 insertions(+), 481 deletions(-) diff --git a/routers/web/repo/webhook.go b/routers/web/repo/webhook.go index d4419a1e10..a5270a7761 100644 --- a/routers/web/repo/webhook.go +++ b/routers/web/repo/webhook.go @@ -185,17 +185,19 @@ func ParseHookEvent(form forms.WebhookForm) *webhook.HookEvent { } } -type webhookCreationParams struct { +type webhookParams struct { + // Type should be imported from webhook package (webhook.XXX) + Type string + URL string ContentType webhook.HookContentType Secret string HTTPMethod string WebhookForm forms.WebhookForm - Type string Meta interface{} } -func createWebhook(ctx *context.Context, params webhookCreationParams) { +func createWebhook(ctx *context.Context, params webhookParams) { ctx.Data["Title"] = ctx.Tr("repo.settings.add_webhook") ctx.Data["PageIsSettingsHooks"] = true ctx.Data["PageIsSettingsHooksNew"] = true @@ -248,8 +250,63 @@ func createWebhook(ctx *context.Context, params webhookCreationParams) { ctx.Redirect(orCtx.Link) } +func editWebhook(ctx *context.Context, params webhookParams) { + ctx.Data["Title"] = ctx.Tr("repo.settings.update_webhook") + ctx.Data["PageIsSettingsHooks"] = true + ctx.Data["PageIsSettingsHooksEdit"] = true + + orCtx, w := checkWebhook(ctx) + if ctx.Written() { + return + } + ctx.Data["Webhook"] = w + + if ctx.HasError() { + ctx.HTML(http.StatusOK, orCtx.NewTemplate) + return + } + + var meta []byte + var err error + if params.Meta != nil { + meta, err = json.Marshal(params.Meta) + if err != nil { + ctx.ServerError("Marshal", err) + return + } + } + + w.URL = params.URL + w.ContentType = params.ContentType + w.Secret = params.Secret + w.HookEvent = ParseHookEvent(params.WebhookForm) + w.IsActive = params.WebhookForm.Active + w.HTTPMethod = params.HTTPMethod + w.Meta = string(meta) + + if err := w.UpdateEvent(); err != nil { + ctx.ServerError("UpdateEvent", err) + return + } else if err := webhook.UpdateWebhook(w); err != nil { + ctx.ServerError("UpdateWebhook", err) + return + } + + ctx.Flash.Success(ctx.Tr("repo.settings.update_hook_success")) + ctx.Redirect(fmt.Sprintf("%s/%d", orCtx.Link, w.ID)) +} + // GiteaHooksNewPost response for creating Gitea webhook func GiteaHooksNewPost(ctx *context.Context) { + createWebhook(ctx, giteaHookParams(ctx)) +} + +// GiteaHooksEditPost response for editing Gitea webhook +func GiteaHooksEditPost(ctx *context.Context) { + editWebhook(ctx, giteaHookParams(ctx)) +} + +func giteaHookParams(ctx *context.Context) webhookParams { form := web.GetForm(ctx).(*forms.NewWebhookForm) contentType := webhook.ContentTypeJSON @@ -257,18 +314,27 @@ func GiteaHooksNewPost(ctx *context.Context) { contentType = webhook.ContentTypeForm } - createWebhook(ctx, webhookCreationParams{ + return webhookParams{ + Type: webhook.GITEA, URL: form.PayloadURL, ContentType: contentType, Secret: form.Secret, HTTPMethod: form.HTTPMethod, WebhookForm: form.WebhookForm, - Type: webhook.GITEA, - }) + } } -// GogsHooksNewPost response for creating webhook +// GogsHooksNewPost response for creating Gogs webhook func GogsHooksNewPost(ctx *context.Context) { + createWebhook(ctx, gogsHookParams(ctx)) +} + +// GogsHooksEditPost response for editing Gogs webhook +func GogsHooksEditPost(ctx *context.Context) { + editWebhook(ctx, gogsHookParams(ctx)) +} + +func gogsHookParams(ctx *context.Context) webhookParams { form := web.GetForm(ctx).(*forms.NewGogshookForm) contentType := webhook.ContentTypeJSON @@ -276,147 +342,228 @@ func GogsHooksNewPost(ctx *context.Context) { contentType = webhook.ContentTypeForm } - createWebhook(ctx, webhookCreationParams{ + return webhookParams{ + Type: webhook.GOGS, URL: form.PayloadURL, ContentType: contentType, Secret: form.Secret, WebhookForm: form.WebhookForm, - Type: webhook.GOGS, - }) + } } -// DiscordHooksNewPost response for creating discord hook +// DiscordHooksNewPost response for creating Discord webhook func DiscordHooksNewPost(ctx *context.Context) { + createWebhook(ctx, discordHookParams(ctx)) +} + +// DiscordHooksEditPost response for editing Discord webhook +func DiscordHooksEditPost(ctx *context.Context) { + editWebhook(ctx, discordHookParams(ctx)) +} + +func discordHookParams(ctx *context.Context) webhookParams { form := web.GetForm(ctx).(*forms.NewDiscordHookForm) - createWebhook(ctx, webhookCreationParams{ + return webhookParams{ + Type: webhook.DISCORD, URL: form.PayloadURL, ContentType: webhook.ContentTypeJSON, WebhookForm: form.WebhookForm, - Type: webhook.DISCORD, Meta: &webhook_service.DiscordMeta{ Username: form.Username, IconURL: form.IconURL, }, - }) + } } -// DingtalkHooksNewPost response for creating dingtalk hook +// DingtalkHooksNewPost response for creating Dingtalk webhook func DingtalkHooksNewPost(ctx *context.Context) { + createWebhook(ctx, dingtalkHookParams(ctx)) +} + +// DingtalkHooksEditPost response for editing Dingtalk webhook +func DingtalkHooksEditPost(ctx *context.Context) { + editWebhook(ctx, dingtalkHookParams(ctx)) +} + +func dingtalkHookParams(ctx *context.Context) webhookParams { form := web.GetForm(ctx).(*forms.NewDingtalkHookForm) - createWebhook(ctx, webhookCreationParams{ + return webhookParams{ + Type: webhook.DINGTALK, URL: form.PayloadURL, ContentType: webhook.ContentTypeJSON, WebhookForm: form.WebhookForm, - Type: webhook.DINGTALK, - }) + } } -// TelegramHooksNewPost response for creating telegram hook +// TelegramHooksNewPost response for creating Telegram webhook func TelegramHooksNewPost(ctx *context.Context) { + createWebhook(ctx, telegramHookParams(ctx)) +} + +// TelegramHooksEditPost response for editing Telegram webhook +func TelegramHooksEditPost(ctx *context.Context) { + editWebhook(ctx, telegramHookParams(ctx)) +} + +func telegramHookParams(ctx *context.Context) webhookParams { form := web.GetForm(ctx).(*forms.NewTelegramHookForm) - createWebhook(ctx, webhookCreationParams{ + return webhookParams{ + Type: webhook.TELEGRAM, URL: fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage?chat_id=%s", url.PathEscape(form.BotToken), url.QueryEscape(form.ChatID)), ContentType: webhook.ContentTypeJSON, WebhookForm: form.WebhookForm, - Type: webhook.TELEGRAM, Meta: &webhook_service.TelegramMeta{ BotToken: form.BotToken, ChatID: form.ChatID, }, - }) + } } -// MatrixHooksNewPost response for creating a Matrix hook +// MatrixHooksNewPost response for creating Matrix webhook func MatrixHooksNewPost(ctx *context.Context) { + createWebhook(ctx, matrixHookParams(ctx)) +} + +// MatrixHooksEditPost response for editing Matrix webhook +func MatrixHooksEditPost(ctx *context.Context) { + editWebhook(ctx, matrixHookParams(ctx)) +} + +func matrixHookParams(ctx *context.Context) webhookParams { form := web.GetForm(ctx).(*forms.NewMatrixHookForm) - createWebhook(ctx, webhookCreationParams{ + return webhookParams{ + Type: webhook.MATRIX, URL: fmt.Sprintf("%s/_matrix/client/r0/rooms/%s/send/m.room.message", form.HomeserverURL, url.PathEscape(form.RoomID)), ContentType: webhook.ContentTypeJSON, HTTPMethod: http.MethodPut, WebhookForm: form.WebhookForm, - Type: webhook.MATRIX, Meta: &webhook_service.MatrixMeta{ HomeserverURL: form.HomeserverURL, Room: form.RoomID, AccessToken: form.AccessToken, MessageType: form.MessageType, }, - }) + } } -// MSTeamsHooksNewPost response for creating MS Teams hook +// MSTeamsHooksNewPost response for creating MSTeams webhook func MSTeamsHooksNewPost(ctx *context.Context) { + createWebhook(ctx, mSTeamsHookParams(ctx)) +} + +// MSTeamsHooksEditPost response for editing MSTeams webhook +func MSTeamsHooksEditPost(ctx *context.Context) { + editWebhook(ctx, mSTeamsHookParams(ctx)) +} + +func mSTeamsHookParams(ctx *context.Context) webhookParams { form := web.GetForm(ctx).(*forms.NewMSTeamsHookForm) - createWebhook(ctx, webhookCreationParams{ + return webhookParams{ + Type: webhook.MSTEAMS, URL: form.PayloadURL, ContentType: webhook.ContentTypeJSON, WebhookForm: form.WebhookForm, - Type: webhook.MSTEAMS, - }) + } } -// SlackHooksNewPost response for creating slack hook +// SlackHooksNewPost response for creating Slack webhook func SlackHooksNewPost(ctx *context.Context) { + createWebhook(ctx, slackHookParams(ctx)) +} + +// SlackHooksEditPost response for editing Slack webhook +func SlackHooksEditPost(ctx *context.Context) { + editWebhook(ctx, slackHookParams(ctx)) +} + +func slackHookParams(ctx *context.Context) webhookParams { form := web.GetForm(ctx).(*forms.NewSlackHookForm) - createWebhook(ctx, webhookCreationParams{ + return webhookParams{ + Type: webhook.SLACK, URL: form.PayloadURL, ContentType: webhook.ContentTypeJSON, WebhookForm: form.WebhookForm, - Type: webhook.SLACK, Meta: &webhook_service.SlackMeta{ Channel: strings.TrimSpace(form.Channel), Username: form.Username, IconURL: form.IconURL, Color: form.Color, }, - }) + } } -// FeishuHooksNewPost response for creating feishu hook +// FeishuHooksNewPost response for creating Feishu webhook func FeishuHooksNewPost(ctx *context.Context) { + createWebhook(ctx, feishuHookParams(ctx)) +} + +// FeishuHooksEditPost response for editing Feishu webhook +func FeishuHooksEditPost(ctx *context.Context) { + editWebhook(ctx, feishuHookParams(ctx)) +} + +func feishuHookParams(ctx *context.Context) webhookParams { form := web.GetForm(ctx).(*forms.NewFeishuHookForm) - createWebhook(ctx, webhookCreationParams{ + return webhookParams{ + Type: webhook.FEISHU, URL: form.PayloadURL, ContentType: webhook.ContentTypeJSON, WebhookForm: form.WebhookForm, - Type: webhook.FEISHU, - }) + } } -// WechatworkHooksNewPost response for creating wechatwork hook +// WechatworkHooksNewPost response for creating Wechatwork webhook func WechatworkHooksNewPost(ctx *context.Context) { + createWebhook(ctx, wechatworkHookParams(ctx)) +} + +// WechatworkHooksEditPost response for editing Wechatwork webhook +func WechatworkHooksEditPost(ctx *context.Context) { + editWebhook(ctx, wechatworkHookParams(ctx)) +} + +func wechatworkHookParams(ctx *context.Context) webhookParams { form := web.GetForm(ctx).(*forms.NewWechatWorkHookForm) - createWebhook(ctx, webhookCreationParams{ + return webhookParams{ + Type: webhook.WECHATWORK, URL: form.PayloadURL, ContentType: webhook.ContentTypeJSON, WebhookForm: form.WebhookForm, - Type: webhook.WECHATWORK, - }) + } } -// PackagistHooksNewPost response for creating packagist hook +// PackagistHooksNewPost response for creating Packagist webhook func PackagistHooksNewPost(ctx *context.Context) { + createWebhook(ctx, packagistHookParams(ctx)) +} + +// PackagistHooksEditPost response for editing Packagist webhook +func PackagistHooksEditPost(ctx *context.Context) { + editWebhook(ctx, packagistHookParams(ctx)) +} + +func packagistHookParams(ctx *context.Context) webhookParams { form := web.GetForm(ctx).(*forms.NewPackagistHookForm) - createWebhook(ctx, webhookCreationParams{ + return webhookParams{ + Type: webhook.PACKAGIST, URL: fmt.Sprintf("https://packagist.org/api/update-package?username=%s&apiToken=%s", url.QueryEscape(form.Username), url.QueryEscape(form.APIToken)), ContentType: webhook.ContentTypeJSON, WebhookForm: form.WebhookForm, - Type: webhook.PACKAGIST, Meta: &webhook_service.PackagistMeta{ Username: form.Username, APIToken: form.APIToken, PackageURL: form.PackageURL, }, - }) + } } func checkWebhook(ctx *context.Context) (*orgRepoCtx, *webhook.Webhook) { @@ -480,438 +627,6 @@ func WebHooksEdit(ctx *context.Context) { ctx.HTML(http.StatusOK, orCtx.NewTemplate) } -// WebHooksEditPost response for editing web hook -func WebHooksEditPost(ctx *context.Context) { - form := web.GetForm(ctx).(*forms.NewWebhookForm) - ctx.Data["Title"] = ctx.Tr("repo.settings.update_webhook") - ctx.Data["PageIsSettingsHooks"] = true - ctx.Data["PageIsSettingsHooksEdit"] = true - - orCtx, w := checkWebhook(ctx) - if ctx.Written() { - return - } - ctx.Data["Webhook"] = w - - if ctx.HasError() { - ctx.HTML(http.StatusOK, orCtx.NewTemplate) - return - } - - contentType := webhook.ContentTypeJSON - if webhook.HookContentType(form.ContentType) == webhook.ContentTypeForm { - contentType = webhook.ContentTypeForm - } - - w.URL = form.PayloadURL - w.ContentType = contentType - w.Secret = form.Secret - w.HookEvent = ParseHookEvent(form.WebhookForm) - w.IsActive = form.Active - w.HTTPMethod = form.HTTPMethod - if err := w.UpdateEvent(); err != nil { - ctx.ServerError("UpdateEvent", err) - return - } else if err := webhook.UpdateWebhook(w); err != nil { - ctx.ServerError("WebHooksEditPost", err) - return - } - - ctx.Flash.Success(ctx.Tr("repo.settings.update_hook_success")) - ctx.Redirect(fmt.Sprintf("%s/%d", orCtx.Link, w.ID)) -} - -// GogsHooksEditPost response for editing gogs hook -func GogsHooksEditPost(ctx *context.Context) { - form := web.GetForm(ctx).(*forms.NewGogshookForm) - ctx.Data["Title"] = ctx.Tr("repo.settings.update_webhook") - ctx.Data["PageIsSettingsHooks"] = true - ctx.Data["PageIsSettingsHooksEdit"] = true - - orCtx, w := checkWebhook(ctx) - if ctx.Written() { - return - } - ctx.Data["Webhook"] = w - - if ctx.HasError() { - ctx.HTML(http.StatusOK, orCtx.NewTemplate) - return - } - - contentType := webhook.ContentTypeJSON - if webhook.HookContentType(form.ContentType) == webhook.ContentTypeForm { - contentType = webhook.ContentTypeForm - } - - w.URL = form.PayloadURL - w.ContentType = contentType - w.Secret = form.Secret - w.HookEvent = ParseHookEvent(form.WebhookForm) - w.IsActive = form.Active - if err := w.UpdateEvent(); err != nil { - ctx.ServerError("UpdateEvent", err) - return - } else if err := webhook.UpdateWebhook(w); err != nil { - ctx.ServerError("GogsHooksEditPost", err) - return - } - - ctx.Flash.Success(ctx.Tr("repo.settings.update_hook_success")) - ctx.Redirect(fmt.Sprintf("%s/%d", orCtx.Link, w.ID)) -} - -// SlackHooksEditPost response for editing slack hook -func SlackHooksEditPost(ctx *context.Context) { - form := web.GetForm(ctx).(*forms.NewSlackHookForm) - ctx.Data["Title"] = ctx.Tr("repo.settings") - ctx.Data["PageIsSettingsHooks"] = true - ctx.Data["PageIsSettingsHooksEdit"] = true - - orCtx, w := checkWebhook(ctx) - if ctx.Written() { - return - } - ctx.Data["Webhook"] = w - - if ctx.HasError() { - ctx.HTML(http.StatusOK, orCtx.NewTemplate) - return - } - - meta, err := json.Marshal(&webhook_service.SlackMeta{ - Channel: strings.TrimSpace(form.Channel), - Username: form.Username, - IconURL: form.IconURL, - Color: form.Color, - }) - if err != nil { - ctx.ServerError("Marshal", err) - return - } - - w.URL = form.PayloadURL - w.Meta = string(meta) - w.HookEvent = ParseHookEvent(form.WebhookForm) - w.IsActive = form.Active - if err := w.UpdateEvent(); err != nil { - ctx.ServerError("UpdateEvent", err) - return - } else if err := webhook.UpdateWebhook(w); err != nil { - ctx.ServerError("UpdateWebhook", err) - return - } - - ctx.Flash.Success(ctx.Tr("repo.settings.update_hook_success")) - ctx.Redirect(fmt.Sprintf("%s/%d", orCtx.Link, w.ID)) -} - -// DiscordHooksEditPost response for editing discord hook -func DiscordHooksEditPost(ctx *context.Context) { - form := web.GetForm(ctx).(*forms.NewDiscordHookForm) - ctx.Data["Title"] = ctx.Tr("repo.settings") - ctx.Data["PageIsSettingsHooks"] = true - ctx.Data["PageIsSettingsHooksEdit"] = true - - orCtx, w := checkWebhook(ctx) - if ctx.Written() { - return - } - ctx.Data["Webhook"] = w - - if ctx.HasError() { - ctx.HTML(http.StatusOK, orCtx.NewTemplate) - return - } - - meta, err := json.Marshal(&webhook_service.DiscordMeta{ - Username: form.Username, - IconURL: form.IconURL, - }) - if err != nil { - ctx.ServerError("Marshal", err) - return - } - - w.URL = form.PayloadURL - w.Meta = string(meta) - w.HookEvent = ParseHookEvent(form.WebhookForm) - w.IsActive = form.Active - if err := w.UpdateEvent(); err != nil { - ctx.ServerError("UpdateEvent", err) - return - } else if err := webhook.UpdateWebhook(w); err != nil { - ctx.ServerError("UpdateWebhook", err) - return - } - - ctx.Flash.Success(ctx.Tr("repo.settings.update_hook_success")) - ctx.Redirect(fmt.Sprintf("%s/%d", orCtx.Link, w.ID)) -} - -// DingtalkHooksEditPost response for editing discord hook -func DingtalkHooksEditPost(ctx *context.Context) { - form := web.GetForm(ctx).(*forms.NewDingtalkHookForm) - ctx.Data["Title"] = ctx.Tr("repo.settings") - ctx.Data["PageIsSettingsHooks"] = true - ctx.Data["PageIsSettingsHooksEdit"] = true - - orCtx, w := checkWebhook(ctx) - if ctx.Written() { - return - } - ctx.Data["Webhook"] = w - - if ctx.HasError() { - ctx.HTML(http.StatusOK, orCtx.NewTemplate) - return - } - - w.URL = form.PayloadURL - w.HookEvent = ParseHookEvent(form.WebhookForm) - w.IsActive = form.Active - if err := w.UpdateEvent(); err != nil { - ctx.ServerError("UpdateEvent", err) - return - } else if err := webhook.UpdateWebhook(w); err != nil { - ctx.ServerError("UpdateWebhook", err) - return - } - - ctx.Flash.Success(ctx.Tr("repo.settings.update_hook_success")) - ctx.Redirect(fmt.Sprintf("%s/%d", orCtx.Link, w.ID)) -} - -// TelegramHooksEditPost response for editing discord hook -func TelegramHooksEditPost(ctx *context.Context) { - form := web.GetForm(ctx).(*forms.NewTelegramHookForm) - ctx.Data["Title"] = ctx.Tr("repo.settings") - ctx.Data["PageIsSettingsHooks"] = true - ctx.Data["PageIsSettingsHooksEdit"] = true - - orCtx, w := checkWebhook(ctx) - if ctx.Written() { - return - } - ctx.Data["Webhook"] = w - - if ctx.HasError() { - ctx.HTML(http.StatusOK, orCtx.NewTemplate) - return - } - - meta, err := json.Marshal(&webhook_service.TelegramMeta{ - BotToken: form.BotToken, - ChatID: form.ChatID, - }) - if err != nil { - ctx.ServerError("Marshal", err) - return - } - w.Meta = string(meta) - w.URL = fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage?chat_id=%s", url.PathEscape(form.BotToken), url.QueryEscape(form.ChatID)) - w.HookEvent = ParseHookEvent(form.WebhookForm) - w.IsActive = form.Active - if err := w.UpdateEvent(); err != nil { - ctx.ServerError("UpdateEvent", err) - return - } else if err := webhook.UpdateWebhook(w); err != nil { - ctx.ServerError("UpdateWebhook", err) - return - } - - ctx.Flash.Success(ctx.Tr("repo.settings.update_hook_success")) - ctx.Redirect(fmt.Sprintf("%s/%d", orCtx.Link, w.ID)) -} - -// MatrixHooksEditPost response for editing a Matrix hook -func MatrixHooksEditPost(ctx *context.Context) { - form := web.GetForm(ctx).(*forms.NewMatrixHookForm) - ctx.Data["Title"] = ctx.Tr("repo.settings") - ctx.Data["PageIsSettingsHooks"] = true - ctx.Data["PageIsSettingsHooksEdit"] = true - - orCtx, w := checkWebhook(ctx) - if ctx.Written() { - return - } - ctx.Data["Webhook"] = w - - if ctx.HasError() { - ctx.HTML(http.StatusOK, orCtx.NewTemplate) - return - } - - meta, err := json.Marshal(&webhook_service.MatrixMeta{ - HomeserverURL: form.HomeserverURL, - Room: form.RoomID, - AccessToken: form.AccessToken, - MessageType: form.MessageType, - }) - if err != nil { - ctx.ServerError("Marshal", err) - return - } - w.Meta = string(meta) - w.URL = fmt.Sprintf("%s/_matrix/client/r0/rooms/%s/send/m.room.message", form.HomeserverURL, url.PathEscape(form.RoomID)) - - w.HookEvent = ParseHookEvent(form.WebhookForm) - w.IsActive = form.Active - if err := w.UpdateEvent(); err != nil { - ctx.ServerError("UpdateEvent", err) - return - } else if err := webhook.UpdateWebhook(w); err != nil { - ctx.ServerError("UpdateWebhook", err) - return - } - - ctx.Flash.Success(ctx.Tr("repo.settings.update_hook_success")) - ctx.Redirect(fmt.Sprintf("%s/%d", orCtx.Link, w.ID)) -} - -// MSTeamsHooksEditPost response for editing MS Teams hook -func MSTeamsHooksEditPost(ctx *context.Context) { - form := web.GetForm(ctx).(*forms.NewMSTeamsHookForm) - ctx.Data["Title"] = ctx.Tr("repo.settings") - ctx.Data["PageIsSettingsHooks"] = true - ctx.Data["PageIsSettingsHooksEdit"] = true - - orCtx, w := checkWebhook(ctx) - if ctx.Written() { - return - } - ctx.Data["Webhook"] = w - - if ctx.HasError() { - ctx.HTML(http.StatusOK, orCtx.NewTemplate) - return - } - - w.URL = form.PayloadURL - w.HookEvent = ParseHookEvent(form.WebhookForm) - w.IsActive = form.Active - if err := w.UpdateEvent(); err != nil { - ctx.ServerError("UpdateEvent", err) - return - } else if err := webhook.UpdateWebhook(w); err != nil { - ctx.ServerError("UpdateWebhook", err) - return - } - - ctx.Flash.Success(ctx.Tr("repo.settings.update_hook_success")) - ctx.Redirect(fmt.Sprintf("%s/%d", orCtx.Link, w.ID)) -} - -// FeishuHooksEditPost response for editing feishu hook -func FeishuHooksEditPost(ctx *context.Context) { - form := web.GetForm(ctx).(*forms.NewFeishuHookForm) - ctx.Data["Title"] = ctx.Tr("repo.settings") - ctx.Data["PageIsSettingsHooks"] = true - ctx.Data["PageIsSettingsHooksEdit"] = true - - orCtx, w := checkWebhook(ctx) - if ctx.Written() { - return - } - ctx.Data["Webhook"] = w - - if ctx.HasError() { - ctx.HTML(http.StatusOK, orCtx.NewTemplate) - return - } - - w.URL = form.PayloadURL - w.HookEvent = ParseHookEvent(form.WebhookForm) - w.IsActive = form.Active - if err := w.UpdateEvent(); err != nil { - ctx.ServerError("UpdateEvent", err) - return - } else if err := webhook.UpdateWebhook(w); err != nil { - ctx.ServerError("UpdateWebhook", err) - return - } - - ctx.Flash.Success(ctx.Tr("repo.settings.update_hook_success")) - ctx.Redirect(fmt.Sprintf("%s/%d", orCtx.Link, w.ID)) -} - -// WechatworkHooksEditPost response for editing wechatwork hook -func WechatworkHooksEditPost(ctx *context.Context) { - form := web.GetForm(ctx).(*forms.NewWechatWorkHookForm) - ctx.Data["Title"] = ctx.Tr("repo.settings") - ctx.Data["PageIsSettingsHooks"] = true - ctx.Data["PageIsSettingsHooksEdit"] = true - - orCtx, w := checkWebhook(ctx) - if ctx.Written() { - return - } - ctx.Data["Webhook"] = w - - if ctx.HasError() { - ctx.HTML(http.StatusOK, orCtx.NewTemplate) - return - } - - w.URL = form.PayloadURL - w.HookEvent = ParseHookEvent(form.WebhookForm) - w.IsActive = form.Active - if err := w.UpdateEvent(); err != nil { - ctx.ServerError("UpdateEvent", err) - return - } else if err := webhook.UpdateWebhook(w); err != nil { - ctx.ServerError("UpdateWebhook", err) - return - } - - ctx.Flash.Success(ctx.Tr("repo.settings.update_hook_success")) - ctx.Redirect(fmt.Sprintf("%s/%d", orCtx.Link, w.ID)) -} - -// PackagistHooksEditPost response for editing packagist hook -func PackagistHooksEditPost(ctx *context.Context) { - form := web.GetForm(ctx).(*forms.NewPackagistHookForm) - ctx.Data["Title"] = ctx.Tr("repo.settings") - ctx.Data["PageIsSettingsHooks"] = true - ctx.Data["PageIsSettingsHooksEdit"] = true - - orCtx, w := checkWebhook(ctx) - if ctx.Written() { - return - } - ctx.Data["Webhook"] = w - - if ctx.HasError() { - ctx.HTML(http.StatusOK, orCtx.NewTemplate) - return - } - - meta, err := json.Marshal(&webhook_service.PackagistMeta{ - Username: form.Username, - APIToken: form.APIToken, - PackageURL: form.PackageURL, - }) - if err != nil { - ctx.ServerError("Marshal", err) - return - } - - w.Meta = string(meta) - w.URL = fmt.Sprintf("https://packagist.org/api/update-package?username=%s&apiToken=%s", url.QueryEscape(form.Username), url.QueryEscape(form.APIToken)) - w.HookEvent = ParseHookEvent(form.WebhookForm) - w.IsActive = form.Active - if err := w.UpdateEvent(); err != nil { - ctx.ServerError("UpdateEvent", err) - return - } else if err := webhook.UpdateWebhook(w); err != nil { - ctx.ServerError("UpdateWebhook", err) - return - } - - ctx.Flash.Success(ctx.Tr("repo.settings.update_hook_success")) - ctx.Redirect(fmt.Sprintf("%s/%d", orCtx.Link, w.ID)) -} - // TestWebhook test if web hook is work fine func TestWebhook(ctx *context.Context) { hookID := ctx.ParamsInt64(":id") diff --git a/routers/web/web.go b/routers/web/web.go index 34d3de6fde..55bce16117 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -525,7 +525,7 @@ func RegisterRoutes(m *web.Route) { m.Get("", repo.WebHooksEdit) m.Post("/replay/{uuid}", repo.ReplayWebhook) }) - m.Post("/gitea/{id}", bindIgnErr(forms.NewWebhookForm{}), repo.WebHooksEditPost) + m.Post("/gitea/{id}", bindIgnErr(forms.NewWebhookForm{}), repo.GiteaHooksEditPost) m.Post("/gogs/{id}", bindIgnErr(forms.NewGogshookForm{}), repo.GogsHooksEditPost) m.Post("/slack/{id}", bindIgnErr(forms.NewSlackHookForm{}), repo.SlackHooksEditPost) m.Post("/discord/{id}", bindIgnErr(forms.NewDiscordHookForm{}), repo.DiscordHooksEditPost) @@ -678,7 +678,7 @@ func RegisterRoutes(m *web.Route) { m.Get("", repo.WebHooksEdit) m.Post("/replay/{uuid}", repo.ReplayWebhook) }) - m.Post("/gitea/{id}", bindIgnErr(forms.NewWebhookForm{}), repo.WebHooksEditPost) + m.Post("/gitea/{id}", bindIgnErr(forms.NewWebhookForm{}), repo.GiteaHooksEditPost) m.Post("/gogs/{id}", bindIgnErr(forms.NewGogshookForm{}), repo.GogsHooksEditPost) m.Post("/slack/{id}", bindIgnErr(forms.NewSlackHookForm{}), repo.SlackHooksEditPost) m.Post("/discord/{id}", bindIgnErr(forms.NewDiscordHookForm{}), repo.DiscordHooksEditPost) @@ -800,7 +800,7 @@ func RegisterRoutes(m *web.Route) { m.Post("/test", repo.TestWebhook) m.Post("/replay/{uuid}", repo.ReplayWebhook) }) - m.Post("/gitea/{id}", bindIgnErr(forms.NewWebhookForm{}), repo.WebHooksEditPost) + m.Post("/gitea/{id}", bindIgnErr(forms.NewWebhookForm{}), repo.GiteaHooksEditPost) m.Post("/gogs/{id}", bindIgnErr(forms.NewGogshookForm{}), repo.GogsHooksEditPost) m.Post("/slack/{id}", bindIgnErr(forms.NewSlackHookForm{}), repo.SlackHooksEditPost) m.Post("/discord/{id}", bindIgnErr(forms.NewDiscordHookForm{}), repo.DiscordHooksEditPost) From 56220515fc882943e366fafbce4d5b2b3ccee702 Mon Sep 17 00:00:00 2001 From: silverwind Date: Tue, 23 Aug 2022 14:58:04 +0200 Subject: [PATCH 19/85] Enable contenthash in filename for dynamic assets (#20813) This should solve the main problem of dynamic assets getting stale after a version upgrade. Everything not affected will use query-string based cache busting, which includes files loaded via HTML or worker scripts. --- jest.config.js | 2 +- modules/setting/setting.go | 3 + modules/templates/helper.go | 4 +- package-lock.json | 1827 +++++++++++++++----------- package.json | 2 +- templates/base/footer.tmpl | 2 +- templates/base/head.tmpl | 6 +- templates/base/head_script.tmpl | 1 + templates/swagger/ui.tmpl | 4 +- web_src/js/features/notification.js | 4 +- web_src/js/features/serviceworker.js | 13 +- web_src/js/features/stopwatch.js | 4 +- web_src/js/utils.js | 5 + web_src/js/utils.test.js | 15 +- webpack.config.js | 12 +- 15 files changed, 1102 insertions(+), 802 deletions(-) diff --git a/jest.config.js b/jest.config.js index d24333aa35..34d47acc43 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,7 +1,7 @@ export default { rootDir: 'web_src', setupFilesAfterEnv: ['jest-extended/all'], - testEnvironment: '@happy-dom/jest-environment', + testEnvironment: 'jest-environment-jsdom', testMatch: ['/**/*.test.js'], testTimeout: 20000, transform: { diff --git a/modules/setting/setting.go b/modules/setting/setting.go index 931b6523ea..becd52f011 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -92,6 +92,8 @@ var ( // LocalURL is the url for locally running applications to contact Gitea. It always has a '/' suffix // It maps to ini:"LOCAL_ROOT_URL" LocalURL string + // AssetVersion holds a opaque value that is used for cache-busting assets + AssetVersion string // Server settings Protocol Scheme @@ -759,6 +761,7 @@ func loadFromConf(allowEmpty bool, extraConfig string) { } AbsoluteAssetURL = MakeAbsoluteAssetURL(AppURL, StaticURLPrefix) + AssetVersion = strings.ReplaceAll(AppVer, "+", "~") // make sure the version string is clear (no real escaping is needed) manifestBytes := MakeManifestData(AppName, AppURL, AbsoluteAssetURL) ManifestData = `application/json;base64,` + base64.StdEncoding.EncodeToString(manifestBytes) diff --git a/modules/templates/helper.go b/modules/templates/helper.go index 410857f37e..10b7022ea6 100644 --- a/modules/templates/helper.go +++ b/modules/templates/helper.go @@ -81,6 +81,9 @@ func NewFuncMap() []template.FuncMap { "AppDomain": func() string { return setting.Domain }, + "AssetVersion": func() string { + return setting.AssetVersion + }, "DisableGravatar": func() bool { return setting.DisableGravatar }, @@ -150,7 +153,6 @@ func NewFuncMap() []template.FuncMap { "DiffTypeToStr": DiffTypeToStr, "DiffLineTypeToStr": DiffLineTypeToStr, "ShortSha": base.ShortSha, - "MD5": base.EncodeMD5, "ActionContent2Commits": ActionContent2Commits, "PathEscape": url.PathEscape, "PathEscapeSegments": util.PathEscapeSegments, diff --git a/package-lock.json b/package-lock.json index aabbd84fd9..2a16ee4939 100644 --- a/package-lock.json +++ b/package-lock.json @@ -46,7 +46,6 @@ "wrap-ansi": "8.0.1" }, "devDependencies": { - "@happy-dom/jest-environment": "6.0.4", "@stoplight/spectral-cli": "6.5.0", "eslint": "8.21.0", "eslint-plugin-import": "2.26.0", @@ -55,6 +54,7 @@ "eslint-plugin-unicorn": "43.0.2", "eslint-plugin-vue": "9.3.0", "jest": "28.1.3", + "jest-environment-jsdom": "28.1.3", "jest-extended": "3.0.1", "markdownlint-cli": "0.32.1", "postcss-less": "6.0.0", @@ -769,20 +769,6 @@ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, - "node_modules/@happy-dom/jest-environment": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/@happy-dom/jest-environment/-/jest-environment-6.0.4.tgz", - "integrity": "sha512-3FEWODVN4H7Hnkbm7IEtRE8lU7uitxSsnVagkQbfjjwKSUfF6BT9MJ95VF7nLimdc5ctOIa7ZbDietRRIpgSHw==", - "dev": true, - "dependencies": { - "@jest/environment": "^27.5.1", - "@jest/fake-timers": "^27.5.1", - "@jest/types": "^27.5.1", - "happy-dom": "^6.0.4", - "jest-mock": "^27.5.1", - "jest-util": "^27.5.1" - } - }, "node_modules/@humanwhocodes/config-array": { "version": "0.10.4", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.4.tgz", @@ -1112,21 +1098,6 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/@jest/environment": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-27.5.1.tgz", - "integrity": "sha512-/WQjhPJe3/ghaol/4Bq480JKXV/Rfw8nQdN7f41fM8VDHLcxKXou6QyXAh3EFr9/bVG3x74z1NWDkP87EiY8gA==", - "dev": true, - "dependencies": { - "@jest/fake-timers": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "jest-mock": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, "node_modules/@jest/expect": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-28.1.3.tgz", @@ -1152,23 +1123,6 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/@jest/fake-timers": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.5.1.tgz", - "integrity": "sha512-/aPowoolwa07k7/oM3aASneNeBGCmGQsc3ugN4u6s4C/+s5M64MFo/+djTdiwcbQlRfFElGuDXWzaWj6QgKObQ==", - "dev": true, - "dependencies": { - "@jest/types": "^27.5.1", - "@sinonjs/fake-timers": "^8.0.1", - "@types/node": "*", - "jest-message-util": "^27.5.1", - "jest-mock": "^27.5.1", - "jest-util": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, "node_modules/@jest/globals": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-28.1.3.tgz", @@ -1558,22 +1512,6 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, "node_modules/@jridgewell/gen-mapping": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", @@ -1820,15 +1758,6 @@ "type-detect": "4.0.8" } }, - "node_modules/@sinonjs/fake-timers": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz", - "integrity": "sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^1.7.0" - } - }, "node_modules/@stoplight/better-ajv-errors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@stoplight/better-ajv-errors/-/better-ajv-errors-1.0.1.tgz", @@ -2404,15 +2333,6 @@ "@types/tern": "*" } }, - "node_modules/@types/concat-stream": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@types/concat-stream/-/concat-stream-1.6.1.tgz", - "integrity": "sha512-eHE4cQPoj6ngxBZMvVf6Hw7Mh4jMW4U9lpGmS5GBPB9RYxlFg+CHaVN7ErNY4W9XfLIEn20b4VDYaIrbq0q4uA==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/es-aggregate-error": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@types/es-aggregate-error/-/es-aggregate-error-1.0.2.tgz", @@ -2445,15 +2365,6 @@ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==" }, - "node_modules/@types/form-data": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-0.0.33.tgz", - "integrity": "sha512-8BSvG1kGm83cyJITQMZSulnl6QV8jqAGreJsc5tPu1Jq0vTSOiY/k24Wx82JRpWwZSqrala6sd5rWi6aNXvqcw==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/graceful-fs": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", @@ -2487,6 +2398,17 @@ "@types/istanbul-lib-report": "*" } }, + "node_modules/@types/jsdom": { + "version": "16.2.15", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-16.2.15.tgz", + "integrity": "sha512-nwF87yjBKuX/roqGYerZZM0Nv1pZDMAT5YhOHYeM/72Fic+VEqJh4nyoqoapzJnW3pUlfxPY5FhgsJtM+dRnQQ==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/parse5": "^6.0.3", + "@types/tough-cookie": "*" + } + }, "node_modules/@types/json-schema": { "version": "7.0.11", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", @@ -2526,18 +2448,18 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "node_modules/@types/parse5": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-6.0.3.tgz", + "integrity": "sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==", + "dev": true + }, "node_modules/@types/prettier": { "version": "2.6.4", "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.6.4.tgz", "integrity": "sha512-fOwvpvQYStpb/zHMx0Cauwywu9yLDmzWiiQBC7gJyq5tYLUXFZvDG7VK1B7WBxxjBJNKFOZ0zLoOQn8vmATbhw==", "dev": true }, - "node_modules/@types/qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", - "dev": true - }, "node_modules/@types/stack-utils": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", @@ -2552,21 +2474,18 @@ "@types/estree": "*" } }, + "node_modules/@types/tough-cookie": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz", + "integrity": "sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==", + "dev": true + }, "node_modules/@types/urijs": { "version": "1.19.19", "resolved": "https://registry.npmjs.org/@types/urijs/-/urijs-1.19.19.tgz", "integrity": "sha512-FDJNkyhmKLw7uEvTxx5tSXfPeQpO0iy73Ry+PmYZJvQy0QIWX8a7kJ4kLWRf+EbTPJEPDSgPXHaM7pzr5lmvCg==", "dev": true }, - "node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, "node_modules/@types/yargs-parser": { "version": "21.0.0", "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", @@ -2800,6 +2719,12 @@ "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" }, + "node_modules/abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "dev": true + }, "node_modules/abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -2823,6 +2748,37 @@ "node": ">=0.4.0" } }, + "node_modules/acorn-globals": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", + "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", + "dev": true, + "dependencies": { + "acorn": "^7.1.1", + "acorn-walk": "^7.1.1" + } + }, + "node_modules/acorn-globals/node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-globals/node_modules/acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/acorn-import-assertions": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", @@ -3080,12 +3036,6 @@ "printable-characters": "^1.0.42" } }, - "node_modules/asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", - "dev": true - }, "node_modules/ast-types": { "version": "0.14.2", "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.14.2.tgz", @@ -3263,6 +3213,12 @@ "node": ">=8" } }, + "node_modules/browser-process-hrtime": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", + "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", + "dev": true + }, "node_modules/browserslist": { "version": "4.21.3", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.3.tgz", @@ -3394,12 +3350,6 @@ } ] }, - "node_modules/caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", - "dev": true - }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -3618,51 +3568,6 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, - "node_modules/concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, - "engines": [ - "node >= 0.8" - ], - "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, - "node_modules/concat-stream/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true - }, - "node_modules/concat-stream/node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/concat-stream/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, "node_modules/consolidate": { "version": "0.15.1", "resolved": "https://registry.npmjs.org/consolidate/-/consolidate-0.15.1.tgz", @@ -3804,12 +3709,6 @@ "url": "https://github.com/sponsors/fb55" } }, - "node_modules/css.escape": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", - "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", - "dev": true - }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -3833,6 +3732,30 @@ "node": ">=8.0.0" } }, + "node_modules/cssom": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", + "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", + "dev": true + }, + "node_modules/cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dev": true, + "dependencies": { + "cssom": "~0.3.6" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cssstyle/node_modules/cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true + }, "node_modules/d3": { "version": "7.6.1", "resolved": "https://registry.npmjs.org/d3/-/d3-7.6.1.tgz", @@ -4531,6 +4454,45 @@ "node": ">= 6" } }, + "node_modules/data-urls": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", + "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", + "dev": true, + "dependencies": { + "abab": "^2.0.6", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/data-urls/node_modules/tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dev": true, + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/data-urls/node_modules/whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "dev": true, + "dependencies": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/de-indent": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", @@ -4584,6 +4546,12 @@ "node": ">=0.10.0" } }, + "node_modules/decimal.js": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.0.tgz", + "integrity": "sha512-Nv6ENEzyPQ6AItkGwLE2PGKinZZ9g59vSh2BeH6NqPu0OTKZ5ruJsVqh/orbAnqXc9pBbgXAIrc2EyaCj8NpGg==", + "dev": true + }, "node_modules/dedent": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", @@ -4769,6 +4737,18 @@ } ] }, + "node_modules/domexception": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", + "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", + "dev": true, + "dependencies": { + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/domhandler": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", @@ -6203,20 +6183,6 @@ "node": ">=0.10.3" } }, - "node_modules/form-data": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", - "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", - "dev": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 0.12" - } - }, "node_modules/fs-extra": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", @@ -6342,15 +6308,6 @@ "node": ">=8.0.0" } }, - "node_modules/get-port": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/get-port/-/get-port-3.2.0.tgz", - "integrity": "sha512-x5UJKlgeUiNT8nyo/AcnwLnZuZNcSjSw0kogRB+Whd1fjjFq4B1hySFxSFWWSn4mIBzg3sRNUDFYc4g5gjPoLg==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/get-source": { "version": "2.0.12", "resolved": "https://registry.npmjs.org/get-source/-/get-source-2.0.12.tgz", @@ -6584,21 +6541,6 @@ "resolved": "https://registry.npmjs.org/gsap/-/gsap-3.10.4.tgz", "integrity": "sha512-6QatdkKxXCMfvCW4rM++0RqyLQAzFX5nwl3yHS0XPgkZBkiSEY3VZVbMltrdtsbER/xZonLtyHt684wRp4erlQ==" }, - "node_modules/happy-dom": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-6.0.4.tgz", - "integrity": "sha512-b+ID23Ms0BY08UNLymsOMG7EI2jSlwEt4cbJs938GZfeNAg+fqgkSO3TokQMgSOFoHznpjWmpVjBUL5boJ9PWw==", - "dev": true, - "dependencies": { - "css.escape": "^1.5.1", - "he": "^1.2.0", - "node-fetch": "^2.x.x", - "sync-request": "^6.1.0", - "webidl-conversions": "^7.0.0", - "whatwg-encoding": "^2.0.0", - "whatwg-mimetype": "^3.0.0" - } - }, "node_modules/hard-rejection": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", @@ -6694,6 +6636,18 @@ "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", "dev": true }, + "node_modules/html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "dev": true, + "dependencies": { + "whatwg-encoding": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -6712,21 +6666,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/http-basic": { - "version": "8.1.3", - "resolved": "https://registry.npmjs.org/http-basic/-/http-basic-8.1.3.tgz", - "integrity": "sha512-/EcDMwJZh3mABI2NhGfHOGOeOZITqfkEO4p/xK+l3NpyncIHUQBoMvCSF/b5GqvKtySC2srL/GGG3+EtlqlmCw==", - "dev": true, - "dependencies": { - "caseless": "^0.12.0", - "concat-stream": "^1.6.2", - "http-response-object": "^3.0.1", - "parse-cache-control": "^1.0.1" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -6757,21 +6696,6 @@ "node": ">= 6" } }, - "node_modules/http-response-object": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/http-response-object/-/http-response-object-3.0.2.tgz", - "integrity": "sha512-bqX0XTF6fnXSQcEJ2Iuyr75yVakyjIDCqroJQ/aHfSdlM743Cwqoi2nDYMzLGWUcuTWGWy8AAvOKXTfiv6q9RA==", - "dev": true, - "dependencies": { - "@types/node": "^10.0.3" - } - }, - "node_modules/http-response-object/node_modules/@types/node": { - "version": "10.17.60", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", - "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==", - "dev": true - }, "node_modules/https-proxy-agent": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", @@ -7143,6 +7067,12 @@ "node": ">=0.10.0" } }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true + }, "node_modules/is-reference": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", @@ -7779,6 +7709,142 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, + "node_modules/jest-environment-jsdom": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-28.1.3.tgz", + "integrity": "sha512-HnlGUmZRdxfCByd3GM2F100DgQOajUBzEitjGqIREcb45kGjZvRrKUdlaF6escXBdcXNl0OBh+1ZrfeZT3GnAg==", + "dev": true, + "dependencies": { + "@jest/environment": "^28.1.3", + "@jest/fake-timers": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/jsdom": "^16.2.4", + "@types/node": "*", + "jest-mock": "^28.1.3", + "jest-util": "^28.1.3", + "jsdom": "^19.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-environment-jsdom/node_modules/@jest/environment": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", + "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", + "dev": true, + "dependencies": { + "@jest/fake-timers": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "jest-mock": "^28.1.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-environment-jsdom/node_modules/@jest/fake-timers": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", + "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", + "dev": true, + "dependencies": { + "@jest/types": "^28.1.3", + "@sinonjs/fake-timers": "^9.1.2", + "@types/node": "*", + "jest-message-util": "^28.1.3", + "jest-mock": "^28.1.3", + "jest-util": "^28.1.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-environment-jsdom/node_modules/@jest/types": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", + "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^28.1.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-environment-jsdom/node_modules/@sinonjs/fake-timers": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz", + "integrity": "sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.7.0" + } + }, + "node_modules/jest-environment-jsdom/node_modules/@types/yargs": { + "version": "17.0.11", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.11.tgz", + "integrity": "sha512-aB4y9UDUXTSMxmM4MH+YnuR0g5Cph3FLQBoWoMB21DSvFVAxRVEHEMx3TLh+zUZYMCQtKiqazz0Q4Rre31f/OA==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/jest-environment-jsdom/node_modules/jest-message-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", + "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^28.1.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^28.1.3", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-environment-jsdom/node_modules/jest-mock": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz", + "integrity": "sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==", + "dev": true, + "dependencies": { + "@jest/types": "^28.1.3", + "@types/node": "*" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-environment-jsdom/node_modules/jest-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", + "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", + "dev": true, + "dependencies": { + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, "node_modules/jest-environment-node": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-28.1.3.tgz", @@ -8034,71 +8100,6 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-message-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-message-util/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-message-util/node_modules/pretty-format": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", - "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-message-util/node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true - }, - "node_modules/jest-mock": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-27.5.1.tgz", - "integrity": "sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og==", - "dev": true, - "dependencies": { - "@jest/types": "^27.5.1", - "@types/node": "*" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, "node_modules/jest-pnp-resolver": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", @@ -8597,23 +8598,6 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "dev": true, - "dependencies": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, "node_modules/jest-validate": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-28.1.3.tgz", @@ -8828,6 +8812,187 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsdom": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-19.0.0.tgz", + "integrity": "sha512-RYAyjCbxy/vri/CfnjUWJQQtZ3LKlLnDqj+9XLNnJPgEGeirZs3hllKR20re8LUZ6o1b1X4Jat+Qd26zmP41+A==", + "dev": true, + "dependencies": { + "abab": "^2.0.5", + "acorn": "^8.5.0", + "acorn-globals": "^6.0.0", + "cssom": "^0.5.0", + "cssstyle": "^2.3.0", + "data-urls": "^3.0.1", + "decimal.js": "^10.3.1", + "domexception": "^4.0.0", + "escodegen": "^2.0.0", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.0", + "parse5": "6.0.1", + "saxes": "^5.0.1", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.0.0", + "w3c-hr-time": "^1.0.2", + "w3c-xmlserializer": "^3.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^2.0.0", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^10.0.0", + "ws": "^8.2.3", + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/jsdom/node_modules/escodegen": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", + "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", + "dev": true, + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/jsdom/node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/jsdom/node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/jsdom/node_modules/levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "dev": true, + "dependencies": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/jsdom/node_modules/optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "dependencies": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/jsdom/node_modules/prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/jsdom/node_modules/tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dev": true, + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/jsdom/node_modules/type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "dev": true, + "dependencies": { + "prelude-ls": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/jsdom/node_modules/whatwg-url": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-10.0.0.tgz", + "integrity": "sha512-CLxxCmdUby142H5FZzn4D8ikO1cmypvXVQktsgosNy4a4BHrDHeciBBGZhb0bNoR5/MltoCatso+vFjjGx8t0w==", + "dev": true, + "dependencies": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/jsep": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/jsep/-/jsep-1.3.6.tgz", @@ -9816,6 +9981,12 @@ "url": "https://github.com/fb55/nth-check?sponsor=1" } }, + "node_modules/nwsapi": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.1.tgz", + "integrity": "sha512-JYOWTeFoS0Z93587vRJgASD5Ut11fYl5NyihP3KrYBvMe1FRRs6RN7m20SA/16GM4P6hTnZjT+UmDOt38UeXNg==", + "dev": true + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -10001,12 +10172,6 @@ "node": ">=6" } }, - "node_modules/parse-cache-control": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parse-cache-control/-/parse-cache-control-1.0.1.tgz", - "integrity": "sha512-60zvsJReQPX5/QP0Kzfd/VrpjScIQ7SHBW6bFCYfEP+fp0Eppr1SHhIO5nd1PjZtvclzSzES9D/p5nFJurwfWg==", - "dev": true - }, "node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -10044,6 +10209,12 @@ "node": ">= 0.10" } }, + "node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -10409,21 +10580,6 @@ "integrity": "sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==", "dev": true }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, - "node_modules/promise": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/promise/-/promise-8.1.0.tgz", - "integrity": "sha512-W04AqnILOL/sPRXziNicCjSNRruLAuIHEOVBazepu0545DDNGYHz7ar9ZgZ1fMU8/MA4mVxp5rkBWRi6OXIy3Q==", - "dev": true, - "dependencies": { - "asap": "~2.0.6" - } - }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -10473,6 +10629,12 @@ "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==" }, + "node_modules/psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", + "dev": true + }, "node_modules/punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", @@ -10481,21 +10643,6 @@ "node": ">=6" } }, - "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "dev": true, - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -10943,6 +11090,18 @@ "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", "optional": true }, + "node_modules/saxes": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", + "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "dev": true, + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/schema-utils": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", @@ -11612,28 +11771,11 @@ "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-4.13.2.tgz", "integrity": "sha512-jHL6UyIYpvEI7NsuWd0R3hJaPQTg6Oo4qSBo+oVfOEkv6rrQm/475RGSMmZgV6ajp+Sgrp9CqrDjQYAgQqiv1A==" }, - "node_modules/sync-request": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/sync-request/-/sync-request-6.1.0.tgz", - "integrity": "sha512-8fjNkrNlNCrVc/av+Jn+xxqfCjYaBoHqCsDz6mt030UMxJGr+GSfCV1dQt2gRtlL63+VPidwDVLr7V2OcTSdRw==", - "dev": true, - "dependencies": { - "http-response-object": "^3.0.1", - "sync-rpc": "^1.2.1", - "then-request": "^6.0.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/sync-rpc": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/sync-rpc/-/sync-rpc-1.3.6.tgz", - "integrity": "sha512-J8jTXuZzRlvU7HemDgHi3pGnh/rkoqR/OZSjhTyyZrEkkYQbk7Z33AXp37mkPfPpfdOuj7Ex3H/TJM1z48uPQw==", - "dev": true, - "dependencies": { - "get-port": "^3.1.0" - } + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true }, "node_modules/table": { "version": "6.8.0", @@ -11831,34 +11973,6 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, - "node_modules/then-request": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/then-request/-/then-request-6.0.2.tgz", - "integrity": "sha512-3ZBiG7JvP3wbDzA9iNY5zJQcHL4jn/0BWtXIkagfz7QgOL/LqjCEOBQuJNZfu0XYnv5JhKh+cDxCPM4ILrqruA==", - "dev": true, - "dependencies": { - "@types/concat-stream": "^1.6.0", - "@types/form-data": "0.0.33", - "@types/node": "^8.0.0", - "@types/qs": "^6.2.31", - "caseless": "~0.12.0", - "concat-stream": "^1.6.0", - "form-data": "^2.2.0", - "http-basic": "^8.1.1", - "http-response-object": "^3.0.1", - "promise": "^8.0.0", - "qs": "^6.4.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/then-request/node_modules/@types/node": { - "version": "8.10.66", - "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.66.tgz", - "integrity": "sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw==", - "dev": true - }, "node_modules/tippy.js": { "version": "6.3.7", "resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-6.3.7.tgz", @@ -11902,6 +12016,20 @@ "node": ">=0.6" } }, + "node_modules/tough-cookie": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz", + "integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==", + "dev": true, + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.1.2" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", @@ -11993,12 +12121,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", - "dev": true - }, "node_modules/typo-js": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/typo-js/-/typo-js-1.2.2.tgz", @@ -12336,6 +12458,27 @@ "resolved": "https://registry.npmjs.org/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz", "integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==" }, + "node_modules/w3c-hr-time": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", + "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", + "dev": true, + "dependencies": { + "browser-process-hrtime": "^1.0.0" + } + }, + "node_modules/w3c-xmlserializer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-3.0.0.tgz", + "integrity": "sha512-3WFqGEgSXIyGhOmAFtlicJNMjEps8b1MG31NCA0/vOF9+nKMUW1ckhi9cnNHmf88Rzw5V+dwIwsm2C7X8k9aQg==", + "dev": true, + "dependencies": { + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", @@ -12831,6 +12974,27 @@ "node": "^12.13.0 || ^14.15.0 || >=16" } }, + "node_modules/ws": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.8.1.tgz", + "integrity": "sha512-bGy2JzvzkPowEJV++hF07hAD6niYSr0JzBNo/J29WsB57A2r7Wlc1UFcTR9IzrPvuNVO4B8LGqF8qcpsVOhJCA==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/xml-name-validator": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", @@ -12840,6 +13004,12 @@ "node": ">=12" } }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true + }, "node_modules/xregexp": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-2.0.0.tgz", @@ -13452,20 +13622,6 @@ } } }, - "@happy-dom/jest-environment": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/@happy-dom/jest-environment/-/jest-environment-6.0.4.tgz", - "integrity": "sha512-3FEWODVN4H7Hnkbm7IEtRE8lU7uitxSsnVagkQbfjjwKSUfF6BT9MJ95VF7nLimdc5ctOIa7ZbDietRRIpgSHw==", - "dev": true, - "requires": { - "@jest/environment": "^27.5.1", - "@jest/fake-timers": "^27.5.1", - "@jest/types": "^27.5.1", - "happy-dom": "^6.0.4", - "jest-mock": "^27.5.1", - "jest-util": "^27.5.1" - } - }, "@humanwhocodes/config-array": { "version": "0.10.4", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.4.tgz", @@ -13735,18 +13891,6 @@ } } }, - "@jest/environment": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-27.5.1.tgz", - "integrity": "sha512-/WQjhPJe3/ghaol/4Bq480JKXV/Rfw8nQdN7f41fM8VDHLcxKXou6QyXAh3EFr9/bVG3x74z1NWDkP87EiY8gA==", - "dev": true, - "requires": { - "@jest/fake-timers": "^27.5.1", - "@jest/types": "^27.5.1", - "@types/node": "*", - "jest-mock": "^27.5.1" - } - }, "@jest/expect": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-28.1.3.tgz", @@ -13766,20 +13910,6 @@ "jest-get-type": "^28.0.2" } }, - "@jest/fake-timers": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.5.1.tgz", - "integrity": "sha512-/aPowoolwa07k7/oM3aASneNeBGCmGQsc3ugN4u6s4C/+s5M64MFo/+djTdiwcbQlRfFElGuDXWzaWj6QgKObQ==", - "dev": true, - "requires": { - "@jest/types": "^27.5.1", - "@sinonjs/fake-timers": "^8.0.1", - "@types/node": "*", - "jest-message-util": "^27.5.1", - "jest-mock": "^27.5.1", - "jest-util": "^27.5.1" - } - }, "@jest/globals": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-28.1.3.tgz", @@ -14112,19 +14242,6 @@ } } }, - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, "@jridgewell/gen-mapping": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", @@ -14292,15 +14409,6 @@ "type-detect": "4.0.8" } }, - "@sinonjs/fake-timers": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz", - "integrity": "sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.7.0" - } - }, "@stoplight/better-ajv-errors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@stoplight/better-ajv-errors/-/better-ajv-errors-1.0.1.tgz", @@ -14788,15 +14896,6 @@ "@types/tern": "*" } }, - "@types/concat-stream": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@types/concat-stream/-/concat-stream-1.6.1.tgz", - "integrity": "sha512-eHE4cQPoj6ngxBZMvVf6Hw7Mh4jMW4U9lpGmS5GBPB9RYxlFg+CHaVN7ErNY4W9XfLIEn20b4VDYaIrbq0q4uA==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, "@types/es-aggregate-error": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@types/es-aggregate-error/-/es-aggregate-error-1.0.2.tgz", @@ -14829,15 +14928,6 @@ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==" }, - "@types/form-data": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-0.0.33.tgz", - "integrity": "sha512-8BSvG1kGm83cyJITQMZSulnl6QV8jqAGreJsc5tPu1Jq0vTSOiY/k24Wx82JRpWwZSqrala6sd5rWi6aNXvqcw==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, "@types/graceful-fs": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", @@ -14871,6 +14961,17 @@ "@types/istanbul-lib-report": "*" } }, + "@types/jsdom": { + "version": "16.2.15", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-16.2.15.tgz", + "integrity": "sha512-nwF87yjBKuX/roqGYerZZM0Nv1pZDMAT5YhOHYeM/72Fic+VEqJh4nyoqoapzJnW3pUlfxPY5FhgsJtM+dRnQQ==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/parse5": "^6.0.3", + "@types/tough-cookie": "*" + } + }, "@types/json-schema": { "version": "7.0.11", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", @@ -14910,18 +15011,18 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "@types/parse5": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-6.0.3.tgz", + "integrity": "sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==", + "dev": true + }, "@types/prettier": { "version": "2.6.4", "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.6.4.tgz", "integrity": "sha512-fOwvpvQYStpb/zHMx0Cauwywu9yLDmzWiiQBC7gJyq5tYLUXFZvDG7VK1B7WBxxjBJNKFOZ0zLoOQn8vmATbhw==", "dev": true }, - "@types/qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", - "dev": true - }, "@types/stack-utils": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", @@ -14936,21 +15037,18 @@ "@types/estree": "*" } }, + "@types/tough-cookie": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz", + "integrity": "sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==", + "dev": true + }, "@types/urijs": { "version": "1.19.19", "resolved": "https://registry.npmjs.org/@types/urijs/-/urijs-1.19.19.tgz", "integrity": "sha512-FDJNkyhmKLw7uEvTxx5tSXfPeQpO0iy73Ry+PmYZJvQy0QIWX8a7kJ4kLWRf+EbTPJEPDSgPXHaM7pzr5lmvCg==", "dev": true }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, "@types/yargs-parser": { "version": "21.0.0", "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", @@ -15164,6 +15262,12 @@ "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" }, + "abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "dev": true + }, "abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -15178,6 +15282,30 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==" }, + "acorn-globals": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", + "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", + "dev": true, + "requires": { + "acorn": "^7.1.1", + "acorn-walk": "^7.1.1" + }, + "dependencies": { + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + }, + "acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "dev": true + } + } + }, "acorn-import-assertions": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", @@ -15350,12 +15478,6 @@ "printable-characters": "^1.0.42" } }, - "asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", - "dev": true - }, "ast-types": { "version": "0.14.2", "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.14.2.tgz", @@ -15497,6 +15619,12 @@ "fill-range": "^7.0.1" } }, + "browser-process-hrtime": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", + "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", + "dev": true + }, "browserslist": { "version": "4.21.3", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.3.tgz", @@ -15578,12 +15706,6 @@ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001373.tgz", "integrity": "sha512-pJYArGHrPp3TUqQzFYRmP/lwJlj8RCbVe3Gd3eJQkAV8SAC6b19XS9BjMvRdvaS8RMkaTN8ZhoHP6S1y8zzwEQ==" }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", - "dev": true - }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -15762,50 +15884,6 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, "consolidate": { "version": "0.15.1", "resolved": "https://registry.npmjs.org/consolidate/-/consolidate-0.15.1.tgz", @@ -15910,12 +15988,6 @@ "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", "dev": true }, - "css.escape": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", - "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", - "dev": true - }, "cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -15930,6 +16002,29 @@ "css-tree": "^1.1.2" } }, + "cssom": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", + "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", + "dev": true + }, + "cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dev": true, + "requires": { + "cssom": "~0.3.6" + }, + "dependencies": { + "cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true + } + } + }, "d3": { "version": "7.6.1", "resolved": "https://registry.npmjs.org/d3/-/d3-7.6.1.tgz", @@ -16505,6 +16600,38 @@ "integrity": "sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og==", "dev": true }, + "data-urls": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", + "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", + "dev": true, + "requires": { + "abab": "^2.0.6", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0" + }, + "dependencies": { + "tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dev": true, + "requires": { + "punycode": "^2.1.1" + } + }, + "whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "dev": true, + "requires": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + } + } + } + }, "de-indent": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", @@ -16543,6 +16670,12 @@ } } }, + "decimal.js": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.0.tgz", + "integrity": "sha512-Nv6ENEzyPQ6AItkGwLE2PGKinZZ9g59vSh2BeH6NqPu0OTKZ5ruJsVqh/orbAnqXc9pBbgXAIrc2EyaCj8NpGg==", + "dev": true + }, "dedent": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", @@ -16681,6 +16814,15 @@ "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", "dev": true }, + "domexception": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", + "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", + "dev": true, + "requires": { + "webidl-conversions": "^7.0.0" + } + }, "domhandler": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", @@ -17677,17 +17819,6 @@ "resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz", "integrity": "sha512-U6kGnykA/6bFmg1M/oT9EkFeIYv7JlX3bozwQJWiiLz6L0w3F5vBVPxHlwyX/vtNq1ckcpRKOB9f2Qal/VtFpg==" }, - "form-data": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", - "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - } - }, "fs-extra": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", @@ -17779,12 +17910,6 @@ "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", "dev": true }, - "get-port": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/get-port/-/get-port-3.2.0.tgz", - "integrity": "sha512-x5UJKlgeUiNT8nyo/AcnwLnZuZNcSjSw0kogRB+Whd1fjjFq4B1hySFxSFWWSn4mIBzg3sRNUDFYc4g5gjPoLg==", - "dev": true - }, "get-source": { "version": "2.0.12", "resolved": "https://registry.npmjs.org/get-source/-/get-source-2.0.12.tgz", @@ -17965,21 +18090,6 @@ "resolved": "https://registry.npmjs.org/gsap/-/gsap-3.10.4.tgz", "integrity": "sha512-6QatdkKxXCMfvCW4rM++0RqyLQAzFX5nwl3yHS0XPgkZBkiSEY3VZVbMltrdtsbER/xZonLtyHt684wRp4erlQ==" }, - "happy-dom": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-6.0.4.tgz", - "integrity": "sha512-b+ID23Ms0BY08UNLymsOMG7EI2jSlwEt4cbJs938GZfeNAg+fqgkSO3TokQMgSOFoHznpjWmpVjBUL5boJ9PWw==", - "dev": true, - "requires": { - "css.escape": "^1.5.1", - "he": "^1.2.0", - "node-fetch": "^2.x.x", - "sync-request": "^6.1.0", - "webidl-conversions": "^7.0.0", - "whatwg-encoding": "^2.0.0", - "whatwg-mimetype": "^3.0.0" - } - }, "hard-rejection": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", @@ -18045,6 +18155,15 @@ "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", "dev": true }, + "html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "dev": true, + "requires": { + "whatwg-encoding": "^2.0.0" + } + }, "html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -18057,18 +18176,6 @@ "integrity": "sha512-vy7ClnArOZwCnqZgvv+ddgHgJiAFXe3Ge9ML5/mBctVJoUoYPCdxVucOywjDARn6CVoh3dRSFdPHy2sX80L0Wg==", "dev": true }, - "http-basic": { - "version": "8.1.3", - "resolved": "https://registry.npmjs.org/http-basic/-/http-basic-8.1.3.tgz", - "integrity": "sha512-/EcDMwJZh3mABI2NhGfHOGOeOZITqfkEO4p/xK+l3NpyncIHUQBoMvCSF/b5GqvKtySC2srL/GGG3+EtlqlmCw==", - "dev": true, - "requires": { - "caseless": "^0.12.0", - "concat-stream": "^1.6.2", - "http-response-object": "^3.0.1", - "parse-cache-control": "^1.0.1" - } - }, "http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -18093,23 +18200,6 @@ "debug": "4" } }, - "http-response-object": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/http-response-object/-/http-response-object-3.0.2.tgz", - "integrity": "sha512-bqX0XTF6fnXSQcEJ2Iuyr75yVakyjIDCqroJQ/aHfSdlM743Cwqoi2nDYMzLGWUcuTWGWy8AAvOKXTfiv6q9RA==", - "dev": true, - "requires": { - "@types/node": "^10.0.3" - }, - "dependencies": { - "@types/node": { - "version": "10.17.60", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", - "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==", - "dev": true - } - } - }, "https-proxy-agent": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", @@ -18355,6 +18445,12 @@ "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", "dev": true }, + "is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true + }, "is-reference": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", @@ -18878,6 +18974,123 @@ } } }, + "jest-environment-jsdom": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-28.1.3.tgz", + "integrity": "sha512-HnlGUmZRdxfCByd3GM2F100DgQOajUBzEitjGqIREcb45kGjZvRrKUdlaF6escXBdcXNl0OBh+1ZrfeZT3GnAg==", + "dev": true, + "requires": { + "@jest/environment": "^28.1.3", + "@jest/fake-timers": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/jsdom": "^16.2.4", + "@types/node": "*", + "jest-mock": "^28.1.3", + "jest-util": "^28.1.3", + "jsdom": "^19.0.0" + }, + "dependencies": { + "@jest/environment": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", + "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", + "dev": true, + "requires": { + "@jest/fake-timers": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "jest-mock": "^28.1.3" + } + }, + "@jest/fake-timers": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", + "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", + "dev": true, + "requires": { + "@jest/types": "^28.1.3", + "@sinonjs/fake-timers": "^9.1.2", + "@types/node": "*", + "jest-message-util": "^28.1.3", + "jest-mock": "^28.1.3", + "jest-util": "^28.1.3" + } + }, + "@jest/types": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", + "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", + "dev": true, + "requires": { + "@jest/schemas": "^28.1.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + } + }, + "@sinonjs/fake-timers": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz", + "integrity": "sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0" + } + }, + "@types/yargs": { + "version": "17.0.11", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.11.tgz", + "integrity": "sha512-aB4y9UDUXTSMxmM4MH+YnuR0g5Cph3FLQBoWoMB21DSvFVAxRVEHEMx3TLh+zUZYMCQtKiqazz0Q4Rre31f/OA==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "jest-message-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", + "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^28.1.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^28.1.3", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + } + }, + "jest-mock": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz", + "integrity": "sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==", + "dev": true, + "requires": { + "@jest/types": "^28.1.3", + "@types/node": "*" + } + }, + "jest-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", + "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", + "dev": true, + "requires": { + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + } + } + } + }, "jest-environment-node": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-28.1.3.tgz", @@ -19090,58 +19303,6 @@ "pretty-format": "^28.1.3" } }, - "jest-message-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", - "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.5.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^27.5.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - }, - "pretty-format": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", - "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - } - }, - "react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true - } - } - }, - "jest-mock": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-27.5.1.tgz", - "integrity": "sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og==", - "dev": true, - "requires": { - "@jest/types": "^27.5.1", - "@types/node": "*" - } - }, "jest-pnp-resolver": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", @@ -19569,20 +19730,6 @@ } } }, - "jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "dev": true, - "requires": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, "jest-validate": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-28.1.3.tgz", @@ -19738,6 +19885,142 @@ "argparse": "^2.0.1" } }, + "jsdom": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-19.0.0.tgz", + "integrity": "sha512-RYAyjCbxy/vri/CfnjUWJQQtZ3LKlLnDqj+9XLNnJPgEGeirZs3hllKR20re8LUZ6o1b1X4Jat+Qd26zmP41+A==", + "dev": true, + "requires": { + "abab": "^2.0.5", + "acorn": "^8.5.0", + "acorn-globals": "^6.0.0", + "cssom": "^0.5.0", + "cssstyle": "^2.3.0", + "data-urls": "^3.0.1", + "decimal.js": "^10.3.1", + "domexception": "^4.0.0", + "escodegen": "^2.0.0", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.0", + "parse5": "6.0.1", + "saxes": "^5.0.1", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.0.0", + "w3c-hr-time": "^1.0.2", + "w3c-xmlserializer": "^3.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^2.0.0", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^10.0.0", + "ws": "^8.2.3", + "xml-name-validator": "^4.0.0" + }, + "dependencies": { + "@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true + }, + "escodegen": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", + "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", + "dev": true, + "requires": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.6.1" + } + }, + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, + "http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "requires": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + } + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "dev": true + }, + "tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dev": true, + "requires": { + "punycode": "^2.1.1" + } + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "whatwg-url": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-10.0.0.tgz", + "integrity": "sha512-CLxxCmdUby142H5FZzn4D8ikO1cmypvXVQktsgosNy4a4BHrDHeciBBGZhb0bNoR5/MltoCatso+vFjjGx8t0w==", + "dev": true, + "requires": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + } + } + } + }, "jsep": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/jsep/-/jsep-1.3.6.tgz", @@ -20501,6 +20784,12 @@ "boolbase": "^1.0.0" } }, + "nwsapi": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.1.tgz", + "integrity": "sha512-JYOWTeFoS0Z93587vRJgASD5Ut11fYl5NyihP3KrYBvMe1FRRs6RN7m20SA/16GM4P6hTnZjT+UmDOt38UeXNg==", + "dev": true + }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -20632,12 +20921,6 @@ "callsites": "^3.0.0" } }, - "parse-cache-control": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parse-cache-control/-/parse-cache-control-1.0.1.tgz", - "integrity": "sha512-60zvsJReQPX5/QP0Kzfd/VrpjScIQ7SHBW6bFCYfEP+fp0Eppr1SHhIO5nd1PjZtvclzSzES9D/p5nFJurwfWg==", - "dev": true - }, "parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -20660,6 +20943,12 @@ "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==" }, + "parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -20896,21 +21185,6 @@ "integrity": "sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==", "dev": true }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, - "promise": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/promise/-/promise-8.1.0.tgz", - "integrity": "sha512-W04AqnILOL/sPRXziNicCjSNRruLAuIHEOVBazepu0545DDNGYHz7ar9ZgZ1fMU8/MA4mVxp5rkBWRi6OXIy3Q==", - "dev": true, - "requires": { - "asap": "~2.0.6" - } - }, "prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -20954,20 +21228,17 @@ "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==" }, + "psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", + "dev": true + }, "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, - "qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "dev": true, - "requires": { - "side-channel": "^1.0.4" - } - }, "queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -21289,6 +21560,15 @@ "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", "optional": true }, + "saxes": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", + "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "dev": true, + "requires": { + "xmlchars": "^2.2.0" + } + }, "schema-utils": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", @@ -21826,25 +22106,11 @@ "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-4.13.2.tgz", "integrity": "sha512-jHL6UyIYpvEI7NsuWd0R3hJaPQTg6Oo4qSBo+oVfOEkv6rrQm/475RGSMmZgV6ajp+Sgrp9CqrDjQYAgQqiv1A==" }, - "sync-request": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/sync-request/-/sync-request-6.1.0.tgz", - "integrity": "sha512-8fjNkrNlNCrVc/av+Jn+xxqfCjYaBoHqCsDz6mt030UMxJGr+GSfCV1dQt2gRtlL63+VPidwDVLr7V2OcTSdRw==", - "dev": true, - "requires": { - "http-response-object": "^3.0.1", - "sync-rpc": "^1.2.1", - "then-request": "^6.0.0" - } - }, - "sync-rpc": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/sync-rpc/-/sync-rpc-1.3.6.tgz", - "integrity": "sha512-J8jTXuZzRlvU7HemDgHi3pGnh/rkoqR/OZSjhTyyZrEkkYQbk7Z33AXp37mkPfPpfdOuj7Ex3H/TJM1z48uPQw==", - "dev": true, - "requires": { - "get-port": "^3.1.0" - } + "symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true }, "table": { "version": "6.8.0", @@ -21982,33 +22248,6 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, - "then-request": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/then-request/-/then-request-6.0.2.tgz", - "integrity": "sha512-3ZBiG7JvP3wbDzA9iNY5zJQcHL4jn/0BWtXIkagfz7QgOL/LqjCEOBQuJNZfu0XYnv5JhKh+cDxCPM4ILrqruA==", - "dev": true, - "requires": { - "@types/concat-stream": "^1.6.0", - "@types/form-data": "0.0.33", - "@types/node": "^8.0.0", - "@types/qs": "^6.2.31", - "caseless": "~0.12.0", - "concat-stream": "^1.6.0", - "form-data": "^2.2.0", - "http-basic": "^8.1.1", - "http-response-object": "^3.0.1", - "promise": "^8.0.0", - "qs": "^6.4.0" - }, - "dependencies": { - "@types/node": { - "version": "8.10.66", - "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.66.tgz", - "integrity": "sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw==", - "dev": true - } - } - }, "tippy.js": { "version": "6.3.7", "resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-6.3.7.tgz", @@ -22043,6 +22282,17 @@ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "dev": true }, + "tough-cookie": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz", + "integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==", + "dev": true, + "requires": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.1.2" + } + }, "tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", @@ -22115,12 +22365,6 @@ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", - "dev": true - }, "typo-js": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/typo-js/-/typo-js-1.2.2.tgz", @@ -22383,6 +22627,24 @@ "resolved": "https://registry.npmjs.org/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz", "integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==" }, + "w3c-hr-time": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", + "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", + "dev": true, + "requires": { + "browser-process-hrtime": "^1.0.0" + } + }, + "w3c-xmlserializer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-3.0.0.tgz", + "integrity": "sha512-3WFqGEgSXIyGhOmAFtlicJNMjEps8b1MG31NCA0/vOF9+nKMUW1ckhi9cnNHmf88Rzw5V+dwIwsm2C7X8k9aQg==", + "dev": true, + "requires": { + "xml-name-validator": "^4.0.0" + } + }, "walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", @@ -22735,12 +22997,25 @@ "signal-exit": "^3.0.7" } }, + "ws": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.8.1.tgz", + "integrity": "sha512-bGy2JzvzkPowEJV++hF07hAD6niYSr0JzBNo/J29WsB57A2r7Wlc1UFcTR9IzrPvuNVO4B8LGqF8qcpsVOhJCA==", + "dev": true, + "requires": {} + }, "xml-name-validator": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", "dev": true }, + "xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true + }, "xregexp": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-2.0.0.tgz", diff --git a/package.json b/package.json index 42cba24f85..7129bf6570 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,6 @@ "wrap-ansi": "8.0.1" }, "devDependencies": { - "@happy-dom/jest-environment": "6.0.4", "@stoplight/spectral-cli": "6.5.0", "eslint": "8.21.0", "eslint-plugin-import": "2.26.0", @@ -55,6 +54,7 @@ "eslint-plugin-unicorn": "43.0.2", "eslint-plugin-vue": "9.3.0", "jest": "28.1.3", + "jest-environment-jsdom": "28.1.3", "jest-extended": "3.0.1", "markdownlint-cli": "0.32.1", "postcss-less": "6.0.0", diff --git a/templates/base/footer.tmpl b/templates/base/footer.tmpl index 9bf16f8aa5..0e1b382d9d 100644 --- a/templates/base/footer.tmpl +++ b/templates/base/footer.tmpl @@ -22,7 +22,7 @@ {{end}} {{end}} - + {{template "custom/footer" .}} diff --git a/templates/base/head.tmpl b/templates/base/head.tmpl index e0d2b26f2c..233cf3e1d5 100644 --- a/templates/base/head.tmpl +++ b/templates/base/head.tmpl @@ -21,7 +21,7 @@ {{end}} - + {{template "base/head_script" .}}