Compare commits

...

21 Commits

Author SHA1 Message Date
Lunny Xiao
2360c7ec6c Add changelog for 1.21.3 (#28569) 2023-12-21 07:47:15 +00:00
Giteabot
8ca32dc873 Fix merging artifact chunks error when minio storage basepath is set (#28555) (#28568)
Backport #28555 by @fuxiaohei

Related to  https://github.com/go-gitea/gitea/issues/28279

When merging artifact chunks, it lists chunks from storage. When storage
is minio, chunk's path contains `MINIO_BASE_PATH` that makes merging
break.

<del>So trim the `MINIO_BASE_PATH` when handle chunks.</del>

Update the chunk file's basename to retain necessary information. It
ensures that the directory in the chunk's path remains unaffected.

Co-authored-by: FuXiaoHei <fuxiaohei@vip.qq.com>
2023-12-21 15:38:39 +08:00
Giteabot
47f9b3f484 Update actions document about comparsion as Github Actions (#28560) (#28564)
Backport #28560 by @lunny

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-12-20 16:00:30 -05:00
Giteabot
16263af971 Fix inperformant query on retrifing review from database. (#28552) (#28562)
Backport #28552 by @6543

can we please PLEAS PLEASE only use raw SQL statements if it is relay
needed!!!

source is https://github.com/go-gitea/gitea/pull/28544 (before
refactoring)

Co-authored-by: 6543 <m.huber@kithara.com>
2023-12-20 16:55:08 +01:00
Giteabot
f096635622 Fix the issue ref rendering for wiki (#28556) (#28559)
Backport #28556 by wxiaoguang

Fix #28526, regression of 
* #26365

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-12-20 22:51:25 +08:00
Giteabot
932e282e15 Fix duplicate ID when deleting repo (#28520) (#28528)
Backport #28520 by @framitdavid


There is an accessibility issue in the interface when attempting to
delete a repository. When I click on "Delete repository," a dialog box
appears, requiring confirmation to proceed with the repository deletion.
However, when I press the "Repo name" label, the wrong input field gains
focus. The focused field is located behind the dialog and is intended
for renaming the repository.

I am submitting these pull requests to ensure that the correct input
field is focused when the user clicks on the label. This change will
also facilitate the writing of tests using Playwright or Testing Library
to retrieve elements based on roles. This PR will also improve
acessibility of this area.

Co-authored-by: David Øvrelid <46874830+framitdavid@users.noreply.github.com>
2023-12-19 16:40:03 +08:00
Giteabot
d9aeb1f09d Only check online runner when detecting matching runners in workflows (#28286) (#28512)
Backport #28286 by @yp05327

Mentioned:
[#28277](https://github.com/go-gitea/gitea/issues/28277#issuecomment-1831325276)

We should only check online runner when detecting matching runners in
workflows,
as if runner is not online, the workflow will not run.


![image](https://github.com/go-gitea/gitea/assets/18380374/11855e9d-7241-4b7a-b8d7-49dbb94ba1c5)

Co-authored-by: yp05327 <576951401@qq.com>
2023-12-19 04:06:31 +00:00
Giteabot
411310d698 chore(api): support ignore password if login source type is LDAP for creating user API (#28491) (#28525)
Backport #28491 by @appleboy

- Modify the `Password` field in `CreateUserOption` struct to remove the
`Required` tag
- Update the `v1_json.tmpl` template to include the `email` field and
remove the `password` field

Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com>
Co-authored-by: Bo-Yi Wu <appleboy.tw@gmail.com>
2023-12-19 11:14:06 +08:00
wxiaoguang
6d002f8e1e Update golang.org/x/crypto (#28519)
ref: https://groups.google.com/g/golang-announce/c/qA3XtxvMUyg,
CVE-2023-48795, https://go.dev/issue/64784
2023-12-19 07:04:21 +08:00
Giteabot
4462628a26 Improve the prompt for "ssh-keygen sign" (#28509) (#28510)
Backport #28509 by wxiaoguang

Close #28505

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-12-18 15:25:52 +00:00
wxiaoguang
a3f403f438 Add option to disable ambiguous unicode characters detection (#28454) (#28499)
Backport #28454 (the only conflict is caused by some comments)

* Close #24483
* Close #28123
* Close #23682
* Close #23149
2023-12-18 12:20:37 +08:00
Giteabot
8ee1ed877b Initalize stroage for orphaned repository doctor (#28487) (#28490)
Backport #28487 by @earl-warren

- When a repository is orphaned and has objects stored in any of the
storages such as repository avatar or attachments the delete function
would error, because the storage module wasn't initalized.
- Add code to initialize the storage module.

Refs: https://codeberg.org/forgejo/forgejo/pulls/1954

Co-authored-by: Earl Warren <109468362+earl-warren@users.noreply.github.com>
Co-authored-by: Gusted <postmaster@gusted.xyz>
2023-12-16 22:06:37 +08:00
Giteabot
2c2e00899d Update docs for DISABLE_QUERY_AUTH_TOKEN (#28485) (#28488)
Backport #28485 by @kdumontnu

As described
[here](https://github.com/go-gitea/gitea/pull/28390#issuecomment-1857553331).

Co-authored-by: Kyle D <kdumontnu@gmail.com>
2023-12-16 00:04:05 -05:00
Giteabot
6cbb6f303a Refactor SSH clone URL generation code (#28421) (#28480)
Backport #28421 by wxiaoguang

Refactor the code and add tests, keep the old logic.

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-12-15 14:54:40 +08:00
Giteabot
6af698fb81 Polyfill SubmitEvent for PaleMoon (#28441) (#28478)
Backport #28441 by wxiaoguang

Fix #28319

It only polyfills if there is no "SubmitEvent" class, so it has no side
effect for most users.

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-12-15 03:04:37 +00:00
Giteabot
94a05a492d Fix Chinese translation of config cheat sheet[API] (#28472) (#28473)
Backport #28472 by @CaiCandong

Co-authored-by: CaiCandong <50507092+CaiCandong@users.noreply.github.com>
2023-12-15 07:54:32 +08:00
Giteabot
6de862abdf Fix documents for "custom/public/assets/" (#28465) (#28467)
Backport #28465 by wxiaoguang

Fix #28463

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-12-14 17:00:39 +08:00
Giteabot
b47482d58e Retry SSH key verification with additional CRLF if it failed (#28392) (#28464)
Backport #28392 by @nekrondev

Windows-based shells will add a CRLF when piping the token into
ssh-keygen command resulting in
verification error. This resolves #21527.

Co-authored-by: nekrondev <heiko@noordsee.de>
Co-authored-by: Heiko Besemann <heiko.besemann@qbeyond.de>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-12-14 12:50:26 +08:00
Giteabot
74ab798033 Add endpoint for not implemented Docker auth (#28457) (#28462)
Backport #28457 by @KN4CK3R

Recently Docker started to use the optional `POST /v2/token` endpoint
which should respond with a `404 Not Found` status code instead of the
current `405 Method Not Allowed`.

> Note: Not all token servers implement oauth2. If the request to the
endpoint returns 404 using the HTTP POST method, refer to Token
Documentation for using the HTTP GET method supported by all token
servers.

Co-authored-by: KN4CK3R <admin@oldschoolhack.me>
2023-12-13 22:06:24 +01:00
Giteabot
97a0bf151a Fix possible nil pointer access (#28428) (#28440)
Backport #28428 by @KN4CK3R

There could be a nil pointer exception if the file is not found because
that specific error is suppressed but not handled.

Co-authored-by: KN4CK3R <admin@oldschoolhack.me>
2023-12-12 23:35:32 +08:00
Giteabot
5e2bae7716 Don't show unnecessary citation JS error on UI (#28433) (#28437)
Backport #28433 by wxiaoguang

Fix #28226

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2023-12-12 21:14:04 +08:00
57 changed files with 378 additions and 265 deletions

View File

@@ -4,6 +4,34 @@ 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.com).
## [1.21.3](https://github.com/go-gitea/gitea/releases/tag/1.21.3) - 2023-12-21
* SECURITY
* Update golang.org/x/crypto (#28519)
* API
* chore(api): support ignore password if login source type is LDAP for creating user API (#28491) (#28525)
* Add endpoint for not implemented Docker auth (#28457) (#28462)
* ENHANCEMENTS
* Add option to disable ambiguous unicode characters detection (#28454) (#28499)
* Refactor SSH clone URL generation code (#28421) (#28480)
* Polyfill SubmitEvent for PaleMoon (#28441) (#28478)
* BUGFIXES
* Fix the issue ref rendering for wiki (#28556) (#28559)
* Fix duplicate ID when deleting repo (#28520) (#28528)
* Only check online runner when detecting matching runners in workflows (#28286) (#28512)
* Initalize stroage for orphaned repository doctor (#28487) (#28490)
* Fix possible nil pointer access (#28428) (#28440)
* Don't show unnecessary citation JS error on UI (#28433) (#28437)
* DOCS
* Update actions document about comparsion as Github Actions (#28560) (#28564)
* Fix documents for "custom/public/assets/" (#28465) (#28467)
* MISC
* Fix inperformant query on retrifing review from database. (#28552) (#28562)
* Improve the prompt for "ssh-keygen sign" (#28509) (#28510)
* Update docs for DISABLE_QUERY_AUTH_TOKEN (#28485) (#28488)
* Fix Chinese translation of config cheat sheet[API] (#28472) (#28473)
* Retry SSH key verification with additional CRLF if it failed (#28392) (#28464)
## [1.21.2](https://github.com/go-gitea/gitea/releases/tag/1.21.2) - 2023-12-12
* SECURITY

View File

@@ -1210,6 +1210,9 @@ LEVEL = Info
;; Max size of files to be displayed (default is 8MiB)
;MAX_DISPLAY_FILE_SIZE = 8388608
;;
;; Detect ambiguous unicode characters in file contents and show warnings on the UI
;AMBIGUOUS_UNICODE_DETECTION = true
;;
;; Whether the email of the user should be shown in the Explore Users page
;SHOW_USER_EMAIL = true
;;

View File

@@ -19,10 +19,10 @@ Some jurisdictions (such as EU), requires certain legal pages (e.g. Privacy Poli
## Getting Pages
Gitea source code ships with sample pages, available in `contrib/legal` directory. Copy them to `custom/public/`. For example, to add Privacy Policy:
Gitea source code ships with sample pages, available in `contrib/legal` directory. Copy them to `custom/public/assets/`. For example, to add Privacy Policy:
```
wget -O /path/to/custom/public/privacy.html https://raw.githubusercontent.com/go-gitea/gitea/main/contrib/legal/privacy.html.sample
wget -O /path/to/custom/public/assets/privacy.html https://raw.githubusercontent.com/go-gitea/gitea/main/contrib/legal/privacy.html.sample
```
Now you need to edit the page to meet your requirements. In particular you must change the email addresses, web addresses and references to "Your Gitea Instance" to match your situation.

View File

@@ -19,10 +19,10 @@ menu:
## 获取页面
Gitea 源代码附带了示例页面,位于 `contrib/legal` 目录中。将它们复制到 `custom/public/` 目录下。例如,如果要添加隐私政策:
Gitea 源代码附带了示例页面,位于 `contrib/legal` 目录中。将它们复制到 `custom/public/assets/` 目录下。例如,如果要添加隐私政策:
```
wget -O /path/to/custom/public/privacy.html https://raw.githubusercontent.com/go-gitea/gitea/main/contrib/legal/privacy.html.sample
wget -O /path/to/custom/public/assets/privacy.html https://raw.githubusercontent.com/go-gitea/gitea/main/contrib/legal/privacy.html.sample
```
现在,你需要编辑该页面以满足你的需求。特别是,你必须更改电子邮件地址、网址以及与 "Your Gitea Instance" 相关的引用,以匹配你的情况。

View File

@@ -220,6 +220,7 @@ The following configuration set `Content-Type: application/vnd.android.package-a
- `THEMES`: **auto,gitea,arc-green**: All available themes. Allow users select personalized themes.
regardless of the value of `DEFAULT_THEME`.
- `MAX_DISPLAY_FILE_SIZE`: **8388608**: Max size of files to be displayed (default is 8MiB)
- `AMBIGUOUS_UNICODE_DETECTION`: **true**: Detect ambiguous unicode characters in file contents and show warnings on the UI
- `REACTIONS`: All available reactions users can choose on issues/prs and comments
Values can be emoji alias (:smile:) or a unicode emoji.
For custom reactions, add a tightly cropped square image to public/assets/img/emoji/reaction_name.png
@@ -571,6 +572,7 @@ And the following unique queues:
- off - do not check password complexity
- `PASSWORD_CHECK_PWN`: **false**: Check [HaveIBeenPwned](https://haveibeenpwned.com/Passwords) to see if a password has been exposed.
- `SUCCESSFUL_TOKENS_CACHE_SIZE`: **20**: Cache successful token hashes. API tokens are stored in the DB as pbkdf2 hashes however, this means that there is a potentially significant hashing load when there are multiple API operations. This cache will store the successfully hashed tokens in a LRU cache as a balance between performance and security.
- `DISABLE_QUERY_AUTH_TOKEN`: **false**: Reject API tokens sent in URL query string (Accept Header-based API tokens only). This setting will default to `true` in Gitea 1.23 and be deprecated in Gitea 1.24.
## Camo (`camo`)

View File

@@ -1040,10 +1040,11 @@ Gitea 创建以下非唯一队列:
## API (`api`)
- `ENABLE_SWAGGER`: **true**: 是否启用swagger路由 (`/api/swagger`, `/api/v1/swagger`, …)。
- `MAX_RESPONSE_ITEMS`: **50**: 单个页面的最大 Feed.
- `ENABLE_OPENID_SIGNIN`: **false**: 允许使用OpenID登录当设置为`true`时可以通过 `/user/login` 页面进行OpenID登录
- `DISABLE_REGISTRATION`: **false**: 关闭用户注册
- `ENABLE_SWAGGER`: **true**: 启用API文档接口 (`/api/swagger`, `/api/v1/swagger`, …). True or false
- `MAX_RESPONSE_ITEMS`: **50**: API分页的最大单页项目数。
- `DEFAULT_PAGING_NUM`: **30**: API分页的默认分页数
- `DEFAULT_GIT_TREES_PER_PAGE`: **1000**: Git trees API的默认单页项目数
- `DEFAULT_MAX_BLOB_SIZE`: **10485760** (10MiB): blobs API的默认最大文件大小。
## OAuth2 (`oauth2`)

View File

@@ -42,11 +42,11 @@ Gitea 引用 `custom` 目录中的自定义配置文件来覆盖配置、模板
将自定义的公共文件(比如页面和图片)作为 webroot 放在 `custom/public/` 中来让 Gitea 提供这些自定义内容(符号链接将被追踪)。
举例说明:`image.png` 存放在 `custom/public/`中,那么它可以通过链接 http://gitea.domain.tld/assets/image.png 访问。
举例说明:`image.png` 存放在 `custom/public/assets/`中,那么它可以通过链接 http://gitea.domain.tld/assets/image.png 访问。
## 修改默认头像
替换以下目录中的 png 图片: `custom/public/img/avatar\_default.png`
替换以下目录中的 png 图片: `custom/public/assets/img/avatar\_default.png`
## 自定义 Gitea 页面

View File

@@ -194,7 +194,7 @@ ALLOW_DATA_URI_IMAGES = true
}
```
将您的样式表添加到自定义目录中,例如 `custom/public/css/my-style-XXXXX.css`,并使用自定义的头文件 `custom/templates/custom/header.tmpl` 进行导入:
将您的样式表添加到自定义目录中,例如 `custom/public/assets/css/my-style-XXXXX.css`,并使用自定义的头文件 `custom/templates/custom/header.tmpl` 进行导入:
```html
<link rel="stylesheet" href="{{AppSubUrl}}/assets/css/my-style-XXXXX.css" />

View File

@@ -190,7 +190,7 @@ Gitea 目前支持三个官方主题,分别是 `gitea`(亮色)、`arc-gree
假设我们的主题是 `arc-blue`(这是一个真实的主题,可以在[此问题](https://github.com/go-gitea/gitea/issues/6011)中找到)
`.css`文件命名为`theme-arc-blue.css`并将其添加到`custom/public/css`文件夹中
`.css`文件命名为`theme-arc-blue.css`并将其添加到`custom/public/assets/css`文件夹中
通过将`arc-blue`添加到`app.ini`中的`THEMES`列表中,允许用户使用该主题

View File

@@ -29,6 +29,10 @@ Like `uses: https://github.com/actions/checkout@v3` or `uses: http://your_gitea.
Gitea Actions supports writing actions in Go.
See [Creating Go Actions](https://blog.gitea.com/creating-go-actions/).
### Support the non-standard syntax @yearly, @monthly, @weekly, @daily, @hourly on schedule
Github Actions doesn't support that. https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#schedule
## Unsupported workflows syntax
### `concurrency`
@@ -110,6 +114,10 @@ It's ignored by Gitea Actions now.
Pre and Post steps don't have their own section in the job log user interface.
### Services steps
Services steps don't have their own section in the job log user interface.
## Different behavior
### Downloading actions

View File

@@ -29,6 +29,10 @@ Gitea Actions支持通过URL绝对路径定义actions这意味着您可以使
Gitea Actions支持使用Go编写Actions。
请参阅[创建Go Actions](https://blog.gitea.com/creating-go-actions/)。
### 支持非标准的调度语法 @yearly, @monthly, @weekly, @daily, @hourly
Github Actions 不支持这些语法,详见: https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#schedule
## 不支持的工作流语法
### `concurrency`
@@ -116,6 +120,10 @@ Gitea Actions目前不支持此功能。
预处理和后处理步骤在Job日志用户界面中没有自己的用户界面。
### 服务步骤
服务步骤在Job日志用户界面中没有自己的用户界面。
## 不一样的行为
### 下载Actions

6
go.mod
View File

@@ -106,12 +106,12 @@ require (
github.com/yuin/goldmark v1.5.6
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
github.com/yuin/goldmark-meta v1.1.0
golang.org/x/crypto v0.14.0
golang.org/x/crypto v0.17.0
golang.org/x/image v0.13.0
golang.org/x/net v0.17.0
golang.org/x/oauth2 v0.13.0
golang.org/x/sys v0.13.0
golang.org/x/text v0.13.0
golang.org/x/sys v0.15.0
golang.org/x/text v0.14.0
golang.org/x/tools v0.14.0
google.golang.org/grpc v1.58.3
google.golang.org/protobuf v1.31.0

15
go.sum
View File

@@ -1152,8 +1152,8 @@ golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2Uz
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -1348,8 +1348,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -1360,8 +1360,8 @@ golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -1376,8 +1376,9 @@ golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=

View File

@@ -51,6 +51,11 @@ type ActionRunner struct {
Deleted timeutil.TimeStamp `xorm:"deleted"`
}
const (
RunnerOfflineTime = time.Minute
RunnerIdleTime = 10 * time.Second
)
// BelongsToOwnerName before calling, should guarantee that all attributes are loaded
func (r *ActionRunner) BelongsToOwnerName() string {
if r.RepoID != 0 {
@@ -76,11 +81,12 @@ func (r *ActionRunner) BelongsToOwnerType() types.OwnerType {
return types.OwnerTypeSystemGlobal
}
// if the logic here changed, you should also modify FindRunnerOptions.ToCond
func (r *ActionRunner) Status() runnerv1.RunnerStatus {
if time.Since(r.LastOnline.AsTime()) > time.Minute {
if time.Since(r.LastOnline.AsTime()) > RunnerOfflineTime {
return runnerv1.RunnerStatus_RUNNER_STATUS_OFFLINE
}
if time.Since(r.LastActive.AsTime()) > 10*time.Second {
if time.Since(r.LastActive.AsTime()) > RunnerIdleTime {
return runnerv1.RunnerStatus_RUNNER_STATUS_IDLE
}
return runnerv1.RunnerStatus_RUNNER_STATUS_ACTIVE
@@ -153,6 +159,7 @@ type FindRunnerOptions struct {
OwnerID int64
Sort string
Filter string
IsOnline util.OptionalBool
WithAvailable bool // not only runners belong to, but also runners can be used
}
@@ -178,6 +185,12 @@ func (opts FindRunnerOptions) toCond() builder.Cond {
if opts.Filter != "" {
cond = cond.And(builder.Like{"name", opts.Filter})
}
if opts.IsOnline.IsTrue() {
cond = cond.And(builder.Gt{"last_online": time.Now().Add(-RunnerOfflineTime).Unix()})
} else if opts.IsOnline.IsFalse() {
cond = cond.And(builder.Lte{"last_online": time.Now().Add(-RunnerOfflineTime).Unix()})
}
return cond
}

View File

@@ -29,10 +29,15 @@ func VerifySSHKey(ownerID int64, fingerprint, token, signature string) (string,
return "", ErrKeyNotExist{}
}
if err := sshsig.Verify(bytes.NewBuffer([]byte(token)), []byte(signature), []byte(key.Content), "gitea"); err != nil {
log.Error("Unable to validate token signature. Error: %v", err)
return "", ErrSSHInvalidTokenSignature{
Fingerprint: key.Fingerprint,
err = sshsig.Verify(bytes.NewBuffer([]byte(token)), []byte(signature), []byte(key.Content), "gitea")
if err != nil {
// edge case for Windows based shells that will add CR LF if piped to ssh-keygen command
// see https://github.com/PowerShell/PowerShell/issues/5974
if sshsig.Verify(bytes.NewBuffer([]byte(token+"\r\n")), []byte(signature), []byte(key.Content), "gitea") != nil {
log.Error("Unable to validate token signature. Error: %v", err)
return "", ErrSSHInvalidTokenSignature{
Fingerprint: key.Fingerprint,
}
}
}

View File

@@ -461,8 +461,10 @@ func SubmitReview(ctx context.Context, doer *user_model.User, issue *Issue, revi
func GetReviewByIssueIDAndUserID(ctx context.Context, issueID, userID int64) (*Review, error) {
review := new(Review)
has, err := db.GetEngine(ctx).SQL("SELECT * FROM review WHERE id IN (SELECT max(id) as id FROM review WHERE issue_id = ? AND reviewer_id = ? AND original_author_id = 0 AND type in (?, ?, ?))",
issueID, userID, ReviewTypeApprove, ReviewTypeReject, ReviewTypeRequest).
has, err := db.GetEngine(ctx).Where(
builder.In("type", ReviewTypeApprove, ReviewTypeReject, ReviewTypeRequest).
And(builder.Eq{"issue_id": issueID, "reviewer_id": userID, "original_author_id": 0})).
Desc("id").
Get(review)
if err != nil {
return nil, err
@@ -476,13 +478,13 @@ func GetReviewByIssueIDAndUserID(ctx context.Context, issueID, userID int64) (*R
}
// GetTeamReviewerByIssueIDAndTeamID get the latest review request of reviewer team for a pull request
func GetTeamReviewerByIssueIDAndTeamID(ctx context.Context, issueID, teamID int64) (review *Review, err error) {
review = new(Review)
func GetTeamReviewerByIssueIDAndTeamID(ctx context.Context, issueID, teamID int64) (*Review, error) {
review := new(Review)
var has bool
if has, err = db.GetEngine(ctx).SQL("SELECT * FROM review WHERE id IN (SELECT max(id) as id FROM review WHERE issue_id = ? AND reviewer_team_id = ?)",
issueID, teamID).
Get(review); err != nil {
has, err := db.GetEngine(ctx).Where(builder.Eq{"issue_id": issueID, "reviewer_team_id": teamID}).
Desc("id").
Get(review)
if err != nil {
return nil, err
}

View File

@@ -602,25 +602,23 @@ func ComposeHTTPSCloneURL(owner, repo string) string {
func ComposeSSHCloneURL(ownerName, repoName string) string {
sshUser := setting.SSH.User
// if we have a ipv6 literal we need to put brackets around it
// for the git cloning to work.
sshDomain := setting.SSH.Domain
ip := net.ParseIP(setting.SSH.Domain)
if ip != nil && ip.To4() == nil {
sshDomain = "[" + setting.SSH.Domain + "]"
// non-standard port, it must use full URI
if setting.SSH.Port != 22 {
sshHost := net.JoinHostPort(sshDomain, strconv.Itoa(setting.SSH.Port))
return fmt.Sprintf("ssh://%s@%s/%s/%s.git", sshUser, sshHost, url.PathEscape(ownerName), url.PathEscape(repoName))
}
if setting.SSH.Port != 22 {
return fmt.Sprintf("ssh://%s@%s/%s/%s.git", sshUser,
net.JoinHostPort(setting.SSH.Domain, strconv.Itoa(setting.SSH.Port)),
url.PathEscape(ownerName),
url.PathEscape(repoName))
// for standard port, it can use a shorter URI (without the port)
sshHost := sshDomain
if ip := net.ParseIP(sshHost); ip != nil && ip.To4() == nil {
sshHost = "[" + sshHost + "]" // for IPv6 address, wrap it with brackets
}
if setting.Repository.UseCompatSSHURI {
return fmt.Sprintf("ssh://%s@%s/%s/%s.git", sshUser, sshDomain, url.PathEscape(ownerName), url.PathEscape(repoName))
return fmt.Sprintf("ssh://%s@%s/%s/%s.git", sshUser, sshHost, url.PathEscape(ownerName), url.PathEscape(repoName))
}
return fmt.Sprintf("%s@%s:%s/%s.git", sshUser, sshDomain, url.PathEscape(ownerName), url.PathEscape(repoName))
return fmt.Sprintf("%s@%s:%s/%s.git", sshUser, sshHost, url.PathEscape(ownerName), url.PathEscape(repoName))
}
func (repo *Repository) cloneLink(isWiki bool) *CloneLink {

View File

@@ -12,6 +12,8 @@ import (
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/modules/util"
"github.com/stretchr/testify/assert"
@@ -186,3 +188,32 @@ func TestGetRepositoryByURL(t *testing.T) {
test(t, "try.gitea.io:user2/repo2.git")
})
}
func TestComposeSSHCloneURL(t *testing.T) {
defer test.MockVariableValue(&setting.SSH, setting.SSH)()
defer test.MockVariableValue(&setting.Repository, setting.Repository)()
setting.SSH.User = "git"
// test SSH_DOMAIN
setting.SSH.Domain = "domain"
setting.SSH.Port = 22
setting.Repository.UseCompatSSHURI = false
assert.Equal(t, "git@domain:user/repo.git", repo_model.ComposeSSHCloneURL("user", "repo"))
setting.Repository.UseCompatSSHURI = true
assert.Equal(t, "ssh://git@domain/user/repo.git", repo_model.ComposeSSHCloneURL("user", "repo"))
// test SSH_DOMAIN while use non-standard SSH port
setting.SSH.Port = 123
setting.Repository.UseCompatSSHURI = false
assert.Equal(t, "ssh://git@domain:123/user/repo.git", repo_model.ComposeSSHCloneURL("user", "repo"))
setting.Repository.UseCompatSSHURI = true
assert.Equal(t, "ssh://git@domain:123/user/repo.git", repo_model.ComposeSSHCloneURL("user", "repo"))
// test IPv6 SSH_DOMAIN
setting.Repository.UseCompatSSHURI = false
setting.SSH.Domain = "::1"
setting.SSH.Port = 22
assert.Equal(t, "git@[::1]:user/repo.git", repo_model.ComposeSSHCloneURL("user", "repo"))
setting.SSH.Port = 123
assert.Equal(t, "ssh://git@[::1]:123/user/repo.git", repo_model.ComposeSSHCloneURL("user", "repo"))
}

View File

@@ -8,11 +8,12 @@
package charset
import (
"bufio"
"html/template"
"io"
"strings"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/translation"
)
@@ -20,20 +21,18 @@ import (
const RuneNBSP = 0xa0
// EscapeControlHTML escapes the unicode control sequences in a provided html document
func EscapeControlHTML(text string, locale translation.Locale, allowed ...rune) (escaped *EscapeStatus, output string) {
func EscapeControlHTML(html template.HTML, locale translation.Locale, allowed ...rune) (escaped *EscapeStatus, output template.HTML) {
sb := &strings.Builder{}
outputStream := &HTMLStreamerWriter{Writer: sb}
streamer := NewEscapeStreamer(locale, outputStream, allowed...).(*escapeStreamer)
if err := StreamHTML(strings.NewReader(text), streamer); err != nil {
streamer.escaped.HasError = true
log.Error("Error whilst escaping: %v", err)
}
return streamer.escaped, sb.String()
escaped, _ = EscapeControlReader(strings.NewReader(string(html)), sb, locale, allowed...) // err has been handled in EscapeControlReader
return escaped, template.HTML(sb.String())
}
// EscapeControlReaders escapes the unicode control sequences in a provided reader of HTML content and writer in a locale and returns the findings as an EscapeStatus and the escaped []byte
// EscapeControlReader escapes the unicode control sequences in a provided reader of HTML content and writer in a locale and returns the findings as an EscapeStatus
func EscapeControlReader(reader io.Reader, writer io.Writer, locale translation.Locale, allowed ...rune) (escaped *EscapeStatus, err error) {
if !setting.UI.AmbiguousUnicodeDetection {
_, err = io.Copy(writer, reader)
return &EscapeStatus{}, err
}
outputStream := &HTMLStreamerWriter{Writer: writer}
streamer := NewEscapeStreamer(locale, outputStream, allowed...).(*escapeStreamer)
@@ -43,41 +42,3 @@ func EscapeControlReader(reader io.Reader, writer io.Writer, locale translation.
}
return streamer.escaped, err
}
// EscapeControlStringReader escapes the unicode control sequences in a provided reader of string content and writer in a locale and returns the findings as an EscapeStatus and the escaped []byte. HTML line breaks are not inserted after every newline by this method.
func EscapeControlStringReader(reader io.Reader, writer io.Writer, locale translation.Locale, allowed ...rune) (escaped *EscapeStatus, err error) {
bufRd := bufio.NewReader(reader)
outputStream := &HTMLStreamerWriter{Writer: writer}
streamer := NewEscapeStreamer(locale, outputStream, allowed...).(*escapeStreamer)
for {
line, rdErr := bufRd.ReadString('\n')
if len(line) > 0 {
if err := streamer.Text(line); err != nil {
streamer.escaped.HasError = true
log.Error("Error whilst escaping: %v", err)
return streamer.escaped, err
}
}
if rdErr != nil {
if rdErr != io.EOF {
err = rdErr
}
break
}
}
return streamer.escaped, err
}
// EscapeControlString escapes the unicode control sequences in a provided string and returns the findings as an EscapeStatus and the escaped string
func EscapeControlString(text string, locale translation.Locale, allowed ...rune) (escaped *EscapeStatus, output string) {
sb := &strings.Builder{}
outputStream := &HTMLStreamerWriter{Writer: sb}
streamer := NewEscapeStreamer(locale, outputStream, allowed...).(*escapeStreamer)
if err := streamer.Text(text); err != nil {
streamer.escaped.HasError = true
log.Error("Error whilst escaping: %v", err)
}
return streamer.escaped, sb.String()
}

View File

@@ -64,7 +64,7 @@ func (e *escapeStreamer) Text(data string) error {
until, next = nextIdxs[0]+pos, nextIdxs[1]+pos
}
// from pos until until we know that the runes are not \r\t\n or even ' '
// from pos until we know that the runes are not \r\t\n or even ' '
runes := make([]rune, 0, next-until)
positions := make([]int, 0, next-until+1)

View File

@@ -4,11 +4,14 @@
package charset
import (
"reflect"
"strings"
"testing"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/modules/translation"
"github.com/stretchr/testify/assert"
)
type escapeControlTest struct {
@@ -132,22 +135,8 @@ then resh (ר), and finally heh (ה) (which should appear leftmost).`,
},
}
func TestEscapeControlString(t *testing.T) {
for _, tt := range escapeControlTests {
t.Run(tt.name, func(t *testing.T) {
status, result := EscapeControlString(tt.text, &translation.MockLocale{})
if !reflect.DeepEqual(*status, tt.status) {
t.Errorf("EscapeControlString() status = %v, wanted= %v", status, tt.status)
}
if result != tt.result {
t.Errorf("EscapeControlString()\nresult= %v,\nwanted= %v", result, tt.result)
}
})
}
}
func TestEscapeControlReader(t *testing.T) {
// lets add some control characters to the tests
// add some control characters to the tests
tests := make([]escapeControlTest, 0, len(escapeControlTests)*3)
copy(tests, escapeControlTests)
@@ -169,29 +158,20 @@ func TestEscapeControlReader(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
input := strings.NewReader(tt.text)
output := &strings.Builder{}
status, err := EscapeControlReader(input, output, &translation.MockLocale{})
result := output.String()
if err != nil {
t.Errorf("EscapeControlReader(): err = %v", err)
}
if !reflect.DeepEqual(*status, tt.status) {
t.Errorf("EscapeControlReader() status = %v, wanted= %v", status, tt.status)
}
if result != tt.result {
t.Errorf("EscapeControlReader()\nresult= %v,\nwanted= %v", result, tt.result)
}
status, err := EscapeControlReader(strings.NewReader(tt.text), output, &translation.MockLocale{})
assert.NoError(t, err)
assert.Equal(t, tt.status, *status)
assert.Equal(t, tt.result, output.String())
})
}
}
func TestEscapeControlReader_panic(t *testing.T) {
bs := make([]byte, 0, 20479)
bs = append(bs, 'A')
for i := 0; i < 6826; i++ {
bs = append(bs, []byte("—")...)
}
_, _ = EscapeControlString(string(bs), &translation.MockLocale{})
func TestSettingAmbiguousUnicodeDetection(t *testing.T) {
defer test.MockVariableValue(&setting.UI.AmbiguousUnicodeDetection, true)()
_, out := EscapeControlHTML("a test", &translation.MockLocale{})
assert.EqualValues(t, `a<span class="escaped-code-point" data-escaped="[U+00A0]"><span class="char"> </span></span>test`, out)
setting.UI.AmbiguousUnicodeDetection = false
_, out = EscapeControlHTML("a test", &translation.MockLocale{})
assert.EqualValues(t, `a test`, out)
}

View File

@@ -9,6 +9,7 @@ import (
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/storage"
repo_service "code.gitea.io/gitea/services/repository"
"xorm.io/builder"
@@ -31,6 +32,10 @@ func countOrphanedRepos(ctx context.Context) (int64, error) {
// deleteOrphanedRepos delete repository where user of owner_id do not exist
func deleteOrphanedRepos(ctx context.Context) (int64, error) {
if err := storage.Init(); err != nil {
return 0, err
}
batchSize := db.MaxBatchInsertSize("repository")
e := db.GetEngine(ctx)
var deleted int64

View File

@@ -14,7 +14,6 @@ import (
"os/exec"
"strings"
"time"
"unsafe"
"code.gitea.io/gitea/modules/git/internal" //nolint:depguard // only this file can use the internal type CmdArg, other files and packages should use AddXxx functions
"code.gitea.io/gitea/modules/log"
@@ -389,15 +388,11 @@ func (r *runStdError) IsExitCode(code int) bool {
return false
}
func bytesToString(b []byte) string {
return *(*string)(unsafe.Pointer(&b)) // that's what Golang's strings.Builder.String() does (go/src/strings/builder.go)
}
// RunStdString runs the command with options and returns stdout/stderr as string. and store stderr to returned error (err combined with stderr).
func (c *Command) RunStdString(opts *RunOpts) (stdout, stderr string, runErr RunStdError) {
stdoutBytes, stderrBytes, err := c.RunStdBytes(opts)
stdout = bytesToString(stdoutBytes)
stderr = bytesToString(stderrBytes)
stdout = util.UnsafeBytesToString(stdoutBytes)
stderr = util.UnsafeBytesToString(stderrBytes)
if err != nil {
return stdout, stderr, &runStdError{err: err, stderr: stderr}
}
@@ -432,7 +427,7 @@ func (c *Command) RunStdBytes(opts *RunOpts) (stdout, stderr []byte, runErr RunS
err := c.Run(newOpts)
stderr = stderrBuf.Bytes()
if err != nil {
return nil, stderr, &runStdError{err: err, stderr: bytesToString(stderr)}
return nil, stderr, &runStdError{err: err, stderr: util.UnsafeBytesToString(stderr)}
}
// even if there is no err, there could still be some stderr output
return stdoutBuf.Bytes(), stderr, nil

View File

@@ -9,6 +9,7 @@ import (
"bytes"
"fmt"
gohtml "html"
"html/template"
"io"
"path/filepath"
"strings"
@@ -55,7 +56,7 @@ func NewContext() {
}
// Code returns a HTML version of code string with chroma syntax highlighting classes and the matched lexer name
func Code(fileName, language, code string) (string, string) {
func Code(fileName, language, code string) (output template.HTML, lexerName string) {
NewContext()
// diff view newline will be passed as empty, change to literal '\n' so it can be copied
@@ -65,7 +66,7 @@ func Code(fileName, language, code string) (string, string) {
}
if len(code) > sizeLimit {
return code, ""
return template.HTML(template.HTMLEscapeString(code)), ""
}
var lexer chroma.Lexer
@@ -102,13 +103,11 @@ func Code(fileName, language, code string) (string, string) {
cache.Add(fileName, lexer)
}
lexerName := formatLexerName(lexer.Config().Name)
return CodeFromLexer(lexer, code), lexerName
return CodeFromLexer(lexer, code), formatLexerName(lexer.Config().Name)
}
// CodeFromLexer returns a HTML version of code string with chroma syntax highlighting classes
func CodeFromLexer(lexer chroma.Lexer, code string) string {
func CodeFromLexer(lexer chroma.Lexer, code string) template.HTML {
formatter := html.New(html.WithClasses(true),
html.WithLineNumbers(false),
html.PreventSurroundingPre(true),
@@ -120,23 +119,23 @@ func CodeFromLexer(lexer chroma.Lexer, code string) string {
iterator, err := lexer.Tokenise(nil, code)
if err != nil {
log.Error("Can't tokenize code: %v", err)
return code
return template.HTML(template.HTMLEscapeString(code))
}
// style not used for live site but need to pass something
err = formatter.Format(htmlw, githubStyles, iterator)
if err != nil {
log.Error("Can't format code: %v", err)
return code
return template.HTML(template.HTMLEscapeString(code))
}
_ = htmlw.Flush()
// Chroma will add newlines for certain lexers in order to highlight them properly
// Once highlighted, strip them here, so they don't cause copy/paste trouble in HTML output
return strings.TrimSuffix(htmlbuf.String(), "\n")
return template.HTML(strings.TrimSuffix(htmlbuf.String(), "\n"))
}
// File returns a slice of chroma syntax highlighted HTML lines of code and the matched lexer name
func File(fileName, language string, code []byte) ([]string, string, error) {
func File(fileName, language string, code []byte) ([]template.HTML, string, error) {
NewContext()
if len(code) > sizeLimit {
@@ -183,14 +182,14 @@ func File(fileName, language string, code []byte) ([]string, string, error) {
tokensLines := chroma.SplitTokensIntoLines(iterator.Tokens())
htmlBuf := &bytes.Buffer{}
lines := make([]string, 0, len(tokensLines))
lines := make([]template.HTML, 0, len(tokensLines))
for _, tokens := range tokensLines {
iterator = chroma.Literator(tokens...)
err = formatter.Format(htmlBuf, githubStyles, iterator)
if err != nil {
return nil, "", fmt.Errorf("can't format code: %w", err)
}
lines = append(lines, htmlBuf.String())
lines = append(lines, template.HTML(htmlBuf.String()))
htmlBuf.Reset()
}
@@ -198,9 +197,9 @@ func File(fileName, language string, code []byte) ([]string, string, error) {
}
// PlainText returns non-highlighted HTML for code
func PlainText(code []byte) []string {
func PlainText(code []byte) []template.HTML {
r := bufio.NewReader(bytes.NewReader(code))
m := make([]string, 0, bytes.Count(code, []byte{'\n'})+1)
m := make([]template.HTML, 0, bytes.Count(code, []byte{'\n'})+1)
for {
content, err := r.ReadString('\n')
if err != nil && err != io.EOF {
@@ -210,7 +209,7 @@ func PlainText(code []byte) []string {
if content == "" && err == io.EOF {
break
}
s := gohtml.EscapeString(content)
s := template.HTML(gohtml.EscapeString(content))
m = append(m, s)
}
return m

View File

@@ -4,21 +4,36 @@
package highlight
import (
"html/template"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func lines(s string) []string {
return strings.Split(strings.ReplaceAll(strings.TrimSpace(s), `\n`, "\n"), "\n")
func lines(s string) (out []template.HTML) {
// "" => [], "a" => ["a"], "a\n" => ["a\n"], "a\nb" => ["a\n", "b"] (each line always includes EOL "\n" if it exists)
out = make([]template.HTML, 0)
s = strings.ReplaceAll(strings.ReplaceAll(strings.TrimSpace(s), "\n", ""), `\n`, "\n")
for {
if p := strings.IndexByte(s, '\n'); p != -1 {
out = append(out, template.HTML(s[:p+1]))
s = s[p+1:]
} else {
break
}
}
if s != "" {
out = append(out, template.HTML(s))
}
return out
}
func TestFile(t *testing.T) {
tests := []struct {
name string
code string
want []string
want []template.HTML
lexerName string
}{
{
@@ -99,10 +114,7 @@ c=2
t.Run(tt.name, func(t *testing.T) {
out, lexerName, err := File(tt.name, "", []byte(tt.code))
assert.NoError(t, err)
expected := strings.Join(tt.want, "\n")
actual := strings.Join(out, "\n")
assert.Equal(t, strings.Count(actual, "<span"), strings.Count(actual, "</span>"))
assert.EqualValues(t, expected, actual)
assert.EqualValues(t, tt.want, out)
assert.Equal(t, tt.lexerName, lexerName)
})
}
@@ -112,7 +124,7 @@ func TestPlainText(t *testing.T) {
tests := []struct {
name string
code string
want []string
want []template.HTML
}{
{
name: "empty.py",
@@ -165,9 +177,7 @@ c=2`),
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
out := PlainText([]byte(tt.code))
expected := strings.Join(tt.want, "\n")
actual := strings.Join(out, "\n")
assert.EqualValues(t, expected, actual)
assert.EqualValues(t, tt.want, out)
})
}
}

View File

@@ -6,6 +6,7 @@ package code
import (
"bytes"
"context"
"html/template"
"strings"
"code.gitea.io/gitea/modules/highlight"
@@ -22,7 +23,7 @@ type Result struct {
Language string
Color string
LineNumbers []int
FormattedLines string
FormattedLines template.HTML
}
type SearchResultLanguages = internal.SearchResultLanguages

View File

@@ -852,7 +852,9 @@ func fullIssuePatternProcessor(ctx *RenderContext, node *html.Node) {
}
func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) {
if ctx.Metas == nil || ctx.Metas["mode"] == "document" {
// FIXME: the use of "mode" is quite dirty and hacky, for example: what is a "document"? how should it be rendered?
// The "mode" approach should be refactored to some other more clear&reliable way.
if ctx.Metas == nil || (ctx.Metas["mode"] == "document" && !ctx.IsWiki) {
return
}
var (

View File

@@ -87,7 +87,7 @@ func Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error
}
lexer = chroma.Coalesce(lexer)
if _, err := w.WriteString(highlight.CodeFromLexer(lexer, source)); err != nil {
if _, err := w.WriteString(string(highlight.CodeFromLexer(lexer, source))); err != nil {
return ""
}
}

View File

@@ -34,6 +34,8 @@ var UI = struct {
SearchRepoDescription bool
OnlyShowRelevantRepos bool
AmbiguousUnicodeDetection bool
Notification struct {
MinTimeout time.Duration
TimeoutStep time.Duration
@@ -81,6 +83,9 @@ var UI = struct {
Reactions: []string{`+1`, `-1`, `laugh`, `hooray`, `confused`, `heart`, `rocket`, `eyes`},
CustomEmojis: []string{`git`, `gitea`, `codeberg`, `gitlab`, `github`, `gogs`},
CustomEmojisMap: map[string]string{"git": ":git:", "gitea": ":gitea:", "codeberg": ":codeberg:", "gitlab": ":gitlab:", "github": ":github:", "gogs": ":gogs:"},
AmbiguousUnicodeDetection: true,
Notification: struct {
MinTimeout time.Duration
TimeoutStep time.Duration

View File

@@ -15,9 +15,8 @@ type CreateUserOption struct {
FullName string `json:"full_name" binding:"MaxSize(100)"`
// required: true
// swagger:strfmt email
Email string `json:"email" binding:"Required;Email;MaxSize(254)"`
// required: true
Password string `json:"password" binding:"Required;MaxSize(255)"`
Email string `json:"email" binding:"Required;Email;MaxSize(254)"`
Password string `json:"password" binding:"MaxSize(255)"`
MustChangePassword *bool `json:"must_change_password"`
SendNotify bool `json:"send_notify"`
Restricted *bool `json:"restricted"`

View File

@@ -3,7 +3,7 @@
package util
import "github.com/yuin/goldmark/util"
import "unsafe"
func isSnakeCaseUpper(c byte) bool {
return 'A' <= c && c <= 'Z'
@@ -83,5 +83,15 @@ func ToSnakeCase(input string) string {
}
}
}
return util.BytesToReadOnlyString(res)
return UnsafeBytesToString(res)
}
// UnsafeBytesToString uses Go's unsafe package to convert a byte slice to a string.
// TODO: replace all "goldmark/util.BytesToReadOnlyString" with this official approach
func UnsafeBytesToString(b []byte) string {
return unsafe.String(unsafe.SliceData(b), len(b))
}
func UnsafeStringToBytes(s string) []byte {
return unsafe.Slice(unsafe.StringData(s), len(s))
}

View File

@@ -3520,7 +3520,7 @@ runs.commit = Commit
runs.scheduled = Scheduled
runs.pushed_by = pushed by
runs.invalid_workflow_helper = Workflow config file is invalid. Please check your config file: %s
runs.no_matching_runner_helper = No matching runner: %s
runs.no_matching_online_runner_helper = No matching online runner with label: %s
runs.actor = Actor
runs.status = Status
runs.actors_no_select = All actors

View File

@@ -25,10 +25,11 @@ func saveUploadChunk(st storage.ObjectStorage, ctx *ArtifactContext,
contentRange := ctx.Req.Header.Get("Content-Range")
start, end, length := int64(0), int64(0), int64(0)
if _, err := fmt.Sscanf(contentRange, "bytes %d-%d/%d", &start, &end, &length); err != nil {
log.Warn("parse content range error: %v, content-range: %s", err, contentRange)
return -1, fmt.Errorf("parse content range error: %v", err)
}
// build chunk store path
storagePath := fmt.Sprintf("tmp%d/%d-%d-%d.chunk", runID, artifact.ID, start, end)
storagePath := fmt.Sprintf("tmp%d/%d-%d-%d-%d.chunk", runID, runID, artifact.ID, start, end)
// use io.TeeReader to avoid reading all body to md5 sum.
// it writes data to hasher after reading end
// if hash is not matched, delete the read-end result
@@ -57,6 +58,7 @@ func saveUploadChunk(st storage.ObjectStorage, ctx *ArtifactContext,
}
type chunkFileItem struct {
RunID int64
ArtifactID int64
Start int64
End int64
@@ -66,9 +68,12 @@ type chunkFileItem struct {
func listChunksByRunID(st storage.ObjectStorage, runID int64) (map[int64][]*chunkFileItem, error) {
storageDir := fmt.Sprintf("tmp%d", runID)
var chunks []*chunkFileItem
if err := st.IterateObjects(storageDir, func(path string, obj storage.Object) error {
item := chunkFileItem{Path: path}
if _, err := fmt.Sscanf(path, filepath.Join(storageDir, "%d-%d-%d.chunk"), &item.ArtifactID, &item.Start, &item.End); err != nil {
if err := st.IterateObjects(storageDir, func(fpath string, obj storage.Object) error {
baseName := filepath.Base(fpath)
// when read chunks from storage, it only contains storage dir and basename,
// no matter the subdirectory setting in storage config
item := chunkFileItem{Path: storageDir + "/" + baseName}
if _, err := fmt.Sscanf(baseName, "%d-%d-%d-%d.chunk", &item.RunID, &item.ArtifactID, &item.Start, &item.End); err != nil {
return fmt.Errorf("parse content range error: %v", err)
}
chunks = append(chunks, &item)

View File

@@ -603,7 +603,10 @@ func ContainerRoutes() *web.Route {
})
r.Get("", container.ReqContainerAccess, container.DetermineSupport)
r.Get("/token", container.Authenticate)
r.Group("/token", func() {
r.Get("", container.Authenticate)
r.Post("", container.AuthenticateNotImplemented)
})
r.Get("/_catalog", container.ReqContainerAccess, container.GetRepositoryList)
r.Group("/{username}", func() {
r.Group("/{image}", func() {

View File

@@ -156,6 +156,17 @@ func Authenticate(ctx *context.Context) {
})
}
// https://distribution.github.io/distribution/spec/auth/oauth/
func AuthenticateNotImplemented(ctx *context.Context) {
// This optional endpoint can be used to authenticate a client.
// It must implement the specification described in:
// https://datatracker.ietf.org/doc/html/rfc6749
// https://distribution.github.io/distribution/spec/auth/oauth/
// Purpose of this stub is to respond with 404 Not Found instead of 405 Method Not Allowed.
ctx.Status(http.StatusNotFound)
}
// https://docs.docker.com/registry/spec/api/#listing-repositories
func GetRepositoryList(ctx *context.Context) {
n := ctx.FormInt("n")

View File

@@ -93,18 +93,28 @@ func CreateUser(ctx *context.APIContext) {
if ctx.Written() {
return
}
if !password.IsComplexEnough(form.Password) {
err := errors.New("PasswordComplexity")
ctx.Error(http.StatusBadRequest, "PasswordComplexity", err)
return
}
pwned, err := password.IsPwned(ctx, form.Password)
if pwned {
if err != nil {
log.Error(err.Error())
if u.LoginType == auth.Plain {
if len(form.Password) < setting.MinPasswordLength {
err := errors.New("PasswordIsRequired")
ctx.Error(http.StatusBadRequest, "PasswordIsRequired", err)
return
}
if !password.IsComplexEnough(form.Password) {
err := errors.New("PasswordComplexity")
ctx.Error(http.StatusBadRequest, "PasswordComplexity", err)
return
}
pwned, err := password.IsPwned(ctx, form.Password)
if pwned {
if err != nil {
log.Error(err.Error())
}
ctx.Error(http.StatusBadRequest, "PasswordPwned", errors.New("PasswordPwned"))
return
}
ctx.Error(http.StatusBadRequest, "PasswordPwned", errors.New("PasswordPwned"))
return
}
overwriteDefault := &user_model.CreateUserOverwriteOptions{

View File

@@ -18,6 +18,7 @@ import (
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/web/repo"
"code.gitea.io/gitea/services/convert"
@@ -77,6 +78,7 @@ func List(ctx *context.Context) {
// Get all runner labels
opts := actions_model.FindRunnerOptions{
RepoID: ctx.Repo.Repository.ID,
IsOnline: util.OptionalBoolTrue,
WithAvailable: true,
}
runners, err := actions_model.FindRunners(ctx, opts)
@@ -114,7 +116,7 @@ func List(ctx *context.Context) {
continue
}
if !allRunnerLabels.Contains(ro) {
workflow.ErrMsg = ctx.Locale.Tr("actions.runs.no_matching_runner_helper", ro)
workflow.ErrMsg = ctx.Locale.Tr("actions.runs.no_matching_online_runner_helper", ro)
break
}
}

View File

@@ -310,8 +310,7 @@ func renderBlame(ctx *context.Context, blameParts []git.BlamePart, commitNames m
lexerName = lexerNameForLine
}
br.EscapeStatus, line = charset.EscapeControlHTML(line, ctx.Locale)
br.Code = gotemplate.HTML(line)
br.EscapeStatus, br.Code = charset.EscapeControlHTML(line, ctx.Locale)
rows = append(rows, br)
escapeStatus = escapeStatus.Or(br.EscapeStatus)
}

View File

@@ -9,6 +9,7 @@ import (
gocontext "context"
"encoding/base64"
"fmt"
"html/template"
"image"
"io"
"net/http"
@@ -317,19 +318,18 @@ func renderReadmeFile(ctx *context.Context, subfolder string, readmeFile *git.Tr
}, rd)
if err != nil {
log.Error("Render failed for %s in %-v: %v Falling back to rendering source", readmeFile.Name(), ctx.Repo.Repository, err)
buf := &bytes.Buffer{}
ctx.Data["EscapeStatus"], _ = charset.EscapeControlStringReader(rd, buf, ctx.Locale)
ctx.Data["FileContent"] = buf.String()
}
} else {
ctx.Data["IsPlainText"] = true
buf := &bytes.Buffer{}
ctx.Data["EscapeStatus"], err = charset.EscapeControlStringReader(rd, buf, ctx.Locale)
if err != nil {
log.Error("Read failed: %v", err)
delete(ctx.Data, "IsMarkup")
}
}
ctx.Data["FileContent"] = buf.String()
if ctx.Data["IsMarkup"] != true {
ctx.Data["IsPlainText"] = true
content, err := io.ReadAll(rd)
if err != nil {
log.Error("Read readme content failed: %v", err)
}
contentEscaped := template.HTMLEscapeString(util.UnsafeBytesToString(content))
ctx.Data["EscapeStatus"], ctx.Data["FileContent"] = charset.EscapeControlHTML(template.HTML(contentEscaped), ctx.Locale)
}
}
@@ -611,7 +611,7 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st
}
}
func markupRender(ctx *context.Context, renderCtx *markup.RenderContext, input io.Reader) (escaped *charset.EscapeStatus, output string, err error) {
func markupRender(ctx *context.Context, renderCtx *markup.RenderContext, input io.Reader) (escaped *charset.EscapeStatus, output template.HTML, err error) {
markupRd, markupWr := io.Pipe()
defer markupWr.Close()
done := make(chan struct{})
@@ -619,7 +619,7 @@ func markupRender(ctx *context.Context, renderCtx *markup.RenderContext, input i
sb := &strings.Builder{}
// We allow NBSP here this is rendered
escaped, _ = charset.EscapeControlReader(markupRd, sb, ctx.Locale, charset.RuneNBSP)
output = sb.String()
output = template.HTML(sb.String())
close(done)
}()
err = markup.Render(renderCtx, input, markupWr)

View File

@@ -285,15 +285,15 @@ type DiffInline struct {
// DiffInlineWithUnicodeEscape makes a DiffInline with hidden unicode characters escaped
func DiffInlineWithUnicodeEscape(s template.HTML, locale translation.Locale) DiffInline {
status, content := charset.EscapeControlHTML(string(s), locale)
return DiffInline{EscapeStatus: status, Content: template.HTML(content)}
status, content := charset.EscapeControlHTML(s, locale)
return DiffInline{EscapeStatus: status, Content: content}
}
// DiffInlineWithHighlightCode makes a DiffInline with code highlight and hidden unicode characters escaped
func DiffInlineWithHighlightCode(fileName, language, code string, locale translation.Locale) DiffInline {
highlighted, _ := highlight.Code(fileName, language, code)
status, content := charset.EscapeControlHTML(highlighted, locale)
return DiffInline{EscapeStatus: status, Content: template.HTML(content)}
return DiffInline{EscapeStatus: status, Content: content}
}
// GetComputedInlineDiffFor computes inline diff for the given line.

View File

@@ -93,10 +93,10 @@ func (hcd *highlightCodeDiff) diffWithHighlight(filename, language, codeA, codeB
highlightCodeA, _ := highlight.Code(filename, language, codeA)
highlightCodeB, _ := highlight.Code(filename, language, codeB)
highlightCodeA = hcd.convertToPlaceholders(highlightCodeA)
highlightCodeB = hcd.convertToPlaceholders(highlightCodeB)
convertedCodeA := hcd.convertToPlaceholders(string(highlightCodeA))
convertedCodeB := hcd.convertToPlaceholders(string(highlightCodeB))
diffs := diffMatchPatch.DiffMain(highlightCodeA, highlightCodeB, true)
diffs := diffMatchPatch.DiffMain(convertedCodeA, convertedCodeB, true)
diffs = diffMatchPatch.DiffCleanupEfficiency(diffs)
for i := range diffs {

View File

@@ -82,10 +82,7 @@ func BuildAllRepositoryFiles(ctx context.Context, ownerID int64) error {
}
for _, pf := range pfs {
if err := packages_model.DeleteAllProperties(ctx, packages_model.PropertyTypeFile, pf.ID); err != nil {
return err
}
if err := packages_model.DeleteFileByID(ctx, pf.ID); err != nil {
if err := packages_service.DeletePackageFile(ctx, pf); err != nil {
return err
}
}
@@ -157,12 +154,11 @@ func buildPackagesIndex(ctx context.Context, ownerID int64, repoVersion *package
pf, err := packages_model.GetFileForVersionByName(ctx, repoVersion.ID, IndexFilename, fmt.Sprintf("%s|%s|%s", branch, repository, architecture))
if err != nil && !errors.Is(err, util.ErrNotExist) {
return err
} else if pf == nil {
return nil
}
if err := packages_model.DeleteAllProperties(ctx, packages_model.PropertyTypeFile, pf.ID); err != nil {
return err
}
return packages_model.DeleteFileByID(ctx, pf.ID)
return packages_service.DeletePackageFile(ctx, pf)
}
// Cache data needed for all repository files

View File

@@ -11,6 +11,7 @@ import (
container_model "code.gitea.io/gitea/models/packages/container"
container_module "code.gitea.io/gitea/modules/packages/container"
"code.gitea.io/gitea/modules/util"
packages_service "code.gitea.io/gitea/services/packages"
digest "github.com/opencontainers/go-digest"
)
@@ -47,10 +48,7 @@ func cleanupExpiredUploadedBlobs(ctx context.Context, olderThan time.Duration) e
}
for _, pf := range pfs {
if err := packages_model.DeleteAllProperties(ctx, packages_model.PropertyTypeFile, pf.ID); err != nil {
return err
}
if err := packages_model.DeleteFileByID(ctx, pf.ID); err != nil {
if err := packages_service.DeletePackageFile(ctx, pf); err != nil {
return err
}
}

View File

@@ -110,10 +110,7 @@ func BuildAllRepositoryFiles(ctx context.Context, ownerID int64) error {
}
for _, pf := range pfs {
if err := packages_model.DeleteAllProperties(ctx, packages_model.PropertyTypeFile, pf.ID); err != nil {
return err
}
if err := packages_model.DeleteFileByID(ctx, pf.ID); err != nil {
if err := packages_service.DeletePackageFile(ctx, pf); err != nil {
return err
}
}
@@ -182,12 +179,11 @@ func buildPackagesIndices(ctx context.Context, ownerID int64, repoVersion *packa
pf, err := packages_model.GetFileForVersionByName(ctx, repoVersion.ID, filename, key)
if err != nil && !errors.Is(err, util.ErrNotExist) {
return err
} else if pf == nil {
continue
}
if err := packages_model.DeleteAllProperties(ctx, packages_model.PropertyTypeFile, pf.ID); err != nil {
return err
}
if err := packages_model.DeleteFileByID(ctx, pf.ID); err != nil {
if err := packages_service.DeletePackageFile(ctx, pf); err != nil {
return err
}
}
@@ -285,12 +281,11 @@ func buildReleaseFiles(ctx context.Context, ownerID int64, repoVersion *packages
pf, err := packages_model.GetFileForVersionByName(ctx, repoVersion.ID, filename, distribution)
if err != nil && !errors.Is(err, util.ErrNotExist) {
return err
} else if pf == nil {
continue
}
if err := packages_model.DeleteAllProperties(ctx, packages_model.PropertyTypeFile, pf.ID); err != nil {
return err
}
if err := packages_model.DeleteFileByID(ctx, pf.ID); err != nil {
if err := packages_service.DeletePackageFile(ctx, pf); err != nil {
return err
}
}

View File

@@ -148,10 +148,7 @@ func BuildRepositoryFiles(ctx context.Context, ownerID int64) error {
return err
}
for _, pf := range pfs {
if err := packages_model.DeleteAllProperties(ctx, packages_model.PropertyTypeFile, pf.ID); err != nil {
return err
}
if err := packages_model.DeleteFileByID(ctx, pf.ID); err != nil {
if err := packages_service.DeletePackageFile(ctx, pf); err != nil {
return err
}
}

View File

@@ -73,8 +73,8 @@
</label>
</div>
<div class="required field">
<label for="repo_name">{{ctx.Locale.Tr "repo.repo_name"}}</label>
<input id="repo_name" name="repo_name" required>
<label for="repo_name_to_delete">{{ctx.Locale.Tr "repo.repo_name"}}</label>
<input id="repo_name_to_delete" name="repo_name" required>
</div>
<div class="text right actions">

View File

@@ -921,8 +921,8 @@
</label>
</div>
<div class="required field">
<label for="repo_name">{{ctx.Locale.Tr "repo.repo_name"}}</label>
<input id="repo_name" name="repo_name" required>
<label for="repo_name_to_delete">{{ctx.Locale.Tr "repo.repo_name"}}</label>
<input id="repo_name_to_delete" name="repo_name" required>
</div>
<div class="text right actions">

View File

@@ -69,9 +69,9 @@
{{end}}
<div class="file-view{{if .IsMarkup}} markup {{.MarkupType}}{{else if .IsPlainText}} plain-text{{else if .IsTextSource}} code-view{{end}}">
{{if .IsMarkup}}
{{if .FileContent}}{{.FileContent | Safe}}{{end}}
{{if .FileContent}}{{.FileContent}}{{end}}
{{else if .IsPlainText}}
<pre>{{if .FileContent}}{{.FileContent | Safe}}{{end}}</pre>
<pre>{{if .FileContent}}{{.FileContent}}{{end}}</pre>
{{else if not .IsTextSource}}
<div class="view-raw">
{{if .IsImageFile}}
@@ -109,7 +109,7 @@
{{if $.EscapeStatus.Escaped}}
<td class="lines-escape">{{if (index $.LineEscapeStatus $idx).Escaped}}<button class="toggle-escape-button btn interact-bg" title="{{if (index $.LineEscapeStatus $idx).HasInvisible}}{{ctx.Locale.Tr "repo.invisible_runes_line"}} {{end}}{{if (index $.LineEscapeStatus $idx).HasAmbiguous}}{{ctx.Locale.Tr "repo.ambiguous_runes_line"}}{{end}}"></button>{{end}}</td>
{{end}}
<td rel="L{{$line}}" class="lines-code chroma"><code class="code-inner">{{$code | Safe}}</code></td>
<td rel="L{{$line}}" class="lines-code chroma"><code class="code-inner">{{$code}}</code></td>
</tr>
{{end}}
</tbody>

View File

@@ -18328,8 +18328,7 @@
"type": "object",
"required": [
"username",
"email",
"password"
"email"
],
"properties": {
"created_at": {

View File

@@ -78,7 +78,7 @@
<input readonly="" value="{{$.TokenToSign}}">
<div class="help">
<p>{{ctx.Locale.Tr "settings.ssh_token_help"}}</p>
<p><code>{{printf "echo -n '%s' | ssh-keygen -Y sign -n gitea -f /path_to_your_privkey" $.TokenToSign}}</code></p>
<p><code>{{printf "echo -n '%s' | ssh-keygen -Y sign -n gitea -f /path_to_PrivateKey_or_RelatedPublicKey" $.TokenToSign}}</code></p>
</div>
<br>
</div>

View File

@@ -2,7 +2,7 @@ import $ from 'jquery';
const {pageData} = window.config;
const initInputCitationValue = async ($citationCopyApa, $citationCopyBibtex) => {
async function initInputCitationValue($citationCopyApa, $citationCopyBibtex) {
const [{Cite, plugins}] = await Promise.all([
import(/* webpackChunkName: "citation-js-core" */'@citation-js/core'),
import(/* webpackChunkName: "citation-js-formats" */'@citation-js/plugin-software-formats'),
@@ -19,9 +19,9 @@ const initInputCitationValue = async ($citationCopyApa, $citationCopyBibtex) =>
const bibtexOutput = citationFormatter.format('bibtex', {lang});
$citationCopyBibtex.attr('data-text', bibtexOutput);
$citationCopyApa.attr('data-text', apaOutput);
};
}
export function initCitationFileCopyContent() {
export async function initCitationFileCopyContent() {
const defaultCitationFormat = 'apa'; // apa or bibtex
if (!pageData.citationFileContent) return;
@@ -39,7 +39,14 @@ export function initCitationFileCopyContent() {
$citationCopyBibtex.toggleClass('primary', isBibtex);
$citationCopyApa.toggleClass('primary', !isBibtex);
};
initInputCitationValue($citationCopyApa, $citationCopyBibtex).then(updateUi);
try {
await initInputCitationValue($citationCopyApa, $citationCopyBibtex);
} catch (e) {
console.error(`initCitationFileCopyContent error: ${e}`, e);
return;
}
updateUi();
$citationCopyApa.on('click', () => {
localStorage.setItem('citation-copy-format', 'apa');

View File

@@ -6,7 +6,7 @@ import {initCompColorPicker} from './comp/ColorPicker.js';
import {showGlobalErrorMessage} from '../bootstrap.js';
import {handleGlobalEnterQuickSubmit} from './comp/QuickSubmit.js';
import {svg} from '../svg.js';
import {hideElem, showElem, toggleElem} from '../utils/dom.js';
import {hideElem, showElem, toggleElem, initSubmitEventPolyfill, submitEventSubmitter} from '../utils/dom.js';
import {htmlEscape} from 'escape-goat';
import {showTemporaryTooltip} from '../modules/tippy.js';
import {confirmModal} from './comp/ConfirmModal.js';
@@ -122,7 +122,8 @@ async function formFetchAction(e) {
const formMethod = formEl.getAttribute('method') || 'get';
const formActionUrl = formEl.getAttribute('action');
const formData = new FormData(formEl);
const [submitterName, submitterValue] = [e.submitter?.getAttribute('name'), e.submitter?.getAttribute('value')];
const formSubmitter = submitEventSubmitter(e);
const [submitterName, submitterValue] = [formSubmitter?.getAttribute('name'), formSubmitter?.getAttribute('value')];
if (submitterName) {
formData.append(submitterName, submitterValue || '');
}
@@ -193,6 +194,7 @@ export function initGlobalCommon() {
$('.tabular.menu .item').tab();
initSubmitEventPolyfill();
document.addEventListener('submit', formFetchAction);
document.addEventListener('click', linkAction);
}

View File

@@ -1,5 +1,5 @@
import $ from 'jquery';
import {isElemHidden, onInputDebounce, toggleElem} from '../utils/dom.js';
import {isElemHidden, onInputDebounce, submitEventSubmitter, toggleElem} from '../utils/dom.js';
import {GET} from '../modules/fetch.js';
const {appSubUrl} = window.config;
@@ -40,7 +40,7 @@ export function initCommonIssueListQuickGoto() {
$form.on('submit', (e) => {
// if there is no goto button, or the form is submitted by non-quick-goto elements, submit the form directly
let doQuickGoto = !isElemHidden($goto);
const submitter = e.originalEvent.submitter;
const submitter = submitEventSubmitter(e.originalEvent);
if (submitter !== $form[0] && submitter !== $input[0] && submitter !== $goto[0]) doQuickGoto = false;
if (!doQuickGoto) return;

View File

@@ -7,6 +7,7 @@ import {validateTextareaNonEmpty} from './comp/ComboMarkdownEditor.js';
import {initViewedCheckboxListenerFor, countAndUpdateViewedFiles, initExpandAndCollapseFilesButton} from './pull-view-file.js';
import {initImageDiff} from './imagediff.js';
import {showErrorToast} from '../modules/toast.js';
import {submitEventSubmitter} from '../utils/dom.js';
const {csrfToken, pageData, i18n} = window.config;
@@ -57,7 +58,7 @@ function initRepoDiffConversationForm() {
const formData = new FormData($form[0]);
// if the form is submitted by a button, append the button's name and value to the form data
const submitter = e.originalEvent?.submitter;
const submitter = submitEventSubmitter(e.originalEvent);
const isSubmittedByButton = (submitter?.nodeName === 'BUTTON') || (submitter?.nodeName === 'INPUT' && submitter.type === 'submit');
if (isSubmittedByButton && submitter.name) {
formData.append(submitter.name, submitter.value);

View File

@@ -106,7 +106,7 @@ function switchTitleToTooltip(target) {
/**
* Creating tooltip tippy instance is expensive, so we only create it when the user hovers over the element
* According to https://www.w3.org/TR/DOM-Level-3-Events/#events-mouseevent-event-order , mouseover event is fired before mouseenter event
* Some old browsers like Pale Moon doesn't support "mouseenter(capture)"
* Some browsers like PaleMoon don't support "addEventListener('mouseenter', capture)"
* The tippy by default uses "mouseenter" event to show, so we use "mouseover" event to switch to tippy
* @param e {Event}
*/

View File

@@ -194,3 +194,24 @@ export function loadElem(el, src) {
el.src = src;
});
}
// some browsers like PaleMoon don't have "SubmitEvent" support, so polyfill it by a tricky method: use the last clicked button as submitter
// it can't use other transparent polyfill patches because PaleMoon also doesn't support "addEventListener(capture)"
const needSubmitEventPolyfill = typeof SubmitEvent === 'undefined';
export function submitEventSubmitter(e) {
return needSubmitEventPolyfill ? (e.target._submitter || null) : e.submitter;
}
function submitEventPolyfillListener(e) {
const form = e.target.closest('form');
if (!form) return;
form._submitter = e.target.closest('button:not([type]), button[type="submit"], input[type="submit"]');
}
export function initSubmitEventPolyfill() {
if (!needSubmitEventPolyfill) return;
console.warn(`This browser doesn't have "SubmitEvent" support, use a tricky method to polyfill`);
document.body.addEventListener('click', submitEventPolyfillListener);
document.body.addEventListener('focus', submitEventPolyfillListener);
}

View File

@@ -1,4 +1,4 @@
import '@webcomponents/custom-elements'; // polyfill for some browsers like Pale Moon
import '@webcomponents/custom-elements'; // polyfill for some browsers like PaleMoon
import './polyfill.js';
import '@github/relative-time-element';