Compare commits

..

18 Commits

Author SHA1 Message Date
Giteabot
1b01d6de82 Fix container push tag overwriting (#35936) (#35954)
Backport #35936 by wxiaoguang

Fix #35853

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-11-14 12:22:23 +08:00
Giteabot
d67cd622d0 Fix corrupted external render content (#35946) (#35950)
Backport #35946 by wxiaoguang

Fix #35944

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-11-14 02:15:36 +00:00
Giteabot
15f3e9d5a5 Don't show unnecessary error message to end users for DeleteBranchAfterMerge (#35937) (#35941)
Backport #35937 by wxiaoguang

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-11-12 23:31:05 +00:00
Giteabot
01fa8b2b7e Limit read bytes instead of ReadAll (#35928) (#35934)
Backport #35928 by wxiaoguang

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-11-13 02:26:27 +08:00
Giteabot
1d9ae7ac23 Load jQuery as early as possible to support custom scripts (#35926) (#35929)
Backport #35926 by wxiaoguang

Fix #35923

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-11-12 07:24:21 +08:00
Giteabot
01873a99c1 Allow to display embed images/pdfs when SERVE_DIRECT was enabled on MinIO storage (#35882) (#35917)
Backport #35882 by lifegpc

Co-authored-by: lifegpc <g1710431395@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-11-11 02:24:06 +00:00
Giteabot
ce70863793 Use correct form field for allowed force push users in branch protection API (#35894) (#35908)
Backport #35894 by zorrobiwan

Signed-off-by: Alberty Pascal <github@albertyorban.be>
Co-authored-by: Alberty Pascal <github@albertyorban.be>
2025-11-11 01:39:35 +00:00
Giteabot
327f2207dc Make OAuth2 issuer configurable (#35915) (#35916)
Backport #35915 by wxiaoguang
2025-11-10 16:12:25 +00:00
Giteabot
db876d8f17 Fix #35763: Add proper page title for project pages (#35773) (#35909)
Backport #35773 by @mithileshgupta12

Co-authored-by: Mithilesh Gupta <mithileshgupta059@gmail.com>
Co-authored-by: Mithilesh Gupta <guptamithilesh@protonmail.com>
2025-11-10 09:42:14 +02:00
Giteabot
2b71bf283b Display source code downloads last for release attachments (#35897) (#35903)
Backport #35897 by lutinglt

Typically, you want to download the binaries, not the source code.

Co-authored-by: 鲁汀 <131967983+lutinglt@users.noreply.github.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2025-11-09 14:51:36 +08:00
Giteabot
1ca4fef611 Fix team member access check (#35899) (#35905)
Backport #35899 by wxiaoguang

Fix #35499

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-11-09 03:44:53 +00:00
Giteabot
70ee6b9029 Fix conda null depend issue (#35900) (#35902)
Backport #35900 by Luohaothu

This fixes issue #35895

Co-authored-by: Luohao Wang <luohaothu@live.com>
2025-11-08 16:37:00 +00:00
wxiaoguang
e5b404ec53 Fix avatar upload error handling (#35887) (#35890)
Backport #35887
2025-11-07 11:25:34 +08:00
Giteabot
5842cd23a6 Contribution heatmap improvements (#35876) (#35880)
Backport #35876 by @silverwind

1. Set a fixed height on the element, preventing the content after the
element from shifting on page load. This uses CSS [container query
length
units](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_containment/Container_queries#container_query_length_units)
as I saw no other way because of the non-linear scaling of the element.
2. Move the "total-contributions" text into the existing vue slot,
eliminating the need for absolute positioning.

Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-11-06 08:51:49 +00:00
Giteabot
289bd9694b Remove padding override on .ui .sha.label (#35864) (#35873)
Backport #35864 by @silverwind

Since upgrading to v1.25, I noticed the SHA labels have slightly
different padding than before. I can't pinpoint exactly which change it
was. Fix it by removing the padding override on `.ui .sha.label` and
make the one on`.ui.label` (`2px 6px`) take effect which matches 1.24
rendering.

Before:

<img width="135" height="172" alt="image"
src="https://github.com/user-attachments/assets/2781a854-be08-4a11-bde0-d3699b2b7454"
/>

After:

<img width="139" height="162" alt="image"
src="https://github.com/user-attachments/assets/5c864fa3-c1f9-4452-ae58-5411dd445865"
/>

Co-authored-by: silverwind <me@silverwind.io>
2025-11-06 06:06:36 +00:00
Giteabot
154d7521a5 fix(api/repo/contents): set the dates to now when not specified by the caller (#35861) (#35874)
Backport #35861 by @divyun

Since 1.25.0, the dates get set to `2001-01-01T00:00:00Z`, when not
specified by the caller.

Fixes #35860

Co-authored-by: Divyun Raje Vaid <mail@divyun.com>
2025-11-06 13:08:06 +08:00
Giteabot
24189dcced Fix pull description code label background (#35865) (#35870)
Backport #35865 by @silverwind

Fix visual regression from https://github.com/go-gitea/gitea/pull/35567:

Before:

<img width="612" height="33" alt="image"
src="https://github.com/user-attachments/assets/aee4017c-b8b9-4ac2-9809-9d3eb3fda56c"
/>

After:

<img width="613" height="32" alt="image"
src="https://github.com/user-attachments/assets/ee6624da-b417-4e3b-8773-88c77c2cd672"
/>

Co-authored-by: silverwind <me@silverwind.io>
2025-11-05 17:29:06 +00:00
wxiaoguang
f84bf259ad Fix gogit ListEntriesRecursiveWithSize (#35862)
It needs to use full git path. Fix #35852.
2025-11-05 19:19:47 +02:00
62 changed files with 417 additions and 310 deletions

View File

@@ -567,6 +567,11 @@ ENABLED = true
;; Alternative location to specify OAuth2 authentication secret. You cannot specify both this and JWT_SECRET, and must pick one ;; Alternative location to specify OAuth2 authentication secret. You cannot specify both this and JWT_SECRET, and must pick one
;JWT_SECRET_URI = file:/etc/gitea/oauth2_jwt_secret ;JWT_SECRET_URI = file:/etc/gitea/oauth2_jwt_secret
;; ;;
;; The "issuer" claim identifies the principal that issued the JWT.
;; Gitea 1.25 makes it default to "ROOT_URL without the last slash" to follow the standard.
;; If you have old logins from before 1.25, you may want to set it to the old (non-standard) value "ROOT_URL with the last slash".
;JWT_CLAIM_ISSUER =
;;
;; Lifetime of an OAuth2 access token in seconds ;; Lifetime of an OAuth2 access token in seconds
;ACCESS_TOKEN_EXPIRATION_TIME = 3600 ;ACCESS_TOKEN_EXPIRATION_TIME = 3600
;; ;;

14
go.mod
View File

@@ -116,13 +116,13 @@ require (
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
github.com/yuin/goldmark-meta v1.1.0 github.com/yuin/goldmark-meta v1.1.0
gitlab.com/gitlab-org/api/client-go v0.142.4 gitlab.com/gitlab-org/api/client-go v0.142.4
golang.org/x/crypto v0.42.0 golang.org/x/crypto v0.44.0
golang.org/x/image v0.30.0 golang.org/x/image v0.30.0
golang.org/x/net v0.44.0 golang.org/x/net v0.46.0
golang.org/x/oauth2 v0.30.0 golang.org/x/oauth2 v0.30.0
golang.org/x/sync v0.17.0 golang.org/x/sync v0.18.0
golang.org/x/sys v0.37.0 golang.org/x/sys v0.38.0
golang.org/x/text v0.30.0 golang.org/x/text v0.31.0
google.golang.org/grpc v1.75.0 google.golang.org/grpc v1.75.0
google.golang.org/protobuf v1.36.8 google.golang.org/protobuf v1.36.8
gopkg.in/ini.v1 v1.67.0 gopkg.in/ini.v1 v1.67.0
@@ -279,9 +279,9 @@ require (
go.uber.org/zap/exp v0.3.0 // indirect go.uber.org/zap/exp v0.3.0 // indirect
go4.org v0.0.0-20230225012048-214862532bf5 // indirect go4.org v0.0.0-20230225012048-214862532bf5 // indirect
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
golang.org/x/mod v0.28.0 // indirect golang.org/x/mod v0.29.0 // indirect
golang.org/x/time v0.12.0 // indirect golang.org/x/time v0.12.0 // indirect
golang.org/x/tools v0.37.0 // indirect golang.org/x/tools v0.38.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect

32
go.sum
View File

@@ -840,8 +840,8 @@ golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDf
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 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-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -878,8 +878,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U= golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI= golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -908,8 +908,8 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -932,8 +932,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -975,8 +975,8 @@ golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 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= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -987,8 +987,8 @@ golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ= golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 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.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -1002,8 +1002,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 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-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
@@ -1039,8 +1039,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE= golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@@ -466,11 +466,13 @@ func updateApprovalWhitelist(ctx context.Context, repo *repo_model.Repository, c
return currentWhitelist, nil return currentWhitelist, nil
} }
prUserIDs, err := access_model.GetUserIDsWithUnitAccess(ctx, repo, perm.AccessModeRead, unit.TypePullRequests)
if err != nil {
return nil, err
}
whitelist = make([]int64, 0, len(newWhitelist)) whitelist = make([]int64, 0, len(newWhitelist))
for _, userID := range newWhitelist { for _, userID := range newWhitelist {
if reader, err := access_model.IsRepoReader(ctx, repo, userID); err != nil { if !prUserIDs.Contains(userID) {
return nil, err
} else if !reader {
continue continue
} }
whitelist = append(whitelist, userID) whitelist = append(whitelist, userID)

View File

@@ -53,24 +53,45 @@ func RemoveTeamRepo(ctx context.Context, teamID, repoID int64) error {
// GetTeamsWithAccessToAnyRepoUnit returns all teams in an organization that have given access level to the repository special unit. // GetTeamsWithAccessToAnyRepoUnit returns all teams in an organization that have given access level to the repository special unit.
// This function is only used for finding some teams that can be used as branch protection allowlist or reviewers, it isn't really used for access control. // This function is only used for finding some teams that can be used as branch protection allowlist or reviewers, it isn't really used for access control.
// FIXME: TEAM-UNIT-PERMISSION this logic is not complete, search the fixme keyword to see more details // FIXME: TEAM-UNIT-PERMISSION this logic is not complete, search the fixme keyword to see more details
func GetTeamsWithAccessToAnyRepoUnit(ctx context.Context, orgID, repoID int64, mode perm.AccessMode, unitType unit.Type, unitTypesMore ...unit.Type) ([]*Team, error) { func GetTeamsWithAccessToAnyRepoUnit(ctx context.Context, orgID, repoID int64, mode perm.AccessMode, unitType unit.Type, unitTypesMore ...unit.Type) (teams []*Team, err error) {
teams := make([]*Team, 0, 5) teamIDs, err := getTeamIDsWithAccessToAnyRepoUnit(ctx, orgID, repoID, mode, unitType, unitTypesMore...)
if err != nil {
return nil, err
}
if len(teamIDs) == 0 {
return teams, nil
}
err = db.GetEngine(ctx).Where(builder.In("id", teamIDs)).OrderBy("team.name").Find(&teams)
return teams, err
}
func getTeamIDsWithAccessToAnyRepoUnit(ctx context.Context, orgID, repoID int64, mode perm.AccessMode, unitType unit.Type, unitTypesMore ...unit.Type) (teamIDs []int64, err error) {
sub := builder.Select("team_id").From("team_unit"). sub := builder.Select("team_id").From("team_unit").
Where(builder.Expr("team_unit.team_id = team.id")). Where(builder.Expr("team_unit.team_id = team.id")).
And(builder.In("team_unit.type", append([]unit.Type{unitType}, unitTypesMore...))). And(builder.In("team_unit.type", append([]unit.Type{unitType}, unitTypesMore...))).
And(builder.Expr("team_unit.access_mode >= ?", mode)) And(builder.Expr("team_unit.access_mode >= ?", mode))
err := db.GetEngine(ctx). err = db.GetEngine(ctx).
Select("team.id").
Table("team").
Join("INNER", "team_repo", "team_repo.team_id = team.id"). Join("INNER", "team_repo", "team_repo.team_id = team.id").
And("team_repo.org_id = ?", orgID). And("team_repo.org_id = ? AND team_repo.repo_id = ?", orgID, repoID).
And("team_repo.repo_id = ?", repoID).
And(builder.Or( And(builder.Or(
builder.Expr("team.authorize >= ?", mode), builder.Expr("team.authorize >= ?", mode),
builder.In("team.id", sub), builder.In("team.id", sub),
)). )).
OrderBy("name"). Find(&teamIDs)
Find(&teams) return teamIDs, err
}
return teams, err
func GetTeamUserIDsWithAccessToAnyRepoUnit(ctx context.Context, orgID, repoID int64, mode perm.AccessMode, unitType unit.Type, unitTypesMore ...unit.Type) (userIDs []int64, err error) {
teamIDs, err := getTeamIDsWithAccessToAnyRepoUnit(ctx, orgID, repoID, mode, unitType, unitTypesMore...)
if err != nil {
return nil, err
}
if len(teamIDs) == 0 {
return userIDs, nil
}
err = db.GetEngine(ctx).Table("team_user").Select("uid").Where(builder.In("team_id", teamIDs)).Find(&userIDs)
return userIDs, err
} }

View File

@@ -14,6 +14,7 @@ import (
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
@@ -458,54 +459,44 @@ func HasAnyUnitAccess(ctx context.Context, userID int64, repo *repo_model.Reposi
return perm.HasAnyUnitAccess(), nil return perm.HasAnyUnitAccess(), nil
} }
// getUsersWithAccessMode returns users that have at least given access mode to the repository. func GetUsersWithUnitAccess(ctx context.Context, repo *repo_model.Repository, mode perm_model.AccessMode, unitType unit.Type) (users []*user_model.User, err error) {
func getUsersWithAccessMode(ctx context.Context, repo *repo_model.Repository, mode perm_model.AccessMode) (_ []*user_model.User, err error) { userIDs, err := GetUserIDsWithUnitAccess(ctx, repo, mode, unitType)
if err = repo.LoadOwner(ctx); err != nil { if err != nil {
return nil, err return nil, err
} }
if len(userIDs) == 0 {
e := db.GetEngine(ctx) return users, nil
accesses := make([]*Access, 0, 10) }
if err = e.Where("repo_id = ? AND mode >= ?", repo.ID, mode).Find(&accesses); err != nil { if err = db.GetEngine(ctx).In("id", userIDs.Values()).OrderBy("`name`").Find(&users); err != nil {
return nil, err return nil, err
} }
// Leave a seat for owner itself to append later, but if owner is an organization
// and just waste 1 unit is cheaper than re-allocate memory once.
users := make([]*user_model.User, 0, len(accesses)+1)
if len(accesses) > 0 {
userIDs := make([]int64, len(accesses))
for i := 0; i < len(accesses); i++ {
userIDs[i] = accesses[i].UserID
}
if err = e.In("id", userIDs).Find(&users); err != nil {
return nil, err
}
}
if !repo.Owner.IsOrganization() {
users = append(users, repo.Owner)
}
return users, nil return users, nil
} }
// GetRepoReaders returns all users that have explicit read access or higher to the repository. func GetUserIDsWithUnitAccess(ctx context.Context, repo *repo_model.Repository, mode perm_model.AccessMode, unitType unit.Type) (container.Set[int64], error) {
func GetRepoReaders(ctx context.Context, repo *repo_model.Repository) (_ []*user_model.User, err error) { userIDs := container.Set[int64]{}
return getUsersWithAccessMode(ctx, repo, perm_model.AccessModeRead) e := db.GetEngine(ctx)
} accesses := make([]*Access, 0, 10)
if err := e.Where("repo_id = ? AND mode >= ?", repo.ID, mode).Find(&accesses); err != nil {
// GetRepoWriters returns all users that have write access to the repository. return nil, err
func GetRepoWriters(ctx context.Context, repo *repo_model.Repository) (_ []*user_model.User, err error) {
return getUsersWithAccessMode(ctx, repo, perm_model.AccessModeWrite)
}
// IsRepoReader returns true if user has explicit read access or higher to the repository.
func IsRepoReader(ctx context.Context, repo *repo_model.Repository, userID int64) (bool, error) {
if repo.OwnerID == userID {
return true, nil
} }
return db.GetEngine(ctx).Where("repo_id = ? AND user_id = ? AND mode >= ?", repo.ID, userID, perm_model.AccessModeRead).Get(&Access{}) for _, a := range accesses {
userIDs.Add(a.UserID)
}
if err := repo.LoadOwner(ctx); err != nil {
return nil, err
}
if !repo.Owner.IsOrganization() {
userIDs.Add(repo.Owner.ID)
} else {
teamUserIDs, err := organization.GetTeamUserIDsWithAccessToAnyRepoUnit(ctx, repo.OwnerID, repo.ID, mode, unitType)
if err != nil {
return nil, err
}
userIDs.AddMultiple(teamUserIDs...)
}
return userIDs, nil
} }
// CheckRepoUnitUser check whether user could visit the unit of this repository // CheckRepoUnitUser check whether user could visit the unit of this repository

View File

@@ -169,9 +169,9 @@ func TestGetUserRepoPermission(t *testing.T) {
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
team := &organization.Team{OrgID: org.ID, LowerName: "test_team"} team := &organization.Team{OrgID: org.ID, LowerName: "test_team"}
require.NoError(t, db.Insert(ctx, team)) require.NoError(t, db.Insert(ctx, team))
require.NoError(t, db.Insert(ctx, &organization.TeamUser{OrgID: org.ID, TeamID: team.ID, UID: user.ID}))
t.Run("DoerInTeamWithNoRepo", func(t *testing.T) { t.Run("DoerInTeamWithNoRepo", func(t *testing.T) {
require.NoError(t, db.Insert(ctx, &organization.TeamUser{OrgID: org.ID, TeamID: team.ID, UID: user.ID}))
perm, err := GetUserRepoPermission(ctx, repo32, user) perm, err := GetUserRepoPermission(ctx, repo32, user)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, perm_model.AccessModeRead, perm.AccessMode) assert.Equal(t, perm_model.AccessModeRead, perm.AccessMode)
@@ -219,6 +219,15 @@ func TestGetUserRepoPermission(t *testing.T) {
assert.Equal(t, perm_model.AccessModeNone, perm.AccessMode) assert.Equal(t, perm_model.AccessModeNone, perm.AccessMode)
assert.Equal(t, perm_model.AccessModeNone, perm.unitsMode[unit.TypeCode]) assert.Equal(t, perm_model.AccessModeNone, perm.unitsMode[unit.TypeCode])
assert.Equal(t, perm_model.AccessModeRead, perm.unitsMode[unit.TypeIssues]) assert.Equal(t, perm_model.AccessModeRead, perm.unitsMode[unit.TypeIssues])
users, err := GetUsersWithUnitAccess(ctx, repo3, perm_model.AccessModeRead, unit.TypeIssues)
require.NoError(t, err)
require.Len(t, users, 1)
assert.Equal(t, user.ID, users[0].ID)
users, err = GetUsersWithUnitAccess(ctx, repo3, perm_model.AccessModeWrite, unit.TypeIssues)
require.NoError(t, err)
require.Empty(t, users)
}) })
require.NoError(t, db.Insert(ctx, repo_model.Collaboration{RepoID: repo3.ID, UserID: user.ID, Mode: perm_model.AccessModeWrite})) require.NoError(t, db.Insert(ctx, repo_model.Collaboration{RepoID: repo3.ID, UserID: user.ID, Mode: perm_model.AccessModeWrite}))
@@ -229,5 +238,10 @@ func TestGetUserRepoPermission(t *testing.T) {
assert.Equal(t, perm_model.AccessModeWrite, perm.AccessMode) assert.Equal(t, perm_model.AccessModeWrite, perm.AccessMode)
assert.Equal(t, perm_model.AccessModeWrite, perm.unitsMode[unit.TypeCode]) assert.Equal(t, perm_model.AccessModeWrite, perm.unitsMode[unit.TypeCode])
assert.Equal(t, perm_model.AccessModeWrite, perm.unitsMode[unit.TypeIssues]) assert.Equal(t, perm_model.AccessModeWrite, perm.unitsMode[unit.TypeIssues])
users, err := GetUsersWithUnitAccess(ctx, repo3, perm_model.AccessModeWrite, unit.TypeIssues)
require.NoError(t, err)
require.Len(t, users, 1)
assert.Equal(t, user.ID, users[0].ID)
}) })
} }

View File

@@ -5,7 +5,6 @@ package actions
import ( import (
"bytes" "bytes"
"io"
"slices" "slices"
"strings" "strings"
@@ -13,6 +12,7 @@ import (
"code.gitea.io/gitea/modules/glob" "code.gitea.io/gitea/modules/glob"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
webhook_module "code.gitea.io/gitea/modules/webhook" webhook_module "code.gitea.io/gitea/modules/webhook"
"github.com/nektos/act/pkg/jobparser" "github.com/nektos/act/pkg/jobparser"
@@ -77,7 +77,7 @@ func GetContentFromEntry(entry *git.TreeEntry) ([]byte, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
content, err := io.ReadAll(f) content, err := util.ReadWithLimit(f, 1024*1024)
_ = f.Close() _ = f.Close()
if err != nil { if err != nil {
return nil, err return nil, err

View File

@@ -19,12 +19,17 @@ type TreeEntry struct {
gogitTreeEntry *object.TreeEntry gogitTreeEntry *object.TreeEntry
ptree *Tree ptree *Tree
fullName string
size int64 size int64
sized bool sized bool
} }
// Name returns the name of the entry // Name returns the name of the entry
func (te *TreeEntry) Name() string { func (te *TreeEntry) Name() string {
if te.fullName != "" {
return te.fullName
}
return te.gogitTreeEntry.Name return te.gogitTreeEntry.Name
} }

View File

@@ -69,7 +69,7 @@ func (t *Tree) ListEntriesRecursiveWithSize() (Entries, error) {
seen := map[plumbing.Hash]bool{} seen := map[plumbing.Hash]bool{}
walker := object.NewTreeWalker(t.gogitTree, true, seen) walker := object.NewTreeWalker(t.gogitTree, true, seen)
for { for {
_, entry, err := walker.Next() fullName, entry, err := walker.Next()
if err == io.EOF { if err == io.EOF {
break break
} }
@@ -84,6 +84,7 @@ func (t *Tree) ListEntriesRecursiveWithSize() (Entries, error) {
ID: ParseGogitHash(entry.Hash), ID: ParseGogitHash(entry.Hash),
gogitTreeEntry: &entry, gogitTreeEntry: &entry,
ptree: t, ptree: t,
fullName: fullName,
} }
entries = append(entries, convertedEntry) entries = append(entries, convertedEntry)
} }

View File

@@ -5,7 +5,6 @@ package template
import ( import (
"fmt" "fmt"
"io"
"path" "path"
"strconv" "strconv"
@@ -76,7 +75,7 @@ func unmarshalFromEntry(entry *git.TreeEntry, filename string) (*api.IssueTempla
} }
defer r.Close() defer r.Close()
content, err := io.ReadAll(r) content, err := util.ReadWithLimit(r, 1024*1024)
if err != nil { if err != nil {
return nil, fmt.Errorf("read all: %w", err) return nil, fmt.Errorf("read all: %w", err)
} }

View File

@@ -216,7 +216,7 @@ func ParseNuspecMetaData(archive *zip.Reader, r io.Reader) (*Package, error) {
if p.Metadata.Readme != "" { if p.Metadata.Readme != "" {
f, err := archive.Open(p.Metadata.Readme) f, err := archive.Open(p.Metadata.Readme)
if err == nil { if err == nil {
buf, _ := io.ReadAll(f) buf, _ := util.ReadWithLimit(f, 1024*1024)
m.Readme = string(buf) m.Readme = string(buf)
_ = f.Close() _ = f.Close()
} }

View File

@@ -89,7 +89,7 @@ func ParsePackage(r io.Reader) (*Package, error) {
return nil, err return nil, err
} }
} else if strings.EqualFold(hd.Name, "readme.md") { } else if strings.EqualFold(hd.Name, "readme.md") {
data, err := io.ReadAll(tr) data, err := util.ReadWithLimit(tr, 1024*1024)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -96,6 +96,7 @@ var OAuth2 = struct {
InvalidateRefreshTokens bool InvalidateRefreshTokens bool
JWTSigningAlgorithm string `ini:"JWT_SIGNING_ALGORITHM"` JWTSigningAlgorithm string `ini:"JWT_SIGNING_ALGORITHM"`
JWTSigningPrivateKeyFile string `ini:"JWT_SIGNING_PRIVATE_KEY_FILE"` JWTSigningPrivateKeyFile string `ini:"JWT_SIGNING_PRIVATE_KEY_FILE"`
JWTClaimIssuer string `ini:"JWT_CLAIM_ISSUER"`
MaxTokenLength int MaxTokenLength int
DefaultApplications []string DefaultApplications []string
}{ }{

View File

@@ -250,6 +250,7 @@ func (a *AzureBlobStorage) Delete(path string) error {
func (a *AzureBlobStorage) URL(path, name, _ string, reqParams url.Values) (*url.URL, error) { func (a *AzureBlobStorage) URL(path, name, _ string, reqParams url.Values) (*url.URL, error) {
blobClient := a.getBlobClient(path) blobClient := a.getBlobClient(path)
// TODO: OBJECT-STORAGE-CONTENT-TYPE: "browser inline rendering images/PDF" needs proper Content-Type header from storage
startTime := time.Now() startTime := time.Now()
u, err := blobClient.GetSASURL(sas.BlobPermissions{ u, err := blobClient.GetSASURL(sas.BlobPermissions{
Read: true, Read: true,

View File

@@ -279,20 +279,44 @@ func (m *MinioStorage) Delete(path string) error {
} }
// URL gets the redirect URL to a file. The presigned link is valid for 5 minutes. // URL gets the redirect URL to a file. The presigned link is valid for 5 minutes.
func (m *MinioStorage) URL(path, name, method string, serveDirectReqParams url.Values) (*url.URL, error) { func (m *MinioStorage) URL(storePath, name, method string, serveDirectReqParams url.Values) (*url.URL, error) {
// copy serveDirectReqParams // copy serveDirectReqParams
reqParams, err := url.ParseQuery(serveDirectReqParams.Encode()) reqParams, err := url.ParseQuery(serveDirectReqParams.Encode())
if err != nil { if err != nil {
return nil, err return nil, err
} }
// TODO it may be good to embed images with 'inline' like ServeData does, but we don't want to have to read the file, do we?
reqParams.Set("response-content-disposition", "attachment; filename=\""+quoteEscaper.Replace(name)+"\"") // Here we might not know the real filename, and it's quite inefficient to detect the mine type by pre-fetching the object head.
// So we just do a quick detection by extension name, at least if works for the "View Raw File" for an LFS file on the Web UI.
// Detect content type by extension name, only support the well-known safe types for inline rendering.
// TODO: OBJECT-STORAGE-CONTENT-TYPE: need a complete solution and refactor for Azure in the future
ext := path.Ext(name)
inlineExtMimeTypes := map[string]string{
".png": "image/png",
".jpg": "image/jpeg",
".jpeg": "image/jpeg",
".gif": "image/gif",
".webp": "image/webp",
".avif": "image/avif",
// ATTENTION! Don't support unsafe types like HTML/SVG due to security concerns: they can contain JS code, and maybe they need proper Content-Security-Policy
// HINT: PDF-RENDER-SANDBOX: PDF won't render in sandboxed context, it seems fine to render it inline
".pdf": "application/pdf",
// TODO: refactor with "modules/public/mime_types.go", for example: "DetectWellKnownSafeInlineMimeType"
}
if mimeType, ok := inlineExtMimeTypes[ext]; ok {
reqParams.Set("response-content-type", mimeType)
reqParams.Set("response-content-disposition", "inline")
} else {
reqParams.Set("response-content-disposition", fmt.Sprintf(`attachment; filename="%s"`, quoteEscaper.Replace(name)))
}
expires := 5 * time.Minute expires := 5 * time.Minute
if method == http.MethodHead { if method == http.MethodHead {
u, err := m.client.PresignedHeadObject(m.ctx, m.bucket, m.buildMinioPath(path), expires, reqParams) u, err := m.client.PresignedHeadObject(m.ctx, m.bucket, m.buildMinioPath(storePath), expires, reqParams)
return u, convertMinioErr(err) return u, convertMinioErr(err)
} }
u, err := m.client.PresignedGetObject(m.ctx, m.bucket, m.buildMinioPath(path), expires, reqParams) u, err := m.client.PresignedGetObject(m.ctx, m.bucket, m.buildMinioPath(storePath), expires, reqParams)
return u, convertMinioErr(err) return u, convertMinioErr(err)
} }

View File

@@ -29,7 +29,7 @@ func ReadAtMost(r io.Reader, buf []byte) (n int, err error) {
// ReadWithLimit reads at most "limit" bytes from r into buf. // ReadWithLimit reads at most "limit" bytes from r into buf.
// If EOF or ErrUnexpectedEOF occurs while reading, err will be nil. // If EOF or ErrUnexpectedEOF occurs while reading, err will be nil.
func ReadWithLimit(r io.Reader, n int) (buf []byte, err error) { func ReadWithLimit(r io.Reader, n int) (buf []byte, err error) {
return readWithLimit(r, 1024, n) return readWithLimit(r, 4*1024, n)
} }
func readWithLimit(r io.Reader, batch, limit int) ([]byte, error) { func readWithLimit(r io.Reader, batch, limit int) ([]byte, error) {

View File

@@ -148,7 +148,7 @@ func EnumeratePackages(ctx *context.Context) {
Timestamp: fileMetadata.Timestamp, Timestamp: fileMetadata.Timestamp,
Build: fileMetadata.Build, Build: fileMetadata.Build,
BuildNumber: fileMetadata.BuildNumber, BuildNumber: fileMetadata.BuildNumber,
Dependencies: fileMetadata.Dependencies, Dependencies: util.SliceNilAsEmpty(fileMetadata.Dependencies),
License: versionMetadata.License, License: versionMetadata.License,
LicenseFamily: versionMetadata.LicenseFamily, LicenseFamily: versionMetadata.LicenseFamily,
HashMD5: pfd.Blob.HashMD5, HashMD5: pfd.Blob.HashMD5,

View File

@@ -10,7 +10,6 @@ import (
"io" "io"
"os" "os"
"strings" "strings"
"time"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
packages_model "code.gitea.io/gitea/models/packages" packages_model "code.gitea.io/gitea/models/packages"
@@ -260,6 +259,13 @@ func createPackageAndVersion(ctx context.Context, mci *manifestCreationInfo, met
return nil, err return nil, err
} }
// "docker buildx imagetools create" multi-arch operations:
// {"type":"oci","is_tagged":false,"platform":"unknown/unknown"}
// {"type":"oci","is_tagged":false,"platform":"linux/amd64","layer_creation":["ADD file:9233f6f2237d79659a9521f7e390df217cec49f1a8aa3a12147bbca1956acdb9 in /","CMD [\"/bin/sh\"]"]}
// {"type":"oci","is_tagged":false,"platform":"unknown/unknown"}
// {"type":"oci","is_tagged":false,"platform":"linux/arm64","layer_creation":["ADD file:df53811312284306901fdaaff0a357a4bf40d631e662fe9ce6d342442e494b6c in /","CMD [\"/bin/sh\"]"]}
// {"type":"oci","is_tagged":true,"manifests":[{"platform":"linux/amd64","digest":"sha256:72bb73e706c0dec424d00a1febb21deaf1175a70ead009ad8b159729cfcf5769","size":2819478},{"platform":"linux/arm64","digest":"sha256:9e1426dd084a3221663b85ca1ee99d140c50b153917a5c5604c1f9b78229fd24","size":2716499},{"platform":"unknown/unknown","digest":"sha256:b93f03d0ae11b988243e1b2cd8d29accf5b9670547b7bd8c7d96abecc7283e6e","size":1798},{"platform":"unknown/unknown","digest":"sha256:f034b182ba66366c63a5d195c6dfcd3333c027409c0ac98e55ade36aaa3b2963","size":1798}]}
_pv := &packages_model.PackageVersion{ _pv := &packages_model.PackageVersion{
PackageID: p.ID, PackageID: p.ID,
CreatorID: mci.Creator.ID, CreatorID: mci.Creator.ID,
@@ -273,27 +279,18 @@ func createPackageAndVersion(ctx context.Context, mci *manifestCreationInfo, met
log.Error("Error inserting package: %v", err) log.Error("Error inserting package: %v", err)
return nil, err return nil, err
} }
if container_module.IsMediaTypeImageIndex(mci.MediaType) {
if pv.CreatedUnix.AsTime().Before(time.Now().Add(-24 * time.Hour)) {
if err = packages_service.DeletePackageVersionAndReferences(ctx, pv); err != nil { if err = packages_service.DeletePackageVersionAndReferences(ctx, pv); err != nil {
return nil, err return nil, err
} }
// keep download count on overwriting // keep download count on overwriting
_pv.DownloadCount = pv.DownloadCount _pv.DownloadCount = pv.DownloadCount
if pv, err = packages_model.GetOrInsertVersion(ctx, _pv); err != nil { pv, err = packages_model.GetOrInsertVersion(ctx, _pv)
if err != nil {
if !errors.Is(err, packages_model.ErrDuplicatePackageVersion) { if !errors.Is(err, packages_model.ErrDuplicatePackageVersion) {
log.Error("Error inserting package: %v", err) log.Error("Error inserting package: %v", err)
return nil, err return nil, err
} }
} }
} else {
err = packages_model.UpdateVersion(ctx, &packages_model.PackageVersion{ID: pv.ID, MetadataJSON: _pv.MetadataJSON})
if err != nil {
return nil, err
}
}
}
} }
if err := packages_service.CheckCountQuotaExceeded(ctx, mci.Creator, mci.Owner); err != nil { if err := packages_service.CheckCountQuotaExceeded(ctx, mci.Creator, mci.Owner); err != nil {

View File

@@ -897,7 +897,7 @@ func EditBranchProtection(ctx *context.APIContext) {
} else { } else {
whitelistUsers = protectBranch.WhitelistUserIDs whitelistUsers = protectBranch.WhitelistUserIDs
} }
if form.ForcePushAllowlistDeployKeys != nil { if form.ForcePushAllowlistUsernames != nil {
forcePushAllowlistUsers, err = user_model.GetUserIDsByNames(ctx, form.ForcePushAllowlistUsernames, false) forcePushAllowlistUsers, err = user_model.GetUserIDsByNames(ctx, form.ForcePushAllowlistUsernames, false)
if err != nil { if err != nil {
if user_model.IsErrUserNotExist(err) { if user_model.IsErrUserNotExist(err) {

View File

@@ -369,11 +369,11 @@ func ReqChangeRepoFileOptionsAndCheck(ctx *context.APIContext) {
}, },
Signoff: commonOpts.Signoff, Signoff: commonOpts.Signoff,
} }
if commonOpts.Dates.Author.IsZero() { if changeFileOpts.Dates.Author.IsZero() {
commonOpts.Dates.Author = time.Now() changeFileOpts.Dates.Author = time.Now()
} }
if commonOpts.Dates.Committer.IsZero() { if changeFileOpts.Dates.Committer.IsZero() {
commonOpts.Dates.Committer = time.Now() changeFileOpts.Dates.Committer = time.Now()
} }
ctx.Data["__APIChangeRepoFilesOptions"] = changeFileOpts ctx.Data["__APIChangeRepoFilesOptions"] = changeFileOpts
} }

View File

@@ -436,6 +436,7 @@ func ViewProject(ctx *context.Context) {
ctx.Data["Project"] = project ctx.Data["Project"] = project
ctx.Data["IssuesMap"] = issuesMap ctx.Data["IssuesMap"] = issuesMap
ctx.Data["Columns"] = columns ctx.Data["Columns"] = columns
ctx.Data["Title"] = fmt.Sprintf("%s - %s", project.Title, ctx.ContextUser.DisplayName())
if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil { if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil {
ctx.ServerError("RenderUserOrgHeader", err) ctx.ServerError("RenderUserOrgHeader", err)

View File

@@ -1208,7 +1208,11 @@ func MergePullRequest(ctx *context.Context) {
func deleteBranchAfterMergeAndFlashMessage(ctx *context.Context, prID int64) { func deleteBranchAfterMergeAndFlashMessage(ctx *context.Context, prID int64) {
var fullBranchName string var fullBranchName string
err := repo_service.DeleteBranchAfterMerge(ctx, ctx.Doer, prID, &fullBranchName) err := repo_service.DeleteBranchAfterMerge(ctx, ctx.Doer, prID, &fullBranchName)
if errTr := util.ErrorAsTranslatable(err); errTr != nil { if errors.Is(err, util.ErrPermissionDenied) || errors.Is(err, util.ErrNotExist) {
// no need to show error to end users if no permission or branch not exist
log.Debug("DeleteBranchAfterMerge (ignore unnecessary error): %v", err)
return
} else if errTr := util.ErrorAsTranslatable(err); errTr != nil {
ctx.Flash.Error(errTr.Translate(ctx.Locale)) ctx.Flash.Error(errTr.Translate(ctx.Locale))
return return
} else if err == nil { } else if err == nil {

View File

@@ -73,10 +73,9 @@ func SettingsProtectedBranch(c *context.Context) {
c.Data["PageIsSettingsBranches"] = true c.Data["PageIsSettingsBranches"] = true
c.Data["Title"] = c.Locale.TrString("repo.settings.protected_branch") + " - " + rule.RuleName c.Data["Title"] = c.Locale.TrString("repo.settings.protected_branch") + " - " + rule.RuleName
users, err := access_model.GetUsersWithUnitAccess(c, c.Repo.Repository, perm.AccessModeRead, unit.TypePullRequests)
users, err := access_model.GetRepoReaders(c, c.Repo.Repository)
if err != nil { if err != nil {
c.ServerError("Repo.Repository.GetReaders", err) c.ServerError("GetUsersWithUnitAccess", err)
return return
} }
c.Data["Users"] = users c.Data["Users"] = users

View File

@@ -149,9 +149,9 @@ func setTagsContext(ctx *context.Context) error {
} }
ctx.Data["ProtectedTags"] = protectedTags ctx.Data["ProtectedTags"] = protectedTags
users, err := access_model.GetRepoReaders(ctx, ctx.Repo.Repository) users, err := access_model.GetUsersWithUnitAccess(ctx, ctx.Repo.Repository, perm.AccessModeRead, unit.TypePullRequests)
if err != nil { if err != nil {
ctx.ServerError("Repo.Repository.GetReaders", err) ctx.ServerError("GetUsersWithUnitAccess", err)
return err return err
} }
ctx.Data["Users"] = users ctx.Data["Users"] = users

View File

@@ -95,6 +95,7 @@ func getFileReader(ctx gocontext.Context, repoID int64, blob *git.Blob) (buf []b
meta, err := git_model.GetLFSMetaObjectByOid(ctx, repoID, pointer.Oid) meta, err := git_model.GetLFSMetaObjectByOid(ctx, repoID, pointer.Oid)
if err != nil { // fallback to a plain file if err != nil { // fallback to a plain file
fi.lfsMeta = &pointer
log.Warn("Unable to access LFS pointer %s in repo %d: %v", pointer.Oid, repoID, err) log.Warn("Unable to access LFS pointer %s in repo %d: %v", pointer.Oid, repoID, err)
return buf, dataRc, fi, nil return buf, dataRc, fi, nil
} }

View File

@@ -92,8 +92,6 @@ func handleFileViewRenderMarkup(ctx *context.Context, filename string, sniffedTy
ctx.ServerError("Render", err) ctx.ServerError("Render", err)
return true return true
} }
// to prevent iframe from loading third-party url
ctx.Resp.Header().Add("Content-Security-Policy", "frame-src 'self'")
return true return true
} }
@@ -241,14 +239,17 @@ func prepareFileView(ctx *context.Context, entry *git.TreeEntry) {
// * IsRenderableXxx: some files are rendered by backend "markup" engine, some are rendered by frontend (pdf, 3d) // * IsRenderableXxx: some files are rendered by backend "markup" engine, some are rendered by frontend (pdf, 3d)
// * DefaultViewMode: when there is no "display" query parameter, which view mode should be used by default, source or rendered // * DefaultViewMode: when there is no "display" query parameter, which view mode should be used by default, source or rendered
utf8Reader := charset.ToUTF8WithFallbackReader(io.MultiReader(bytes.NewReader(buf), dataRc), charset.ConvertOpts{}) contentReader := io.MultiReader(bytes.NewReader(buf), dataRc)
if fInfo.st.IsRepresentableAsText() {
contentReader = charset.ToUTF8WithFallbackReader(contentReader, charset.ConvertOpts{})
}
switch { switch {
case fInfo.blobOrLfsSize >= setting.UI.MaxDisplayFileSize: case fInfo.blobOrLfsSize >= setting.UI.MaxDisplayFileSize:
ctx.Data["IsFileTooLarge"] = true ctx.Data["IsFileTooLarge"] = true
case handleFileViewRenderMarkup(ctx, entry.Name(), fInfo.st, buf, utf8Reader): case handleFileViewRenderMarkup(ctx, entry.Name(), fInfo.st, buf, contentReader):
// it also sets ctx.Data["FileContent"] and more // it also sets ctx.Data["FileContent"] and more
ctx.Data["IsMarkup"] = true ctx.Data["IsMarkup"] = true
case handleFileViewRenderSource(ctx, entry.Name(), attrs, fInfo, utf8Reader): case handleFileViewRenderSource(ctx, entry.Name(), attrs, fInfo, contentReader):
// it also sets ctx.Data["FileContent"] and more // it also sets ctx.Data["FileContent"] and more
ctx.Data["IsDisplayingSource"] = true ctx.Data["IsDisplayingSource"] = true
case handleFileViewRenderImage(ctx, fInfo, buf): case handleFileViewRenderImage(ctx, fInfo, buf):

View File

@@ -133,7 +133,7 @@ func wikiContentsByEntry(ctx *context.Context, entry *git.TreeEntry) []byte {
return nil return nil
} }
defer reader.Close() defer reader.Close()
content, err := io.ReadAll(reader) content, err := util.ReadWithLimit(reader, 5*1024*1024) // 5MB should be enough for a wiki page
if err != nil { if err != nil {
ctx.ServerError("ReadAll", err) ctx.ServerError("ReadAll", err)
return nil return nil

View File

@@ -105,9 +105,7 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u
} }
} }
if source.AttributeAvatar != "" { if source.AttributeAvatar != "" {
if err := user_service.UploadAvatar(ctx, user, sr.Avatar); err != nil { _ = user_service.UploadAvatar(ctx, user, sr.Avatar)
return user, err
}
} }
} }

View File

@@ -139,7 +139,7 @@ func getWhitelistEntities[T *user_model.User | *organization.Team](entities []T,
// ToBranchProtection convert a ProtectedBranch to api.BranchProtection // ToBranchProtection convert a ProtectedBranch to api.BranchProtection
func ToBranchProtection(ctx context.Context, bp *git_model.ProtectedBranch, repo *repo_model.Repository) *api.BranchProtection { func ToBranchProtection(ctx context.Context, bp *git_model.ProtectedBranch, repo *repo_model.Repository) *api.BranchProtection {
readers, err := access_model.GetRepoReaders(ctx, repo) readers, err := access_model.GetUsersWithUnitAccess(ctx, repo, perm.AccessModeRead, unit.TypePullRequests)
if err != nil { if err != nil {
log.Error("GetRepoReaders: %v", err) log.Error("GetRepoReaders: %v", err)
} }
@@ -720,7 +720,7 @@ func ToAnnotatedTagObject(repo *repo_model.Repository, commit *git.Commit) *api.
// ToTagProtection convert a git.ProtectedTag to an api.TagProtection // ToTagProtection convert a git.ProtectedTag to an api.TagProtection
func ToTagProtection(ctx context.Context, pt *git_model.ProtectedTag, repo *repo_model.Repository) *api.TagProtection { func ToTagProtection(ctx context.Context, pt *git_model.ProtectedTag, repo *repo_model.Repository) *api.TagProtection {
readers, err := access_model.GetRepoReaders(ctx, repo) readers, err := access_model.GetUsersWithUnitAccess(ctx, repo, perm.AccessModeRead, unit.TypePullRequests)
if err != nil { if err != nil {
log.Error("GetRepoReaders: %v", err) log.Error("GetRepoReaders: %v", err)
} }

View File

@@ -5,7 +5,6 @@ package issue
import ( import (
"fmt" "fmt"
"io"
"net/url" "net/url"
"path" "path"
"strings" "strings"
@@ -15,6 +14,7 @@ import (
"code.gitea.io/gitea/modules/issue/template" "code.gitea.io/gitea/modules/issue/template"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
@@ -65,7 +65,7 @@ func GetTemplateConfig(gitRepo *git.Repository, path string, commit *git.Commit)
defer reader.Close() defer reader.Close()
configContent, err := io.ReadAll(reader) configContent, err := util.ReadWithLimit(reader, 1024*1024)
if err != nil { if err != nil {
return GetDefaultTemplateConfig(), err return GetDefaultTemplateConfig(), err
} }

View File

@@ -112,8 +112,12 @@ func NewJwtRegisteredClaimsFromUser(clientID string, grantUserID int64, exp *jwt
// to retrieve the configuration information. This MUST also be identical to the "iss" Claim value in ID Tokens issued from this Issuer. // to retrieve the configuration information. This MUST also be identical to the "iss" Claim value in ID Tokens issued from this Issuer.
// * https://accounts.google.com/.well-known/openid-configuration // * https://accounts.google.com/.well-known/openid-configuration
// * https://github.com/login/oauth/.well-known/openid-configuration // * https://github.com/login/oauth/.well-known/openid-configuration
issuer := setting.OAuth2.JWTClaimIssuer
if issuer == "" {
issuer = strings.TrimSuffix(setting.AppURL, "/")
}
return jwt.RegisteredClaims{ return jwt.RegisteredClaims{
Issuer: strings.TrimSuffix(setting.AppURL, "/"), Issuer: issuer,
Audience: []string{clientID}, Audience: []string{clientID},
Subject: strconv.FormatInt(grantUserID, 10), Subject: strconv.FormatInt(grantUserID, 10),
ExpiresAt: exp, ExpiresAt: exp,

View File

@@ -7,7 +7,9 @@ import (
"bufio" "bufio"
"bytes" "bytes"
"context" "context"
"errors"
"fmt" "fmt"
"io/fs"
"os" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
@@ -138,31 +140,37 @@ func (gt *giteaTemplateFileMatcher) Match(s string) bool {
return false return false
} }
func readGiteaTemplateFile(tmpDir string) (*giteaTemplateFileMatcher, error) { func readLocalTmpRepoFileContent(localPath string, limit int) ([]byte, error) {
localPath := filepath.Join(tmpDir, ".gitea", "template") ok, err := util.IsRegularFile(localPath)
if _, err := os.Stat(localPath); os.IsNotExist(err) { if err != nil {
return nil, nil
} else if err != nil {
return nil, err return nil, err
} else if !ok {
return nil, fs.ErrNotExist
} }
content, err := os.ReadFile(localPath) f, err := os.Open(localPath)
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer f.Close()
return util.ReadWithLimit(f, limit)
}
func readGiteaTemplateFile(tmpDir string) (*giteaTemplateFileMatcher, error) {
localPath := filepath.Join(tmpDir, ".gitea", "template")
content, err := readLocalTmpRepoFileContent(localPath, 1024*1024)
if err != nil {
return nil, err
}
return newGiteaTemplateFileMatcher(localPath, content), nil return newGiteaTemplateFileMatcher(localPath, content), nil
} }
func substGiteaTemplateFile(ctx context.Context, tmpDir, tmpDirSubPath string, templateRepo, generateRepo *repo_model.Repository) error { func substGiteaTemplateFile(ctx context.Context, tmpDir, tmpDirSubPath string, templateRepo, generateRepo *repo_model.Repository) error {
tmpFullPath := filepath.Join(tmpDir, tmpDirSubPath) tmpFullPath := filepath.Join(tmpDir, tmpDirSubPath)
if ok, err := util.IsRegularFile(tmpFullPath); !ok { content, err := readLocalTmpRepoFileContent(tmpFullPath, 1024*1024)
return err
}
content, err := os.ReadFile(tmpFullPath)
if err != nil { if err != nil {
return err return util.Iif(errors.Is(err, fs.ErrNotExist), nil, err)
} }
if err := util.Remove(tmpFullPath); err != nil { if err := util.Remove(tmpFullPath); err != nil {
return err return err
@@ -172,7 +180,7 @@ func substGiteaTemplateFile(ctx context.Context, tmpDir, tmpDirSubPath string, t
substSubPath := filepath.Clean(filePathSanitize(generateExpansion(ctx, tmpDirSubPath, templateRepo, generateRepo))) substSubPath := filepath.Clean(filePathSanitize(generateExpansion(ctx, tmpDirSubPath, templateRepo, generateRepo)))
newLocalPath := filepath.Join(tmpDir, substSubPath) newLocalPath := filepath.Join(tmpDir, substSubPath)
regular, err := util.IsRegularFile(newLocalPath) regular, err := util.IsRegularFile(newLocalPath)
if canWrite := regular || os.IsNotExist(err); !canWrite { if canWrite := regular || errors.Is(err, fs.ErrNotExist); !canWrite {
return nil return nil
} }
if err := os.MkdirAll(filepath.Dir(newLocalPath), 0o755); err != nil { if err := os.MkdirAll(filepath.Dir(newLocalPath), 0o755); err != nil {
@@ -242,15 +250,15 @@ func generateRepoCommit(ctx context.Context, repo, templateRepo, generateRepo *r
// Variable expansion // Variable expansion
fileMatcher, err := readGiteaTemplateFile(tmpDir) fileMatcher, err := readGiteaTemplateFile(tmpDir)
if err != nil { if err == nil {
return fmt.Errorf("readGiteaTemplateFile: %w", err)
}
if fileMatcher != nil {
err = processGiteaTemplateFile(ctx, tmpDir, templateRepo, generateRepo, fileMatcher) err = processGiteaTemplateFile(ctx, tmpDir, templateRepo, generateRepo, fileMatcher)
if err != nil { if err != nil {
return err return fmt.Errorf("processGiteaTemplateFile: %w", err)
} }
} else if errors.Is(err, fs.ErrNotExist) {
log.Debug("skip processing repo template files: no available .gitea/template")
} else {
return fmt.Errorf("readGiteaTemplateFile: %w", err)
} }
if err = git.InitRepository(ctx, tmpDir, false, templateRepo.ObjectFormatName); err != nil { if err = git.InitRepository(ctx, tmpDir, false, templateRepo.ObjectFormatName); err != nil {

View File

@@ -4,6 +4,7 @@
package repository package repository
import ( import (
"io/fs"
"os" "os"
"path/filepath" "path/filepath"
"testing" "testing"
@@ -175,6 +176,31 @@ func TestProcessGiteaTemplateFile(t *testing.T) {
// subst from a link, skip, and the target is unchanged // subst from a link, skip, and the target is unchanged
assertSymLink("subst-${TEMPLATE_NAME}-from-link", tmpDir+"/sub/link-target") assertSymLink("subst-${TEMPLATE_NAME}-from-link", tmpDir+"/sub/link-target")
} }
{
templateFilePath := tmpDir + "/.gitea/template"
_ = os.Remove(templateFilePath)
_, err := os.Lstat(templateFilePath)
require.ErrorIs(t, err, fs.ErrNotExist)
_, err = readGiteaTemplateFile(tmpDir) // no template file
require.ErrorIs(t, err, fs.ErrNotExist)
_ = os.WriteFile(templateFilePath+".target", []byte("test-data-target"), 0o644)
_ = os.Symlink(templateFilePath+".target", templateFilePath)
content, _ := os.ReadFile(templateFilePath)
require.Equal(t, "test-data-target", string(content))
_, err = readGiteaTemplateFile(tmpDir) // symlinked template file
require.ErrorIs(t, err, fs.ErrNotExist)
_ = os.Remove(templateFilePath)
_ = os.WriteFile(templateFilePath, []byte("test-data-regular"), 0o644)
content, _ = os.ReadFile(templateFilePath)
require.Equal(t, "test-data-regular", string(content))
fm, err := readGiteaTemplateFile(tmpDir) // regular template file
require.NoError(t, err)
assert.Len(t, fm.globs, 1)
}
} }
func TestTransformers(t *testing.T) { func TestTransformers(t *testing.T) {

View File

@@ -30,6 +30,7 @@ import (
"code.gitea.io/gitea/modules/queue" "code.gitea.io/gitea/modules/queue"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
webhook_module "code.gitea.io/gitea/modules/webhook" webhook_module "code.gitea.io/gitea/modules/webhook"
) )
@@ -264,7 +265,7 @@ func Deliver(ctx context.Context, t *webhook_model.HookTask) error {
t.ResponseInfo.Headers[k] = strings.Join(vals, ",") t.ResponseInfo.Headers[k] = strings.Join(vals, ",")
} }
p, err := io.ReadAll(resp.Body) p, err := util.ReadWithLimit(resp.Body, 1024*1024)
if err != nil { if err != nil {
t.ResponseInfo.Body = fmt.Sprintf("read body: %s", err) t.ResponseInfo.Body = fmt.Sprintf("read body: %s", err)
return fmt.Errorf("unable to deliver webhook task[%d] in %s as unable to read response body: %w", t.ID, w.URL, err) return fmt.Errorf("unable to deliver webhook task[%d] in %s as unable to read response body: %w", t.ID, w.URL, err)

View File

@@ -78,18 +78,6 @@
{{ctx.Locale.Tr "repo.release.downloads"}} {{ctx.Locale.Tr "repo.release.downloads"}}
</summary> </summary>
<ul class="ui divided list attachment-list"> <ul class="ui divided list attachment-list">
{{if and (not $.DisableDownloadSourceArchives) (not $release.IsDraft) ($.Permission.CanRead ctx.Consts.RepoUnitTypeCode)}}
<li class="item">
<a class="archive-link" download href="{{$.RepoLink}}/archive/{{$release.TagName | PathEscapeSegments}}.zip" rel="nofollow">
<strong class="flex-text-inline">{{svg "octicon-file-zip" 16 "download-icon"}}{{ctx.Locale.Tr "repo.release.source_code"}} (ZIP)</strong>
</a>
</li>
<li class="item">
<a class="archive-link" download href="{{$.RepoLink}}/archive/{{$release.TagName | PathEscapeSegments}}.tar.gz" rel="nofollow">
<strong class="flex-text-inline">{{svg "octicon-file-zip" 16 "download-icon"}}{{ctx.Locale.Tr "repo.release.source_code"}} (TAR.GZ)</strong>
</a>
</li>
{{end}}
{{range $att := $release.Attachments}} {{range $att := $release.Attachments}}
<li class="item"> <li class="item">
<a target="_blank" class="tw-flex-1 gt-ellipsis" rel="nofollow" download href="{{$att.DownloadURL}}"> <a target="_blank" class="tw-flex-1 gt-ellipsis" rel="nofollow" download href="{{$att.DownloadURL}}">
@@ -105,6 +93,18 @@
</div> </div>
</li> </li>
{{end}} {{end}}
{{if and (not $.DisableDownloadSourceArchives) (not $release.IsDraft) ($.Permission.CanRead ctx.Consts.RepoUnitTypeCode)}}
<li class="item">
<a class="archive-link" download href="{{$.RepoLink}}/archive/{{$release.TagName | PathEscapeSegments}}.zip" rel="nofollow">
<strong class="flex-text-inline">{{svg "octicon-file-zip" 16 "download-icon"}}{{ctx.Locale.Tr "repo.release.source_code"}} (ZIP)</strong>
</a>
</li>
<li class="item">
<a class="archive-link" download href="{{$.RepoLink}}/archive/{{$release.TagName | PathEscapeSegments}}.tar.gz" rel="nofollow">
<strong class="flex-text-inline">{{svg "octicon-file-zip" 16 "download-icon"}}{{ctx.Locale.Tr "repo.release.source_code"}} (TAR.GZ)</strong>
</a>
</li>
{{end}}
</ul> </ul>
</details> </details>
</div> </div>

View File

@@ -1,4 +1,5 @@
{{if .HeatmapData}} {{if .HeatmapData}}
<div class="activity-heatmap-container">
<div id="user-heatmap" class="is-loading" <div id="user-heatmap" class="is-loading"
data-heatmap-data="{{JsonUtils.EncodeToString .HeatmapData}}" data-heatmap-data="{{JsonUtils.EncodeToString .HeatmapData}}"
data-locale-total-contributions="{{ctx.Locale.Tr "heatmap.number_of_contributions_in_the_last_12_months" (ctx.Locale.PrettyNumber .HeatmapTotalContributions)}}" data-locale-total-contributions="{{ctx.Locale.Tr "heatmap.number_of_contributions_in_the_last_12_months" (ctx.Locale.PrettyNumber .HeatmapTotalContributions)}}"
@@ -6,5 +7,6 @@
data-locale-more="{{ctx.Locale.Tr "heatmap.more"}}" data-locale-more="{{ctx.Locale.Tr "heatmap.more"}}"
data-locale-less="{{ctx.Locale.Tr "heatmap.less"}}" data-locale-less="{{ctx.Locale.Tr "heatmap.less"}}"
></div> ></div>
<div class="divider"></div> </div>
<div class="divider"></div>
{{end}} {{end}}

View File

@@ -1 +0,0 @@
ref: refs/heads/master

View File

@@ -1,6 +0,0 @@
[core]
repositoryformatversion = 0
filemode = true
bare = true
ignorecase = true
precomposeunicode = true

View File

@@ -1 +0,0 @@
The repository will be used to test third-party renderer in TestExternalMarkupRenderer

View File

@@ -1,15 +0,0 @@
#!/usr/bin/env bash
data=$(cat)
exitcodes=""
hookname=$(basename $0)
GIT_DIR=${GIT_DIR:-$(dirname $0)}
for hook in ${GIT_DIR}/hooks/${hookname}.d/*; do
test -x "${hook}" && test -f "${hook}" || continue
echo "${data}" | "${hook}"
exitcodes="${exitcodes} $?"
done
for i in ${exitcodes}; do
[ ${i} -eq 0 ] || exit ${i}
done

View File

@@ -1,2 +0,0 @@
#!/usr/bin/env bash
"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" post-receive

View File

@@ -1,15 +0,0 @@
#!/usr/bin/env bash
data=$(cat)
exitcodes=""
hookname=$(basename $0)
GIT_DIR=${GIT_DIR:-$(dirname $0)}
for hook in ${GIT_DIR}/hooks/${hookname}.d/*; do
test -x "${hook}" && test -f "${hook}" || continue
echo "${data}" | "${hook}"
exitcodes="${exitcodes} $?"
done
for i in ${exitcodes}; do
[ ${i} -eq 0 ] || exit ${i}
done

View File

@@ -1,2 +0,0 @@
#!/usr/bin/env bash
"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" pre-receive

View File

@@ -1,14 +0,0 @@
#!/usr/bin/env bash
exitcodes=""
hookname=$(basename $0)
GIT_DIR=${GIT_DIR:-$(dirname $0)}
for hook in ${GIT_DIR}/hooks/${hookname}.d/*; do
test -x "${hook}" && test -f "${hook}" || continue
"${hook}" $1 $2 $3
exitcodes="${exitcodes} $?"
done
for i in ${exitcodes}; do
[ ${i} -eq 0 ] || exit ${i}
done

View File

@@ -1,2 +0,0 @@
#!/usr/bin/env bash
"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" update $1 $2 $3

View File

@@ -1,2 +0,0 @@
# pack-refs with: peeled fully-peeled sorted
c961cc4d1ba6b7ee1ba228a9a02b00b7746d8033 refs/heads/master

View File

@@ -237,6 +237,8 @@ func TestPackageConda(t *testing.T) {
assert.Equal(t, pd.Files[0].Blob.HashMD5, packageInfo.HashMD5) assert.Equal(t, pd.Files[0].Blob.HashMD5, packageInfo.HashMD5)
assert.Equal(t, pd.Files[0].Blob.HashSHA256, packageInfo.HashSHA256) assert.Equal(t, pd.Files[0].Blob.HashSHA256, packageInfo.HashSHA256)
assert.Equal(t, pd.Files[0].Blob.Size, packageInfo.Size) assert.Equal(t, pd.Files[0].Blob.Size, packageInfo.Size)
assert.NotNil(t, packageInfo.Dependencies)
assert.Empty(t, packageInfo.Dependencies)
}) })
t.Run(".conda", func(t *testing.T) { t.Run(".conda", func(t *testing.T) {
@@ -268,6 +270,8 @@ func TestPackageConda(t *testing.T) {
assert.Equal(t, pd.Files[0].Blob.HashMD5, packageInfo.HashMD5) assert.Equal(t, pd.Files[0].Blob.HashMD5, packageInfo.HashMD5)
assert.Equal(t, pd.Files[0].Blob.HashSHA256, packageInfo.HashSHA256) assert.Equal(t, pd.Files[0].Blob.HashSHA256, packageInfo.HashSHA256)
assert.Equal(t, pd.Files[0].Blob.Size, packageInfo.Size) assert.Equal(t, pd.Files[0].Blob.Size, packageInfo.Size)
assert.NotNil(t, packageInfo.Dependencies)
assert.Empty(t, packageInfo.Dependencies)
}) })
}) })
} }

View File

@@ -28,6 +28,7 @@ import (
oci "github.com/opencontainers/image-spec/specs-go/v1" oci "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestPackageContainer(t *testing.T) { func TestPackageContainer(t *testing.T) {
@@ -70,13 +71,12 @@ func TestPackageContainer(t *testing.T) {
manifestDigest := "sha256:4f10484d1c1bb13e3956b4de1cd42db8e0f14a75be1617b60f2de3cd59c803c6" manifestDigest := "sha256:4f10484d1c1bb13e3956b4de1cd42db8e0f14a75be1617b60f2de3cd59c803c6"
manifestContent := `{"schemaVersion":2,"mediaType":"` + container_module.ContentTypeDockerDistributionManifestV2 + `","config":{"mediaType":"application/vnd.docker.container.image.v1+json","digest":"sha256:4607e093bec406eaadb6f3a340f63400c9d3a7038680744c406903766b938f0d","size":1069},"layers":[{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4","size":32}]}` manifestContent := `{"schemaVersion":2,"mediaType":"` + container_module.ContentTypeDockerDistributionManifestV2 + `","config":{"mediaType":"application/vnd.docker.container.image.v1+json","digest":"sha256:4607e093bec406eaadb6f3a340f63400c9d3a7038680744c406903766b938f0d","size":1069},"layers":[{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4","size":32}]}`
manifestContentType := container_module.ContentTypeDockerDistributionManifestV2
untaggedManifestDigest := "sha256:4305f5f5572b9a426b88909b036e52ee3cf3d7b9c1b01fac840e90747f56623d" untaggedManifestDigest := "sha256:4305f5f5572b9a426b88909b036e52ee3cf3d7b9c1b01fac840e90747f56623d"
untaggedManifestContent := `{"schemaVersion":2,"mediaType":"` + oci.MediaTypeImageManifest + `","config":{"mediaType":"application/vnd.docker.container.image.v1+json","digest":"sha256:4607e093bec406eaadb6f3a340f63400c9d3a7038680744c406903766b938f0d","size":1069},"layers":[{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4","size":32}]}` untaggedManifestContent := `{"schemaVersion":2,"mediaType":"` + oci.MediaTypeImageManifest + `","config":{"mediaType":"application/vnd.docker.container.image.v1+json","digest":"sha256:4607e093bec406eaadb6f3a340f63400c9d3a7038680744c406903766b938f0d","size":1069},"layers":[{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4","size":32}]}`
indexManifestDigest := "sha256:bab112d6efb9e7f221995caaaa880352feb5bd8b1faf52fae8d12c113aa123ec" indexManifestDigest := "sha256:2c6b5afb967d5de02795ee1d177c3746d005df4b4c2b829385b0d186b3414b6b"
indexManifestContent := `{"schemaVersion":2,"mediaType":"` + oci.MediaTypeImageIndex + `","manifests":[{"mediaType":"application/vnd.docker.distribution.manifest.v2+json","digest":"` + manifestDigest + `","platform":{"os":"linux","architecture":"arm","variant":"v7"}},{"mediaType":"` + oci.MediaTypeImageManifest + `","digest":"` + untaggedManifestDigest + `","platform":{"os":"linux","architecture":"arm64","variant":"v8"}}]}` indexManifestContent := `{"schemaVersion":2,"mediaType":"` + oci.MediaTypeImageIndex + `","is_tagged":true,"manifests":[{"mediaType":"application/vnd.docker.distribution.manifest.v2+json","digest":"` + manifestDigest + `","platform":{"os":"linux","architecture":"arm","variant":"v7"}},{"mediaType":"` + oci.MediaTypeImageManifest + `","digest":"` + untaggedManifestDigest + `","platform":{"os":"linux","architecture":"arm64","variant":"v8"}}]}`
anonymousToken := "" anonymousToken := ""
userToken := "" userToken := ""
@@ -467,7 +467,7 @@ func TestPackageContainer(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, 1, pv.DownloadCount) assert.EqualValues(t, 1, pv.DownloadCount)
// Overwrite existing tag should keep the download count t.Run("OverwriteTagKeepDownloadCount", func(t *testing.T) {
req = NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/manifests/%s", url, tag), strings.NewReader(manifestContent)). req = NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/manifests/%s", url, tag), strings.NewReader(manifestContent)).
AddTokenAuth(userToken). AddTokenAuth(userToken).
SetHeader("Content-Type", oci.MediaTypeImageManifest) SetHeader("Content-Type", oci.MediaTypeImageManifest)
@@ -477,6 +477,7 @@ func TestPackageContainer(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, 1, pv.DownloadCount) assert.EqualValues(t, 1, pv.DownloadCount)
}) })
})
t.Run("HeadManifest", func(t *testing.T) { t.Run("HeadManifest", func(t *testing.T) {
defer tests.PrintCurrentTest(t)() defer tests.PrintCurrentTest(t)()
@@ -505,7 +506,7 @@ func TestPackageContainer(t *testing.T) {
resp := MakeRequest(t, req, http.StatusOK) resp := MakeRequest(t, req, http.StatusOK)
assert.Equal(t, strconv.Itoa(len(manifestContent)), resp.Header().Get("Content-Length")) assert.Equal(t, strconv.Itoa(len(manifestContent)), resp.Header().Get("Content-Length"))
assert.Equal(t, manifestContentType, resp.Header().Get("Content-Type")) assert.Equal(t, oci.MediaTypeImageManifest, resp.Header().Get("Content-Type")) // the manifest is overwritten by above OverwriteTagKeepDownloadCount
assert.Equal(t, manifestDigest, resp.Header().Get("Docker-Content-Digest")) assert.Equal(t, manifestDigest, resp.Header().Get("Docker-Content-Digest"))
assert.Equal(t, manifestContent, resp.Body.String()) assert.Equal(t, manifestContent, resp.Body.String())
}) })
@@ -599,6 +600,17 @@ func TestPackageContainer(t *testing.T) {
assert.True(t, pd.Files[0].File.IsLead) assert.True(t, pd.Files[0].File.IsLead)
assert.Equal(t, oci.MediaTypeImageIndex, pd.Files[0].Properties.GetByName(container_module.PropertyMediaType)) assert.Equal(t, oci.MediaTypeImageIndex, pd.Files[0].Properties.GetByName(container_module.PropertyMediaType))
assert.Equal(t, indexManifestDigest, pd.Files[0].Properties.GetByName(container_module.PropertyDigest)) assert.Equal(t, indexManifestDigest, pd.Files[0].Properties.GetByName(container_module.PropertyDigest))
lastPackageVersionID := pv.ID
t.Run("UploadAgain", func(t *testing.T) {
req := NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/manifests/%s", url, multiTag), strings.NewReader(indexManifestContent)).
AddTokenAuth(userToken).
SetHeader("Content-Type", oci.MediaTypeImageIndex)
MakeRequest(t, req, http.StatusCreated)
pv, err := packages_model.GetVersionByNameAndVersion(t.Context(), user.ID, packages_model.TypeContainer, image, multiTag)
require.NoError(t, err)
assert.NotEqual(t, lastPackageVersionID, pv.ID)
})
}) })
t.Run("HeadBlob", func(t *testing.T) { t.Run("HeadBlob", func(t *testing.T) {

View File

@@ -12,6 +12,7 @@ import (
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/charset"
"code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/markup/external" "code.gitea.io/gitea/modules/markup/external"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
@@ -25,29 +26,45 @@ import (
func TestExternalMarkupRenderer(t *testing.T) { func TestExternalMarkupRenderer(t *testing.T) {
defer tests.PrepareTestEnv(t)() defer tests.PrepareTestEnv(t)()
if !setting.Database.Type.IsSQLite3() { if !setting.Database.Type.IsSQLite3() {
t.Skip() t.Skip("only SQLite3 test config supports external markup renderer")
return return
} }
const binaryContentPrefix = "any prefix text."
const binaryContent = binaryContentPrefix + "\xfe\xfe\xfe\x00\xff\xff"
detectedEncoding, _ := charset.DetectEncoding([]byte(binaryContent))
assert.NotEqual(t, binaryContent, strings.ToValidUTF8(binaryContent, "?"))
assert.Equal(t, "ISO-8859-2", detectedEncoding) // even if the binary content can be detected as text encoding, it shouldn't affect the raw rendering
onGiteaRun(t, func(t *testing.T, _ *url.URL) { onGiteaRun(t, func(t *testing.T, _ *url.URL) {
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
_, err := createFile(user2, repo1, "file.no-sanitizer", "master", `any content`) _, err := createFileInBranch(user2, repo1, createFileInBranchOptions{}, map[string]string{
"test.html": `<div><any attr="val"><script></script></div>`,
"html.no-sanitizer": `<script>foo("raw")</script>`,
"bin.no-sanitizer": binaryContent,
})
require.NoError(t, err) require.NoError(t, err)
t.Run("RenderNoSanitizer", func(t *testing.T) { t.Run("RenderNoSanitizer", func(t *testing.T) {
req := NewRequest(t, "GET", "/user2/repo1/src/branch/master/file.no-sanitizer") req := NewRequest(t, "GET", "/user2/repo1/src/branch/master/html.no-sanitizer")
resp := MakeRequest(t, req, http.StatusOK) resp := MakeRequest(t, req, http.StatusOK)
doc := NewHTMLParser(t, resp.Body) div := NewHTMLParser(t, resp.Body).Find("div.file-view")
div := doc.Find("div.file-view")
data, err := div.Html() data, err := div.Html()
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, `<script>window.alert("hi")</script>`, strings.TrimSpace(data)) assert.Equal(t, `<script>foo("raw")</script>`, strings.TrimSpace(data))
req = NewRequest(t, "GET", "/user2/repo1/src/branch/master/bin.no-sanitizer")
resp = MakeRequest(t, req, http.StatusOK)
div = NewHTMLParser(t, resp.Body).Find("div.file-view")
data, err = div.Html()
assert.NoError(t, err)
assert.Equal(t, strings.ReplaceAll(binaryContent, "\x00", ""), strings.TrimSpace(data)) // HTML template engine removes the null bytes
}) })
}) })
t.Run("RenderContentDirectly", func(t *testing.T) { t.Run("RenderContentDirectly", func(t *testing.T) {
req := NewRequest(t, "GET", "/user30/renderer/src/branch/master/README.html") req := NewRequest(t, "GET", "/user2/repo1/src/branch/master/test.html")
resp := MakeRequest(t, req, http.StatusOK) resp := MakeRequest(t, req, http.StatusOK)
assert.Equal(t, "text/html; charset=utf-8", resp.Header().Get("Content-Type")) assert.Equal(t, "text/html; charset=utf-8", resp.Header().Get("Content-Type"))
@@ -55,18 +72,21 @@ func TestExternalMarkupRenderer(t *testing.T) {
div := doc.Find("div.file-view") div := doc.Find("div.file-view")
data, err := div.Html() data, err := div.Html()
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, "<div>\n\ttest external renderer\n</div>", strings.TrimSpace(data)) // the content is fully sanitized
assert.Equal(t, `<div>&lt;script&gt;&lt;/script&gt;</div>`, strings.TrimSpace(data))
}) })
// above tested "no-sanitizer" mode, then we test iframe mode below // above tested in-page rendering (no iframe), then we test iframe mode below
r := markup.GetRendererByFileName("any-file.html").(*external.Renderer) r := markup.GetRendererByFileName("any-file.html").(*external.Renderer)
defer test.MockVariableValue(&r.RenderContentMode, setting.RenderContentModeIframe)() defer test.MockVariableValue(&r.RenderContentMode, setting.RenderContentModeIframe)()
assert.True(t, r.NeedPostProcess())
r = markup.GetRendererByFileName("any-file.no-sanitizer").(*external.Renderer) r = markup.GetRendererByFileName("any-file.no-sanitizer").(*external.Renderer)
defer test.MockVariableValue(&r.RenderContentMode, setting.RenderContentModeIframe)() defer test.MockVariableValue(&r.RenderContentMode, setting.RenderContentModeIframe)()
assert.False(t, r.NeedPostProcess())
t.Run("RenderContentInIFrame", func(t *testing.T) { t.Run("RenderContentInIFrame", func(t *testing.T) {
t.Run("DefaultSandbox", func(t *testing.T) { t.Run("DefaultSandbox", func(t *testing.T) {
req := NewRequest(t, "GET", "/user30/renderer/src/branch/master/README.html") req := NewRequest(t, "GET", "/user2/repo1/src/branch/master/test.html")
t.Run("ParentPage", func(t *testing.T) { t.Run("ParentPage", func(t *testing.T) {
respParent := MakeRequest(t, req, http.StatusOK) respParent := MakeRequest(t, req, http.StatusOK)
@@ -77,31 +97,42 @@ func TestExternalMarkupRenderer(t *testing.T) {
// default sandbox on parent page // default sandbox on parent page
assert.Equal(t, "allow-scripts allow-popups", iframe.AttrOr("sandbox", "")) assert.Equal(t, "allow-scripts allow-popups", iframe.AttrOr("sandbox", ""))
assert.Equal(t, "/user30/renderer/render/branch/master/README.html", iframe.AttrOr("data-src", "")) assert.Equal(t, "/user2/repo1/render/branch/master/test.html", iframe.AttrOr("data-src", ""))
}) })
t.Run("SubPage", func(t *testing.T) { t.Run("SubPage", func(t *testing.T) {
req = NewRequest(t, "GET", "/user30/renderer/render/branch/master/README.html") req = NewRequest(t, "GET", "/user2/repo1/render/branch/master/test.html")
respSub := MakeRequest(t, req, http.StatusOK) respSub := MakeRequest(t, req, http.StatusOK)
assert.Equal(t, "text/html; charset=utf-8", respSub.Header().Get("Content-Type")) assert.Equal(t, "text/html; charset=utf-8", respSub.Header().Get("Content-Type"))
// default sandbox in sub page response // default sandbox in sub page response
assert.Equal(t, "frame-src 'self'; sandbox allow-scripts allow-popups", respSub.Header().Get("Content-Security-Policy")) assert.Equal(t, "frame-src 'self'; sandbox allow-scripts allow-popups", respSub.Header().Get("Content-Security-Policy"))
assert.Equal(t, "<script src=\"/assets/js/external-render-iframe.js\"></script><link rel=\"stylesheet\" href=\"/assets/css/external-render-iframe.css\"><div>\n\ttest external renderer\n</div>\n", respSub.Body.String()) // FIXME: actually here is a bug (legacy design problem), the "PostProcess" will escape "<script>" tag, but it indeed is the sanitizer's job
assert.Equal(t, `<script src="/assets/js/external-render-iframe.js"></script><link rel="stylesheet" href="/assets/css/external-render-iframe.css"><div><any attr="val">&lt;script&gt;&lt;/script&gt;</any></div>`, respSub.Body.String())
}) })
}) })
t.Run("NoSanitizerNoSandbox", func(t *testing.T) { t.Run("NoSanitizerNoSandbox", func(t *testing.T) {
req := NewRequest(t, "GET", "/user2/repo1/src/branch/master/file.no-sanitizer") t.Run("BinaryContent", func(t *testing.T) {
req := NewRequest(t, "GET", "/user2/repo1/src/branch/master/bin.no-sanitizer")
respParent := MakeRequest(t, req, http.StatusOK) respParent := MakeRequest(t, req, http.StatusOK)
iframe := NewHTMLParser(t, respParent.Body).Find("iframe.external-render-iframe") iframe := NewHTMLParser(t, respParent.Body).Find("iframe.external-render-iframe")
assert.Equal(t, "/user2/repo1/render/branch/master/file.no-sanitizer", iframe.AttrOr("data-src", "")) assert.Equal(t, "/user2/repo1/render/branch/master/bin.no-sanitizer", iframe.AttrOr("data-src", ""))
req = NewRequest(t, "GET", "/user2/repo1/render/branch/master/file.no-sanitizer") req = NewRequest(t, "GET", "/user2/repo1/render/branch/master/bin.no-sanitizer")
respSub := MakeRequest(t, req, http.StatusOK) respSub := MakeRequest(t, req, http.StatusOK)
assert.Equal(t, binaryContent, respSub.Body.String()) // raw content should keep the raw bytes (including invalid UTF-8 bytes), and no "external-render-iframe" helpers
// no sandbox (disabled by RENDER_CONTENT_SANDBOX) // no sandbox (disabled by RENDER_CONTENT_SANDBOX)
assert.Empty(t, iframe.AttrOr("sandbox", "")) assert.Empty(t, iframe.AttrOr("sandbox", ""))
assert.Equal(t, "frame-src 'self'", respSub.Header().Get("Content-Security-Policy")) assert.Equal(t, "frame-src 'self'", respSub.Header().Get("Content-Security-Policy"))
}) })
t.Run("HTMLContentWithExternalRenderIframeHelper", func(t *testing.T) {
req := NewRequest(t, "GET", "/user2/repo1/render/branch/master/html.no-sanitizer")
respSub := MakeRequest(t, req, http.StatusOK)
assert.Equal(t, `<script src="/assets/js/external-render-iframe.js"></script><link rel="stylesheet" href="/assets/css/external-render-iframe.css"><script>foo("raw")</script>`, respSub.Body.String())
assert.Equal(t, "frame-src 'self'", respSub.Header().Get("Content-Security-Policy"))
})
})
}) })
} }

View File

@@ -919,9 +919,10 @@ func TestOAuth_GrantScopesClaimAllGroups(t *testing.T) {
} }
func testOAuth2WellKnown(t *testing.T) { func testOAuth2WellKnown(t *testing.T) {
defer test.MockVariableValue(&setting.AppURL, "https://try.gitea.io/")()
urlOpenidConfiguration := "/.well-known/openid-configuration" urlOpenidConfiguration := "/.well-known/openid-configuration"
defer test.MockVariableValue(&setting.AppURL, "https://try.gitea.io/")() t.Run("WellKnown", func(t *testing.T) {
req := NewRequest(t, "GET", urlOpenidConfiguration) req := NewRequest(t, "GET", urlOpenidConfiguration)
resp := MakeRequest(t, req, http.StatusOK) resp := MakeRequest(t, req, http.StatusOK)
var respMap map[string]any var respMap map[string]any
@@ -933,6 +934,17 @@ func testOAuth2WellKnown(t *testing.T) {
assert.Equal(t, "https://try.gitea.io/login/oauth/userinfo", respMap["userinfo_endpoint"]) assert.Equal(t, "https://try.gitea.io/login/oauth/userinfo", respMap["userinfo_endpoint"])
assert.Equal(t, "https://try.gitea.io/login/oauth/introspect", respMap["introspection_endpoint"]) assert.Equal(t, "https://try.gitea.io/login/oauth/introspect", respMap["introspection_endpoint"])
assert.Equal(t, []any{"RS256"}, respMap["id_token_signing_alg_values_supported"]) assert.Equal(t, []any{"RS256"}, respMap["id_token_signing_alg_values_supported"])
})
t.Run("WellKnownWithIssuer", func(t *testing.T) {
defer test.MockVariableValue(&setting.OAuth2.JWTClaimIssuer, "https://try.gitea.io/")()
req := NewRequest(t, "GET", urlOpenidConfiguration)
resp := MakeRequest(t, req, http.StatusOK)
var respMap map[string]any
DecodeJSON(t, resp, &respMap)
assert.Equal(t, "https://try.gitea.io/", respMap["issuer"]) // has trailing by JWTClaimIssuer
assert.Equal(t, "https://try.gitea.io/login/oauth/authorize", respMap["authorization_endpoint"])
})
defer test.MockVariableValue(&setting.OAuth2.Enabled, false)() defer test.MockVariableValue(&setting.OAuth2.Enabled, false)()
MakeRequest(t, NewRequest(t, "GET", urlOpenidConfiguration), http.StatusNotFound) MakeRequest(t, NewRequest(t, "GET", urlOpenidConfiguration), http.StatusNotFound)

View File

@@ -122,7 +122,7 @@ RENDER_CONTENT_MODE = sanitized
[markup.no-sanitizer] [markup.no-sanitizer]
ENABLED = true ENABLED = true
FILE_EXTENSIONS = .no-sanitizer FILE_EXTENSIONS = .no-sanitizer
RENDER_COMMAND = echo '<script>window.alert("hi")</script>' RENDER_COMMAND = go run build/test-echo.go
; This test case is reused, at first it is used to test "no-sanitizer" (sandbox doesn't take effect here) ; This test case is reused, at first it is used to test "no-sanitizer" (sandbox doesn't take effect here)
; Then it will be updated and used to test "iframe + sandbox-disabled" ; Then it will be updated and used to test "iframe + sandbox-disabled"
RENDER_CONTENT_MODE = no-sanitizer RENDER_CONTENT_MODE = no-sanitizer

View File

@@ -626,7 +626,6 @@ img.ui.avatar,
font-family: var(--fonts-monospace); font-family: var(--fonts-monospace);
font-size: 13px; font-size: 13px;
font-weight: var(--font-weight-normal); font-weight: var(--font-weight-normal);
padding: 3px 5px;
flex-shrink: 0; flex-shrink: 0;
} }

View File

@@ -4,23 +4,44 @@
position: relative; position: relative;
} }
/* before the Vue component is mounted, show a loading indicator with dummy size */ .activity-heatmap-container {
/* the ratio is guesswork, see https://github.com/razorness/vue3-calendar-heatmap/issues/26 */ container-type: inline-size;
#user-heatmap.is-loading {
aspect-ratio: 5.415; /* the size is about 790 x 145 */
} }
.user.profile #user-heatmap.is-loading {
aspect-ratio: 5.645; /* the size is about 953 x 169 */ @container (width > 0) {
#user-heatmap {
/* Set element to fixed height so that it does not resize after load. The calculation is complex
because the element does not scale with a fixed aspect ratio. */
height: calc((100cqw / 5) - (100cqw / 25) + 20px);
}
}
/* Fallback height adjustment above for browsers that don't support container queries */
@supports not (container-type: inline-size) {
/* Before the Vue component is mounted, show a loading indicator with dummy size */
/* The ratio is guesswork for legacy browsers, new browsers use the "@container" approach above */
#user-heatmap.is-loading {
aspect-ratio: 5.4823972051; /* the size is about 816 x 148.84 */
}
.user.profile #user-heatmap.is-loading {
aspect-ratio: 5.6290608387; /* the size is about 953 x 169.3 */
}
} }
#user-heatmap text { #user-heatmap text {
fill: currentcolor !important; fill: currentcolor !important;
} }
/* root legend */
#user-heatmap .vch__container > .vch__legend {
display: flex;
font-size: 11px;
justify-content: space-between;
}
/* for the "Less" and "More" legend */ /* for the "Less" and "More" legend */
#user-heatmap .vch__legend .vch__legend { #user-heatmap .vch__legend .vch__legend {
display: flex; display: flex;
font-size: 11px;
align-items: center; align-items: center;
justify-content: right; justify-content: right;
} }
@@ -34,25 +55,3 @@
#user-heatmap .vch__day__square:hover { #user-heatmap .vch__day__square:hover {
outline: 1.5px solid var(--color-text); outline: 1.5px solid var(--color-text);
} }
/* move the "? contributions in the last ? months" text from top to bottom */
#user-heatmap .total-contributions {
font-size: 11px;
position: absolute;
bottom: 0;
left: 25px;
}
@media (max-width: 1200px) {
#user-heatmap .total-contributions {
left: 21px;
}
}
@media (max-width: 1000px) {
#user-heatmap .total-contributions {
font-size: 10px;
left: 17px;
bottom: -4px;
}
}

View File

@@ -387,6 +387,7 @@ td .commit-summary {
.repository.view.issue .pull-desc code { .repository.view.issue .pull-desc code {
color: var(--color-primary); color: var(--color-primary);
background: transparent;
} }
.repository.view.issue .pull-desc a[data-clipboard-text] { .repository.view.issue .pull-desc a[data-clipboard-text] {

View File

@@ -53,9 +53,6 @@ function handleDayClick(e: Event & {date: Date}) {
} }
</script> </script>
<template> <template>
<div class="total-contributions">
{{ locale.textTotalContributions }}
</div>
<calendar-heatmap <calendar-heatmap
:locale="locale.heatMapLocale" :locale="locale.heatMapLocale"
:no-data-text="locale.noDataText" :no-data-text="locale.noDataText"
@@ -65,5 +62,7 @@ function handleDayClick(e: Event & {date: Date}) {
:range-color="colorRange" :range-color="colorRange"
@day-click="handleDayClick($event)" @day-click="handleDayClick($event)"
:tippy-props="{theme: 'tooltip'}" :tippy-props="{theme: 'tooltip'}"
/> >
<template #vch__legend-left>{{ locale.textTotalContributions }}</template>
</calendar-heatmap>
</template> </template>

View File

@@ -1,4 +1,3 @@
import './globals.ts';
import '../fomantic/build/fomantic.js'; import '../fomantic/build/fomantic.js';
import '../../node_modules/easymde/dist/easymde.min.css'; // TODO: lazy load in "switchToEasyMDE" import '../../node_modules/easymde/dist/easymde.min.css'; // TODO: lazy load in "switchToEasyMDE"

View File

@@ -1,5 +1,10 @@
// bootstrap module must be the first one to be imported, it handles webpack lazy-loading and global errors // bootstrap module must be the first one to be imported, it handles webpack lazy-loading and global errors
import './bootstrap.ts'; import './bootstrap.ts';
// many users expect to use jQuery in their custom scripts (https://docs.gitea.com/administration/customizing-gitea#example-plantuml)
// so load globals (including jQuery) as early as possible
import './globals.ts';
import './webcomponents/index.ts'; import './webcomponents/index.ts';
import {onDomReady} from './utils/dom.ts'; import {onDomReady} from './utils/dom.ts';