Compare commits

...

64 Commits

Author SHA1 Message Date
David Schneiderbauer
96c63a4937 add 1.4.3 changelog (#4321) 2018-06-27 00:17:16 +02:00
Kim "BKC" Carlbäcker
459a2656bf
Backport #4312 to v1.4 (#4320) 2018-06-26 23:55:00 +02:00
Russell Aunger
a3b10538ec Fix webhook type conflation. (#4285) (#4289)
- Fix typo that caused Gogs hooks to be created as Gitea hooks.
- Fix typo that caused Gogs hooks to be duplicated upon edit (though this bug was masked by the previous one).

Signed-off-by: Russell Aunger <rba@live.com>
2018-06-21 12:06:29 +02:00
nickolas360
caee4870da HTML-escape text READMEs (#4192) (#4214) 2018-06-19 12:06:05 +03:00
Clément Lafont
8b2df7310b Fix code tab link when viewing tags (#3908) (#4263)
Signed-off-by: Ambrose Chua <ambrose@chua.family>
2018-06-17 15:28:03 +02:00
Antoine GIRARD
00ad4745ba backport(#4091): Fix #4090 by escaping filename page/img link (without path) for short link (#4254) 2018-06-17 15:33:44 +08:00
Lauris BH
c746f820ab Delete reactions added to issues and comments when deleting repository (#4232) (#4237) 2018-06-12 19:19:30 -04:00
Lunny Xiao
f10640433f fix delete comment bug (#4216) (#4228) 2018-06-12 13:39:37 +03:00
David Schneiderbauer
1ff480baa6 undo #3142 because processes don't use the util.Timestamp (#4203) (#4208) 2018-06-11 09:21:34 +03:00
techknowlogick
df941f5c39 Add 1.4.2 changelog (#4115) 2018-06-05 01:45:21 +03:00
David Schneiderbauer
1b10bc0cdf update git vendor (#4059) (#4075)
* update git vendor (#4059)

* fix errors resulting from git vendor update

* fmt
2018-06-04 23:33:42 +03:00
Antoine GIRARD
561f459364 Fix #4081 Check for leading / in base before removing it (#4083) 2018-05-30 11:46:30 -04:00
Jonas Franz
1177a19a5b Use ResolveReference instead of path.Join (#4073)
Signed-off-by: Jonas Franz <info@jonasfranz.software>
2018-05-29 12:12:04 -04:00
Alexey Terentyev
d1954ae4e7 Added deletion of an empty line at the end of file (#4054) (#4074)
Signed-off-by: Alexey Terentyev <axifnx@gmail.com>
2018-05-29 17:42:57 +08:00
Lunny Xiao
7673ca9646
fix attachment ENABLED (#4064) (#4066)
* fix attachment ENABLED

* change wrong name on app.ini.sample
2018-05-29 08:27:44 +08:00
Michael Kuhn
2d0db24083 Fix wiki redirects (#3919) (#4065)
When creating or editing a wiki page, the redirect to the wiki page does
not work because the file name is used instead of the page name.
2018-05-28 11:13:53 -04:00
Morgan Bazalgette
dec663cc0a Don't force due date to current time (#3830) (#4057) 2018-05-27 21:34:33 +03:00
Lunny Xiao
804148294a
fix writer cannot read bare repo guide (#4033) (#4039) 2018-05-24 22:44:21 +08:00
Lunny Xiao
910e379265 fix webhook bug of response info is not displayed in UI (#4023) 2018-05-23 11:57:21 +03:00
奶爸
f1720ad133 webhook and hook_task clean up (#4006)
webhook and hook_task clean up
2018-05-22 02:08:21 +03:00
David Schneiderbauer
5f169bfcfd add missing token validation on application settings page (#3976) (#3978) 2018-05-16 23:55:14 +08:00
David Schneiderbauer
50adbb7134 Adjust z-index for floating labels (#3939) (#3950) 2018-05-12 00:36:03 +03:00
Lauris BH
abc159637c
Changelog for release v1.4.1 (#3891)
* Changelog for release v1.4.1

* Fix typo
2018-05-03 07:06:40 +03:00
Lauris BH
e35d7ae1fa
Do not allow inactive users to access repositories using private keys (#3887) (#3889) 2018-05-02 20:37:23 +03:00
Lauris BH
40c6eb0d85
Fix to use only needed columns from tables to get repository git paths (#3870) (#3883) 2018-05-02 19:34:21 +03:00
Lauris BH
2996573976
Add "error" as reserved username (#3882) (#3886) 2018-05-02 12:47:24 +03:00
Lauris BH
d0a9957c32
Fix docs site index page (#3868) (#3881) 2018-05-02 11:06:10 +03:00
Lauris BH
adbf576a6e fix gpg expired bug when time is zero (#3584) (#3884) 2018-05-02 15:23:07 +08:00
Lauris BH
15cdb19d77 Fix path cleanup in multiple places (#3871) (#3873) 2018-05-01 17:10:50 +08:00
kolaente
9be48f04cb Fix only updated_unix when adding a comment (Backport of #3855 to 1.4 ) (#3860) 2018-04-30 21:42:37 +03:00
Lauris BH
24dd77eca6
Fix building of docs with latest hugo version (#3856) (#3862) 2018-04-30 21:19:46 +03:00
Bwko
af7779daf8 Remove unnecessary Safe tags (#3779) 2018-04-11 20:39:27 +03:00
Michael Kuhn
31c0a338d6 Fix typo in deleteUser (#3781) (#3783)
num_watches was used where num_stars should have been used.
2018-04-11 11:24:46 +08:00
Bwko
2ec85e0b70 Use the active branch for the code tab (#3776) 2018-04-10 11:10:32 +08:00
Bo-Yi Wu
251e1b68b4
Backport: fix: show Clipboard button if disable HTTP of git protocol #3773 (#3774) 2018-04-09 23:35:30 +08:00
Lauris BH
641d481c38
Correctly check http git access rights for reverse proxy authorized users (#3721) (#3743) 2018-04-04 20:06:21 +03:00
Lunny Xiao
6c6d1ff08c fix go vet error (#3740) (#3744) 2018-03-30 23:28:10 +03:00
Bwko
d9ad876d97 Set default branchname on first push (#3723) 2018-03-27 12:51:20 +08:00
Lauris BH
832e2ebe91 Changelog for release 1.4.0 (#3714) 2018-03-25 09:12:48 +08:00
Jonas Franz
68134e6441 Escape branch name in dropdown menu (#3692)
Signed-off-by: Jonas Franz <info@jonasfranz.software>

(cherry picked from commit 61ce616)
Signed-off-by: Jonas Franz <info@jonasfranz.software>
2018-03-19 22:31:01 +08:00
Lauris BH
3022681432
Changelog for 1.4.0-rc3 (#3679) 2018-03-16 22:11:34 +02:00
Lauris BH
c0e0fb7d39 Refactor and simplify redirect to url (#3674) (#3676) 2018-03-16 19:59:47 +08:00
Lauris BH
0c612124f9
Update markbates/goth libary to fix OAuth2 support (#3661) (#3663) 2018-03-13 09:08:26 +02:00
Lauris BH
5d0c9872a9 Fix MySQL and PostgreSQL column drop SQL (#3649) (#3651) 2018-03-10 10:27:19 +08:00
Lauris BH
92a3061753
Fix column removal in MSSQL (#3638) (#3640)
* Fix column removal in MSSQL

* Use xorm session in MSSQL drop column operations

* Add transaction as MSSQL alter table is transactional
2018-03-07 11:59:20 +02:00
Lauris BH
efc5a7171b Fix wiki inter-links with spaces (#3560) (#3632) 2018-03-06 16:43:50 +08:00
Lauris BH
93f34fd8a2
Changelog for release 1.4.0-rc2 (#3610) 2018-03-03 00:10:37 +02:00
Wendell Sun
dd784396ce Fix query protected branch bug (#3563) (#3571)
Signed-off-by: Wendell Sun <iwendellsun@gmail.com>
2018-02-24 00:07:25 +02:00
Wendell Sun
4a0ce6896b Fix remove team member issue (#3566) (#3570)
Signed-off-by: Wendell Sun <iwendellsun@gmail.com>
2018-02-23 20:02:45 +02:00
Wendell Sun
d87fb0a6fd Fix the protected branch panic issue (#3567) (#3569)
Signed-off-by: Wendell Sun <iwendellsun@gmail.com>
2018-02-23 19:27:34 +02:00
Bo-Yi Wu
e55eaa8545 if Mirrors repo no content is fetched, updated time should not be changed #3551 (#3565)
Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com>
2018-02-23 13:48:40 +02:00
Wendell Sun
423c642fdb Bug fix for repo releases sorted (#3522) (#3555)
Signed-off-by: Wendell Sun <iwendellsun@gmail.com>

Use TimeStampNow function
2018-02-22 09:56:38 +08:00
Bo-Yi Wu
ed2ba84525 refactor: reduce sql query in retrieveFeeds (#3554) 2018-02-21 18:15:00 +02:00
Lauris BH
e8015a59bb
Add issue closed time column to fix activity closed issues list (#3537) (#3540)
Signed-off-by: Lauris Bukšis-Haberkorns <lauris@nix.lv>
2018-02-19 21:51:37 +02:00
Lauris BH
8327300809 Update markbates/goth library (#3533) (#3539)
Signed-off-by: Lauris Bukšis-Haberkorns <lauris@nix.lv>
2018-02-19 20:10:38 +08:00
Lauris BH
ade183957d
Force remove test repo root path in case previous test is still locking it (#3528) (#3536)
Signed-off-by: Lauris Bukšis-Haberkorns <lauris@nix.lv>
2018-02-19 08:29:37 +02:00
Lauris BH
c503eac206 Fix escaping changed title in comments (#3530) (#3534)
* Fix escaping changed title in comments

* Fix escaping of wiki page titile

Signed-off-by: Lauris Bukšis-Haberkorns <lauris@nix.lv>
2018-02-19 10:33:51 +08:00
Jonas Bröms
221e502297 Clarify Indexer MAX_FILE_SIZE (#3469) (#3474) 2018-02-15 11:20:41 +02:00
Codruț Constantin Gușoi
1c3d712fe7 Fixes missing avatars in offline mode (#3471) (#3477)
Signed-off-by: Codruț Constantin Gușoi <codrut.gusoi@gmail.com>
2018-02-14 23:37:35 +02:00
Ethan Koenig
85f90187f3 Improve wiki test (#3493) (#3511) 2018-02-14 22:40:59 +02:00
Jonas Franz
c0675ef6c2 Escape search query (Backport 1.4) (#3488)
* Escape search query

Signed-off-by: Jonas Franz <info@jonasfranz.de>

(cherry picked from commit 2970889)

* Reordered imports

Signed-off-by: Jonas Franz <info@jonasfranz.de>
2018-02-11 21:25:02 +02:00
Ethan Koenig
4e27cc4813 Fix synchronization bug in repo indexer (#3455) (#3461) 2018-02-08 08:42:45 +02:00
Lauris BH
f61ef28f26
Fix rendering of wiki page list if wiki repo contains other files (#3454) (#3463)
* Fix rendering of wiki page list if wiki repo contains other files

* Improve wiki filename tests
2018-02-06 07:59:44 +02:00
Lauris Bukšis-Haberkorns
b27e10d6e4 Change 1.4.0-rc1 version release date
Signed-off-by: Lauris Bukšis-Haberkorns <lauris@nix.lv>
2018-02-01 10:22:24 +02:00
99 changed files with 1574 additions and 589 deletions

View File

@ -4,11 +4,59 @@ 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 without substantial changes to our git log; to see the highlights of what has
been added to each release, please refer to the [blog](https://blog.gitea.io). been added to each release, please refer to the [blog](https://blog.gitea.io).
## [1.4.0-rc1](https://github.com/go-gitea/gitea/releases/tag/v1.4.0-rc1) - 2018-01-31 ## [1.4.3](https://github.com/go-gitea/gitea/releases/tag/v1.4.3) - 2018-06-26
* SECURITY
* HTML-escape plain-text READMEs (#4192) (#4214)
* Fix open redirect vulnerability on login screen (#4312) (#4312)
* BUGFIXES
* Fix broken monitoring page when running processes are shown (#4203) (#4208)
* Fix delete comment bug (#4216) (#4228)
* Delete reactions added to issues and comments when deleting repository (#4232) (#4237)
* Fix wiki URL encoding bug (#4091) (#4254)
* Fix code tab link when viewing tags (#3908) (#4263)
* Fix webhook type conflation (#4285) (#4285)
## [1.4.2](https://github.com/go-gitea/gitea/releases/tag/v1.4.2) - 2018-06-04
* BUGFIXES
* Adjust z-index for floating labels (#3939) (#3950)
* Add missing token validation on application settings page (#3976) #3978
* Webhook and hook_task clean up (#4006)
* Fix webhook bug of response info is not displayed in UI (#4023)
* Fix writer cannot read bare repo guide (#4033) (#4039)
* Don't force due date to current time (#3830) (#4057)
* Fix wiki redirects (#3919) (#4065)
* Fix attachment ENABLED (#4064) (#4066)
* Added deletion of an empty line at the end of file (#4054) (#4074)
* Use ResolveReference instead of path.Join (#4073)
* Fix #4081 Check for leading / in base before removing it (#4083)
* Respository's home page not updated after first push (#4075)
## [1.4.1](https://github.com/go-gitea/gitea/releases/tag/v1.4.1) - 2018-05-03
* BREAKING
* Add "error" as reserved username (#3882) (#3886)
* SECURITY
* Do not allow inactive users to access repositories using private key (#3887) (#3889)
* Fix path cleanup in file editor, when initilizing new repository and LFS oids (#3871) (#3873)
* Remove unnecessary allowed safe HTML (#3778) (#3779)
* Correctly check http git access rights for reverse proxy authorized users (#3721) (#3743)
* BUGFIXES
* Fix to use only needed columns from tables to get repository git paths (#3870) (#3883)
* Fix GPG expire time display when time is zero (#3584) (#3884)
* Fix to update only issue last update time when adding a comment (#3855) (#3860)
* Fix repository star count after deleting user (#3781) (#3783)
* Use the active branch for the code tab (#3720) (#3776)
* Set default branch name on first push (#3715) (#3723)
* Show clipboard button if disable HTTP of git protocol (#3773) (#3774)
## [1.4.0](https://github.com/go-gitea/gitea/releases/tag/v1.4.0) - 2018-03-25
* BREAKING * BREAKING
* Drop deprecated GOGS\_WORK\_DIR use (#2946) * Drop deprecated GOGS\_WORK\_DIR use (#2946)
* Fix API status code for hook creation (#2814) * Fix API status code for hook creation (#2814)
* SECURITY * SECURITY
* Escape branch name in dropdown menu (#3691) (#3692)
* Refactor and simplify to correctly validate redirect to URL (#3674) (#3676)
* Fix escaping changed title in comments (#3530) (#3534)
 * Escape search query (#3486) (#3488)
* Sanitize logs for mirror sync (#3057) * Sanitize logs for mirror sync (#3057)
* FEATURE * FEATURE
* Serve .patch and .diff for pull requests (#3305, #3293) * Serve .patch and .diff for pull requests (#3305, #3293)
@ -24,6 +72,17 @@ been added to each release, please refer to the [blog](https://blog.gitea.io).
* Add dingtalk webhook (#2777) * Add dingtalk webhook (#2777)
* Responsive view (#2750) * Responsive view (#2750)
* BUGFIXES * BUGFIXES
* Fix wiki inter-links with spaces (#3560) (#3632)
* Fix query protected branch bug (#3563) (#3571)
* Fix remove team member issue (#3566) (#3570)
* Fix the protected branch panic issue (#3567) (#3569)
* If Mirrors repository no content is fetched, updated time should not be changed (#3551) (#3565)
* Bug fix for mirrored repository releases sorted (#3522) (#3555)
* Add issue closed time column to fix activity closed issues list (#3537) (#3540)
 * Update markbates/goth library to support OAuth2 with new dropbox API (#3533) (#3539)
 * Fixes missing avatars in offline mode (#3471) (#3477)
 * Fix synchronization bug in repo indexer (#3455) (#3461)
 * Fix rendering of wiki page list if wiki repo contains other files (#3454) (#3463)
* Fix webhook X-GitHub-* headers casing for better compatibility (#3429) * Fix webhook X-GitHub-* headers casing for better compatibility (#3429)
* Add content type and doctype to requests made with go-get (#3426, #3423) * Add content type and doctype to requests made with go-get (#3426, #3423)
* Fix SQL type error for webhooks (#3424) * Fix SQL type error for webhooks (#3424)

View File

@ -230,6 +230,12 @@ func runServ(c *cli.Context) error {
fail("internal error", "Failed to get user by key ID(%d): %v", keyID, err) fail("internal error", "Failed to get user by key ID(%d): %v", keyID, err)
} }
if !user.IsActive || user.ProhibitLogin {
fail("Your account is not active or has been disabled by Administrator",
"User %s is disabled and have no access to repository %s",
user.Name, repoPath)
}
mode, err := models.AccessLevel(user.ID, repo) mode, err := models.AccessLevel(user.ID, repo)
if err != nil { if err != nil {
fail("Internal error", "Failed to check access: %v", err) fail("Internal error", "Failed to check access: %v", err)

View File

@ -403,7 +403,7 @@ ENABLE_FEDERATED_AVATAR = false
[attachment] [attachment]
; Whether attachments are enabled. Defaults to `true` ; Whether attachments are enabled. Defaults to `true`
ENABLE = true ENABLED = true
; Path for attachments. Defaults to `data/attachments` ; Path for attachments. Defaults to `data/attachments`
PATH = data/attachments PATH = data/attachments
; One or more allowed types, e.g. image/jpeg|image/png ; One or more allowed types, e.g. image/jpeg|image/png

View File

@ -128,7 +128,7 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
- `REPO_INDEXER_ENABLED`: **false**: Enables code search (uses a lot of disk space). - `REPO_INDEXER_ENABLED`: **false**: Enables code search (uses a lot of disk space).
- `REPO_INDEXER_PATH`: **indexers/repos.bleve**: Index file used for code search. - `REPO_INDEXER_PATH`: **indexers/repos.bleve**: Index file used for code search.
- `UPDATE_BUFFER_LEN`: **20**: Buffer length of index request. - `UPDATE_BUFFER_LEN`: **20**: Buffer length of index request.
- `MAX_FILE_SIZE`: **1048576**: Maximum size in bytes of each index files. - `MAX_FILE_SIZE`: **1048576**: Maximum size in bytes of files to be indexed.
## Security (`security`) ## Security (`security`)

View File

@ -2,6 +2,7 @@
date: "2016-11-08T16:00:00+02:00" date: "2016-11-08T16:00:00+02:00"
title: "Documentation" title: "Documentation"
slug: "documentation" slug: "documentation"
url: "/en-us/"
weight: 10 weight: 10
toc: true toc: true
draft: false draft: false

View File

@ -2,6 +2,7 @@
date: "2017-08-23T09:00:00+02:00" date: "2017-08-23T09:00:00+02:00"
title: "Documentation" title: "Documentation"
slug: "documentation" slug: "documentation"
url: "/fr-fr/"
weight: 10 weight: 10
toc: true toc: true
draft: false draft: false
@ -48,7 +49,7 @@ Le but de ce projet est de fournir de la manière la plus simple, la plus rapide
- Migré - Migré
- Notifications (courriel et web) - Notifications (courriel et web)
- Lu - Lu
- Non lu - Non lu
- Épinglé - Épinglé
- Page d'exploration - Page d'exploration
- Utilisateurs - Utilisateurs

View File

@ -2,6 +2,7 @@
date: "2016-11-08T16:00:00+02:00" date: "2016-11-08T16:00:00+02:00"
title: "文档" title: "文档"
slug: "documentation" slug: "documentation"
url: "/zh-cn/"
weight: 10 weight: 10
toc: true toc: true
draft: false draft: false

View File

@ -2,6 +2,7 @@
date: "2016-11-08T16:00:00+02:00" date: "2016-11-08T16:00:00+02:00"
title: "文件" title: "文件"
slug: "documentation" slug: "documentation"
url: "/zh-tw/"
weight: 10 weight: 10
toc: true toc: true
draft: false draft: false

View File

@ -28,6 +28,8 @@ for SOURCE in $(find ${ROOT}/content -type f -iname *.en-us.md); do
if [[ ! -f ${DEST} ]]; then if [[ ! -f ${DEST} ]]; then
echo "Creating fallback for ${DEST#${ROOT}/content/}" echo "Creating fallback for ${DEST#${ROOT}/content/}"
cp ${SOURCE} ${DEST} cp ${SOURCE} ${DEST}
sed -i.bak "s/en\-us/${LOCALE}/g" ${DEST}
rm ${DEST}.bak
fi fi
done done
done done

View File

@ -14,7 +14,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func testPullCreate(t *testing.T, session *TestSession, user, repo, branch string) *httptest.ResponseRecorder { func testPullCreate(t *testing.T, session *TestSession, user, repo, branch, title string) *httptest.ResponseRecorder {
req := NewRequest(t, "GET", path.Join(user, repo)) req := NewRequest(t, "GET", path.Join(user, repo))
resp := session.MakeRequest(t, req, http.StatusOK) resp := session.MakeRequest(t, req, http.StatusOK)
@ -35,7 +35,7 @@ func testPullCreate(t *testing.T, session *TestSession, user, repo, branch strin
assert.True(t, exists, "The template has changed") assert.True(t, exists, "The template has changed")
req = NewRequestWithValues(t, "POST", link, map[string]string{ req = NewRequestWithValues(t, "POST", link, map[string]string{
"_csrf": htmlDoc.GetCSRF(), "_csrf": htmlDoc.GetCSRF(),
"title": "This is a pull title", "title": title,
}) })
resp = session.MakeRequest(t, req, http.StatusFound) resp = session.MakeRequest(t, req, http.StatusFound)
@ -47,7 +47,7 @@ func TestPullCreate(t *testing.T) {
session := loginUser(t, "user1") session := loginUser(t, "user1")
testRepoFork(t, session, "user2", "repo1", "user1", "repo1") testRepoFork(t, session, "user2", "repo1", "user1", "repo1")
testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n") testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n")
resp := testPullCreate(t, session, "user1", "repo1", "master") resp := testPullCreate(t, session, "user1", "repo1", "master", "This is a pull title")
// check the redirected URL // check the redirected URL
url := resp.HeaderMap.Get("Location") url := resp.HeaderMap.Get("Location")
@ -68,3 +68,38 @@ func TestPullCreate(t *testing.T) {
assert.Regexp(t, `Subject: \[PATCH\] Update 'README.md'`, resp.Body) assert.Regexp(t, `Subject: \[PATCH\] Update 'README.md'`, resp.Body)
assert.NotRegexp(t, "diff.*diff", resp.Body) // not two diffs, just one assert.NotRegexp(t, "diff.*diff", resp.Body) // not two diffs, just one
} }
func TestPullCreate_TitleEscape(t *testing.T) {
prepareTestEnv(t)
session := loginUser(t, "user1")
testRepoFork(t, session, "user2", "repo1", "user1", "repo1")
testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n")
resp := testPullCreate(t, session, "user1", "repo1", "master", "<i>XSS PR</i>")
// check the redirected URL
url := resp.HeaderMap.Get("Location")
assert.Regexp(t, "^/user2/repo1/pulls/[0-9]*$", url)
// Edit title
req := NewRequest(t, "GET", url)
resp = session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
editTestTitleURL, exists := htmlDoc.doc.Find("#save-edit-title").First().Attr("data-update-url")
assert.True(t, exists, "The template has changed")
req = NewRequestWithValues(t, "POST", editTestTitleURL, map[string]string{
"_csrf": htmlDoc.GetCSRF(),
"title": "<u>XSS PR</u>",
})
session.MakeRequest(t, req, http.StatusOK)
req = NewRequest(t, "GET", url)
resp = session.MakeRequest(t, req, http.StatusOK)
htmlDoc = NewHTMLParser(t, resp.Body)
titleHTML, err := htmlDoc.doc.Find(".comments .event .text b").First().Html()
assert.NoError(t, err)
assert.Equal(t, "&lt;i&gt;XSS PR&lt;/i&gt;", titleHTML)
titleHTML, err = htmlDoc.doc.Find(".comments .event .text b").Next().Html()
assert.NoError(t, err)
assert.Equal(t, "&lt;u&gt;XSS PR&lt;/u&gt;", titleHTML)
}

View File

@ -56,7 +56,7 @@ func TestPullMerge(t *testing.T) {
testRepoFork(t, session, "user2", "repo1", "user1", "repo1") testRepoFork(t, session, "user2", "repo1", "user1", "repo1")
testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n") testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n")
resp := testPullCreate(t, session, "user1", "repo1", "master") resp := testPullCreate(t, session, "user1", "repo1", "master", "This is a pull title")
elem := strings.Split(test.RedirectURL(resp), "/") elem := strings.Split(test.RedirectURL(resp), "/")
assert.EqualValues(t, "pulls", elem[3]) assert.EqualValues(t, "pulls", elem[3])
@ -69,7 +69,7 @@ func TestPullRebase(t *testing.T) {
testRepoFork(t, session, "user2", "repo1", "user1", "repo1") testRepoFork(t, session, "user2", "repo1", "user1", "repo1")
testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n") testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n")
resp := testPullCreate(t, session, "user1", "repo1", "master") resp := testPullCreate(t, session, "user1", "repo1", "master", "This is a pull title")
elem := strings.Split(test.RedirectURL(resp), "/") elem := strings.Split(test.RedirectURL(resp), "/")
assert.EqualValues(t, "pulls", elem[3]) assert.EqualValues(t, "pulls", elem[3])
@ -83,7 +83,7 @@ func TestPullSquash(t *testing.T) {
testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n") testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n")
testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited!)\n") testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited!)\n")
resp := testPullCreate(t, session, "user1", "repo1", "master") resp := testPullCreate(t, session, "user1", "repo1", "master", "This is a pull title")
elem := strings.Split(test.RedirectURL(resp), "/") elem := strings.Split(test.RedirectURL(resp), "/")
assert.EqualValues(t, "pulls", elem[3]) assert.EqualValues(t, "pulls", elem[3])
@ -96,7 +96,7 @@ func TestPullCleanUpAfterMerge(t *testing.T) {
testRepoFork(t, session, "user2", "repo1", "user1", "repo1") testRepoFork(t, session, "user2", "repo1", "user1", "repo1")
testEditFileToNewBranch(t, session, "user1", "repo1", "master", "feature/test", "README.md", "Hello, World (Edited)\n") testEditFileToNewBranch(t, session, "user1", "repo1", "master", "feature/test", "README.md", "Hello, World (Edited)\n")
resp := testPullCreate(t, session, "user1", "repo1", "feature/test") resp := testPullCreate(t, session, "user1", "repo1", "feature/test", "This is a pull title")
elem := strings.Split(test.RedirectURL(resp), "/") elem := strings.Split(test.RedirectURL(resp), "/")
assert.EqualValues(t, "pulls", elem[3]) assert.EqualValues(t, "pulls", elem[3])

View File

@ -22,16 +22,16 @@ func TestRepoActivity(t *testing.T) {
// Create PRs (1 merged & 2 proposed) // Create PRs (1 merged & 2 proposed)
testRepoFork(t, session, "user2", "repo1", "user1", "repo1") testRepoFork(t, session, "user2", "repo1", "user1", "repo1")
testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n") testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n")
resp := testPullCreate(t, session, "user1", "repo1", "master") resp := testPullCreate(t, session, "user1", "repo1", "master", "This is a pull title")
elem := strings.Split(test.RedirectURL(resp), "/") elem := strings.Split(test.RedirectURL(resp), "/")
assert.EqualValues(t, "pulls", elem[3]) assert.EqualValues(t, "pulls", elem[3])
testPullMerge(t, session, elem[1], elem[2], elem[4], models.MergeStyleMerge) testPullMerge(t, session, elem[1], elem[2], elem[4], models.MergeStyleMerge)
testEditFileToNewBranch(t, session, "user1", "repo1", "master", "feat/better_readme", "README.md", "Hello, World (Edited Again)\n") testEditFileToNewBranch(t, session, "user1", "repo1", "master", "feat/better_readme", "README.md", "Hello, World (Edited Again)\n")
testPullCreate(t, session, "user1", "repo1", "feat/better_readme") testPullCreate(t, session, "user1", "repo1", "feat/better_readme", "This is a pull title")
testEditFileToNewBranch(t, session, "user1", "repo1", "master", "feat/much_better_readme", "README.md", "Hello, World (Edited More)\n") testEditFileToNewBranch(t, session, "user1", "repo1", "master", "feat/much_better_readme", "README.md", "Hello, World (Edited More)\n")
testPullCreate(t, session, "user1", "repo1", "feat/much_better_readme") testPullCreate(t, session, "user1", "repo1", "feat/much_better_readme", "This is a pull title")
// Create issues (3 new issues) // Create issues (3 new issues)
testNewIssue(t, session, "user2", "repo1", "Issue 1", "Description 1") testNewIssue(t, session, "user2", "repo1", "Issue 1", "Description 1")

View File

@ -523,6 +523,11 @@ func CommitRepoAction(opts CommitRepoActionOptions) error {
return fmt.Errorf("GetRepositoryByName [owner_id: %d, name: %s]: %v", opts.RepoOwnerID, opts.RepoName, err) return fmt.Errorf("GetRepositoryByName [owner_id: %d, name: %s]: %v", opts.RepoOwnerID, opts.RepoName, err)
} }
refName := git.RefEndName(opts.RefFullName)
if repo.IsBare && refName != repo.DefaultBranch {
repo.DefaultBranch = refName
}
// Change repository bare status and update last updated time. // Change repository bare status and update last updated time.
repo.IsBare = repo.IsBare && opts.Commits.Len <= 0 repo.IsBare = repo.IsBare && opts.Commits.Len <= 0
if err = UpdateRepository(repo, false); err != nil { if err = UpdateRepository(repo, false); err != nil {
@ -563,7 +568,6 @@ func CommitRepoAction(opts CommitRepoActionOptions) error {
return fmt.Errorf("Marshal: %v", err) return fmt.Errorf("Marshal: %v", err)
} }
refName := git.RefEndName(opts.RefFullName)
if err = NotifyWatchers(&Action{ if err = NotifyWatchers(&Action{
ActUserID: pusher.ID, ActUserID: pusher.ID,
ActUser: pusher, ActUser: pusher,
@ -742,5 +746,14 @@ func GetFeeds(opts GetFeedsOptions) ([]*Action, error) {
} }
actions := make([]*Action, 0, 20) actions := make([]*Action, 0, 20)
return actions, x.Limit(20).Desc("id").Where(cond).Find(&actions)
if err := x.Limit(20).Desc("id").Where(cond).Find(&actions); err != nil {
return nil, fmt.Errorf("Find: %v", err)
}
if err := ActionList(actions).LoadAttributes(); err != nil {
return nil, fmt.Errorf("LoadAttributes: %v", err)
}
return actions, nil
} }

98
models/action_list.go Normal file
View File

@ -0,0 +1,98 @@
// Copyright 2018 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package models
import "fmt"
// ActionList defines a list of actions
type ActionList []*Action
func (actions ActionList) getUserIDs() []int64 {
userIDs := make(map[int64]struct{}, len(actions))
for _, action := range actions {
if _, ok := userIDs[action.ActUserID]; !ok {
userIDs[action.ActUserID] = struct{}{}
}
}
return keysInt64(userIDs)
}
func (actions ActionList) loadUsers(e Engine) ([]*User, error) {
if len(actions) == 0 {
return nil, nil
}
userIDs := actions.getUserIDs()
userMaps := make(map[int64]*User, len(userIDs))
err := e.
In("id", userIDs).
Find(&userMaps)
if err != nil {
return nil, fmt.Errorf("find user: %v", err)
}
for _, action := range actions {
action.ActUser = userMaps[action.ActUserID]
}
return valuesUser(userMaps), nil
}
// LoadUsers loads actions' all users
func (actions ActionList) LoadUsers() ([]*User, error) {
return actions.loadUsers(x)
}
func (actions ActionList) getRepoIDs() []int64 {
repoIDs := make(map[int64]struct{}, len(actions))
for _, action := range actions {
if _, ok := repoIDs[action.RepoID]; !ok {
repoIDs[action.RepoID] = struct{}{}
}
}
return keysInt64(repoIDs)
}
func (actions ActionList) loadRepositories(e Engine) ([]*Repository, error) {
if len(actions) == 0 {
return nil, nil
}
repoIDs := actions.getRepoIDs()
repoMaps := make(map[int64]*Repository, len(repoIDs))
err := e.
In("id", repoIDs).
Find(&repoMaps)
if err != nil {
return nil, fmt.Errorf("find repository: %v", err)
}
for _, action := range actions {
action.Repo = repoMaps[action.RepoID]
}
return valuesRepository(repoMaps), nil
}
// LoadRepositories loads actions' all repositories
func (actions ActionList) LoadRepositories() ([]*Repository, error) {
return actions.loadRepositories(x)
}
// loadAttributes loads all attributes
func (actions ActionList) loadAttributes(e Engine) (err error) {
if _, err = actions.loadUsers(e); err != nil {
return
}
if _, err = actions.loadRepositories(e); err != nil {
return
}
return nil
}
// LoadAttributes loads attributes of the actions
func (actions ActionList) LoadAttributes() error {
return actions.loadAttributes(x)
}

View File

@ -6,7 +6,6 @@ package models
import ( import (
"fmt" "fmt"
"strings"
"time" "time"
"code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/base"
@ -70,7 +69,7 @@ func GetProtectedBranchByRepoID(RepoID int64) ([]*ProtectedBranch, error) {
// GetProtectedBranchBy getting protected branch by ID/Name // GetProtectedBranchBy getting protected branch by ID/Name
func GetProtectedBranchBy(repoID int64, BranchName string) (*ProtectedBranch, error) { func GetProtectedBranchBy(repoID int64, BranchName string) (*ProtectedBranch, error) {
rel := &ProtectedBranch{RepoID: repoID, BranchName: strings.ToLower(BranchName)} rel := &ProtectedBranch{RepoID: repoID, BranchName: BranchName}
has, err := x.Get(rel) has, err := x.Get(rel)
if err != nil { if err != nil {
return nil, err return nil, err
@ -156,6 +155,10 @@ func (repo *Repository) GetProtectedBranches() ([]*ProtectedBranch, error) {
// IsProtectedBranch checks if branch is protected // IsProtectedBranch checks if branch is protected
func (repo *Repository) IsProtectedBranch(branchName string, doer *User) (bool, error) { func (repo *Repository) IsProtectedBranch(branchName string, doer *User) (bool, error) {
if doer == nil {
return true, nil
}
protectedBranch := &ProtectedBranch{ protectedBranch := &ProtectedBranch{
RepoID: repo.ID, RepoID: repo.ID,
BranchName: branchName, BranchName: branchName,

View File

@ -216,6 +216,21 @@ func (err ErrWikiReservedName) Error() string {
return fmt.Sprintf("wiki title is reserved: %s", err.Title) return fmt.Sprintf("wiki title is reserved: %s", err.Title)
} }
// ErrWikiInvalidFileName represents an invalid wiki file name.
type ErrWikiInvalidFileName struct {
FileName string
}
// IsErrWikiInvalidFileName checks if an error is an ErrWikiInvalidFileName.
func IsErrWikiInvalidFileName(err error) bool {
_, ok := err.(ErrWikiInvalidFileName)
return ok
}
func (err ErrWikiInvalidFileName) Error() string {
return fmt.Sprintf("Invalid wiki filename: %s", err.FileName)
}
// __________ ___. .__ .__ ____ __. // __________ ___. .__ .__ ____ __.
// \______ \__ _\_ |__ | | |__| ____ | |/ _|____ ___.__. // \______ \__ _\_ |__ | | |__| ____ | |/ _|____ ___.__.
// | ___/ | \ __ \| | | |/ ___\ | <_/ __ < | | // | ___/ | \ __ \| | | |/ ___\ | <_/ __ < | |

View File

@ -14,6 +14,7 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"os/exec" "os/exec"
"strconv"
"strings" "strings"
"code.gitea.io/git" "code.gitea.io/git"
@ -368,8 +369,15 @@ func ParsePatch(maxLines, maxLineCharacters, maxFiles int, reader io.Reader) (*D
a := line[beg+2 : middle] a := line[beg+2 : middle]
b := line[middle+3:] b := line[middle+3:]
if hasQuote { if hasQuote {
a = string(git.UnescapeChars([]byte(a[1 : len(a)-1]))) var err error
b = string(git.UnescapeChars([]byte(b[1 : len(b)-1]))) a, err = strconv.Unquote(a)
if err != nil {
return nil, fmt.Errorf("Unquote: %v", err)
}
b, err = strconv.Unquote(b)
if err != nil {
return nil, fmt.Errorf("Unquote: %v", err)
}
} }
curFile = &DiffFile{ curFile = &DiffFile{

View File

@ -21,16 +21,16 @@ func assertLineEqual(t *testing.T, d1 *DiffLine, d2 *DiffLine) {
func TestDiffToHTML(t *testing.T) { func TestDiffToHTML(t *testing.T) {
assertEqual(t, "+foo <span class=\"added-code\">bar</span> biz", diffToHTML([]dmp.Diff{ assertEqual(t, "+foo <span class=\"added-code\">bar</span> biz", diffToHTML([]dmp.Diff{
{dmp.DiffEqual, "foo "}, {Type: dmp.DiffEqual, Text: "foo "},
{dmp.DiffInsert, "bar"}, {Type: dmp.DiffInsert, Text: "bar"},
{dmp.DiffDelete, " baz"}, {Type: dmp.DiffDelete, Text: " baz"},
{dmp.DiffEqual, " biz"}, {Type: dmp.DiffEqual, Text: " biz"},
}, DiffLineAdd)) }, DiffLineAdd))
assertEqual(t, "-foo <span class=\"removed-code\">bar</span> biz", diffToHTML([]dmp.Diff{ assertEqual(t, "-foo <span class=\"removed-code\">bar</span> biz", diffToHTML([]dmp.Diff{
{dmp.DiffEqual, "foo "}, {Type: dmp.DiffEqual, Text: "foo "},
{dmp.DiffDelete, "bar"}, {Type: dmp.DiffDelete, Text: "bar"},
{dmp.DiffInsert, " baz"}, {Type: dmp.DiffInsert, Text: " baz"},
{dmp.DiffEqual, " biz"}, {Type: dmp.DiffEqual, Text: " biz"},
}, DiffLineDel)) }, DiffLineDel))
} }

View File

@ -49,6 +49,7 @@ type Issue struct {
DeadlineUnix util.TimeStamp `xorm:"INDEX"` DeadlineUnix util.TimeStamp `xorm:"INDEX"`
CreatedUnix util.TimeStamp `xorm:"INDEX created"` CreatedUnix util.TimeStamp `xorm:"INDEX created"`
UpdatedUnix util.TimeStamp `xorm:"INDEX updated"` UpdatedUnix util.TimeStamp `xorm:"INDEX updated"`
ClosedUnix util.TimeStamp `xorm:"INDEX"`
Attachments []*Attachment `xorm:"-"` Attachments []*Attachment `xorm:"-"`
Comments []*Comment `xorm:"-"` Comments []*Comment `xorm:"-"`
@ -612,8 +613,13 @@ func (issue *Issue) changeStatus(e *xorm.Session, doer *User, repo *Repository,
return nil return nil
} }
issue.IsClosed = isClosed issue.IsClosed = isClosed
if isClosed {
issue.ClosedUnix = util.TimeStampNow()
} else {
issue.ClosedUnix = 0
}
if err = updateIssueCols(e, issue, "is_closed"); err != nil { if err = updateIssueCols(e, issue, "is_closed", "closed_unix"); err != nil {
return err return err
} }

View File

@ -132,6 +132,10 @@ func (c *Comment) AfterLoad(session *xorm.Session) {
// AfterDelete is invoked from XORM after the object is deleted. // AfterDelete is invoked from XORM after the object is deleted.
func (c *Comment) AfterDelete() { func (c *Comment) AfterDelete() {
if c.ID <= 0 {
return
}
_, err := DeleteAttachmentsByComment(c.ID, true) _, err := DeleteAttachmentsByComment(c.ID, true)
if err != nil { if err != nil {
@ -418,7 +422,7 @@ func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err
} }
// update the issue's updated_unix column // update the issue's updated_unix column
if err = updateIssueCols(e, opts.Issue); err != nil { if err = updateIssueCols(e, opts.Issue, "updated_unix"); err != nil {
return nil, err return nil, err
} }

View File

@ -31,11 +31,6 @@ type Milestone struct {
ClosedDateUnix util.TimeStamp ClosedDateUnix util.TimeStamp
} }
// BeforeInsert is invoked from XORM before inserting an object of this type.
func (m *Milestone) BeforeInsert() {
m.DeadlineUnix = util.TimeStampNow()
}
// BeforeUpdate is invoked from XORM before updating this object. // BeforeUpdate is invoked from XORM before updating this object.
func (m *Milestone) BeforeUpdate() { func (m *Milestone) BeforeUpdate() {
if m.NumIssues > 0 { if m.NumIssues > 0 {

View File

@ -214,13 +214,15 @@ func TestChangeMilestoneIssueStats(t *testing.T) {
"is_closed=0").(*Issue) "is_closed=0").(*Issue)
issue.IsClosed = true issue.IsClosed = true
_, err := x.Cols("is_closed").Update(issue) issue.ClosedUnix = util.TimeStampNow()
_, err := x.Cols("is_closed", "closed_unix").Update(issue)
assert.NoError(t, err) assert.NoError(t, err)
assert.NoError(t, changeMilestoneIssueStats(x.NewSession(), issue)) assert.NoError(t, changeMilestoneIssueStats(x.NewSession(), issue))
CheckConsistencyFor(t, &Milestone{}) CheckConsistencyFor(t, &Milestone{})
issue.IsClosed = false issue.IsClosed = false
_, err = x.Cols("is_closed").Update(issue) issue.ClosedUnix = 0
_, err = x.Cols("is_closed", "closed_unix").Update(issue)
assert.NoError(t, err) assert.NoError(t, err)
assert.NoError(t, changeMilestoneIssueStats(x.NewSession(), issue)) assert.NoError(t, changeMilestoneIssueStats(x.NewSession(), issue))
CheckConsistencyFor(t, &Milestone{}) CheckConsistencyFor(t, &Milestone{})

View File

@ -166,6 +166,8 @@ var migrations = []Migration{
NewMigration("add writable deploy keys", addModeToDeploKeys), NewMigration("add writable deploy keys", addModeToDeploKeys),
// v56 -> v57 // v56 -> v57
NewMigration("remove is_owner, num_teams columns from org_user", removeIsOwnerColumnFromOrgUser), NewMigration("remove is_owner, num_teams columns from org_user", removeIsOwnerColumnFromOrgUser),
// v57 -> v58
NewMigration("add closed_unix column for issues", addIssueClosedTime),
} }
// Migrate database to current version // Migrate database to current version
@ -215,6 +217,66 @@ Please try to upgrade to a lower version (>= v0.6.0) first, then upgrade to curr
return nil return nil
} }
func dropTableColumns(x *xorm.Engine, tableName string, columnNames ...string) (err error) {
if tableName == "" || len(columnNames) == 0 {
return nil
}
switch {
case setting.UseSQLite3:
log.Warn("Unable to drop columns in SQLite")
case setting.UseMySQL, setting.UseTiDB, setting.UsePostgreSQL:
cols := ""
for _, col := range columnNames {
if cols != "" {
cols += ", "
}
cols += "DROP COLUMN `" + col + "`"
}
if _, err := x.Exec(fmt.Sprintf("ALTER TABLE `%s` %s", tableName, cols)); err != nil {
return fmt.Errorf("Drop table `%s` columns %v: %v", tableName, columnNames, err)
}
case setting.UseMSSQL:
sess := x.NewSession()
defer sess.Close()
if err = sess.Begin(); err != nil {
return err
}
cols := ""
for _, col := range columnNames {
if cols != "" {
cols += ", "
}
cols += "`" + strings.ToLower(col) + "`"
}
sql := fmt.Sprintf("SELECT Name FROM SYS.DEFAULT_CONSTRAINTS WHERE PARENT_OBJECT_ID = OBJECT_ID('%[1]s') AND PARENT_COLUMN_ID IN (SELECT column_id FROM sys.columns WHERE lower(NAME) IN (%[2]s) AND object_id = OBJECT_ID('%[1]s'))",
tableName, strings.Replace(cols, "`", "'", -1))
constraints := make([]string, 0)
if err := sess.SQL(sql).Find(&constraints); err != nil {
sess.Rollback()
return fmt.Errorf("Find constraints: %v", err)
}
for _, constraint := range constraints {
if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` DROP CONSTRAINT `%s`", tableName, constraint)); err != nil {
sess.Rollback()
return fmt.Errorf("Drop table `%s` constraint `%s`: %v", tableName, constraint, err)
}
}
if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` DROP COLUMN %s", tableName, cols)); err != nil {
sess.Rollback()
return fmt.Errorf("Drop table `%s` columns %v: %v", tableName, columnNames, err)
}
return sess.Commit()
default:
log.Fatal(4, "Unrecognized DB")
}
return nil
}
func fixLocaleFileLoadPanic(_ *xorm.Engine) error { func fixLocaleFileLoadPanic(_ *xorm.Engine) error {
cfg, err := ini.Load(setting.CustomConf) cfg, err := ini.Load(setting.CustomConf)
if err != nil { if err != nil {

View File

@ -5,29 +5,9 @@
package migrations package migrations
import ( import (
"fmt"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"github.com/go-xorm/xorm" "github.com/go-xorm/xorm"
) )
func removeIsOwnerColumnFromOrgUser(x *xorm.Engine) (err error) { func removeIsOwnerColumnFromOrgUser(x *xorm.Engine) (err error) {
switch { return dropTableColumns(x, "org_user", "is_owner", "num_teams")
case setting.UseSQLite3:
log.Warn("Unable to drop columns in SQLite")
case setting.UseMySQL, setting.UseTiDB, setting.UsePostgreSQL:
if _, err := x.Exec("ALTER TABLE org_user DROP COLUMN is_owner, DROP COLUMN num_teams"); err != nil {
return fmt.Errorf("DROP COLUMN org_user.is_owner, org_user.num_teams: %v", err)
}
case setting.UseMSSQL:
if _, err := x.Exec("ALTER TABLE org_user DROP COLUMN is_owner, num_teams"); err != nil {
return fmt.Errorf("DROP COLUMN org_user.is_owner, org_user.num_teams: %v", err)
}
default:
log.Fatal(4, "Unrecognized DB")
}
return nil
} }

30
models/migrations/v57.go Normal file
View File

@ -0,0 +1,30 @@
// Copyright 2017 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package migrations
import (
"fmt"
"code.gitea.io/gitea/modules/util"
"github.com/go-xorm/xorm"
)
func addIssueClosedTime(x *xorm.Engine) error {
// Issue see models/issue.go
type Issue struct {
ClosedUnix util.TimeStamp `xorm:"INDEX"`
}
if err := x.Sync2(new(Issue)); err != nil {
return fmt.Errorf("Sync2: %v", err)
}
if _, err := x.Exec("UPDATE `issue` SET `closed_unix` = `updated_unix` WHERE `is_closed` = ?", true); err != nil {
return err
}
return nil
}

View File

@ -436,8 +436,7 @@ func AddOrgUser(orgID, uid int64) error {
return sess.Commit() return sess.Commit()
} }
// RemoveOrgUser removes user from given organization. func removeOrgUser(sess *xorm.Session, orgID, userID int64) error {
func RemoveOrgUser(orgID, userID int64) error {
ou := new(OrgUser) ou := new(OrgUser)
has, err := x. has, err := x.
@ -473,12 +472,6 @@ func RemoveOrgUser(orgID, userID int64) error {
} }
} }
sess := x.NewSession()
defer sess.Close()
if err := sess.Begin(); err != nil {
return err
}
if _, err := sess.ID(ou.ID).Delete(ou); err != nil { if _, err := sess.ID(ou.ID).Delete(ou); err != nil {
return err return err
} else if _, err = sess.Exec("UPDATE `user` SET num_members=num_members-1 WHERE id=?", orgID); err != nil { } else if _, err = sess.Exec("UPDATE `user` SET num_members=num_members-1 WHERE id=?", orgID); err != nil {
@ -520,6 +513,19 @@ func RemoveOrgUser(orgID, userID int64) error {
} }
} }
return nil
}
// RemoveOrgUser removes user from given organization.
func RemoveOrgUser(orgID, userID int64) error {
sess := x.NewSession()
defer sess.Close()
if err := sess.Begin(); err != nil {
return err
}
if err := removeOrgUser(sess, orgID, userID); err != nil {
return err
}
return sess.Commit() return sess.Commit()
} }

View File

@ -10,6 +10,8 @@ import (
"strings" "strings"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"github.com/go-xorm/xorm"
) )
const ownerTeamName = "Owners" const ownerTeamName = "Owners"
@ -521,7 +523,7 @@ func AddTeamMember(team *Team, userID int64) error {
return sess.Commit() return sess.Commit()
} }
func removeTeamMember(e Engine, team *Team, userID int64) error { func removeTeamMember(e *xorm.Session, team *Team, userID int64) error {
isMember, err := isTeamMember(e, team.OrgID, team.ID, userID) isMember, err := isTeamMember(e, team.OrgID, team.ID, userID)
if err != nil || !isMember { if err != nil || !isMember {
return err return err
@ -558,6 +560,16 @@ func removeTeamMember(e Engine, team *Team, userID int64) error {
} }
} }
// Check if the user is a member of any team in the organization.
if count, err := e.Count(&TeamUser{
UID: userID,
OrgID: team.OrgID,
}); err != nil {
return err
} else if count == 0 {
return removeOrgUser(e, team.OrgID, userID)
}
return nil return nil
} }

View File

@ -36,7 +36,7 @@ type Release struct {
IsPrerelease bool `xorm:"NOT NULL DEFAULT false"` IsPrerelease bool `xorm:"NOT NULL DEFAULT false"`
IsTag bool `xorm:"NOT NULL DEFAULT false"` IsTag bool `xorm:"NOT NULL DEFAULT false"`
Attachments []*Attachment `xorm:"-"` Attachments []*Attachment `xorm:"-"`
CreatedUnix util.TimeStamp `xorm:"created INDEX"` CreatedUnix util.TimeStamp `xorm:"INDEX"`
} }
func (r *Release) loadAttributes(e Engine) error { func (r *Release) loadAttributes(e Engine) error {
@ -134,6 +134,8 @@ func createTag(gitRepo *git.Repository, rel *Release) error {
if err != nil { if err != nil {
return fmt.Errorf("CommitsCount: %v", err) return fmt.Errorf("CommitsCount: %v", err)
} }
} else {
rel.CreatedUnix = util.TimeStampNow()
} }
return nil return nil
} }

View File

@ -163,6 +163,7 @@ func NewRepoContext() {
type Repository struct { type Repository struct {
ID int64 `xorm:"pk autoincr"` ID int64 `xorm:"pk autoincr"`
OwnerID int64 `xorm:"UNIQUE(s)"` OwnerID int64 `xorm:"UNIQUE(s)"`
OwnerName string `xorm:"-"`
Owner *User `xorm:"-"` Owner *User `xorm:"-"`
LowerName string `xorm:"UNIQUE(s) INDEX NOT NULL"` LowerName string `xorm:"UNIQUE(s) INDEX NOT NULL"`
Name string `xorm:"INDEX NOT NULL"` Name string `xorm:"INDEX NOT NULL"`
@ -223,9 +224,17 @@ func (repo *Repository) MustOwner() *User {
return repo.mustOwner(x) return repo.mustOwner(x)
} }
// MustOwnerName always returns valid owner name to avoid
// conceptually impossible error handling.
// It returns "error" and logs error details when error
// occurs.
func (repo *Repository) MustOwnerName() string {
return repo.mustOwnerName(x)
}
// FullName returns the repository full name // FullName returns the repository full name
func (repo *Repository) FullName() string { func (repo *Repository) FullName() string {
return repo.MustOwner().Name + "/" + repo.Name return repo.MustOwnerName() + "/" + repo.Name
} }
// HTMLURL returns the repository HTML URL // HTMLURL returns the repository HTML URL
@ -477,6 +486,41 @@ func (repo *Repository) mustOwner(e Engine) *User {
return repo.Owner return repo.Owner
} }
func (repo *Repository) getOwnerName(e Engine) error {
if len(repo.OwnerName) > 0 {
return nil
}
if repo.Owner != nil {
repo.OwnerName = repo.Owner.Name
return nil
}
u := new(User)
has, err := e.ID(repo.OwnerID).Cols("name").Get(u)
if err != nil {
return err
} else if !has {
return ErrUserNotExist{repo.OwnerID, "", 0}
}
repo.OwnerName = u.Name
return nil
}
// GetOwnerName returns the repository owner name
func (repo *Repository) GetOwnerName() error {
return repo.getOwnerName(x)
}
func (repo *Repository) mustOwnerName(e Engine) string {
if err := repo.getOwnerName(e); err != nil {
log.Error(4, "Error loading repository owner name: %v", err)
return "error"
}
return repo.OwnerName
}
// ComposeMetas composes a map of metas for rendering external issue tracker URL. // ComposeMetas composes a map of metas for rendering external issue tracker URL.
func (repo *Repository) ComposeMetas() map[string]string { func (repo *Repository) ComposeMetas() map[string]string {
unit, err := repo.GetUnit(UnitTypeExternalTracker) unit, err := repo.GetUnit(UnitTypeExternalTracker)
@ -588,7 +632,7 @@ func (repo *Repository) GetBaseRepo() (err error) {
} }
func (repo *Repository) repoPath(e Engine) string { func (repo *Repository) repoPath(e Engine) string {
return RepoPath(repo.mustOwner(e).Name, repo.Name) return RepoPath(repo.mustOwnerName(e), repo.Name)
} }
// RepoPath returns the repository path // RepoPath returns the repository path
@ -1131,7 +1175,7 @@ type CreateRepoOptions struct {
} }
func getRepoInitFile(tp, name string) ([]byte, error) { func getRepoInitFile(tp, name string) ([]byte, error) {
cleanedName := strings.TrimLeft(name, "./") cleanedName := strings.TrimLeft(path.Clean("/"+name), "/")
relPath := path.Join("options", tp, cleanedName) relPath := path.Join("options", tp, cleanedName)
// Use custom file when available. // Use custom file when available.
@ -1778,6 +1822,8 @@ func DeleteRepository(doer *User, uid, repoID int64) error {
&PullRequest{BaseRepoID: repoID}, &PullRequest{BaseRepoID: repoID},
&RepoUnit{RepoID: repoID}, &RepoUnit{RepoID: repoID},
&RepoRedirect{RedirectRepoID: repoID}, &RepoRedirect{RedirectRepoID: repoID},
&Webhook{RepoID: repoID},
&HookTask{RepoID: repoID},
); err != nil { ); err != nil {
return fmt.Errorf("deleteBeans: %v", err) return fmt.Errorf("deleteBeans: %v", err)
} }
@ -1800,6 +1846,9 @@ func DeleteRepository(doer *User, uid, repoID int64) error {
if _, err = sess.In("issue_id", issueIDs).Delete(&IssueUser{}); err != nil { if _, err = sess.In("issue_id", issueIDs).Delete(&IssueUser{}); err != nil {
return err return err
} }
if _, err = sess.In("issue_id", issueIDs).Delete(&Reaction{}); err != nil {
return err
}
attachments := make([]*Attachment, 0, 5) attachments := make([]*Attachment, 0, 5)
if err = sess. if err = sess.
@ -2133,7 +2182,7 @@ func ReinitMissingRepositories() error {
// SyncRepositoryHooks rewrites all repositories' pre-receive, update and post-receive hooks // SyncRepositoryHooks rewrites all repositories' pre-receive, update and post-receive hooks
// to make sure the binary and custom conf path are up-to-date. // to make sure the binary and custom conf path are up-to-date.
func SyncRepositoryHooks() error { func SyncRepositoryHooks() error {
return x.Where("id > 0").Iterate(new(Repository), return x.Cols("owner_id", "name").Where("id > 0").Iterate(new(Repository),
func(idx int, bean interface{}) error { func(idx int, bean interface{}) error {
if err := createDelegateHooks(bean.(*Repository).RepoPath()); err != nil { if err := createDelegateHooks(bean.(*Repository).RepoPath()); err != nil {
return fmt.Errorf("SyncRepositoryHook: %v", err) return fmt.Errorf("SyncRepositoryHook: %v", err)

View File

@ -176,7 +176,7 @@ func (stats *ActivityStats) FillIssues(repoID int64, fromTime time.Time) error {
// Closed issues // Closed issues
sess := issuesForActivityStatement(repoID, fromTime, true, false) sess := issuesForActivityStatement(repoID, fromTime, true, false)
sess.OrderBy("issue.updated_unix DESC") sess.OrderBy("issue.closed_unix DESC")
stats.ClosedIssues = make(IssueList, 0) stats.ClosedIssues = make(IssueList, 0)
if err = sess.Find(&stats.ClosedIssues); err != nil { if err = sess.Find(&stats.ClosedIssues); err != nil {
return err return err
@ -228,7 +228,11 @@ func issuesForActivityStatement(repoID int64, fromTime time.Time, closed, unreso
if !unresolved { if !unresolved {
sess.And("issue.is_pull = ?", false) sess.And("issue.is_pull = ?", false)
sess.And("issue.created_unix >= ?", fromTime.Unix()) if closed {
sess.And("issue.closed_unix >= ?", fromTime.Unix())
} else {
sess.And("issue.created_unix >= ?", fromTime.Unix())
}
} else { } else {
sess.And("issue.created_unix < ?", fromTime.Unix()) sess.And("issue.created_unix < ?", fromTime.Unix())
sess.And("issue.updated_unix >= ?", fromTime.Unix()) sess.And("issue.updated_unix >= ?", fromTime.Unix())

View File

@ -5,9 +5,7 @@
package models package models
import ( import (
"io/ioutil" "fmt"
"os"
"path"
"strconv" "strconv"
"strings" "strings"
@ -16,8 +14,6 @@ import (
"code.gitea.io/gitea/modules/indexer" "code.gitea.io/gitea/modules/indexer"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"github.com/Unknwon/com"
) )
// RepoIndexerStatus status of a repo's entry in the repo indexer // RepoIndexerStatus status of a repo's entry in the repo indexer
@ -132,7 +128,11 @@ func populateRepoIndexer(maxRepoID int64) {
} }
func updateRepoIndexer(repo *Repository) error { func updateRepoIndexer(repo *Repository) error {
changes, err := getRepoChanges(repo) sha, err := getDefaultBranchSha(repo)
if err != nil {
return err
}
changes, err := getRepoChanges(repo, sha)
if err != nil { if err != nil {
return err return err
} else if changes == nil { } else if changes == nil {
@ -140,12 +140,12 @@ func updateRepoIndexer(repo *Repository) error {
} }
batch := indexer.RepoIndexerBatch() batch := indexer.RepoIndexerBatch()
for _, filename := range changes.UpdatedFiles { for _, update := range changes.Updates {
if err := addUpdate(filename, repo, batch); err != nil { if err := addUpdate(update, repo, batch); err != nil {
return err return err
} }
} }
for _, filename := range changes.RemovedFiles { for _, filename := range changes.RemovedFilenames {
if err := addDelete(filename, repo, batch); err != nil { if err := addDelete(filename, repo, batch); err != nil {
return err return err
} }
@ -153,56 +153,61 @@ func updateRepoIndexer(repo *Repository) error {
if err = batch.Flush(); err != nil { if err = batch.Flush(); err != nil {
return err return err
} }
return updateLastIndexSync(repo) return repo.updateIndexerStatus(sha)
} }
// repoChanges changes (file additions/updates/removals) to a repo // repoChanges changes (file additions/updates/removals) to a repo
type repoChanges struct { type repoChanges struct {
UpdatedFiles []string Updates []fileUpdate
RemovedFiles []string RemovedFilenames []string
}
type fileUpdate struct {
Filename string
BlobSha string
}
func getDefaultBranchSha(repo *Repository) (string, error) {
stdout, err := git.NewCommand("show-ref", "-s", repo.DefaultBranch).RunInDir(repo.RepoPath())
if err != nil {
return "", err
}
return strings.TrimSpace(stdout), nil
} }
// getRepoChanges returns changes to repo since last indexer update // getRepoChanges returns changes to repo since last indexer update
func getRepoChanges(repo *Repository) (*repoChanges, error) { func getRepoChanges(repo *Repository, revision string) (*repoChanges, error) {
repoWorkingPool.CheckIn(com.ToStr(repo.ID)) if err := repo.getIndexerStatus(); err != nil {
defer repoWorkingPool.CheckOut(com.ToStr(repo.ID))
if err := repo.UpdateLocalCopyBranch(""); err != nil {
return nil, err
} else if !git.IsBranchExist(repo.LocalCopyPath(), repo.DefaultBranch) {
// repo does not have any commits yet, so nothing to update
return nil, nil
} else if err = repo.UpdateLocalCopyBranch(repo.DefaultBranch); err != nil {
return nil, err
} else if err = repo.getIndexerStatus(); err != nil {
return nil, err return nil, err
} }
if len(repo.IndexerStatus.CommitSha) == 0 { if len(repo.IndexerStatus.CommitSha) == 0 {
return genesisChanges(repo) return genesisChanges(repo, revision)
} }
return nonGenesisChanges(repo) return nonGenesisChanges(repo, revision)
} }
func addUpdate(filename string, repo *Repository, batch *indexer.Batch) error { func addUpdate(update fileUpdate, repo *Repository, batch *indexer.Batch) error {
filepath := path.Join(repo.LocalCopyPath(), filename) stdout, err := git.NewCommand("cat-file", "-s", update.BlobSha).
if stat, err := os.Stat(filepath); err != nil { RunInDir(repo.RepoPath())
if err != nil {
return err return err
} else if stat.Size() > setting.Indexer.MaxIndexerFileSize { }
return nil if size, err := strconv.Atoi(strings.TrimSpace(stdout)); err != nil {
} else if stat.IsDir() { return fmt.Errorf("Misformatted git cat-file output: %v", err)
// file could actually be a directory, if it is the root of a submodule. } else if int64(size) > setting.Indexer.MaxIndexerFileSize {
// We do not index submodule contents, so don't do anything.
return nil return nil
} }
fileContents, err := ioutil.ReadFile(filepath)
fileContents, err := git.NewCommand("cat-file", "blob", update.BlobSha).
RunInDirBytes(repo.RepoPath())
if err != nil { if err != nil {
return err return err
} else if !base.IsTextFile(fileContents) { } else if !base.IsTextFile(fileContents) {
return nil return nil
} }
return batch.Add(indexer.RepoIndexerUpdate{ return batch.Add(indexer.RepoIndexerUpdate{
Filepath: filename, Filepath: update.Filename,
Op: indexer.RepoIndexerOpUpdate, Op: indexer.RepoIndexerOpUpdate,
Data: &indexer.RepoIndexerData{ Data: &indexer.RepoIndexerData{
RepoID: repo.ID, RepoID: repo.ID,
@ -221,42 +226,76 @@ func addDelete(filename string, repo *Repository, batch *indexer.Batch) error {
}) })
} }
// genesisChanges get changes to add repo to the indexer for the first time // parseGitLsTreeOutput parses the output of a `git ls-tree -r --full-name` command
func genesisChanges(repo *Repository) (*repoChanges, error) { func parseGitLsTreeOutput(stdout string) ([]fileUpdate, error) {
var changes repoChanges lines := strings.Split(stdout, "\n")
stdout, err := git.NewCommand("ls-files").RunInDir(repo.LocalCopyPath()) updates := make([]fileUpdate, 0, len(lines))
if err != nil { for _, line := range lines {
return nil, err // expect line to be "<mode> <object-type> <object-sha>\t<filename>"
} line = strings.TrimSpace(line)
for _, line := range strings.Split(stdout, "\n") { if len(line) == 0 {
filename := strings.TrimSpace(line)
if len(filename) == 0 {
continue continue
} else if filename[0] == '"' { }
firstSpaceIndex := strings.IndexByte(line, ' ')
if firstSpaceIndex < 0 {
log.Error(4, "Misformatted git ls-tree output: %s", line)
continue
}
tabIndex := strings.IndexByte(line, '\t')
if tabIndex < 42+firstSpaceIndex || tabIndex == len(line)-1 {
log.Error(4, "Misformatted git ls-tree output: %s", line)
continue
}
if objectType := line[firstSpaceIndex+1 : tabIndex-41]; objectType != "blob" {
// submodules appear as commit objects, we do not index submodules
continue
}
blobSha := line[tabIndex-40 : tabIndex]
filename := line[tabIndex+1:]
if filename[0] == '"' {
var err error
filename, err = strconv.Unquote(filename) filename, err = strconv.Unquote(filename)
if err != nil { if err != nil {
return nil, err return nil, err
} }
} }
changes.UpdatedFiles = append(changes.UpdatedFiles, filename) updates = append(updates, fileUpdate{
Filename: filename,
BlobSha: blobSha,
})
} }
return &changes, nil return updates, nil
}
// genesisChanges get changes to add repo to the indexer for the first time
func genesisChanges(repo *Repository, revision string) (*repoChanges, error) {
var changes repoChanges
stdout, err := git.NewCommand("ls-tree", "--full-tree", "-r", revision).
RunInDir(repo.RepoPath())
if err != nil {
return nil, err
}
changes.Updates, err = parseGitLsTreeOutput(stdout)
return &changes, err
} }
// nonGenesisChanges get changes since the previous indexer update // nonGenesisChanges get changes since the previous indexer update
func nonGenesisChanges(repo *Repository) (*repoChanges, error) { func nonGenesisChanges(repo *Repository, revision string) (*repoChanges, error) {
diffCmd := git.NewCommand("diff", "--name-status", diffCmd := git.NewCommand("diff", "--name-status",
repo.IndexerStatus.CommitSha, "HEAD") repo.IndexerStatus.CommitSha, revision)
stdout, err := diffCmd.RunInDir(repo.LocalCopyPath()) stdout, err := diffCmd.RunInDir(repo.RepoPath())
if err != nil { if err != nil {
// previous commit sha may have been removed by a force push, so // previous commit sha may have been removed by a force push, so
// try rebuilding from scratch // try rebuilding from scratch
log.Warn("git diff: %v", err)
if err = indexer.DeleteRepoFromIndexer(repo.ID); err != nil { if err = indexer.DeleteRepoFromIndexer(repo.ID); err != nil {
return nil, err return nil, err
} }
return genesisChanges(repo) return genesisChanges(repo, revision)
} }
var changes repoChanges var changes repoChanges
updatedFilenames := make([]string, 0, 10)
for _, line := range strings.Split(stdout, "\n") { for _, line := range strings.Split(stdout, "\n") {
line = strings.TrimSpace(line) line = strings.TrimSpace(line)
if len(line) == 0 { if len(line) == 0 {
@ -274,23 +313,22 @@ func nonGenesisChanges(repo *Repository) (*repoChanges, error) {
switch status := line[0]; status { switch status := line[0]; status {
case 'M', 'A': case 'M', 'A':
changes.UpdatedFiles = append(changes.UpdatedFiles, filename) updatedFilenames = append(updatedFilenames, filename)
case 'D': case 'D':
changes.RemovedFiles = append(changes.RemovedFiles, filename) changes.RemovedFilenames = append(changes.RemovedFilenames, filename)
default: default:
log.Warn("Unrecognized status: %c (line=%s)", status, line) log.Warn("Unrecognized status: %c (line=%s)", status, line)
} }
} }
return &changes, nil
}
func updateLastIndexSync(repo *Repository) error { cmd := git.NewCommand("ls-tree", "--full-tree", revision, "--")
stdout, err := git.NewCommand("rev-parse", "HEAD").RunInDir(repo.LocalCopyPath()) cmd.AddArguments(updatedFilenames...)
stdout, err = cmd.RunInDir(repo.RepoPath())
if err != nil { if err != nil {
return err return nil, err
} }
sha := strings.TrimSpace(stdout) changes.Updates, err = parseGitLsTreeOutput(stdout)
return repo.updateIndexerStatus(sha) return &changes, err
} }
func processRepoIndexerOperationQueue() { func processRepoIndexerOperationQueue() {

View File

@ -244,6 +244,8 @@ func MirrorUpdate() {
// SyncMirrors checks and syncs mirrors. // SyncMirrors checks and syncs mirrors.
// TODO: sync more mirrors at same time. // TODO: sync more mirrors at same time.
func SyncMirrors() { func SyncMirrors() {
sess := x.NewSession()
defer sess.Close()
// Start listening on new sync requests. // Start listening on new sync requests.
for repoID := range MirrorQueue.Queue() { for repoID := range MirrorQueue.Queue() {
log.Trace("SyncMirrors [repo_id: %v]", repoID) log.Trace("SyncMirrors [repo_id: %v]", repoID)
@ -260,10 +262,22 @@ func SyncMirrors() {
} }
m.ScheduleNextUpdate() m.ScheduleNextUpdate()
if err = UpdateMirror(m); err != nil { if err = updateMirror(sess, m); err != nil {
log.Error(4, "UpdateMirror [%s]: %v", repoID, err) log.Error(4, "UpdateMirror [%s]: %v", repoID, err)
continue continue
} }
// Get latest commit date and update to current repository updated time
commitDate, err := git.GetLatestCommitTime(m.Repo.RepoPath())
if err != nil {
log.Error(2, "GetLatestCommitDate [%s]: %v", m.RepoID, err)
continue
}
if _, err = sess.Exec("UPDATE repository SET updated_unix = ? WHERE id = ?", commitDate.Unix(), m.RepoID); err != nil {
log.Error(2, "Update repository 'updated_unix' [%s]: %v", m.RepoID, err)
continue
}
} }
} }

View File

@ -9,6 +9,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"testing" "testing"
"time"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
@ -72,6 +73,18 @@ func createTestEngine(fixturesDir string) error {
return InitFixtures(&testfixtures.SQLite{}, fixturesDir) return InitFixtures(&testfixtures.SQLite{}, fixturesDir)
} }
func removeAllWithRetry(dir string) error {
var err error
for i := 0; i < 20; i++ {
err = os.RemoveAll(dir)
if err == nil {
break
}
time.Sleep(100 * time.Millisecond)
}
return err
}
// PrepareTestDatabase load test fixtures into test database // PrepareTestDatabase load test fixtures into test database
func PrepareTestDatabase() error { func PrepareTestDatabase() error {
return LoadFixtures() return LoadFixtures()
@ -81,7 +94,7 @@ func PrepareTestDatabase() error {
// by tests that use the above MainTest(..) function. // by tests that use the above MainTest(..) function.
func PrepareTestEnv(t testing.TB) { func PrepareTestEnv(t testing.TB) {
assert.NoError(t, PrepareTestDatabase()) assert.NoError(t, PrepareTestDatabase())
assert.NoError(t, os.RemoveAll(setting.RepoRootPath)) assert.NoError(t, removeAllWithRetry(setting.RepoRootPath))
metaPath := filepath.Join(giteaRoot, "integrations", "gitea-repositories-meta") metaPath := filepath.Join(giteaRoot, "integrations", "gitea-repositories-meta")
assert.NoError(t, com.CopyDir(metaPath, setting.RepoRootPath)) assert.NoError(t, com.CopyDir(metaPath, setting.RepoRootPath))
} }

View File

@ -299,7 +299,9 @@ func (u *User) generateRandomAvatar(e Engine) error {
} }
// NOTICE for random avatar, it still uses id as avatar name, but custom avatar use md5 // NOTICE for random avatar, it still uses id as avatar name, but custom avatar use md5
// since random image is not a user's photo, there is no security for enumable // since random image is not a user's photo, there is no security for enumable
u.Avatar = fmt.Sprintf("%d", u.ID) if u.Avatar == "" {
u.Avatar = fmt.Sprintf("%d", u.ID)
}
if err = os.MkdirAll(filepath.Dir(u.CustomAvatarPath()), os.ModePerm); err != nil { if err = os.MkdirAll(filepath.Dir(u.CustomAvatarPath()), os.ModePerm); err != nil {
return fmt.Errorf("MkdirAll: %v", err) return fmt.Errorf("MkdirAll: %v", err)
} }
@ -649,7 +651,7 @@ func NewGhostUser() *User {
} }
var ( var (
reservedUsernames = []string{"assets", "css", "explore", "img", "js", "less", "plugins", "debug", "raw", "install", "api", "avatars", "user", "org", "help", "stars", "issues", "pulls", "commits", "repo", "template", "admin", "new", ".", ".."} reservedUsernames = []string{"assets", "css", "explore", "img", "js", "less", "plugins", "debug", "raw", "install", "api", "avatars", "user", "org", "help", "stars", "issues", "pulls", "commits", "repo", "template", "admin", "error", "new", ".", ".."}
reservedUserPatterns = []string{"*.keys"} reservedUserPatterns = []string{"*.keys"}
) )
@ -932,7 +934,7 @@ func deleteUser(e *xorm.Session, u *User) error {
if err = e.Table("star").Cols("star.repo_id"). if err = e.Table("star").Cols("star.repo_id").
Where("star.uid = ?", u.ID).Find(&starredRepoIDs); err != nil { Where("star.uid = ?", u.ID).Find(&starredRepoIDs); err != nil {
return fmt.Errorf("get all stars: %v", err) return fmt.Errorf("get all stars: %v", err)
} else if _, err = e.Decr("num_watches").In("id", starredRepoIDs).Update(new(Repository)); err != nil { } else if _, err = e.Decr("num_stars").In("id", starredRepoIDs).Update(new(Repository)); err != nil {
return fmt.Errorf("decrease repository num_stars: %v", err) return fmt.Errorf("decrease repository num_stars: %v", err)
} }
// ***** END: Star ***** // ***** END: Star *****

View File

@ -437,7 +437,14 @@ func (t *HookTask) AfterLoad() {
t.RequestInfo = &HookRequest{} t.RequestInfo = &HookRequest{}
if err := json.Unmarshal([]byte(t.RequestContent), t.RequestInfo); err != nil { if err := json.Unmarshal([]byte(t.RequestContent), t.RequestInfo); err != nil {
log.Error(3, "Unmarshal[%d]: %v", t.ID, err) log.Error(3, "Unmarshal RequestContent[%d]: %v", t.ID, err)
}
if len(t.ResponseContent) > 0 {
t.ResponseInfo = &HookResponse{}
if err := json.Unmarshal([]byte(t.ResponseContent), t.ResponseInfo); err != nil {
log.Error(3, "Unmarshal ResponseContent[%d]: %v", t.ID, err)
}
} }
} }
@ -619,6 +626,10 @@ func (t *HookTask) deliver() {
log.Trace("Hook delivery failed: %s", t.UUID) log.Trace("Hook delivery failed: %s", t.UUID)
} }
if err := UpdateHookTask(t); err != nil {
log.Error(4, "UpdateHookTask [%d]: %v", t.ID, err)
}
// Update webhook last delivery status. // Update webhook last delivery status.
w, err := GetWebhookByID(t.HookID) w, err := GetWebhookByID(t.HookID)
if err != nil { if err != nil {
@ -671,10 +682,6 @@ func DeliverHooks() {
// Update hook task status. // Update hook task status.
for _, t := range tasks { for _, t := range tasks {
t.deliver() t.deliver()
if err := UpdateHookTask(t); err != nil {
log.Error(4, "UpdateHookTask [%d]: %v", t.ID, err)
}
} }
// Start listening on new hook requests. // Start listening on new hook requests.
@ -695,10 +702,6 @@ func DeliverHooks() {
} }
for _, t := range tasks { for _, t := range tasks {
t.deliver() t.deliver()
if err := UpdateHookTask(t); err != nil {
log.Error(4, "UpdateHookTask [%d]: %v", t.ID, err)
continue
}
} }
} }
} }

View File

@ -45,7 +45,7 @@ func WikiNameToFilename(name string) string {
// WikiFilenameToName converts a wiki filename to its corresponding page name. // WikiFilenameToName converts a wiki filename to its corresponding page name.
func WikiFilenameToName(filename string) (string, error) { func WikiFilenameToName(filename string) (string, error) {
if !strings.HasSuffix(filename, ".md") { if !strings.HasSuffix(filename, ".md") {
return "", fmt.Errorf("Invalid wiki filename: %s", filename) return "", ErrWikiInvalidFileName{filename}
} }
basename := filename[:len(filename)-3] basename := filename[:len(filename)-3]
unescaped, err := url.QueryUnescape(basename) unescaped, err := url.QueryUnescape(basename)
@ -67,7 +67,7 @@ func WikiPath(userName, repoName string) string {
// WikiPath returns wiki data path for given repository. // WikiPath returns wiki data path for given repository.
func (repo *Repository) WikiPath() string { func (repo *Repository) WikiPath() string {
return WikiPath(repo.MustOwner().Name, repo.Name) return WikiPath(repo.MustOwnerName(), repo.Name)
} }
// HasWiki returns true if repository has wiki. // HasWiki returns true if repository has wiki.

View File

@ -77,11 +77,14 @@ func TestWikiFilenameToName(t *testing.T) {
for _, badFilename := range []string{ for _, badFilename := range []string{
"nofileextension", "nofileextension",
"wrongfileextension.txt", "wrongfileextension.txt",
"badescaping%%.md",
} { } {
_, err := WikiFilenameToName(badFilename) _, err := WikiFilenameToName(badFilename)
assert.Error(t, err) assert.Error(t, err)
assert.True(t, IsErrWikiInvalidFileName(err))
} }
_, err := WikiFilenameToName("badescaping%%.md")
assert.Error(t, err)
assert.False(t, IsErrWikiInvalidFileName(err))
} }
func TestWikiNameToFilenameToName(t *testing.T) { func TestWikiNameToFilenameToName(t *testing.T) {

View File

@ -182,7 +182,7 @@ func (f *AddKeyForm) Validate(ctx *macaron.Context, errs binding.Errors) binding
// NewAccessTokenForm form for creating access token // NewAccessTokenForm form for creating access token
type NewAccessTokenForm struct { type NewAccessTokenForm struct {
Name string `binding:"Required"` Name string `binding:"Required;MaxSize(255)"`
} }
// Validate valideates the fields // Validate valideates the fields

View File

@ -9,6 +9,7 @@ import (
"html/template" "html/template"
"io" "io"
"net/http" "net/http"
"net/url"
"path" "path"
"strings" "strings"
"time" "time"
@ -75,6 +76,26 @@ func (ctx *Context) HasValue(name string) bool {
return ok return ok
} }
// RedirectToFirst redirects to first not empty URL
func (ctx *Context) RedirectToFirst(location ...string) {
for _, loc := range location {
if len(loc) == 0 {
continue
}
u, err := url.Parse(loc)
if err != nil || (u.Scheme != "" && !strings.HasPrefix(strings.ToLower(loc), strings.ToLower(setting.AppURL))) {
continue
}
ctx.Redirect(loc)
return
}
ctx.Redirect(setting.AppSubURL + "/")
return
}
// HTML calls Context.HTML and converts template name to string. // HTML calls Context.HTML and converts template name to string.
func (ctx *Context) HTML(status int, name base.TplName) { func (ctx *Context) HTML(status int, name base.TplName) {
log.Debug("Template: %s", name) log.Debug("Template: %s", name)

View File

@ -83,6 +83,8 @@ type link struct {
ExpiresAt time.Time `json:"expires_at,omitempty"` ExpiresAt time.Time `json:"expires_at,omitempty"`
} }
var oidRegExp = regexp.MustCompile(`^[A-Fa-f0-9]+$`)
// ObjectOidHandler is the main request routing entry point into LFS server functions // ObjectOidHandler is the main request routing entry point into LFS server functions
func ObjectOidHandler(ctx *context.Context) { func ObjectOidHandler(ctx *context.Context) {
@ -217,6 +219,12 @@ func PostHandler(ctx *context.Context) {
if !authenticate(ctx, repository, rv.Authorization, true) { if !authenticate(ctx, repository, rv.Authorization, true) {
requireAuth(ctx) requireAuth(ctx)
return
}
if !oidRegExp.MatchString(rv.Oid) {
writeStatus(ctx, 404)
return
} }
meta, err := models.NewLFSMetaObject(&models.LFSMetaObject{Oid: rv.Oid, Size: rv.Size, RepositoryID: repository.ID}) meta, err := models.NewLFSMetaObject(&models.LFSMetaObject{Oid: rv.Oid, Size: rv.Size, RepositoryID: repository.ID})
@ -284,10 +292,12 @@ func BatchHandler(ctx *context.Context) {
continue continue
} }
// Object is not found if oidRegExp.MatchString(object.Oid) {
meta, err = models.NewLFSMetaObject(&models.LFSMetaObject{Oid: object.Oid, Size: object.Size, RepositoryID: repository.ID}) // Object is not found
if err == nil { meta, err = models.NewLFSMetaObject(&models.LFSMetaObject{Oid: object.Oid, Size: object.Size, RepositoryID: repository.ID})
responseObjects = append(responseObjects, Represent(object, meta, meta.Existing, !contentStore.Exists(meta))) if err == nil {
responseObjects = append(responseObjects, Represent(object, meta, meta.Existing, !contentStore.Exists(meta)))
}
} }
} }

View File

@ -114,16 +114,25 @@ func cutoutVerbosePrefix(prefix string) string {
// URLJoin joins url components, like path.Join, but preserving contents // URLJoin joins url components, like path.Join, but preserving contents
func URLJoin(base string, elems ...string) string { func URLJoin(base string, elems ...string) string {
u, err := url.Parse(base) if !strings.HasSuffix(base, "/") {
base += "/"
}
baseURL, err := url.Parse(base)
if err != nil { if err != nil {
log.Error(4, "URLJoin: Invalid base URL %s", base) log.Error(4, "URLJoin: Invalid base URL %s", base)
return "" return ""
} }
joinArgs := make([]string, 0, len(elems)+1) joinedPath := path.Join(elems...)
joinArgs = append(joinArgs, u.Path) argURL, err := url.Parse(joinedPath)
joinArgs = append(joinArgs, elems...) if err != nil {
u.Path = path.Join(joinArgs...) log.Error(4, "URLJoin: Invalid arg %s", joinedPath)
return u.String() return ""
}
joinedURL := baseURL.ResolveReference(argURL).String()
if !baseURL.IsAbs() && !strings.HasPrefix(base, "/") {
return joinedURL[1:] // Removing leading '/' if needed
}
return joinedURL
} }
// RenderIssueIndexPatternOptions options for RenderIssueIndexPattern function // RenderIssueIndexPatternOptions options for RenderIssueIndexPattern function
@ -391,7 +400,14 @@ func RenderShortLinks(rawBytes []byte, urlPrefix string, noLink bool, isWikiMark
} }
absoluteLink := isLink([]byte(link)) absoluteLink := isLink([]byte(link))
if !absoluteLink { if !absoluteLink {
link = strings.Replace(link, " ", "+", -1) if image {
link = strings.Replace(link, " ", "+", -1)
} else {
link = strings.Replace(link, " ", "-", -1)
}
if !strings.Contains(link, "/") {
link = url.PathEscape(link)
}
} }
if image { if image {
if !absoluteLink { if !absoluteLink {

View File

@ -83,6 +83,14 @@ func TestURLJoin(t *testing.T) {
"a", "b/c/"), "a", "b/c/"),
newTest("a/b/d", newTest("a/b/d",
"a/", "b/c/", "/../d/"), "a/", "b/c/", "/../d/"),
newTest("https://try.gitea.io/a/b/c#d",
"https://try.gitea.io", "a/b", "c#d"),
newTest("/a/b/d",
"/a/", "b/c/", "/../d/"),
newTest("/a/b/c",
"/a", "b/c/"),
newTest("/a/b/c#hash",
"/a", "b/c#hash"),
} { } {
assert.Equal(t, test.Expected, URLJoin(test.Base, test.Elements...)) assert.Equal(t, test.Expected, URLJoin(test.Base, test.Elements...))
} }

View File

@ -55,10 +55,16 @@ func TestRender_ShortLinks(t *testing.T) {
rawtree := markup.URLJoin(AppSubURL, "raw", "master") rawtree := markup.URLJoin(AppSubURL, "raw", "master")
url := markup.URLJoin(tree, "Link") url := markup.URLJoin(tree, "Link")
otherUrl := markup.URLJoin(tree, "OtherLink") otherUrl := markup.URLJoin(tree, "OtherLink")
encodedURL := markup.URLJoin(tree, "Link%3F")
imgurl := markup.URLJoin(rawtree, "Link.jpg") imgurl := markup.URLJoin(rawtree, "Link.jpg")
encodedImgurl := markup.URLJoin(rawtree, "Link+%23.jpg")
notencodedImgurl := markup.URLJoin(rawtree, "some", "path", "Link+#.jpg")
urlWiki := markup.URLJoin(AppSubURL, "wiki", "Link") urlWiki := markup.URLJoin(AppSubURL, "wiki", "Link")
otherUrlWiki := markup.URLJoin(AppSubURL, "wiki", "OtherLink") otherUrlWiki := markup.URLJoin(AppSubURL, "wiki", "OtherLink")
encodedURLWiki := markup.URLJoin(AppSubURL, "wiki", "Link%3F")
imgurlWiki := markup.URLJoin(AppSubURL, "wiki", "raw", "Link.jpg") imgurlWiki := markup.URLJoin(AppSubURL, "wiki", "raw", "Link.jpg")
encodedImgurlWiki := markup.URLJoin(AppSubURL, "wiki", "raw", "Link+%23.jpg")
notencodedImgurlWiki := markup.URLJoin(AppSubURL, "wiki", "raw", "some", "path", "Link+#.jpg")
favicon := "http://google.com/favicon.ico" favicon := "http://google.com/favicon.ico"
test( test(
@ -101,6 +107,26 @@ func TestRender_ShortLinks(t *testing.T) {
"[[Link]] [[OtherLink]]", "[[Link]] [[OtherLink]]",
`<p><a href="`+url+`" rel="nofollow">Link</a> <a href="`+otherUrl+`" rel="nofollow">OtherLink</a></p>`, `<p><a href="`+url+`" rel="nofollow">Link</a> <a href="`+otherUrl+`" rel="nofollow">OtherLink</a></p>`,
`<p><a href="`+urlWiki+`" rel="nofollow">Link</a> <a href="`+otherUrlWiki+`" rel="nofollow">OtherLink</a></p>`) `<p><a href="`+urlWiki+`" rel="nofollow">Link</a> <a href="`+otherUrlWiki+`" rel="nofollow">OtherLink</a></p>`)
test(
"[[Link?]]",
`<p><a href="`+encodedURL+`" rel="nofollow">Link?</a></p>`,
`<p><a href="`+encodedURLWiki+`" rel="nofollow">Link?</a></p>`)
test(
"[[Link]] [[OtherLink]] [[Link?]]",
`<p><a href="`+url+`" rel="nofollow">Link</a> <a href="`+otherUrl+`" rel="nofollow">OtherLink</a> <a href="`+encodedURL+`" rel="nofollow">Link?</a></p>`,
`<p><a href="`+urlWiki+`" rel="nofollow">Link</a> <a href="`+otherUrlWiki+`" rel="nofollow">OtherLink</a> <a href="`+encodedURLWiki+`" rel="nofollow">Link?</a></p>`)
test(
"[[Link #.jpg]]",
`<p><a href="`+encodedImgurl+`" rel="nofollow"><img src="`+encodedImgurl+`"/></a></p>`,
`<p><a href="`+encodedImgurlWiki+`" rel="nofollow"><img src="`+encodedImgurlWiki+`"/></a></p>`)
test(
"[[Name|Link #.jpg|alt=\"AltName\"|title='Title']]",
`<p><a href="`+encodedImgurl+`" rel="nofollow"><img src="`+encodedImgurl+`" alt="AltName" title="Title"/></a></p>`,
`<p><a href="`+encodedImgurlWiki+`" rel="nofollow"><img src="`+encodedImgurlWiki+`" alt="AltName" title="Title"/></a></p>`)
test(
"[[some/path/Link #.jpg]]",
`<p><a href="`+notencodedImgurl+`" rel="nofollow"><img src="`+notencodedImgurl+`"/></a></p>`,
`<p><a href="`+notencodedImgurlWiki+`" rel="nofollow"><img src="`+notencodedImgurlWiki+`"/></a></p>`)
} }
func TestMisc_IsMarkdownFile(t *testing.T) { func TestMisc_IsMarkdownFile(t *testing.T) {

View File

@ -956,7 +956,7 @@ func NewContext() {
AttachmentAllowedTypes = strings.Replace(sec.Key("ALLOWED_TYPES").MustString("image/jpeg,image/png,application/zip,application/gzip"), "|", ",", -1) AttachmentAllowedTypes = strings.Replace(sec.Key("ALLOWED_TYPES").MustString("image/jpeg,image/png,application/zip,application/gzip"), "|", ",", -1)
AttachmentMaxSize = sec.Key("MAX_SIZE").MustInt64(4) AttachmentMaxSize = sec.Key("MAX_SIZE").MustInt64(4)
AttachmentMaxFiles = sec.Key("MAX_FILES").MustInt(5) AttachmentMaxFiles = sec.Key("MAX_FILES").MustInt(5)
AttachmentEnabled = sec.Key("ENABLE").MustBool(true) AttachmentEnabled = sec.Key("ENABLED").MustBool(true)
TimeFormatKey := Cfg.Section("time").Key("FORMAT").MustString("RFC1123") TimeFormatKey := Cfg.Section("time").Key("FORMAT").MustString("RFC1123")
TimeFormat = map[string]string{ TimeFormat = map[string]string{

View File

@ -10,6 +10,7 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"html"
"html/template" "html/template"
"mime" "mime"
"net/url" "net/url"
@ -179,6 +180,7 @@ func NewFuncMap() []template.FuncMap {
return dict, nil return dict, nil
}, },
"Printf": fmt.Sprintf, "Printf": fmt.Sprintf,
"Escape": Escape,
}} }}
} }
@ -197,6 +199,11 @@ func Str2html(raw string) template.HTML {
return template.HTML(markup.Sanitize(raw)) return template.HTML(markup.Sanitize(raw))
} }
// Escape escapes a HTML string
func Escape(raw string) string {
return html.EscapeString(raw)
}
// List traversings the list // List traversings the list
func List(l *list.List) chan interface{} { func List(l *list.List) chan interface{} {
e := l.Front() e := l.Front()

View File

@ -59,3 +59,8 @@ func (ts TimeStamp) FormatLong() string {
func (ts TimeStamp) FormatShort() string { func (ts TimeStamp) FormatShort() string {
return ts.Format("Jan 02, 2006") return ts.Format("Jan 02, 2006")
} }
// IsZero is zero time
func (ts TimeStamp) IsZero() bool {
return ts.AsTime().IsZero()
}

View File

@ -4,6 +4,15 @@
package util package util
import (
"net/url"
"path"
"strings"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
)
// OptionalBool a boolean that can be "null" // OptionalBool a boolean that can be "null"
type OptionalBool byte type OptionalBool byte
@ -47,6 +56,41 @@ func Max(a, b int) int {
return a return a
} }
// URLJoin joins url components, like path.Join, but preserving contents
func URLJoin(base string, elems ...string) string {
if !strings.HasSuffix(base, "/") {
base += "/"
}
baseURL, err := url.Parse(base)
if err != nil {
log.Error(4, "URLJoin: Invalid base URL %s", base)
return ""
}
joinedPath := path.Join(elems...)
argURL, err := url.Parse(joinedPath)
if err != nil {
log.Error(4, "URLJoin: Invalid arg %s", joinedPath)
return ""
}
joinedURL := baseURL.ResolveReference(argURL).String()
if !baseURL.IsAbs() && !strings.HasPrefix(base, "/") {
return joinedURL[1:] // Removing leading '/' if needed
}
return joinedURL
}
// IsExternalURL checks if rawURL points to an external URL like http://example.com
func IsExternalURL(rawURL string) bool {
parsed, err := url.Parse(rawURL)
if err != nil {
return true
}
if len(parsed.Host) != 0 && strings.Replace(parsed.Host, "www.", "", 1) != strings.Replace(setting.Domain, "www.", "", 1) {
return true
}
return false
}
// Min min of two ints // Min min of two ints
func Min(a, b int) int { func Min(a, b int) int {
if a > b { if a > b {

79
modules/util/util_test.go Normal file
View File

@ -0,0 +1,79 @@
// Copyright 2018 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package util
import (
"testing"
"code.gitea.io/gitea/modules/setting"
"github.com/stretchr/testify/assert"
)
func TestURLJoin(t *testing.T) {
type test struct {
Expected string
Base string
Elements []string
}
newTest := func(expected, base string, elements ...string) test {
return test{Expected: expected, Base: base, Elements: elements}
}
for _, test := range []test{
newTest("https://try.gitea.io/a/b/c",
"https://try.gitea.io", "a/b", "c"),
newTest("https://try.gitea.io/a/b/c",
"https://try.gitea.io/", "/a/b/", "/c/"),
newTest("https://try.gitea.io/a/c",
"https://try.gitea.io/", "/a/./b/", "../c/"),
newTest("a/b/c",
"a", "b/c/"),
newTest("a/b/d",
"a/", "b/c/", "/../d/"),
newTest("https://try.gitea.io/a/b/c#d",
"https://try.gitea.io", "a/b", "c#d"),
newTest("/a/b/d",
"/a/", "b/c/", "/../d/"),
newTest("/a/b/c",
"/a", "b/c/"),
newTest("/a/b/c#hash",
"/a", "b/c#hash"),
} {
assert.Equal(t, test.Expected, URLJoin(test.Base, test.Elements...))
}
}
func TestIsExternalURL(t *testing.T) {
setting.Domain = "try.gitea.io"
type test struct {
Expected bool
RawURL string
}
newTest := func(expected bool, rawURL string) test {
return test{Expected: expected, RawURL: rawURL}
}
for _, test := range []test{
newTest(false,
"https://try.gitea.io"),
newTest(true,
"https://example.com/"),
newTest(true,
"//example.com"),
newTest(true,
"http://example.com"),
newTest(false,
"a/"),
newTest(false,
"https://try.gitea.io/test?param=false"),
newTest(false,
"test?param=false"),
newTest(false,
"//try.gitea.io/test?param=false"),
newTest(false,
"/hey/hey/hey#3244"),
} {
assert.Equal(t, test.Expected, IsExternalURL(test.RawURL))
}
}

File diff suppressed because one or more lines are too long

View File

@ -152,6 +152,10 @@ pre, code {
} }
} }
&.floating.label {
z-index: 10;
}
&.menu, &.menu,
&.vertical.menu, &.vertical.menu,
&.segment { &.segment {
@ -167,6 +171,14 @@ pre, code {
font-size: .92857143rem; font-size: .92857143rem;
} }
&.dropdown .menu>.item>.floating.label {
z-index: 11;
}
&.dropdown .menu .menu>.item>.floating.label {
z-index: 21;
}
.text { .text {
&.red { &.red {
color: #d95c5c !important; color: #d95c5c !important;

View File

@ -163,7 +163,7 @@ func editFilePost(ctx *context.Context, form auth.EditRepoFileForm, isNewFile bo
branchName = form.NewBranchName branchName = form.NewBranchName
} }
form.TreePath = strings.Trim(form.TreePath, " /") form.TreePath = strings.Trim(path.Clean("/"+form.TreePath), " /")
treeNames, treePaths := getParentTreeFields(form.TreePath) treeNames, treePaths := getParentTreeFields(form.TreePath)
ctx.Data["TreePath"] = form.TreePath ctx.Data["TreePath"] = form.TreePath
@ -477,7 +477,7 @@ func UploadFilePost(ctx *context.Context, form auth.UploadRepoFileForm) {
branchName = form.NewBranchName branchName = form.NewBranchName
} }
form.TreePath = strings.Trim(form.TreePath, " /") form.TreePath = strings.Trim(path.Clean("/"+form.TreePath), " /")
treeNames, treePaths := getParentTreeFields(form.TreePath) treeNames, treePaths := getParentTreeFields(form.TreePath)
if len(treeNames) == 0 { if len(treeNames) == 0 {
// We must at least have one element for user to input. // We must at least have one element for user to input.

View File

@ -184,33 +184,33 @@ func HTTP(ctx *context.Context) {
return return
} }
} }
}
if !isPublicPull { if !isPublicPull {
has, err := models.HasAccess(authUser.ID, repo, accessMode) has, err := models.HasAccess(authUser.ID, repo, accessMode)
if err != nil { if err != nil {
ctx.ServerError("HasAccess", err) ctx.ServerError("HasAccess", err)
return return
} else if !has { } else if !has {
if accessMode == models.AccessModeRead { if accessMode == models.AccessModeRead {
has, err = models.HasAccess(authUser.ID, repo, models.AccessModeWrite) has, err = models.HasAccess(authUser.ID, repo, models.AccessModeWrite)
if err != nil { if err != nil {
ctx.ServerError("HasAccess2", err) ctx.ServerError("HasAccess2", err)
return return
} else if !has { } else if !has {
ctx.HandleText(http.StatusForbidden, "User permission denied")
return
}
} else {
ctx.HandleText(http.StatusForbidden, "User permission denied") ctx.HandleText(http.StatusForbidden, "User permission denied")
return return
} }
} } else {
ctx.HandleText(http.StatusForbidden, "User permission denied")
if !isPull && repo.IsMirror {
ctx.HandleText(http.StatusForbidden, "mirror repository is read-only")
return return
} }
} }
if !isPull && repo.IsMirror {
ctx.HandleText(http.StatusForbidden, "mirror repository is read-only")
return
}
} }
if !repo.CheckUnitUser(authUser.ID, authUser.IsAdmin, unitType) { if !repo.CheckUnitUser(authUser.ID, authUser.IsAdmin, unitType) {

View File

@ -32,7 +32,7 @@ func TestInitializeLabels(t *testing.T) {
ctx := test.MockContext(t, "user2/repo1/labels/initialize") ctx := test.MockContext(t, "user2/repo1/labels/initialize")
test.LoadUser(t, ctx, 2) test.LoadUser(t, ctx, 2)
test.LoadRepo(t, ctx, 2) test.LoadRepo(t, ctx, 2)
InitializeLabels(ctx, auth.InitializeLabelsForm{"Default"}) InitializeLabels(ctx, auth.InitializeLabelsForm{TemplateName: "Default"})
assert.EqualValues(t, http.StatusFound, ctx.Resp.Status()) assert.EqualValues(t, http.StatusFound, ctx.Resp.Status())
models.AssertExistsAndLoadBean(t, &models.Label{ models.AssertExistsAndLoadBean(t, &models.Label{
RepoID: 2, RepoID: 2,

View File

@ -307,11 +307,7 @@ func Action(ctx *context.Context) {
return return
} }
redirectTo := ctx.Query("redirect_to") ctx.RedirectToFirst(ctx.Query("redirect_to"), ctx.Repo.RepoLink)
if len(redirectTo) == 0 {
redirectTo = ctx.Repo.RepoLink
}
ctx.Redirect(redirectTo)
} }
// Download download an archive of a repository // Download download an archive of a repository

View File

@ -105,7 +105,9 @@ func renderDirectory(ctx *context.Context, treeLink string) {
ctx.Data["FileContent"] = string(markup.Render(readmeFile.Name(), buf, treeLink, ctx.Repo.Repository.ComposeMetas())) ctx.Data["FileContent"] = string(markup.Render(readmeFile.Name(), buf, treeLink, ctx.Repo.Repository.ComposeMetas()))
} else { } else {
ctx.Data["IsRenderedHTML"] = true ctx.Data["IsRenderedHTML"] = true
ctx.Data["FileContent"] = string(bytes.Replace(buf, []byte("\n"), []byte(`<br>`), -1)) ctx.Data["FileContent"] = strings.Replace(
gotemplate.HTMLEscapeString(string(buf)), "\n", `<br>`, -1,
)
} }
} }
} }
@ -208,7 +210,9 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st
ctx.Data["FileContent"] = string(markup.Render(blob.Name(), buf, path.Dir(treeLink), ctx.Repo.Repository.ComposeMetas())) ctx.Data["FileContent"] = string(markup.Render(blob.Name(), buf, path.Dir(treeLink), ctx.Repo.Repository.ComposeMetas()))
} else if readmeExist { } else if readmeExist {
ctx.Data["IsRenderedHTML"] = true ctx.Data["IsRenderedHTML"] = true
ctx.Data["FileContent"] = string(bytes.Replace(buf, []byte("\n"), []byte(`<br>`), -1)) ctx.Data["FileContent"] = strings.Replace(
gotemplate.HTMLEscapeString(string(buf)), "\n", `<br>`, -1,
)
} else { } else {
// Building code view blocks with line number on server side. // Building code view blocks with line number on server side.
var fileContent string var fileContent string
@ -223,6 +227,10 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st
var output bytes.Buffer var output bytes.Buffer
lines := strings.Split(fileContent, "\n") lines := strings.Split(fileContent, "\n")
//Remove blank line at the end of file
if len(lines) > 0 && lines[len(lines)-1] == "" {
lines = lines[:len(lines)-1]
}
for index, line := range lines { for index, line := range lines {
line = gotemplate.HTMLEscapeString(line) line = gotemplate.HTMLEscapeString(line)
if index != len(lines)-1 { if index != len(lines)-1 {

View File

@ -205,7 +205,7 @@ func GogsHooksNewPost(ctx *context.Context, form auth.NewGogshookForm) {
Secret: form.Secret, Secret: form.Secret,
HookEvent: ParseHookEvent(form.WebhookForm), HookEvent: ParseHookEvent(form.WebhookForm),
IsActive: form.Active, IsActive: form.Active,
HookTaskType: models.GITEA, HookTaskType: models.GOGS,
OrgID: orCtx.OrgID, OrgID: orCtx.OrgID,
} }
if err := w.UpdateEvent(); err != nil { if err := w.UpdateEvent(); err != nil {

View File

@ -128,6 +128,9 @@ func renderWikiPage(ctx *context.Context, isViewPage bool) (*git.Repository, *gi
} }
wikiName, err := models.WikiFilenameToName(entry.Name()) wikiName, err := models.WikiFilenameToName(entry.Name())
if err != nil { if err != nil {
if models.IsErrWikiInvalidFileName(err) {
continue
}
ctx.ServerError("WikiFilenameToName", err) ctx.ServerError("WikiFilenameToName", err)
return nil, nil return nil, nil
} else if wikiName == "_Sidebar" || wikiName == "_Footer" { } else if wikiName == "_Sidebar" || wikiName == "_Footer" {
@ -262,6 +265,9 @@ func WikiPages(ctx *context.Context) {
} }
wikiName, err := models.WikiFilenameToName(entry.Name()) wikiName, err := models.WikiFilenameToName(entry.Name())
if err != nil { if err != nil {
if models.IsErrWikiInvalidFileName(err) {
continue
}
ctx.ServerError("WikiFilenameToName", err) ctx.ServerError("WikiFilenameToName", err)
return return
} }
@ -344,7 +350,7 @@ func NewWikiPost(ctx *context.Context, form auth.NewWikiForm) {
return return
} }
ctx.Redirect(ctx.Repo.RepoLink + "/wiki/" + models.WikiNameToFilename(wikiName)) ctx.Redirect(ctx.Repo.RepoLink + "/wiki/" + models.WikiNameToSubURL(wikiName))
} }
// EditWiki render wiki modify page // EditWiki render wiki modify page
@ -385,7 +391,7 @@ func EditWikiPost(ctx *context.Context, form auth.NewWikiForm) {
return return
} }
ctx.Redirect(ctx.Repo.RepoLink + "/wiki/" + models.WikiNameToFilename(newWikiName)) ctx.Redirect(ctx.Repo.RepoLink + "/wiki/" + models.WikiNameToSubURL(newWikiName))
} }
// DeleteWikiPagePost delete wiki page // DeleteWikiPagePost delete wiki page

View File

@ -7,36 +7,52 @@ package repo
import ( import (
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"path/filepath"
"testing" "testing"
"code.gitea.io/git"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth" "code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/modules/test"
"github.com/Unknwon/com"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
const content = "Wiki contents for unit tests" const content = "Wiki contents for unit tests"
const message = "Wiki commit message for unit tests" const message = "Wiki commit message for unit tests"
func wikiPath(repo *models.Repository, wikiName string) string { func wikiEntry(t *testing.T, repo *models.Repository, wikiName string) *git.TreeEntry {
return filepath.Join(repo.LocalWikiPath(), models.WikiNameToFilename(wikiName)) wikiRepo, err := git.OpenRepository(repo.WikiPath())
assert.NoError(t, err)
commit, err := wikiRepo.GetBranchCommit("master")
assert.NoError(t, err)
entries, err := commit.ListEntries()
assert.NoError(t, err)
for _, entry := range entries {
if entry.Name() == models.WikiNameToFilename(wikiName) {
return entry
}
}
return nil
} }
func wikiContent(t *testing.T, repo *models.Repository, wikiName string) string { func wikiContent(t *testing.T, repo *models.Repository, wikiName string) string {
bytes, err := ioutil.ReadFile(wikiPath(repo, wikiName)) entry := wikiEntry(t, repo, wikiName)
if !assert.NotNil(t, entry) {
return ""
}
reader, err := entry.Blob().Data()
assert.NoError(t, err)
bytes, err := ioutil.ReadAll(reader)
assert.NoError(t, err) assert.NoError(t, err)
return string(bytes) return string(bytes)
} }
func assertWikiExists(t *testing.T, repo *models.Repository, wikiName string) { func assertWikiExists(t *testing.T, repo *models.Repository, wikiName string) {
assert.True(t, com.IsExist(wikiPath(repo, wikiName))) assert.NotNil(t, wikiEntry(t, repo, wikiName))
} }
func assertWikiNotExists(t *testing.T, repo *models.Repository, wikiName string) { func assertWikiNotExists(t *testing.T, repo *models.Repository, wikiName string) {
assert.False(t, com.IsExist(wikiPath(repo, wikiName))) assert.Nil(t, wikiEntry(t, repo, wikiName))
} }
func assertPagesMetas(t *testing.T, expectedNames []string, metas interface{}) { func assertPagesMetas(t *testing.T, expectedNames []string, metas interface{}) {

View File

@ -446,7 +446,7 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Get("/:id", repo.WebHooksEdit) m.Get("/:id", repo.WebHooksEdit)
m.Post("/:id/test", repo.TestWebhook) m.Post("/:id/test", repo.TestWebhook)
m.Post("/gitea/:id", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksEditPost) m.Post("/gitea/:id", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksEditPost)
m.Post("/gogs/:id", bindIgnErr(auth.NewGogshookForm{}), repo.GogsHooksNewPost) m.Post("/gogs/:id", bindIgnErr(auth.NewGogshookForm{}), repo.GogsHooksEditPost)
m.Post("/slack/:id", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksEditPost) m.Post("/slack/:id", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksEditPost)
m.Post("/discord/:id", bindIgnErr(auth.NewDiscordHookForm{}), repo.DiscordHooksEditPost) m.Post("/discord/:id", bindIgnErr(auth.NewDiscordHookForm{}), repo.DiscordHooksEditPost)
m.Post("/dingtalk/:id", bindIgnErr(auth.NewDingtalkHookForm{}), repo.DingtalkHooksEditPost) m.Post("/dingtalk/:id", bindIgnErr(auth.NewDingtalkHookForm{}), repo.DingtalkHooksEditPost)

View File

@ -18,6 +18,7 @@ import (
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"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"
"github.com/go-macaron/captcha" "github.com/go-macaron/captcha"
"github.com/markbates/goth" "github.com/markbates/goth"
@ -93,12 +94,8 @@ func checkAutoLogin(ctx *context.Context) bool {
} }
if isSucceed { if isSucceed {
if len(redirectTo) > 0 { ctx.SetCookie("redirect_to", "", -1, setting.AppSubURL)
ctx.SetCookie("redirect_to", "", -1, setting.AppSubURL) ctx.RedirectToFirst(redirectTo, setting.AppSubURL+string(setting.LandingPageURL))
ctx.Redirect(redirectTo)
} else {
ctx.Redirect(setting.AppSubURL + string(setting.LandingPageURL))
}
return true return true
} }
@ -347,10 +344,10 @@ func handleSignInFull(ctx *context.Context, u *models.User, remember bool, obeyR
return return
} }
if redirectTo, _ := url.QueryUnescape(ctx.GetCookie("redirect_to")); len(redirectTo) > 0 { if redirectTo, _ := url.QueryUnescape(ctx.GetCookie("redirect_to")); len(redirectTo) > 0 && !util.IsExternalURL(redirectTo) {
ctx.SetCookie("redirect_to", "", -1, setting.AppSubURL) ctx.SetCookie("redirect_to", "", -1, setting.AppSubURL)
if obeyRedirect { if obeyRedirect {
ctx.Redirect(redirectTo) ctx.RedirectToFirst(redirectTo)
} }
return return
} }
@ -439,7 +436,7 @@ func handleOAuth2SignIn(u *models.User, gothUser goth.User, ctx *context.Context
if redirectTo, _ := url.QueryUnescape(ctx.GetCookie("redirect_to")); len(redirectTo) > 0 { if redirectTo, _ := url.QueryUnescape(ctx.GetCookie("redirect_to")); len(redirectTo) > 0 {
ctx.SetCookie("redirect_to", "", -1, setting.AppSubURL) ctx.SetCookie("redirect_to", "", -1, setting.AppSubURL)
ctx.Redirect(redirectTo) ctx.RedirectToFirst(redirectTo)
return return
} }

View File

@ -49,12 +49,8 @@ func SignInOpenID(ctx *context.Context) {
} }
if isSucceed { if isSucceed {
if len(redirectTo) > 0 { ctx.SetCookie("redirect_to", "", -1, setting.AppSubURL)
ctx.SetCookie("redirect_to", "", -1, setting.AppSubURL) ctx.RedirectToFirst(redirectTo)
ctx.Redirect(redirectTo)
} else {
ctx.Redirect(setting.AppSubURL + "/")
}
return return
} }

View File

@ -66,12 +66,14 @@ func retrieveFeeds(ctx *context.Context, options models.GetFeedsOptions) {
if ctx.User != nil { if ctx.User != nil {
userCache[ctx.User.ID] = ctx.User userCache[ctx.User.ID] = ctx.User
} }
repoCache := map[int64]*models.Repository{}
for _, act := range actions { for _, act := range actions {
// Cache results to reduce queries. if act.ActUser != nil {
u, ok := userCache[act.ActUserID] userCache[act.ActUserID] = act.ActUser
}
repoOwner, ok := userCache[act.Repo.OwnerID]
if !ok { if !ok {
u, err = models.GetUserByID(act.ActUserID) repoOwner, err = models.GetUserByID(act.Repo.OwnerID)
if err != nil { if err != nil {
if models.IsErrUserNotExist(err) { if models.IsErrUserNotExist(err) {
continue continue
@ -79,35 +81,9 @@ func retrieveFeeds(ctx *context.Context, options models.GetFeedsOptions) {
ctx.ServerError("GetUserByID", err) ctx.ServerError("GetUserByID", err)
return return
} }
userCache[act.ActUserID] = u userCache[repoOwner.ID] = repoOwner
} }
act.ActUser = u act.Repo.Owner = repoOwner
repo, ok := repoCache[act.RepoID]
if !ok {
repo, err = models.GetRepositoryByID(act.RepoID)
if err != nil {
if models.IsErrRepoNotExist(err) {
continue
}
ctx.ServerError("GetRepositoryByID", err)
return
}
}
act.Repo = repo
repoOwner, ok := userCache[repo.OwnerID]
if !ok {
repoOwner, err = models.GetUserByID(repo.OwnerID)
if err != nil {
if models.IsErrUserNotExist(err) {
continue
}
ctx.ServerError("GetUserByID", err)
return
}
}
repo.Owner = repoOwner
} }
ctx.Data["Feeds"] = actions ctx.Data["Feeds"] = actions
} }
@ -154,7 +130,8 @@ func Dashboard(ctx *context.Context) {
ctx.Data["MirrorCount"] = len(mirrors) ctx.Data["MirrorCount"] = len(mirrors)
ctx.Data["Mirrors"] = mirrors ctx.Data["Mirrors"] = mirrors
retrieveFeeds(ctx, models.GetFeedsOptions{RequestedUser: ctxUser, retrieveFeeds(ctx, models.GetFeedsOptions{
RequestedUser: ctxUser,
IncludePrivate: true, IncludePrivate: true,
OnlyPerformedBy: false, OnlyPerformedBy: false,
IncludeDeleted: false, IncludeDeleted: false,

View File

@ -271,9 +271,5 @@ func Action(ctx *context.Context) {
return return
} }
redirectTo := ctx.Query("redirect_to") ctx.RedirectToFirst(ctx.Query("redirect_to"), u.HomeLink())
if len(redirectTo) == 0 {
redirectTo = u.HomeLink()
}
ctx.Redirect(redirectTo)
} }

View File

@ -49,8 +49,8 @@
<tr> <tr>
<td>{{.PID}}</td> <td>{{.PID}}</td>
<td>{{.Description}}</td> <td>{{.Description}}</td>
<td>{{.Start.FormatLong}}</td> <td>{{DateFmtLong .Start}}</td>
<td>{{TimeSinceUnix .Start $.Lang}}</td> <td>{{TimeSince .Start $.Lang}}</td>
</tr> </tr>
{{end}} {{end}}
</tbody> </tbody>

View File

@ -134,7 +134,7 @@
<p class="desc"> <p class="desc">
<div class="ui red label">{{$.i18n.Tr "repo.activity.closed_issue_label"}}</div> <div class="ui red label">{{$.i18n.Tr "repo.activity.closed_issue_label"}}</div>
#{{.Index}} <a class="title has-emoji" href="{{$.Repository.HTMLURL}}/issues/{{.Index}}">{{.Title}}</a> #{{.Index}} <a class="title has-emoji" href="{{$.Repository.HTMLURL}}/issues/{{.Index}}">{{.Title}}</a>
{{TimeSinceUnix .UpdatedUnix $.Lang}} {{TimeSinceUnix .ClosedUnix $.Lang}}
</p> </p>
{{end}} {{end}}
</div> </div>

View File

@ -5,7 +5,7 @@
<div class="ui grid"> <div class="ui grid">
<div class="sixteen wide column content"> <div class="sixteen wide column content">
{{template "base/alert" .}} {{template "base/alert" .}}
{{if .IsRepositoryAdmin}} {{if .IsRepositoryWriter}}
<h4 class="ui top attached header"> <h4 class="ui top attached header">
{{.i18n.Tr "repo.quick_guide"}} {{.i18n.Tr "repo.quick_guide"}}
</h4> </h4>

View File

@ -69,12 +69,12 @@
<div class="ui small basic delete modal"> <div class="ui small basic delete modal">
<div class="ui icon header"> <div class="ui icon header">
<i class="trash icon"></i> <i class="trash icon"></i>
{{.i18n.Tr "repo.branch.delete_html"| Safe}} <span class="branch-name"></span> {{.i18n.Tr "repo.branch.delete_html"}} <span class="branch-name"></span>
</div> </div>
<div class="content"> <div class="content">
<p>{{.i18n.Tr "repo.branch.delete_desc" | Safe}}</p> <p>{{.i18n.Tr "repo.branch.delete_desc"}}</p>
{{.i18n.Tr "repo.branch.delete_notices_1" | Safe}}<br> {{.i18n.Tr "repo.branch.delete_notices_1" | Safe}}<br>
{{.i18n.Tr "repo.branch.delete_notices_html" | Safe}} <span class="branch-name"></span><br> {{.i18n.Tr "repo.branch.delete_notices_html"}} <span class="branch-name"></span><br>
</div> </div>
{{template "base/delete_modal_actions" .}} {{template "base/delete_modal_actions" .}}
</div> </div>

View File

@ -47,9 +47,9 @@
</div> </div>
<div class="text small"> <div class="text small">
{{if .IsViewBranch}} {{if .IsViewBranch}}
{{.i18n.Tr "repo.branch.create_from" .BranchName | Safe}} {{.i18n.Tr "repo.branch.create_from" .BranchName}}
{{else}} {{else}}
{{.i18n.Tr "repo.branch.create_from" (ShortSha .BranchName) | Safe}} {{.i18n.Tr "repo.branch.create_from" (ShortSha .BranchName)}}
{{end}} {{end}}
</div> </div>
</a> </a>

View File

@ -36,7 +36,7 @@
<div class="inline required field {{if .Err_RepoName}}error{{end}}"> <div class="inline required field {{if .Err_RepoName}}error{{end}}">
<label for="repo_name">{{.i18n.Tr "repo.repo_name"}}</label> <label for="repo_name">{{.i18n.Tr "repo.repo_name"}}</label>
<input id="repo_name" name="repo_name" value="{{.repo_name}}" autofocus required> <input id="repo_name" name="repo_name" value="{{.repo_name}}" autofocus required>
<span class="help">{{.i18n.Tr "repo.repo_name_helper" | Safe}}</span> <span class="help">{{.i18n.Tr "repo.repo_name_helper"}}</span>
</div> </div>
<div class="inline field"> <div class="inline field">
<label>{{.i18n.Tr "repo.visibility"}}</label> <label>{{.i18n.Tr "repo.visibility"}}</label>

View File

@ -14,8 +14,7 @@
<input type="radio" class="js-quick-pull-choice-option" name="commit_choice" value="direct" {{if eq .commit_choice "direct"}}checked{{end}}> <input type="radio" class="js-quick-pull-choice-option" name="commit_choice" value="direct" {{if eq .commit_choice "direct"}}checked{{end}}>
<label> <label>
<i class="octicon octicon-git-commit" height="16" width="14"></i> <i class="octicon octicon-git-commit" height="16" width="14"></i>
{{$branchName := .BranchName | Str2html}} {{.i18n.Tr "repo.editor.commit_directly_to_this_branch" (.BranchName|Escape) | Safe}}
{{.i18n.Tr "repo.editor.commit_directly_to_this_branch" $branchName | Safe}}
</label> </label>
</div> </div>
</div> </div>

View File

@ -48,7 +48,7 @@
<div class="ui tabs container"> <div class="ui tabs container">
<div class="ui tabular stackable menu navbar"> <div class="ui tabular stackable menu navbar">
{{if .Repository.UnitEnabled $.UnitTypeCode}} {{if .Repository.UnitEnabled $.UnitTypeCode}}
<a class="{{if .PageIsViewCode}}active{{end}} item" href="{{.RepoLink}}"> <a class="{{if .PageIsViewCode}}active{{end}} item" href="{{.RepoLink}}{{if (ne .BranchName .Repository.DefaultBranch)}}/src/{{.BranchNameSubURL}}{{end}}">
<i class="octicon octicon-code"></i> {{.i18n.Tr "repo.code"}} <i class="octicon octicon-code"></i> {{.i18n.Tr "repo.code"}}
</a> </a>
{{end}} {{end}}

View File

@ -73,7 +73,7 @@
{{else if and (not $.DisableSSH) (or $.IsSigned $.ExposeAnonSSH)}} {{else if and (not $.DisableSSH) (or $.IsSigned $.ExposeAnonSSH)}}
<input id="repo-clone-url" value="{{$.CloneLink.SSH}}" readonly> <input id="repo-clone-url" value="{{$.CloneLink.SSH}}" readonly>
{{end}} {{end}}
{{if or ((not $.DisableHTTP) (and (not $.DisableSSH) (or $.IsSigned $.ExposeAnonSSH)))}} {{if or (not $.DisableHTTP) (and (not $.DisableSSH) (or $.IsSigned $.ExposeAnonSSH))}}
<button class="ui basic icon button poping up clipboard" id="clipboard-btn" data-original="{{.i18n.Tr "repo.copy_link"}}" data-success="{{.i18n.Tr "repo.copy_link_success"}}" data-error="{{.i18n.Tr "repo.copy_link_error"}}" data-content="{{.i18n.Tr "repo.copy_link"}}" data-variation="inverted tiny" data-clipboard-target="#repo-clone-url"> <button class="ui basic icon button poping up clipboard" id="clipboard-btn" data-original="{{.i18n.Tr "repo.copy_link"}}" data-success="{{.i18n.Tr "repo.copy_link_success"}}" data-error="{{.i18n.Tr "repo.copy_link_error"}}" data-content="{{.i18n.Tr "repo.copy_link"}}" data-variation="inverted tiny" data-clipboard-target="#repo-clone-url">
<i class="octicon octicon-clippy"></i> <i class="octicon octicon-clippy"></i>
</button> </button>

View File

@ -134,12 +134,12 @@
<div class="ui small basic delete modal"> <div class="ui small basic delete modal">
<div class="ui icon header"> <div class="ui icon header">
<i class="trash icon"></i> <i class="trash icon"></i>
{{.i18n.Tr "repo.branch.delete" .HeadTarget | Safe}} {{.i18n.Tr "repo.branch.delete" .HeadTarget }}
</div> </div>
<div class="content"> <div class="content">
<p>{{.i18n.Tr "repo.branch.delete_desc" | Safe}}</p> <p>{{.i18n.Tr "repo.branch.delete_desc"}}</p>
{{.i18n.Tr "repo.branch.delete_notices_1" | Safe}}<br> {{.i18n.Tr "repo.branch.delete_notices_1" | Safe}}<br>
{{.i18n.Tr "repo.branch.delete_notices_2" .HeadTarget | Safe}}<br> {{.i18n.Tr "repo.branch.delete_notices_2" .HeadTarget}}<br>
</div> </div>
{{template "base/delete_modal_actions" .}} {{template "base/delete_modal_actions" .}}
</div> </div>

View File

@ -103,7 +103,7 @@
<img src="{{.Poster.RelAvatarLink}}"> <img src="{{.Poster.RelAvatarLink}}">
</a> </a>
<span class="text grey"><a href="{{.Poster.HomeLink}}">{{.Poster.Name}}</a> <span class="text grey"><a href="{{.Poster.HomeLink}}">{{.Poster.Name}}</a>
{{if .Content}}{{$.i18n.Tr "repo.issues.add_label_at" .Label.ForegroundColor .Label.Color .Label.Name $createdStr | Safe}}{{else}}{{$.i18n.Tr "repo.issues.remove_label_at" .Label.ForegroundColor .Label.Color .Label.Name $createdStr | Safe}}{{end}}</span> {{if .Content}}{{$.i18n.Tr "repo.issues.add_label_at" .Label.ForegroundColor .Label.Color (.Label.Name|Escape) $createdStr | Safe}}{{else}}{{$.i18n.Tr "repo.issues.remove_label_at" .Label.ForegroundColor .Label.Color (.Label.Name|Escape) $createdStr | Safe}}{{end}}</span>
</div> </div>
{{end}} {{end}}
{{else if eq .Type 8}} {{else if eq .Type 8}}
@ -113,7 +113,7 @@
<img src="{{.Poster.RelAvatarLink}}"> <img src="{{.Poster.RelAvatarLink}}">
</a> </a>
<span class="text grey"><a href="{{.Poster.HomeLink}}">{{.Poster.Name}}</a> <span class="text grey"><a href="{{.Poster.HomeLink}}">{{.Poster.Name}}</a>
{{if gt .OldMilestoneID 0}}{{if gt .MilestoneID 0}}{{$.i18n.Tr "repo.issues.change_milestone_at" .OldMilestone.Name .Milestone.Name $createdStr | Safe}}{{else}}{{$.i18n.Tr "repo.issues.remove_milestone_at" .OldMilestone.Name $createdStr | Safe}}{{end}}{{else if gt .MilestoneID 0}}{{$.i18n.Tr "repo.issues.add_milestone_at" .Milestone.Name $createdStr | Safe}}{{end}}</span> {{if gt .OldMilestoneID 0}}{{if gt .MilestoneID 0}}{{$.i18n.Tr "repo.issues.change_milestone_at" (.OldMilestone.Name|Escape) (.Milestone.Name|Escape) $createdStr | Safe}}{{else}}{{$.i18n.Tr "repo.issues.remove_milestone_at" (.OldMilestone.Name|Escape) $createdStr | Safe}}{{end}}{{else if gt .MilestoneID 0}}{{$.i18n.Tr "repo.issues.add_milestone_at" (.Milestone.Name|Escape) $createdStr | Safe}}{{end}}</span>
</div> </div>
{{else if eq .Type 9}} {{else if eq .Type 9}}
<div class="event"> <div class="event">
@ -131,23 +131,23 @@
{{else if eq .Type 10}} {{else if eq .Type 10}}
<div class="event"> <div class="event">
<span class="octicon octicon-primitive-dot"></span> <span class="octicon octicon-primitive-dot"></span>
<a class="ui avatar image" href="{{.Poster.HomeLink}}">
<img src="{{.Poster.RelAvatarLink}}">
</a>
<span class="text grey"><a href="{{.Poster.HomeLink}}">{{.Poster.Name}}</a>
{{$.i18n.Tr "repo.issues.change_title_at" (.OldTitle|Escape) (.NewTitle|Escape) $createdStr | Safe}}
</span>
</div> </div>
<a class="ui avatar image" href="{{.Poster.HomeLink}}">
<img src="{{.Poster.RelAvatarLink}}">
</a>
<span class="text grey"><a href="{{.Poster.HomeLink}}">{{.Poster.Name}}</a>
{{$.i18n.Tr "repo.issues.change_title_at" .OldTitle .NewTitle $createdStr | Safe}}
</span>
{{else if eq .Type 11}} {{else if eq .Type 11}}
<div class="event"> <div class="event">
<span class="octicon octicon-primitive-dot"></span> <span class="octicon octicon-primitive-dot"></span>
<a class="ui avatar image" href="{{.Poster.HomeLink}}">
<img src="{{.Poster.RelAvatarLink}}">
</a>
<span class="text grey"><a href="{{.Poster.HomeLink}}">{{.Poster.Name}}</a>
{{$.i18n.Tr "repo.issues.delete_branch_at" (.CommitSHA|Escape) $createdStr | Safe}}
</span>
</div> </div>
<a class="ui avatar image" href="{{.Poster.HomeLink}}">
<img src="{{.Poster.RelAvatarLink}}">
</a>
<span class="text grey"><a href="{{.Poster.HomeLink}}">{{.Poster.Name}}</a>
{{$.i18n.Tr "repo.issues.delete_branch_at" .CommitSHA $createdStr | Safe}}
</span>
{{else if eq .Type 12}} {{else if eq .Type 12}}
<div class="event"> <div class="event">
<span class="octicon octicon-primitive-dot"></span> <span class="octicon octicon-primitive-dot"></span>

View File

@ -14,7 +14,7 @@
</div> </div>
{{if .Keyword}} {{if .Keyword}}
<h3> <h3>
{{.i18n.Tr "repo.search.results" .Keyword .RepoLink .RepoName | Str2html}} {{.i18n.Tr "repo.search.results" (.Keyword|Escape) .RepoLink .RepoName | Str2html }}
</h3> </h3>
<div class="repository search"> <div class="repository search">
{{range $result := .SearchResults}} {{range $result := .SearchResults}}

View File

@ -302,7 +302,7 @@
</div> </div>
<div class="content"> <div class="content">
<div class="ui warning message text left"> <div class="ui warning message text left">
{{.i18n.Tr "repo.settings.convert_notices_1" | Safe}} {{.i18n.Tr "repo.settings.convert_notices_1"}}
</div> </div>
<form class="ui form" action="{{.Link}}" method="post"> <form class="ui form" action="{{.Link}}" method="post">
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
@ -333,8 +333,8 @@
</div> </div>
<div class="content"> <div class="content">
<div class="ui warning message text left"> <div class="ui warning message text left">
{{.i18n.Tr "repo.settings.transfer_notices_1" | Safe}} <br> {{.i18n.Tr "repo.settings.transfer_notices_1"}} <br>
{{.i18n.Tr "repo.settings.transfer_notices_2" | Safe}} {{.i18n.Tr "repo.settings.transfer_notices_2"}}
</div> </div>
<form class="ui form" action="{{.Link}}" method="post"> <form class="ui form" action="{{.Link}}" method="post">
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
@ -371,7 +371,7 @@
{{.i18n.Tr "repo.settings.delete_notices_1" | Safe}}<br> {{.i18n.Tr "repo.settings.delete_notices_1" | Safe}}<br>
{{.i18n.Tr "repo.settings.delete_notices_2" .Repository.FullName | Safe}} {{.i18n.Tr "repo.settings.delete_notices_2" .Repository.FullName | Safe}}
{{if .Repository.NumForks}}<br> {{if .Repository.NumForks}}<br>
{{.i18n.Tr "repo.settings.delete_notices_fork_1" | Safe}} {{.i18n.Tr "repo.settings.delete_notices_fork_1"}}
{{end}} {{end}}
</div> </div>
<form class="ui form" action="{{.Link}}" method="post"> <form class="ui form" action="{{.Link}}" method="post">

View File

@ -104,7 +104,7 @@
{{.i18n.Tr "repo.wiki.delete_page_button"}} {{.i18n.Tr "repo.wiki.delete_page_button"}}
</div> </div>
<div class="content"> <div class="content">
<p>{{.i18n.Tr "repo.wiki.delete_page_notice_1" $title | Safe}}</p> <p>{{.i18n.Tr "repo.wiki.delete_page_notice_1" ($title|Escape) | Safe}}</p>
</div> </div>
{{template "base/delete_modal_actions" .}} {{template "base/delete_modal_actions" .}}
</div> </div>

View File

@ -26,7 +26,7 @@
<div class="activity meta"> <div class="activity meta">
<i>{{$.i18n.Tr "settings.add_on"}} <span>{{.AddedUnix.FormatShort}}</span></i> <i>{{$.i18n.Tr "settings.add_on"}} <span>{{.AddedUnix.FormatShort}}</span></i>
- -
<i>{{if .ExpiredUnix}}{{$.i18n.Tr "settings.valid_until"}} <span>{{.ExpiredUnix.FormatShort}}</span>{{else}}{{$.i18n.Tr "settings.valid_forever"}}{{end}}</i> <i>{{if not .ExpiredUnix.IsZero}}{{$.i18n.Tr "settings.valid_until"}} <span>{{.ExpiredUnix.FormatShort}}</span>{{else}}{{$.i18n.Tr "settings.valid_forever"}}{{end}}</i>
</div> </div>
</div> </div>
</div> </div>

38
vendor/code.gitea.io/git/Gopkg.lock generated vendored Normal file
View File

@ -0,0 +1,38 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
branch = "master"
name = "github.com/Unknwon/com"
packages = ["."]
revision = "7677a1d7c1137cd3dd5ba7a076d0c898a1ef4520"
[[projects]]
name = "github.com/davecgh/go-spew"
packages = ["spew"]
revision = "6d212800a42e8ab5c146b8ace3490ee17e5225f9"
[[projects]]
name = "github.com/mcuadros/go-version"
packages = ["."]
revision = "257f7b9a7d87427c8d7f89469a5958d57f8abd7c"
[[projects]]
name = "github.com/pmezard/go-difflib"
packages = ["difflib"]
revision = "d8ed2627bdf02c080bf22230dbb337003b7aba2d"
[[projects]]
name = "github.com/stretchr/testify"
packages = [
"assert",
"require"
]
revision = "976c720a22c8eb4eb6a0b4348ad85ad12491a506"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "d37e90051cd58dd1f99f808626e82d64eac47f2b2334c6fcb9179bfcf2814622"
solver-name = "gps-cdcl"
solver-version = 1

34
vendor/code.gitea.io/git/Gopkg.toml generated vendored Normal file
View File

@ -0,0 +1,34 @@
# Gopkg.toml
#
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
#
# [prune]
# non-go = false
# go-tests = true
# unused-packages = true
[[constraint]]
name = "github.com/Unknwon/com"
branch = "master"
[prune]
go-tests = true
unused-packages = true

View File

@ -18,3 +18,4 @@ Antoine Girard <sapk@sapk.fr> (@sapk)
Jonas Östanbäck <jonas.ostanback@gmail.com> (@cez81) Jonas Östanbäck <jonas.ostanback@gmail.com> (@cez81)
David Schneiderbauer <dschneiderbauer@gmail.com> (@daviian) David Schneiderbauer <dschneiderbauer@gmail.com> (@daviian)
Peter Žeby <morlinest@gmail.com> (@morlinest) Peter Žeby <morlinest@gmail.com> (@morlinest)
Jonas Franz <info@jonasfranz.software> (@JonasFranzDEV)

10
vendor/code.gitea.io/git/error.go generated vendored
View File

@ -40,6 +40,16 @@ func (err ErrNotExist) Error() string {
return fmt.Sprintf("object does not exist [id: %s, rel_path: %s]", err.ID, err.RelPath) return fmt.Sprintf("object does not exist [id: %s, rel_path: %s]", err.ID, err.RelPath)
} }
// ErrBadLink entry.FollowLink error
type ErrBadLink struct {
Name string
Message string
}
func (err ErrBadLink) Error() string {
return fmt.Sprintf("%s: %s", err.Name, err.Message)
}
// ErrUnsupportedVersion error when required git version not matched // ErrUnsupportedVersion error when required git version not matched
type ErrUnsupportedVersion struct { type ErrUnsupportedVersion struct {
Required string Required string

81
vendor/code.gitea.io/git/parse.go generated vendored Normal file
View File

@ -0,0 +1,81 @@
// Copyright 2018 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package git
import (
"bytes"
"fmt"
"strconv"
)
// ParseTreeEntries parses the output of a `git ls-tree` command.
func ParseTreeEntries(data []byte) ([]*TreeEntry, error) {
return parseTreeEntries(data, nil)
}
func parseTreeEntries(data []byte, ptree *Tree) ([]*TreeEntry, error) {
entries := make([]*TreeEntry, 0, 10)
for pos := 0; pos < len(data); {
// expect line to be of the form "<mode> <type> <sha>\t<filename>"
entry := new(TreeEntry)
entry.ptree = ptree
if pos+6 > len(data) {
return nil, fmt.Errorf("Invalid ls-tree output: %s", string(data))
}
switch string(data[pos : pos+6]) {
case "100644":
entry.mode = EntryModeBlob
entry.Type = ObjectBlob
pos += 12 // skip over "100644 blob "
case "100755":
entry.mode = EntryModeExec
entry.Type = ObjectBlob
pos += 12 // skip over "100755 blob "
case "120000":
entry.mode = EntryModeSymlink
entry.Type = ObjectBlob
pos += 12 // skip over "120000 blob "
case "160000":
entry.mode = EntryModeCommit
entry.Type = ObjectCommit
pos += 14 // skip over "160000 object "
case "040000":
entry.mode = EntryModeTree
entry.Type = ObjectTree
pos += 12 // skip over "040000 tree "
default:
return nil, fmt.Errorf("unknown type: %v", string(data[pos:pos+6]))
}
if pos+40 > len(data) {
return nil, fmt.Errorf("Invalid ls-tree output: %s", string(data))
}
id, err := NewIDFromString(string(data[pos : pos+40]))
if err != nil {
return nil, fmt.Errorf("Invalid ls-tree output: %v", err)
}
entry.ID = id
pos += 41 // skip over sha and trailing space
end := pos + bytes.IndexByte(data[pos:], '\n')
if end < pos {
return nil, fmt.Errorf("Invalid ls-tree output: %s", string(data))
}
// In case entry name is surrounded by double quotes(it happens only in git-shell).
if data[pos] == '"' {
entry.name, err = strconv.Unquote(string(data[pos:end]))
if err != nil {
return nil, fmt.Errorf("Invalid ls-tree output: %v", err)
}
} else {
entry.name = string(data[pos:end])
}
pos = end + 1
entries = append(entries, entry)
}
return entries, nil
}

View File

@ -4,7 +4,21 @@
package git package git
import "fmt"
// FileBlame return the Blame object of file // FileBlame return the Blame object of file
func (repo *Repository) FileBlame(revision, path, file string) ([]byte, error) { func (repo *Repository) FileBlame(revision, path, file string) ([]byte, error) {
return NewCommand("blame", "--root", file).RunInDirBytes(path) return NewCommand("blame", "--root", "--", file).RunInDirBytes(path)
}
// LineBlame returns the latest commit at the given line
func (repo *Repository) LineBlame(revision, path, file string, line uint) (*Commit, error) {
res, err := NewCommand("blame", fmt.Sprintf("-L %d,%d", line, line), "-p", revision, "--", file).RunInDir(path)
if err != nil {
return nil, err
}
if len(res) < 40 {
return nil, fmt.Errorf("invalid result of blame: %s", res)
}
return repo.GetCommit(string(res[:40]))
} }

View File

@ -9,6 +9,8 @@ import (
"container/list" "container/list"
"strconv" "strconv"
"strings" "strings"
"github.com/mcuadros/go-version"
) )
// GetRefCommitID returns the last commit ID string of given reference (branch or tag). // GetRefCommitID returns the last commit ID string of given reference (branch or tag).
@ -274,7 +276,7 @@ func (repo *Repository) CommitsCountBetween(start, end string) (int64, error) {
func (repo *Repository) commitsBefore(id SHA1, limit int) (*list.List, error) { func (repo *Repository) commitsBefore(id SHA1, limit int) (*list.List, error) {
cmd := NewCommand("log") cmd := NewCommand("log")
if limit > 0 { if limit > 0 {
cmd.AddArguments("-"+ strconv.Itoa(limit), prettyLogFormat, id.String()) cmd.AddArguments("-"+strconv.Itoa(limit), prettyLogFormat, id.String())
} else { } else {
cmd.AddArguments(prettyLogFormat, id.String()) cmd.AddArguments(prettyLogFormat, id.String())
} }
@ -316,15 +318,35 @@ func (repo *Repository) getCommitsBeforeLimit(id SHA1, num int) (*list.List, err
} }
func (repo *Repository) getBranches(commit *Commit, limit int) ([]string, error) { func (repo *Repository) getBranches(commit *Commit, limit int) ([]string, error) {
stdout, err := NewCommand("for-each-ref", "--count="+ strconv.Itoa(limit), "--format=%(refname)", "--contains", commit.ID.String(), BranchPrefix).RunInDir(repo.Path) if version.Compare(gitVersion, "2.7.0", ">=") {
stdout, err := NewCommand("for-each-ref", "--count="+strconv.Itoa(limit), "--format=%(refname:strip=2)", "--contains", commit.ID.String(), BranchPrefix).RunInDir(repo.Path)
if err != nil {
return nil, err
}
branches := strings.Fields(stdout)
return branches, nil
}
stdout, err := NewCommand("branch", "--contains", commit.ID.String()).RunInDir(repo.Path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
refs := strings.Split(stdout, "\n") refs := strings.Split(stdout, "\n")
branches := make([]string, len(refs)-1)
for i, ref := range refs[:len(refs)-1] { var max int
branches[i] = strings.TrimPrefix(ref, BranchPrefix) if len(refs) > limit {
max = limit
} else {
max = len(refs) - 1
}
branches := make([]string, max)
for i, ref := range refs[:max] {
parts := strings.Fields(ref)
branches[i] = parts[len(parts)-1]
} }
return branches, nil return branches, nil
} }

29
vendor/code.gitea.io/git/sha1.go generated vendored
View File

@ -5,6 +5,7 @@
package git package git
import ( import (
"bytes"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"strings" "strings"
@ -26,43 +27,23 @@ func (id SHA1) Equal(s2 interface{}) bool {
} }
return v == id.String() return v == id.String()
case []byte: case []byte:
if len(v) != 20 { return bytes.Equal(v, id[:])
return false
}
for i, v := range v {
if id[i] != v {
return false
}
}
case SHA1: case SHA1:
for i, v := range v { return v == id
if id[i] != v {
return false
}
}
default: default:
return false return false
} }
return true
} }
// String returns string (hex) representation of the Oid. // String returns string (hex) representation of the Oid.
func (id SHA1) String() string { func (id SHA1) String() string {
result := make([]byte, 0, 40) return hex.EncodeToString(id[:])
hexvalues := []byte("0123456789abcdef")
for i := 0; i < 20; i++ {
result = append(result, hexvalues[id[i]>>4])
result = append(result, hexvalues[id[i]&0xf])
}
return string(result)
} }
// MustID always creates a new SHA1 from a [20]byte array with no validation of input. // MustID always creates a new SHA1 from a [20]byte array with no validation of input.
func MustID(b []byte) SHA1 { func MustID(b []byte) SHA1 {
var id SHA1 var id SHA1
for i := 0; i < 20; i++ { copy(id[:], b)
id[i] = b[i]
}
return id return id
} }

83
vendor/code.gitea.io/git/tree.go generated vendored
View File

@ -5,8 +5,6 @@
package git package git
import ( import (
"bytes"
"fmt"
"strings" "strings"
) )
@ -30,84 +28,6 @@ func NewTree(repo *Repository, id SHA1) *Tree {
} }
} }
var escapeChar = []byte("\\")
// UnescapeChars reverses escaped characters.
func UnescapeChars(in []byte) []byte {
if bytes.Index(in, escapeChar) == -1 {
return in
}
endIdx := len(in) - 1
isEscape := false
out := make([]byte, 0, endIdx+1)
for i := range in {
if in[i] == '\\' && !isEscape {
isEscape = true
continue
}
isEscape = false
out = append(out, in[i])
}
return out
}
// parseTreeData parses tree information from the (uncompressed) raw
// data from the tree object.
func parseTreeData(tree *Tree, data []byte) ([]*TreeEntry, error) {
entries := make([]*TreeEntry, 0, 10)
l := len(data)
pos := 0
for pos < l {
entry := new(TreeEntry)
entry.ptree = tree
step := 6
switch string(data[pos : pos+step]) {
case "100644":
entry.mode = EntryModeBlob
entry.Type = ObjectBlob
case "100755":
entry.mode = EntryModeExec
entry.Type = ObjectBlob
case "120000":
entry.mode = EntryModeSymlink
entry.Type = ObjectBlob
case "160000":
entry.mode = EntryModeCommit
entry.Type = ObjectCommit
step = 8
case "040000":
entry.mode = EntryModeTree
entry.Type = ObjectTree
default:
return nil, fmt.Errorf("unknown type: %v", string(data[pos:pos+step]))
}
pos += step + 6 // Skip string type of entry type.
step = 40
id, err := NewIDFromString(string(data[pos : pos+step]))
if err != nil {
return nil, err
}
entry.ID = id
pos += step + 1 // Skip half of SHA1.
step = bytes.IndexByte(data[pos:], '\n')
// In case entry name is surrounded by double quotes(it happens only in git-shell).
if data[pos] == '"' {
entry.name = string(UnescapeChars(data[pos+1 : pos+step-1]))
} else {
entry.name = string(data[pos : pos+step])
}
pos += step + 1
entries = append(entries, entry)
}
return entries, nil
}
// SubTree get a sub tree by the sub dir path // SubTree get a sub tree by the sub dir path
func (t *Tree) SubTree(rpath string) (*Tree, error) { func (t *Tree) SubTree(rpath string) (*Tree, error) {
if len(rpath) == 0 { if len(rpath) == 0 {
@ -142,12 +62,11 @@ func (t *Tree) ListEntries() (Entries, error) {
if t.entriesParsed { if t.entriesParsed {
return t.entries, nil return t.entries, nil
} }
t.entriesParsed = true
stdout, err := NewCommand("ls-tree", t.ID.String()).RunInDirBytes(t.repo.Path) stdout, err := NewCommand("ls-tree", t.ID.String()).RunInDirBytes(t.repo.Path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
t.entries, err = parseTreeData(t, stdout) t.entries, err = parseTreeEntries(stdout, t)
return t.entries, err return t.entries, err
} }

View File

@ -5,6 +5,7 @@
package git package git
import ( import (
"io"
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
@ -90,6 +91,45 @@ func (te *TreeEntry) Blob() *Blob {
} }
} }
// FollowLink returns the entry pointed to by a symlink
func (te *TreeEntry) FollowLink() (*TreeEntry, error) {
if !te.IsLink() {
return nil, ErrBadLink{te.Name(), "not a symlink"}
}
// read the link
r, err := te.Blob().Data()
if err != nil {
return nil, err
}
buf := make([]byte, te.Size())
_, err = io.ReadFull(r, buf)
if err != nil {
return nil, err
}
lnk := string(buf)
t := te.ptree
// traverse up directories
for ; t != nil && strings.HasPrefix(lnk, "../"); lnk = lnk[3:] {
t = t.ptree
}
if t == nil {
return nil, ErrBadLink{te.Name(), "points outside of repo"}
}
target, err := t.GetTreeEntryByPath(lnk)
if err != nil {
if IsErrNotExist(err) {
return nil, ErrBadLink{te.Name(), "broken link"}
}
return nil, err
}
return target, nil
}
// GetSubJumpablePathName return the full path of subdirectory jumpable ( contains only one directory ) // GetSubJumpablePathName return the full path of subdirectory jumpable ( contains only one directory )
func (te *TreeEntry) GetSubJumpablePathName() string { func (te *TreeEntry) GetSubJumpablePathName() string {
if te.IsSubModule() || !te.IsDir() { if te.IsSubModule() || !te.IsDir() {

View File

@ -8,6 +8,10 @@ protocol providers, as long as they implement the `Provider` and `Session` inter
This package was inspired by [https://github.com/intridea/omniauth](https://github.com/intridea/omniauth). This package was inspired by [https://github.com/intridea/omniauth](https://github.com/intridea/omniauth).
## Goth Needs a New Maintainer
[https://blog.gobuffalo.io/goth-needs-a-new-maintainer-626cd47ca37b](https://blog.gobuffalo.io/goth-needs-a-new-maintainer-626cd47ca37b) - TL;DR: I, @markbates, won't be responding to any more issues, PRs, etc... for this package. A new maintainer needs to be found ASAP. Is this you?
## Installation ## Installation
```text ```text
@ -18,6 +22,8 @@ $ go get github.com/markbates/goth
* Amazon * Amazon
* Auth0 * Auth0
* Azure AD
* Battle.net
* Bitbucket * Bitbucket
* Box * Box
* Cloud Foundry * Cloud Foundry
@ -26,6 +32,7 @@ $ go get github.com/markbates/goth
* Digital Ocean * Digital Ocean
* Discord * Discord
* Dropbox * Dropbox
* Eve Online
* Facebook * Facebook
* Fitbit * Fitbit
* GitHub * GitHub
@ -38,6 +45,7 @@ $ go get github.com/markbates/goth
* Lastfm * Lastfm
* Linkedin * Linkedin
* Meetup * Meetup
* MicrosoftOnline
* OneDrive * OneDrive
* OpenID Connect (auto discovery) * OpenID Connect (auto discovery)
* Paypal * Paypal
@ -50,7 +58,9 @@ $ go get github.com/markbates/goth
* Twitch * Twitch
* Twitter * Twitter
* Uber * Uber
* VK
* Wepay * Wepay
* Xero
* Yahoo * Yahoo
* Yammer * Yammer
@ -71,17 +81,51 @@ $ go get github.com/markbates/goth
```text ```text
$ cd goth/examples $ cd goth/examples
$ go get -v $ go get -v
$ go build $ go build
$ ./examples $ ./examples
``` ```
Now open up your browser and go to [http://localhost:3000](http://localhost:3000) to see the example. Now open up your browser and go to [http://localhost:3000](http://localhost:3000) to see the example.
To actually use the different providers, please make sure you configure them given the system environments as defined in the examples/main.go file To actually use the different providers, please make sure you set environment variables. Example given in the examples/main.go file
## Security Notes
By default, gothic uses a `CookieStore` from the `gorilla/sessions` package to store session data.
As configured, this default store (`gothic.Store`) will generate cookies with `Options`:
```go
&Options{
Path: "/",
Domain: "",
MaxAge: 86400 * 30,
HttpOnly: true,
Secure: false,
}
```
To tailor these fields for your application, you can override the `gothic.Store` variable at startup.
The follow snippet show one way to do this:
```go
key := "" // Replace with your SESSION_SECRET or similar
maxAge := 86400 * 30 // 30 days
isProd := false // Set to true when serving over https
store := sessions.NewCookieStore([]byte(key))
store.MaxAge(maxAge)
store.Options.Path = "/"
store.Options.HttpOnly = true // HttpOnly should always be enabled
store.Options.Secure = isProd
gothic.Store = store
```
## Issues ## Issues
Issues always stand a significantly better chance of getting fixed if the are accompanied by a Issues always stand a significantly better chance of getting fixed if they are accompanied by a
pull request. pull request.
## Contributing ## Contributing
@ -94,50 +138,3 @@ Would I love to see more providers? Certainly! Would you love to contribute one?
4. Commit your changes (git commit -am 'Add some feature') 4. Commit your changes (git commit -am 'Add some feature')
5. Push to the branch (git push origin my-new-feature) 5. Push to the branch (git push origin my-new-feature)
6. Create new Pull Request 6. Create new Pull Request
## Contributors
* Mark Bates
* Tyler Bunnell
* Corey McGrillis
* willemvd
* Rakesh Goyal
* Andy Grunwald
* Glenn Walker
* Kevin Fitzpatrick
* Ben Tranter
* Sharad Ganapathy
* Andrew Chilton
* sharadgana
* Aurorae
* Craig P Jolicoeur
* Zac Bergquist
* Geoff Franks
* Raphael Geronimi
* Noah Shibley
* lumost
* oov
* Felix Lamouroux
* Rafael Quintela
* Tyler
* DenSm
* Samy KACIMI
* dante gray
* Noah
* Jacob Walker
* Marin Martinic
* Roy
* Omni Adams
* Sasa Brankovic
* dkhamsing
* Dante Swift
* Attila Domokos
* Albin Gilles
* Syed Zubairuddin
* Johnny Boursiquot
* Jerome Touffe-Blin
* bryanl
* Masanobu YOSHIOKA
* Jonathan Hall
* HaiMing.Yin
* Sairam Kunala

View File

@ -8,10 +8,18 @@ See https://github.com/markbates/goth/examples/main.go to see this in action.
package gothic package gothic
import ( import (
"bytes"
"compress/gzip"
"encoding/base64"
"errors" "errors"
"fmt" "fmt"
"io/ioutil"
"math/rand"
"net/http" "net/http"
"net/url"
"os" "os"
"strings"
"time"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/gorilla/sessions" "github.com/gorilla/sessions"
@ -27,15 +35,21 @@ var defaultStore sessions.Store
var keySet = false var keySet = false
var gothicRand *rand.Rand
func init() { func init() {
key := []byte(os.Getenv("SESSION_SECRET")) key := []byte(os.Getenv("SESSION_SECRET"))
keySet = len(key) != 0 keySet = len(key) != 0
Store = sessions.NewCookieStore([]byte(key))
cookieStore := sessions.NewCookieStore([]byte(key))
cookieStore.Options.HttpOnly = true
Store = cookieStore
defaultStore = Store defaultStore = Store
gothicRand = rand.New(rand.NewSource(time.Now().UnixNano()))
} }
/* /*
BeginAuthHandler is a convienence handler for starting the authentication process. BeginAuthHandler is a convenience handler for starting the authentication process.
It expects to be able to get the name of the provider from the query parameters It expects to be able to get the name of the provider from the query parameters
as either "provider" or ":provider". as either "provider" or ":provider".
@ -65,8 +79,16 @@ var SetState = func(req *http.Request) string {
return state return state
} }
return "state" // If a state query param is not passed in, generate a random
// base64-encoded nonce so that the state on the auth URL
// is unguessable, preventing CSRF attacks, as described in
//
// https://auth0.com/docs/protocols/oauth2/oauth-state#keep-reading
nonceBytes := make([]byte, 64)
for i := 0; i < 64; i++ {
nonceBytes[i] = byte(gothicRand.Int63() % 256)
}
return base64.URLEncoding.EncodeToString(nonceBytes)
} }
// GetState gets the state returned by the provider during the callback. // GetState gets the state returned by the provider during the callback.
@ -87,7 +109,6 @@ I would recommend using the BeginAuthHandler instead of doing all of these steps
yourself, but that's entirely up to you. yourself, but that's entirely up to you.
*/ */
func GetAuthURL(res http.ResponseWriter, req *http.Request) (string, error) { func GetAuthURL(res http.ResponseWriter, req *http.Request) (string, error) {
if !keySet && defaultStore == Store { if !keySet && defaultStore == Store {
fmt.Println("goth/gothic: no SESSION_SECRET environment variable is set. The default cookie store is not available and any calls will fail. Ignore this warning if you are using a different store.") fmt.Println("goth/gothic: no SESSION_SECRET environment variable is set. The default cookie store is not available and any calls will fail. Ignore this warning if you are using a different store.")
} }
@ -111,7 +132,7 @@ func GetAuthURL(res http.ResponseWriter, req *http.Request) (string, error) {
return "", err return "", err
} }
err = storeInSession(providerName, sess.Marshal(), req, res) err = StoreInSession(providerName, sess.Marshal(), req, res)
if err != nil { if err != nil {
return "", err return "", err
@ -130,7 +151,7 @@ as either "provider" or ":provider".
See https://github.com/markbates/goth/examples/main.go to see this in action. See https://github.com/markbates/goth/examples/main.go to see this in action.
*/ */
var CompleteUserAuth = func(res http.ResponseWriter, req *http.Request) (goth.User, error) { var CompleteUserAuth = func(res http.ResponseWriter, req *http.Request) (goth.User, error) {
defer Logout(res, req)
if !keySet && defaultStore == Store { if !keySet && defaultStore == Store {
fmt.Println("goth/gothic: no SESSION_SECRET environment variable is set. The default cookie store is not available and any calls will fail. Ignore this warning if you are using a different store.") fmt.Println("goth/gothic: no SESSION_SECRET environment variable is set. The default cookie store is not available and any calls will fail. Ignore this warning if you are using a different store.")
} }
@ -145,7 +166,7 @@ var CompleteUserAuth = func(res http.ResponseWriter, req *http.Request) (goth.Us
return goth.User{}, err return goth.User{}, err
} }
value, err := getFromSession(providerName, req) value, err := GetFromSession(providerName, req)
if err != nil { if err != nil {
return goth.User{}, err return goth.User{}, err
} }
@ -155,6 +176,11 @@ var CompleteUserAuth = func(res http.ResponseWriter, req *http.Request) (goth.Us
return goth.User{}, err return goth.User{}, err
} }
err = validateState(req, sess)
if err != nil {
return goth.User{}, err
}
user, err := provider.FetchUser(sess) user, err := provider.FetchUser(sess)
if err == nil { if err == nil {
// user can be found with existing session data // user can be found with existing session data
@ -167,13 +193,49 @@ var CompleteUserAuth = func(res http.ResponseWriter, req *http.Request) (goth.Us
return goth.User{}, err return goth.User{}, err
} }
err = storeInSession(providerName, sess.Marshal(), req, res) err = StoreInSession(providerName, sess.Marshal(), req, res)
if err != nil { if err != nil {
return goth.User{}, err return goth.User{}, err
} }
return provider.FetchUser(sess) gu, err := provider.FetchUser(sess)
return gu, err
}
// validateState ensures that the state token param from the original
// AuthURL matches the one included in the current (callback) request.
func validateState(req *http.Request, sess goth.Session) error {
rawAuthURL, err := sess.GetAuthURL()
if err != nil {
return err
}
authURL, err := url.Parse(rawAuthURL)
if err != nil {
return err
}
originalState := authURL.Query().Get("state")
if originalState != "" && (originalState != req.URL.Query().Get("state")) {
return errors.New("state token mismatch")
}
return nil
}
// Logout invalidates a user session.
func Logout(res http.ResponseWriter, req *http.Request) error {
session, err := Store.Get(req, SessionName)
if err != nil {
return err
}
session.Options.MaxAge = -1
session.Values = make(map[interface{}]interface{})
err = session.Save(req, res)
if err != nil {
return errors.New("Could not delete user session ")
}
return nil
} }
// GetProviderName is a function used to get the name of a provider // GetProviderName is a function used to get the name of a provider
@ -184,36 +246,99 @@ var CompleteUserAuth = func(res http.ResponseWriter, req *http.Request) (goth.Us
var GetProviderName = getProviderName var GetProviderName = getProviderName
func getProviderName(req *http.Request) (string, error) { func getProviderName(req *http.Request) (string, error) {
provider := req.URL.Query().Get("provider")
if provider == "" { // get all the used providers
if p, ok := mux.Vars(req)["provider"]; ok { providers := goth.GetProviders()
// loop over the used providers, if we already have a valid session for any provider (ie. user is already logged-in with a provider), then return that provider name
for _, provider := range providers {
p := provider.Name()
session, _ := Store.Get(req, p+SessionName)
value := session.Values[p]
if _, ok := value.(string); ok {
return p, nil return p, nil
} }
} }
if provider == "" {
provider = req.URL.Query().Get(":provider") // try to get it from the url param "provider"
if p := req.URL.Query().Get("provider"); p != "" {
return p, nil
} }
if provider == "" {
return provider, errors.New("you must select a provider") // try to get it from the url param ":provider"
if p := req.URL.Query().Get(":provider"); p != "" {
return p, nil
} }
return provider, nil
// try to get it from the context's value of "provider" key
if p, ok := mux.Vars(req)["provider"]; ok {
return p, nil
}
// try to get it from the go-context's value of "provider" key
if p, ok := req.Context().Value("provider").(string); ok {
return p, nil
}
// if not found then return an empty string with the corresponding error
return "", errors.New("you must select a provider")
} }
func storeInSession(key string, value string, req *http.Request, res http.ResponseWriter) error { // StoreInSession stores a specified key/value pair in the session.
session, _ := Store.Get(req, key + SessionName) func StoreInSession(key string, value string, req *http.Request, res http.ResponseWriter) error {
session, _ := Store.New(req, SessionName)
session.Values[key] = value if err := updateSessionValue(session, key, value); err != nil {
return err
}
return session.Save(req, res) return session.Save(req, res)
} }
func getFromSession(key string, req *http.Request) (string, error) { // GetFromSession retrieves a previously-stored value from the session.
session, _ := Store.Get(req, key + SessionName) // If no value has previously been stored at the specified key, it will return an error.
func GetFromSession(key string, req *http.Request) (string, error) {
value := session.Values[key] session, _ := Store.Get(req, SessionName)
if value == nil { value, err := getSessionValue(session, key)
if err != nil {
return "", errors.New("could not find a matching session for this request") return "", errors.New("could not find a matching session for this request")
} }
return value.(string), nil return value, nil
} }
func getSessionValue(session *sessions.Session, key string) (string, error) {
value := session.Values[key]
if value == nil {
return "", fmt.Errorf("could not find a matching session for this request")
}
rdata := strings.NewReader(value.(string))
r, err := gzip.NewReader(rdata)
if err != nil {
return "", err
}
s, err := ioutil.ReadAll(r)
if err != nil {
return "", err
}
return string(s), nil
}
func updateSessionValue(session *sessions.Session, key, value string) error {
var b bytes.Buffer
gz := gzip.NewWriter(&b)
if _, err := gz.Write([]byte(value)); err != nil {
return err
}
if err := gz.Flush(); err != nil {
return err
}
if err := gz.Close(); err != nil {
return err
}
session.Values[key] = b.String()
return nil
}

View File

@ -9,9 +9,9 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"fmt"
"github.com/markbates/goth" "github.com/markbates/goth"
"golang.org/x/oauth2" "golang.org/x/oauth2"
"fmt"
) )
const ( const (
@ -26,10 +26,10 @@ const (
// one manually. // one manually.
func New(clientKey, secret, callbackURL string, scopes ...string) *Provider { func New(clientKey, secret, callbackURL string, scopes ...string) *Provider {
p := &Provider{ p := &Provider{
ClientKey: clientKey, ClientKey: clientKey,
Secret: secret, Secret: secret,
CallbackURL: callbackURL, CallbackURL: callbackURL,
providerName: "bitbucket", providerName: "bitbucket",
} }
p.config = newConfig(p, scopes) p.config = newConfig(p, scopes)
return p return p
@ -125,7 +125,7 @@ func (p *Provider) FetchUser(session goth.Session) (goth.User, error) {
func userFromReader(reader io.Reader, user *goth.User) error { func userFromReader(reader io.Reader, user *goth.User) error {
u := struct { u := struct {
ID string `json:"uuid"` ID string `json:"uuid"`
Links struct { Links struct {
Avatar struct { Avatar struct {
URL string `json:"href"` URL string `json:"href"`

View File

@ -2,21 +2,24 @@
package dropbox package dropbox
import ( import (
"bytes"
"encoding/json" "encoding/json"
"errors" "errors"
"io" "io"
"io/ioutil"
"net/http" "net/http"
"strings" "strings"
"fmt"
"github.com/markbates/goth" "github.com/markbates/goth"
"golang.org/x/oauth2" "golang.org/x/oauth2"
"fmt"
) )
const ( const (
authURL = "https://www.dropbox.com/1/oauth2/authorize" authURL = "https://www.dropbox.com/oauth2/authorize"
tokenURL = "https://api.dropbox.com/1/oauth2/token" tokenURL = "https://api.dropbox.com/oauth2/token"
accountURL = "https://api.dropbox.com/1/account/info" accountURL = "https://api.dropbox.com/2/users/get_current_account"
) )
// Provider is the implementation of `goth.Provider` for accessing Dropbox. // Provider is the implementation of `goth.Provider` for accessing Dropbox.
@ -24,6 +27,7 @@ type Provider struct {
ClientKey string ClientKey string
Secret string Secret string
CallbackURL string CallbackURL string
AccountURL string
HTTPClient *http.Client HTTPClient *http.Client
config *oauth2.Config config *oauth2.Config
providerName string providerName string
@ -40,10 +44,11 @@ type Session struct {
// create one manually. // create one manually.
func New(clientKey, secret, callbackURL string, scopes ...string) *Provider { func New(clientKey, secret, callbackURL string, scopes ...string) *Provider {
p := &Provider{ p := &Provider{
ClientKey: clientKey, ClientKey: clientKey,
Secret: secret, Secret: secret,
CallbackURL: callbackURL, CallbackURL: callbackURL,
providerName: "dropbox", AccountURL: accountURL,
providerName: "dropbox",
} }
p.config = newConfig(p, scopes) p.config = newConfig(p, scopes)
return p return p
@ -86,7 +91,7 @@ func (p *Provider) FetchUser(session goth.Session) (goth.User, error) {
return user, fmt.Errorf("%s cannot get user information without accessToken", p.providerName) return user, fmt.Errorf("%s cannot get user information without accessToken", p.providerName)
} }
req, err := http.NewRequest("GET", accountURL, nil) req, err := http.NewRequest("POST", p.AccountURL, nil)
if err != nil { if err != nil {
return user, err return user, err
} }
@ -101,7 +106,17 @@ func (p *Provider) FetchUser(session goth.Session) (goth.User, error) {
return user, fmt.Errorf("%s responded with a %d trying to fetch user information", p.providerName, resp.StatusCode) return user, fmt.Errorf("%s responded with a %d trying to fetch user information", p.providerName, resp.StatusCode)
} }
err = userFromReader(resp.Body, &user) bits, err := ioutil.ReadAll(resp.Body)
if err != nil {
return user, err
}
err = json.NewDecoder(bytes.NewReader(bits)).Decode(&user.RawData)
if err != nil {
return user, err
}
err = userFromReader(bytes.NewReader(bits), &user)
return user, err return user, err
} }
@ -161,22 +176,29 @@ func newConfig(p *Provider, scopes []string) *oauth2.Config {
func userFromReader(r io.Reader, user *goth.User) error { func userFromReader(r io.Reader, user *goth.User) error {
u := struct { u := struct {
Name string `json:"display_name"` AccountID string `json:"account_id"`
NameDetails struct { Name struct {
NickName string `json:"familiar_name"` GivenName string `json:"given_name"`
} `json:"name_details"` Surname string `json:"surname"`
Location string `json:"country"` DisplayName string `json:"display_name"`
Email string `json:"email"` } `json:"name"`
Country string `json:"country"`
Email string `json:"email"`
ProfilePhotoURL string `json:"profile_photo_url"`
}{} }{}
err := json.NewDecoder(r).Decode(&u) err := json.NewDecoder(r).Decode(&u)
if err != nil { if err != nil {
return err return err
} }
user.UserID = u.AccountID // The user's unique Dropbox ID.
user.FirstName = u.Name.GivenName
user.LastName = u.Name.Surname
user.Name = strings.TrimSpace(fmt.Sprintf("%s %s", u.Name.GivenName, u.Name.Surname))
user.Description = u.Name.DisplayName // Full name plus parenthetical team naem
user.Email = u.Email user.Email = u.Email
user.Name = u.Name user.NickName = u.Email // Email is the dropbox username
user.NickName = u.NameDetails.NickName user.Location = u.Country
user.UserID = u.Email // Dropbox doesn't provide a separate user ID user.AvatarURL = u.ProfilePhotoURL // May be blank
user.Location = u.Location
return nil return nil
} }

View File

@ -11,12 +11,12 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"github.com/markbates/goth"
"golang.org/x/oauth2"
"fmt"
"crypto/hmac" "crypto/hmac"
"crypto/sha256" "crypto/sha256"
"encoding/hex" "encoding/hex"
"fmt"
"github.com/markbates/goth"
"golang.org/x/oauth2"
) )
const ( const (
@ -30,10 +30,10 @@ const (
// one manually. // one manually.
func New(clientKey, secret, callbackURL string, scopes ...string) *Provider { func New(clientKey, secret, callbackURL string, scopes ...string) *Provider {
p := &Provider{ p := &Provider{
ClientKey: clientKey, ClientKey: clientKey,
Secret: secret, Secret: secret,
CallbackURL: callbackURL, CallbackURL: callbackURL,
providerName: "facebook", providerName: "facebook",
} }
p.config = newConfig(p, scopes) p.config = newConfig(p, scopes)
return p return p
@ -129,7 +129,7 @@ func userFromReader(reader io.Reader, user *goth.User) error {
FirstName string `json:"first_name"` FirstName string `json:"first_name"`
LastName string `json:"last_name"` LastName string `json:"last_name"`
Link string `json:"link"` Link string `json:"link"`
Picture struct { Picture struct {
Data struct { Data struct {
URL string `json:"url"` URL string `json:"url"`
} `json:"data"` } `json:"data"`

View File

@ -11,9 +11,9 @@ import (
"net/url" "net/url"
"strconv" "strconv"
"fmt"
"github.com/markbates/goth" "github.com/markbates/goth"
"golang.org/x/oauth2" "golang.org/x/oauth2"
"fmt"
) )
// These vars define the Authentication, Token, and Profile URLS for Gitlab. If // These vars define the Authentication, Token, and Profile URLS for Gitlab. If

View File

@ -11,9 +11,9 @@ import (
"net/url" "net/url"
"strings" "strings"
"fmt"
"github.com/markbates/goth" "github.com/markbates/goth"
"golang.org/x/oauth2" "golang.org/x/oauth2"
"fmt"
) )
const ( const (
@ -27,10 +27,10 @@ const (
// one manually. // one manually.
func New(clientKey, secret, callbackURL string, scopes ...string) *Provider { func New(clientKey, secret, callbackURL string, scopes ...string) *Provider {
p := &Provider{ p := &Provider{
ClientKey: clientKey, ClientKey: clientKey,
Secret: secret, Secret: secret,
CallbackURL: callbackURL, CallbackURL: callbackURL,
providerName: "gplus", providerName: "gplus",
} }
p.config = newConfig(p, scopes) p.config = newConfig(p, scopes)
return p return p

View File

@ -1,17 +1,17 @@
package openidConnect package openidConnect
import ( import (
"bytes"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"github.com/markbates/goth"
"golang.org/x/oauth2"
"io/ioutil"
"net/http" "net/http"
"strings" "strings"
"fmt"
"encoding/json"
"encoding/base64"
"io/ioutil"
"errors"
"golang.org/x/oauth2"
"github.com/markbates/goth"
"time" "time"
"bytes"
) )
const ( const (
@ -89,14 +89,14 @@ func New(clientKey, secret, callbackURL, openIDAutoDiscoveryURL string, scopes .
Secret: secret, Secret: secret,
CallbackURL: callbackURL, CallbackURL: callbackURL,
UserIdClaims: []string{subjectClaim}, UserIdClaims: []string{subjectClaim},
NameClaims: []string{NameClaim}, NameClaims: []string{NameClaim},
NickNameClaims: []string{NicknameClaim, PreferredUsernameClaim}, NickNameClaims: []string{NicknameClaim, PreferredUsernameClaim},
EmailClaims: []string{EmailClaim}, EmailClaims: []string{EmailClaim},
AvatarURLClaims:[]string{PictureClaim}, AvatarURLClaims: []string{PictureClaim},
FirstNameClaims:[]string{GivenNameClaim}, FirstNameClaims: []string{GivenNameClaim},
LastNameClaims: []string{FamilyNameClaim}, LastNameClaims: []string{FamilyNameClaim},
LocationClaims: []string{AddressClaim}, LocationClaims: []string{AddressClaim},
providerName: "openid-connect", providerName: "openid-connect",
} }

View File

@ -1,12 +1,12 @@
package openidConnect package openidConnect
import ( import (
"encoding/json"
"errors" "errors"
"github.com/markbates/goth" "github.com/markbates/goth"
"encoding/json" "golang.org/x/oauth2"
"strings" "strings"
"time" "time"
"golang.org/x/oauth2"
) )
// Session stores data during the auth process with the OpenID Connect provider. // Session stores data during the auth process with the OpenID Connect provider.

View File

@ -9,10 +9,11 @@ import (
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"fmt"
"github.com/markbates/goth" "github.com/markbates/goth"
"github.com/mrjones/oauth" "github.com/mrjones/oauth"
"golang.org/x/oauth2" "golang.org/x/oauth2"
"fmt"
) )
var ( var (
@ -30,10 +31,10 @@ var (
// If you'd like to use authenticate instead of authorize, use NewAuthenticate instead. // If you'd like to use authenticate instead of authorize, use NewAuthenticate instead.
func New(clientKey, secret, callbackURL string) *Provider { func New(clientKey, secret, callbackURL string) *Provider {
p := &Provider{ p := &Provider{
ClientKey: clientKey, ClientKey: clientKey,
Secret: secret, Secret: secret,
CallbackURL: callbackURL, CallbackURL: callbackURL,
providerName: "twitter", providerName: "twitter",
} }
p.consumer = newConsumer(p, authorizeURL) p.consumer = newConsumer(p, authorizeURL)
return p return p
@ -43,10 +44,10 @@ func New(clientKey, secret, callbackURL string) *Provider {
// NewAuthenticate uses the authenticate URL instead of the authorize URL. // NewAuthenticate uses the authenticate URL instead of the authorize URL.
func NewAuthenticate(clientKey, secret, callbackURL string) *Provider { func NewAuthenticate(clientKey, secret, callbackURL string) *Provider {
p := &Provider{ p := &Provider{
ClientKey: clientKey, ClientKey: clientKey,
Secret: secret, Secret: secret,
CallbackURL: callbackURL, CallbackURL: callbackURL,
providerName: "twitter", providerName: "twitter",
} }
p.consumer = newConsumer(p, authenticateURL) p.consumer = newConsumer(p, authenticateURL)
return p return p
@ -107,7 +108,7 @@ func (p *Provider) FetchUser(session goth.Session) (goth.User, error) {
response, err := p.consumer.Get( response, err := p.consumer.Get(
endpointProfile, endpointProfile,
map[string]string{"include_entities": "false", "skip_status": "true"}, map[string]string{"include_entities": "false", "skip_status": "true", "include_email": "true"},
sess.AccessToken) sess.AccessToken)
if err != nil { if err != nil {
return user, err return user, err
@ -126,6 +127,9 @@ func (p *Provider) FetchUser(session goth.Session) (goth.User, error) {
user.Name = user.RawData["name"].(string) user.Name = user.RawData["name"].(string)
user.NickName = user.RawData["screen_name"].(string) user.NickName = user.RawData["screen_name"].(string)
if user.RawData["email"] != nil {
user.Email = user.RawData["email"].(string)
}
user.Description = user.RawData["description"].(string) user.Description = user.RawData["description"].(string)
user.AvatarURL = user.RawData["profile_image_url"].(string) user.AvatarURL = user.RawData["profile_image_url"].(string)
user.UserID = user.RawData["id_str"].(string) user.UserID = user.RawData["id_str"].(string)

74
vendor/vendor.json vendored
View File

@ -3,10 +3,10 @@
"ignore": "test appengine", "ignore": "test appengine",
"package": [ "package": [
{ {
"checksumSHA1": "Gz+a5Qo4PCiB/Gf2f02v8HEAxDM=", "checksumSHA1": "xwQNnA5geMAdbiBjBABtsjqZZMw=",
"path": "code.gitea.io/git", "path": "code.gitea.io/git",
"revision": "6798d0f202cdc7187c00a467b586a4bdee27e8c9", "revision": "31f4b8e8c805438ac6d8914b38accb1d8aaf695e",
"revisionTime": "2018-01-14T14:37:32Z" "revisionTime": "2018-05-26T05:17:21Z"
}, },
{ {
"checksumSHA1": "Qtq0kW+BnpYMOriaoCjMa86WGG8=", "checksumSHA1": "Qtq0kW+BnpYMOriaoCjMa86WGG8=",
@ -654,64 +654,74 @@
"revisionTime": "2017-10-25T03:15:54Z" "revisionTime": "2017-10-25T03:15:54Z"
}, },
{ {
"checksumSHA1": "O3KUfEXQPfdQ+tCMpP2RAIRJJqY=", "checksumSHA1": "q9MD1ienC+kmKq5i51oAktQEV1E=",
"origin": "github.com/go-gitea/goth",
"path": "github.com/markbates/goth", "path": "github.com/markbates/goth",
"revision": "90362394a367f9d77730911973462a53d69662ba", "revision": "3b54d96084a5e11030f19556cf68a6ab5d93ba20",
"revisionTime": "2017-02-23T14:12:10Z" "revisionTime": "2018-03-12T06:32:04Z"
}, },
{ {
"checksumSHA1": "MkFKwLV3icyUo4oP0BgEs+7+R1Y=", "checksumSHA1": "FISfgOkoMtn98wglLUvfBTZ6baE=",
"origin": "github.com/go-gitea/goth/gothic",
"path": "github.com/markbates/goth/gothic", "path": "github.com/markbates/goth/gothic",
"revision": "90362394a367f9d77730911973462a53d69662ba", "revision": "3b54d96084a5e11030f19556cf68a6ab5d93ba20",
"revisionTime": "2017-02-23T14:12:10Z" "revisionTime": "2018-03-12T06:32:04Z"
}, },
{ {
"checksumSHA1": "crNSlQADjX6hcxykON2tFCqY4iw=", "checksumSHA1": "pJ+Cws/TU22K6tZ/ALFOvvH1K5U=",
"origin": "github.com/go-gitea/goth/providers/bitbucket",
"path": "github.com/markbates/goth/providers/bitbucket", "path": "github.com/markbates/goth/providers/bitbucket",
"revision": "90362394a367f9d77730911973462a53d69662ba", "revision": "3b54d96084a5e11030f19556cf68a6ab5d93ba20",
"revisionTime": "2017-02-23T14:12:10Z" "revisionTime": "2018-03-12T06:32:04Z"
}, },
{ {
"checksumSHA1": "1Kp4DKkJNVn135Xg8H4a6CFBNy8=", "checksumSHA1": "XsF5HI4240QHbFXbtWWnGgTsoq8=",
"origin": "github.com/go-gitea/goth/providers/dropbox",
"path": "github.com/markbates/goth/providers/dropbox", "path": "github.com/markbates/goth/providers/dropbox",
"revision": "90362394a367f9d77730911973462a53d69662ba", "revision": "3b54d96084a5e11030f19556cf68a6ab5d93ba20",
"revisionTime": "2017-02-23T14:12:10Z" "revisionTime": "2018-03-12T06:32:04Z"
}, },
{ {
"checksumSHA1": "cGs1da29iOBJh5EAH0icKDbN8CA=", "checksumSHA1": "VzbroIA9R00Ig3iGnOlZLU7d4ls=",
"origin": "github.com/go-gitea/goth/providers/facebook",
"path": "github.com/markbates/goth/providers/facebook", "path": "github.com/markbates/goth/providers/facebook",
"revision": "90362394a367f9d77730911973462a53d69662ba", "revision": "3b54d96084a5e11030f19556cf68a6ab5d93ba20",
"revisionTime": "2017-02-23T14:12:10Z" "revisionTime": "2018-03-12T06:32:04Z"
}, },
{ {
"checksumSHA1": "P6nBZ850aaekpOcoXNdRhK86bH8=", "checksumSHA1": "P6nBZ850aaekpOcoXNdRhK86bH8=",
"origin": "github.com/go-gitea/goth/providers/github",
"path": "github.com/markbates/goth/providers/github", "path": "github.com/markbates/goth/providers/github",
"revision": "90362394a367f9d77730911973462a53d69662ba", "revision": "3b54d96084a5e11030f19556cf68a6ab5d93ba20",
"revisionTime": "2017-02-23T14:12:10Z" "revisionTime": "2018-03-12T06:32:04Z"
}, },
{ {
"checksumSHA1": "o/109paSRy9HqV87gR4zUZMMSzs=", "checksumSHA1": "ld488t+yGoTwtmiCSSggEX4fxVk=",
"origin": "github.com/go-gitea/goth/providers/gitlab",
"path": "github.com/markbates/goth/providers/gitlab", "path": "github.com/markbates/goth/providers/gitlab",
"revision": "90362394a367f9d77730911973462a53d69662ba", "revision": "3b54d96084a5e11030f19556cf68a6ab5d93ba20",
"revisionTime": "2017-02-23T14:12:10Z" "revisionTime": "2018-03-12T06:32:04Z"
}, },
{ {
"checksumSHA1": "cX6kR9y94BWFZvI/7UFrsFsP3FQ=", "checksumSHA1": "qXEulD7vnwY9hFrxh91Pm5YrvTM=",
"origin": "github.com/go-gitea/goth/providers/gplus",
"path": "github.com/markbates/goth/providers/gplus", "path": "github.com/markbates/goth/providers/gplus",
"revision": "90362394a367f9d77730911973462a53d69662ba", "revision": "3b54d96084a5e11030f19556cf68a6ab5d93ba20",
"revisionTime": "2017-02-23T14:12:10Z" "revisionTime": "2018-03-12T06:32:04Z"
}, },
{ {
"checksumSHA1": "sMYKhqAUZXM1+T/TjlMhWh8Vveo=", "checksumSHA1": "wsOBzyp4LKDhfCPmX1LLP7T0S3U=",
"origin": "github.com/go-gitea/goth/providers/openidConnect",
"path": "github.com/markbates/goth/providers/openidConnect", "path": "github.com/markbates/goth/providers/openidConnect",
"revision": "90362394a367f9d77730911973462a53d69662ba", "revision": "3b54d96084a5e11030f19556cf68a6ab5d93ba20",
"revisionTime": "2017-02-23T14:12:10Z" "revisionTime": "2018-03-12T06:32:04Z"
}, },
{ {
"checksumSHA1": "1w0V6jYXaGlEtZcMeYTOAAucvgw=", "checksumSHA1": "o6RqMbbE8QNZhNT9TsAIRMPI8tg=",
"origin": "github.com/go-gitea/goth/providers/twitter",
"path": "github.com/markbates/goth/providers/twitter", "path": "github.com/markbates/goth/providers/twitter",
"revision": "90362394a367f9d77730911973462a53d69662ba", "revision": "3b54d96084a5e11030f19556cf68a6ab5d93ba20",
"revisionTime": "2017-02-23T14:12:10Z" "revisionTime": "2018-03-12T06:32:04Z"
}, },
{ {
"checksumSHA1": "61HNjGetaBoMp8HBOpuEZRSim8g=", "checksumSHA1": "61HNjGetaBoMp8HBOpuEZRSim8g=",