mirror of
https://github.com/go-gitea/gitea.git
synced 2025-08-26 20:30:38 +02:00
Compare commits
22 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
30560b0f9b | ||
|
6076674d3a | ||
|
28cc3bd662 | ||
|
2631f7f64d | ||
|
af4626a270 | ||
|
21c70e1ed2 | ||
|
b45d58805a | ||
|
200b974e19 | ||
|
800271ee1f | ||
|
e6362f3d23 | ||
|
716c2918be | ||
|
60d7b614fe | ||
|
9cf9a54dca | ||
|
2b4f87da46 | ||
|
ad9f9cdc30 | ||
|
8237fd4a2d | ||
|
8e4a0a978a | ||
|
c1275e2ba6 | ||
|
7bc1faabdb | ||
|
e406dc058d | ||
|
328e38ebc7 | ||
|
773addf727 |
30
CHANGELOG.md
30
CHANGELOG.md
@@ -4,6 +4,36 @@ This changelog goes through all the changes that have been made in each release
|
||||
without substantial changes to our git log; to see the highlights of what has
|
||||
been added to each release, please refer to the [blog](https://blog.gitea.io).
|
||||
|
||||
## [1.6.3](https://github.com/go-gitea/gitea/releases/tag/v1.6.3) - 2019-01-04
|
||||
* SECURITY
|
||||
* Prevent DeleteFilePost doing arbitrary deletion (#5631)
|
||||
* BUGFIX
|
||||
* Fix wrong text getting saved on editing second comment on an issue (#5608)
|
||||
|
||||
## [1.6.2](https://github.com/go-gitea/gitea/releases/tag/v1.6.2) - 2018-12-21
|
||||
* SECURITY
|
||||
* Sanitize uploaded file names (#5571) (#5573)
|
||||
* HTMLEncode user added text (#5570) (#5575)
|
||||
* BUGFIXES
|
||||
* Fix indexer reindex bug when gitea restart (#5563) (#5564)
|
||||
* Remove a double slash in the HTTPS redirect with Let's Encrypt (#5537) (#5539)
|
||||
* Fix bug when a read perm user to edit his issue (#5516) (#5534)
|
||||
* Detect force push failure on deletion of protected branches (#5522) (#5531)
|
||||
* Let's Encrypt handler listens on correct port for certificate validation (#5525) (#5527)
|
||||
* Fix forgot deletion of notification when delete repository (#5506) (#5514)
|
||||
* Fix undeleted content when deleting user (#5429) (#5509)
|
||||
* Fix empty wiki (#5504) (#5508)
|
||||
|
||||
## [1.6.1](https://github.com/go-gitea/gitea/releases/tag/v1.6.1) - 2018-12-08
|
||||
* BUGFIXES
|
||||
* Fix dependent issue searching when gitea is run in subpath (#5392) (#5400)
|
||||
* API: '/orgs/:org/repos': return private repos with read access (#5393)
|
||||
* Fix repository deletion when there is large number of issues in it (#5426) (#5434)
|
||||
* Word-break the WebHook url to prevent a ui-break (#5445)
|
||||
* Admin should be able to delete repos via the API even if they are not a member of the organization (#5443) (#5447)
|
||||
* Ensure that the `closed_at` is set for closed (#5450)
|
||||
* Fix topic name length on database (#5493) (#5495)
|
||||
|
||||
## [1.6.0](https://github.com/go-gitea/gitea/releases/tag/v1.6.0) - 2018-11-22
|
||||
* BREAKING
|
||||
* Respect email privacy option in user search via API (#4512)
|
||||
|
24
cmd/hook.go
24
cmd/hook.go
@@ -112,10 +112,15 @@ func runHookPreReceive(c *cli.Context) error {
|
||||
branchName := strings.TrimPrefix(refFullName, git.BranchPrefix)
|
||||
protectBranch, err := private.GetProtectedBranchBy(repoID, branchName)
|
||||
if err != nil {
|
||||
log.GitLogger.Fatal(2, "retrieve protected branches information failed")
|
||||
fail("Internal error", fmt.Sprintf("retrieve protected branches information failed: %v", err))
|
||||
}
|
||||
|
||||
if protectBranch != nil && protectBranch.IsProtected() {
|
||||
// check and deletion
|
||||
if newCommitID == git.EmptySHA {
|
||||
fail(fmt.Sprintf("branch %s is protected from deletion", branchName), "")
|
||||
}
|
||||
|
||||
// detect force push
|
||||
if git.EmptySHA != oldCommitID {
|
||||
output, err := git.NewCommand("rev-list", "--max-count=1", oldCommitID, "^"+newCommitID).RunInDir(repoPath)
|
||||
@@ -126,17 +131,12 @@ func runHookPreReceive(c *cli.Context) error {
|
||||
}
|
||||
}
|
||||
|
||||
// check and deletion
|
||||
if newCommitID == git.EmptySHA {
|
||||
fail(fmt.Sprintf("branch %s is protected from deletion", branchName), "")
|
||||
} else {
|
||||
userID, _ := strconv.ParseInt(userIDStr, 10, 64)
|
||||
canPush, err := private.CanUserPush(protectBranch.ID, userID)
|
||||
if err != nil {
|
||||
fail("Internal error", "Fail to detect user can push: %v", err)
|
||||
} else if !canPush {
|
||||
fail(fmt.Sprintf("protected branch %s can not be pushed to", branchName), "")
|
||||
}
|
||||
userID, _ := strconv.ParseInt(userIDStr, 10, 64)
|
||||
canPush, err := private.CanUserPush(protectBranch.ID, userID)
|
||||
if err != nil {
|
||||
fail("Internal error", "Fail to detect user can push: %v", err)
|
||||
} else if !canPush {
|
||||
fail(fmt.Sprintf("protected branch %s can not be pushed to", branchName), "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
13
cmd/web.go
13
cmd/web.go
@@ -80,7 +80,13 @@ func runLetsEncrypt(listenAddr, domain, directory, email string, m http.Handler)
|
||||
Cache: autocert.DirCache(directory),
|
||||
Email: email,
|
||||
}
|
||||
go http.ListenAndServe(listenAddr+":"+setting.PortToRedirect, certManager.HTTPHandler(http.HandlerFunc(runLetsEncryptFallbackHandler))) // all traffic coming into HTTP will be redirect to HTTPS automatically (LE HTTP-01 validatio happens here)
|
||||
go func() {
|
||||
log.Info("Running Let's Encrypt handler on %s", setting.HTTPAddr+":"+setting.PortToRedirect)
|
||||
var err = http.ListenAndServe(setting.HTTPAddr+":"+setting.PortToRedirect, certManager.HTTPHandler(http.HandlerFunc(runLetsEncryptFallbackHandler))) // all traffic coming into HTTP will be redirect to HTTPS automatically (LE HTTP-01 validation happens here)
|
||||
if err != nil {
|
||||
log.Fatal(4, "Failed to start the Let's Encrypt handler on port %s: %v", setting.PortToRedirect, err)
|
||||
}
|
||||
}()
|
||||
server := &http.Server{
|
||||
Addr: listenAddr,
|
||||
Handler: m,
|
||||
@@ -96,7 +102,10 @@ func runLetsEncryptFallbackHandler(w http.ResponseWriter, r *http.Request) {
|
||||
http.Error(w, "Use HTTPS", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
target := setting.AppURL + r.URL.RequestURI()
|
||||
// Remove the trailing slash at the end of setting.AppURL, the request
|
||||
// URI always contains a leading slash, which would result in a double
|
||||
// slash
|
||||
target := strings.TrimRight(setting.AppURL, "/") + r.URL.RequestURI()
|
||||
http.Redirect(w, r, target, http.StatusFound)
|
||||
}
|
||||
|
||||
|
@@ -212,21 +212,46 @@ func TestAPIViewRepo(t *testing.T) {
|
||||
func TestAPIOrgRepos(t *testing.T) {
|
||||
prepareTestEnv(t)
|
||||
user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User)
|
||||
user2 := models.AssertExistsAndLoadBean(t, &models.User{ID: 1}).(*models.User)
|
||||
user3 := models.AssertExistsAndLoadBean(t, &models.User{ID: 5}).(*models.User)
|
||||
// User3 is an Org. Check their repos.
|
||||
sourceOrg := models.AssertExistsAndLoadBean(t, &models.User{ID: 3}).(*models.User)
|
||||
// Login as User2.
|
||||
session := loginUser(t, user.Name)
|
||||
token := getTokenForLoggedInUser(t, session)
|
||||
req := NewRequestf(t, "GET", "/api/v1/orgs/%s/repos?token="+token, sourceOrg.Name)
|
||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
var apiRepos []*api.Repository
|
||||
DecodeJSON(t, resp, &apiRepos)
|
||||
expectedLen := models.GetCount(t, models.Repository{OwnerID: sourceOrg.ID},
|
||||
models.Cond("is_private = ?", false))
|
||||
assert.Len(t, apiRepos, expectedLen)
|
||||
for _, repo := range apiRepos {
|
||||
assert.False(t, repo.Private)
|
||||
expectedResults := map[*models.User]struct {
|
||||
count int
|
||||
includesPrivate bool
|
||||
}{
|
||||
nil: {count: 1},
|
||||
user: {count: 2, includesPrivate: true},
|
||||
user2: {count: 3, includesPrivate: true},
|
||||
user3: {count: 1},
|
||||
}
|
||||
|
||||
for userToLogin, expected := range expectedResults {
|
||||
var session *TestSession
|
||||
var testName string
|
||||
var token string
|
||||
if userToLogin != nil && userToLogin.ID > 0 {
|
||||
testName = fmt.Sprintf("LoggedUser%d", userToLogin.ID)
|
||||
session = loginUser(t, userToLogin.Name)
|
||||
token = getTokenForLoggedInUser(t, session)
|
||||
} else {
|
||||
testName = "AnonymousUser"
|
||||
session = emptyTestSession(t)
|
||||
}
|
||||
t.Run(testName, func(t *testing.T) {
|
||||
req := NewRequestf(t, "GET", "/api/v1/orgs/%s/repos?token="+token, sourceOrg.Name)
|
||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
var apiRepos []*api.Repository
|
||||
DecodeJSON(t, resp, &apiRepos)
|
||||
assert.Len(t, apiRepos, expected.count)
|
||||
for _, repo := range apiRepos {
|
||||
if !expected.includesPrivate {
|
||||
assert.False(t, repo.Private)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -325,6 +325,10 @@ func (issue *Issue) APIFormat() *api.Issue {
|
||||
Updated: issue.UpdatedUnix.AsTime(),
|
||||
}
|
||||
|
||||
if issue.ClosedUnix != 0 {
|
||||
apiIssue.Closed = issue.ClosedUnix.AsTimePtr()
|
||||
}
|
||||
|
||||
if issue.Milestone != nil {
|
||||
apiIssue.Milestone = issue.Milestone.APIFormat()
|
||||
}
|
||||
|
@@ -25,7 +25,7 @@ func reformatAndRemoveIncorrectTopics(x *xorm.Engine) (err error) {
|
||||
|
||||
type Topic struct {
|
||||
ID int64
|
||||
Name string `xorm:"UNIQUE"`
|
||||
Name string `xorm:"UNIQUE VARCHAR(25)"`
|
||||
RepoCount int
|
||||
CreatedUnix int64 `xorm:"INDEX created"`
|
||||
UpdatedUnix int64 `xorm:"INDEX updated"`
|
||||
|
@@ -32,6 +32,7 @@ import (
|
||||
|
||||
"github.com/Unknwon/cae/zip"
|
||||
"github.com/Unknwon/com"
|
||||
"github.com/go-xorm/builder"
|
||||
"github.com/go-xorm/xorm"
|
||||
"github.com/mcuadros/go-version"
|
||||
"gopkg.in/ini.v1"
|
||||
@@ -1837,55 +1838,56 @@ func DeleteRepository(doer *User, uid, repoID int64) error {
|
||||
&RepoRedirect{RedirectRepoID: repoID},
|
||||
&Webhook{RepoID: repoID},
|
||||
&HookTask{RepoID: repoID},
|
||||
&Notification{RepoID: repoID},
|
||||
); err != nil {
|
||||
return fmt.Errorf("deleteBeans: %v", err)
|
||||
}
|
||||
|
||||
// Delete comments and attachments.
|
||||
issueIDs := make([]int64, 0, 25)
|
||||
attachmentPaths := make([]string, 0, len(issueIDs))
|
||||
if err = sess.
|
||||
Table("issue").
|
||||
Cols("id").
|
||||
Where("repo_id=?", repoID).
|
||||
Find(&issueIDs); err != nil {
|
||||
deleteCond := builder.Select("id").From("issue").Where(builder.Eq{"repo_id": repoID})
|
||||
// Delete comments and attachments
|
||||
if _, err = sess.In("issue_id", deleteCond).
|
||||
Delete(&Comment{}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(issueIDs) > 0 {
|
||||
if _, err = sess.In("issue_id", issueIDs).Delete(&Comment{}); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = sess.In("issue_id", issueIDs).Delete(&IssueUser{}); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = sess.In("issue_id", issueIDs).Delete(&Reaction{}); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = sess.In("issue_id", issueIDs).Delete(&IssueWatch{}); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = sess.In("issue_id", issueIDs).Delete(&Stopwatch{}); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = sess.In("issue_id", deleteCond).
|
||||
Delete(&IssueUser{}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
attachments := make([]*Attachment, 0, 5)
|
||||
if err = sess.
|
||||
In("issue_id", issueIDs).
|
||||
Find(&attachments); err != nil {
|
||||
return err
|
||||
}
|
||||
for j := range attachments {
|
||||
attachmentPaths = append(attachmentPaths, attachments[j].LocalPath())
|
||||
}
|
||||
if _, err = sess.In("issue_id", deleteCond).
|
||||
Delete(&Reaction{}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = sess.In("issue_id", issueIDs).Delete(&Attachment{}); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = sess.In("issue_id", deleteCond).
|
||||
Delete(&IssueWatch{}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = sess.Delete(&Issue{RepoID: repoID}); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = sess.In("issue_id", deleteCond).
|
||||
Delete(&Stopwatch{}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
attachmentPaths := make([]string, 0, 20)
|
||||
attachments := make([]*Attachment, 0, len(attachmentPaths))
|
||||
if err = sess.Join("INNER", "issue", "issue.id = attachment.issue_id").
|
||||
Where("issue.repo_id = ?", repoID).
|
||||
Find(&attachments); err != nil {
|
||||
return err
|
||||
}
|
||||
for j := range attachments {
|
||||
attachmentPaths = append(attachmentPaths, attachments[j].LocalPath())
|
||||
}
|
||||
|
||||
if _, err = sess.In("issue_id", deleteCond).
|
||||
Delete(&Attachment{}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = sess.Delete(&Issue{RepoID: repoID}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = sess.Where("repo_id = ?", repoID).Delete(new(RepoUnit)); err != nil {
|
||||
|
@@ -535,6 +535,7 @@ func DeletePublicKey(doer *User, id int64) (err error) {
|
||||
if err = sess.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
sess.Close()
|
||||
|
||||
return RewriteAllPublicKeys()
|
||||
}
|
||||
@@ -543,6 +544,10 @@ func DeletePublicKey(doer *User, id int64) (err error) {
|
||||
// Note: x.Iterate does not get latest data after insert/delete, so we have to call this function
|
||||
// outside any session scope independently.
|
||||
func RewriteAllPublicKeys() error {
|
||||
return rewriteAllPublicKeys(x)
|
||||
}
|
||||
|
||||
func rewriteAllPublicKeys(e Engine) error {
|
||||
//Don't rewrite key if internal server
|
||||
if setting.SSH.StartBuiltinServer {
|
||||
return nil
|
||||
@@ -569,7 +574,7 @@ func RewriteAllPublicKeys() error {
|
||||
}
|
||||
}
|
||||
|
||||
err = x.Iterate(new(PublicKey), func(idx int, bean interface{}) (err error) {
|
||||
err = e.Iterate(new(PublicKey), func(idx int, bean interface{}) (err error) {
|
||||
_, err = t.WriteString((bean.(*PublicKey)).AuthorizedString())
|
||||
return err
|
||||
})
|
||||
|
@@ -26,7 +26,7 @@ var topicPattern = regexp.MustCompile(`^[a-z0-9][a-z0-9-]*$`)
|
||||
// Topic represents a topic of repositories
|
||||
type Topic struct {
|
||||
ID int64
|
||||
Name string `xorm:"UNIQUE"`
|
||||
Name string `xorm:"UNIQUE VARCHAR(25)"`
|
||||
RepoCount int
|
||||
CreatedUnix util.TimeStamp `xorm:"INDEX created"`
|
||||
UpdatedUnix util.TimeStamp `xorm:"INDEX updated"`
|
||||
|
@@ -1038,25 +1038,26 @@ func deleteUser(e *xorm.Session, u *User) error {
|
||||
&EmailAddress{UID: u.ID},
|
||||
&UserOpenID{UID: u.ID},
|
||||
&Reaction{UserID: u.ID},
|
||||
&TeamUser{UID: u.ID},
|
||||
&Collaboration{UserID: u.ID},
|
||||
&Stopwatch{UserID: u.ID},
|
||||
); err != nil {
|
||||
return fmt.Errorf("deleteBeans: %v", err)
|
||||
}
|
||||
|
||||
// ***** START: PublicKey *****
|
||||
keys := make([]*PublicKey, 0, 10)
|
||||
if err = e.Find(&keys, &PublicKey{OwnerID: u.ID}); err != nil {
|
||||
return fmt.Errorf("get all public keys: %v", err)
|
||||
}
|
||||
|
||||
keyIDs := make([]int64, len(keys))
|
||||
for i := range keys {
|
||||
keyIDs[i] = keys[i].ID
|
||||
}
|
||||
if err = deletePublicKeys(e, keyIDs...); err != nil {
|
||||
if _, err = e.Delete(&PublicKey{OwnerID: u.ID}); err != nil {
|
||||
return fmt.Errorf("deletePublicKeys: %v", err)
|
||||
}
|
||||
rewriteAllPublicKeys(e)
|
||||
// ***** END: PublicKey *****
|
||||
|
||||
// ***** START: GPGPublicKey *****
|
||||
if _, err = e.Delete(&GPGKey{OwnerID: u.ID}); err != nil {
|
||||
return fmt.Errorf("deleteGPGKeys: %v", err)
|
||||
}
|
||||
// ***** END: GPGPublicKey *****
|
||||
|
||||
// Clear assignee.
|
||||
if err = clearAssigneeByUserID(e, u.ID); err != nil {
|
||||
return fmt.Errorf("clear assignee: %v", err)
|
||||
@@ -1110,6 +1111,7 @@ func DeleteUser(u *User) (err error) {
|
||||
if err = sess.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
sess.Close()
|
||||
|
||||
return RewriteAllPublicKeys()
|
||||
}
|
||||
|
@@ -60,7 +60,7 @@ func InitIssueIndexer(populateIndexer func() error) {
|
||||
return
|
||||
}
|
||||
|
||||
if err = createIssueIndexer(); err != nil {
|
||||
if err = createIssueIndexer(setting.Indexer.IssuePath, issueIndexerLatestVersion); err != nil {
|
||||
log.Fatal(4, "InitIssuesIndexer: create index, %v", err)
|
||||
}
|
||||
if err = populateIndexer(); err != nil {
|
||||
@@ -69,7 +69,7 @@ func InitIssueIndexer(populateIndexer func() error) {
|
||||
}
|
||||
|
||||
// createIssueIndexer create an issue indexer if one does not already exist
|
||||
func createIssueIndexer() error {
|
||||
func createIssueIndexer(path string, latestVersion int) error {
|
||||
mapping := bleve.NewIndexMapping()
|
||||
docMapping := bleve.NewDocumentMapping()
|
||||
|
||||
@@ -100,8 +100,14 @@ func createIssueIndexer() error {
|
||||
mapping.AddDocumentMapping("_all", bleve.NewDocumentDisabledMapping())
|
||||
|
||||
var err error
|
||||
issueIndexer, err = bleve.New(setting.Indexer.IssuePath, mapping)
|
||||
return err
|
||||
issueIndexer, err = bleve.New(path, mapping)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return rupture.WriteIndexMetadata(path, &rupture.IndexMetadata{
|
||||
Version: latestVersion,
|
||||
})
|
||||
}
|
||||
|
||||
// IssueIndexerBatch batch to add updates to
|
||||
|
@@ -84,7 +84,7 @@ func InitRepoIndexer(populateIndexer func() error) {
|
||||
return
|
||||
}
|
||||
|
||||
if err = createRepoIndexer(); err != nil {
|
||||
if err = createRepoIndexer(setting.Indexer.RepoPath, repoIndexerLatestVersion); err != nil {
|
||||
log.Fatal(4, "CreateRepoIndexer: %v", err)
|
||||
}
|
||||
if err = populateIndexer(); err != nil {
|
||||
@@ -93,7 +93,7 @@ func InitRepoIndexer(populateIndexer func() error) {
|
||||
}
|
||||
|
||||
// createRepoIndexer create a repo indexer if one does not already exist
|
||||
func createRepoIndexer() error {
|
||||
func createRepoIndexer(path string, latestVersion int) error {
|
||||
var err error
|
||||
docMapping := bleve.NewDocumentMapping()
|
||||
numericFieldMapping := bleve.NewNumericFieldMapping()
|
||||
@@ -119,8 +119,13 @@ func createRepoIndexer() error {
|
||||
mapping.AddDocumentMapping(repoIndexerDocType, docMapping)
|
||||
mapping.AddDocumentMapping("_all", bleve.NewDocumentDisabledMapping())
|
||||
|
||||
repoIndexer, err = bleve.New(setting.Indexer.RepoPath, mapping)
|
||||
return err
|
||||
repoIndexer, err = bleve.New(path, mapping)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return rupture.WriteIndexMetadata(path, &rupture.IndexMetadata{
|
||||
Version: latestVersion,
|
||||
})
|
||||
}
|
||||
|
||||
func filenameIndexerID(repoID int64, filename string) string {
|
||||
|
@@ -1,5 +1,9 @@
|
||||
'use strict';
|
||||
|
||||
function htmlEncode(text) {
|
||||
return jQuery('<div />').text(text).html()
|
||||
}
|
||||
|
||||
var csrf;
|
||||
var suburl;
|
||||
|
||||
@@ -312,12 +316,12 @@ function initCommentForm() {
|
||||
switch (input_id) {
|
||||
case '#milestone_id':
|
||||
$list.find('.selected').html('<a class="item" href=' + $(this).data('href') + '>' +
|
||||
$(this).text() + '</a>');
|
||||
htmlEncode($(this).text()) + '</a>');
|
||||
break;
|
||||
case '#assignee_id':
|
||||
$list.find('.selected').html('<a class="item" href=' + $(this).data('href') + '>' +
|
||||
'<img class="ui avatar image" src=' + $(this).data('avatar') + '>' +
|
||||
$(this).text() + '</a>');
|
||||
htmlEncode($(this).text()) + '</a>');
|
||||
}
|
||||
$('.ui' + select_id + '.list .no-select').addClass('hide');
|
||||
$(input_id).val($(this).data('id'));
|
||||
@@ -604,7 +608,7 @@ function initRepository() {
|
||||
// Setup new form
|
||||
if ($editContentZone.html().length == 0) {
|
||||
$editContentZone.html($('#edit-content-form').html());
|
||||
$textarea = $('#content');
|
||||
$textarea = $editContentZone.find('textarea');
|
||||
issuesTribute.attach($textarea.get());
|
||||
emojiTribute.attach($textarea.get());
|
||||
|
||||
@@ -1456,7 +1460,7 @@ function searchUsers() {
|
||||
$.each(response.data, function (i, item) {
|
||||
var title = item.login;
|
||||
if (item.full_name && item.full_name.length > 0) {
|
||||
title += ' (' + item.full_name + ')';
|
||||
title += ' (' + htmlEncode(item.full_name) + ')';
|
||||
}
|
||||
items.push({
|
||||
title: title,
|
||||
@@ -2510,7 +2514,7 @@ function initTopicbar() {
|
||||
if (res.topics) {
|
||||
formattedResponse.success = true;
|
||||
for (var i=0;i < res.topics.length;i++) {
|
||||
formattedResponse.results.push({"description": res.topics[i].Name, "data-value":res.topics[i].Name})
|
||||
formattedResponse.results.push({"description": res.topics[i].Name, "data-value": res.topics[i].Name})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2625,13 +2629,13 @@ function initIssueList() {
|
||||
$('.new-dependency-drop-list')
|
||||
.dropdown({
|
||||
apiSettings: {
|
||||
url: suburl + '/api/v1/repos' + repolink + '/issues?q={query}',
|
||||
url: suburl + '/api/v1/repos/' + repolink + '/issues?q={query}',
|
||||
onResponse: function(response) {
|
||||
var filteredResponse = {'success': true, 'results': []};
|
||||
// Parse the response from the api to work with our dropdown
|
||||
$.each(response, function(index, issue) {
|
||||
filteredResponse.results.push({
|
||||
'name' : '#' + issue.number + ' ' + issue.title,
|
||||
'name' : '#' + issue.number + ' ' + htmlEncode(issue.title),
|
||||
'value' : issue.id
|
||||
});
|
||||
});
|
||||
|
@@ -508,7 +508,7 @@ func Delete(ctx *context.APIContext) {
|
||||
owner := ctx.Repo.Owner
|
||||
repo := ctx.Repo.Repository
|
||||
|
||||
if owner.IsOrganization() {
|
||||
if owner.IsOrganization() && !ctx.User.IsAdmin {
|
||||
isOwner, err := owner.IsOwnedBy(ctx.User.ID)
|
||||
if err != nil {
|
||||
ctx.Error(500, "IsOwnedBy", err)
|
||||
|
@@ -11,14 +11,13 @@ import (
|
||||
)
|
||||
|
||||
// listUserRepos - List the repositories owned by the given user.
|
||||
func listUserRepos(ctx *context.APIContext, u *models.User) {
|
||||
showPrivateRepos := ctx.IsSigned && (ctx.User.ID == u.ID || ctx.User.IsAdmin)
|
||||
repos, err := models.GetUserRepositories(u.ID, showPrivateRepos, 1, u.NumRepos, "")
|
||||
func listUserRepos(ctx *context.APIContext, u *models.User, private bool) {
|
||||
repos, err := models.GetUserRepositories(u.ID, private, 1, u.NumRepos, "")
|
||||
if err != nil {
|
||||
ctx.Error(500, "GetUserRepositories", err)
|
||||
return
|
||||
}
|
||||
apiRepos := make([]*api.Repository, len(repos))
|
||||
apiRepos := make([]*api.Repository, 0, len(repos))
|
||||
var ctxUserID int64
|
||||
if ctx.User != nil {
|
||||
ctxUserID = ctx.User.ID
|
||||
@@ -29,7 +28,9 @@ func listUserRepos(ctx *context.APIContext, u *models.User) {
|
||||
ctx.Error(500, "AccessLevel", err)
|
||||
return
|
||||
}
|
||||
apiRepos[i] = repos[i].APIFormat(access)
|
||||
if ctx.IsSigned && ctx.User.IsAdmin || access >= models.AccessModeRead {
|
||||
apiRepos = append(apiRepos, repos[i].APIFormat(access))
|
||||
}
|
||||
}
|
||||
ctx.JSON(200, &apiRepos)
|
||||
}
|
||||
@@ -54,7 +55,8 @@ func ListUserRepos(ctx *context.APIContext) {
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
listUserRepos(ctx, user)
|
||||
private := ctx.IsSigned && (ctx.User.ID == user.ID || ctx.User.IsAdmin)
|
||||
listUserRepos(ctx, user, private)
|
||||
}
|
||||
|
||||
// ListMyRepos - list the repositories you own or have access to.
|
||||
@@ -106,5 +108,5 @@ func ListOrgRepos(ctx *context.APIContext) {
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/RepositoryList"
|
||||
listUserRepos(ctx, ctx.Org.Organization)
|
||||
listUserRepos(ctx, ctx.Org.Organization, ctx.IsSigned)
|
||||
}
|
||||
|
@@ -163,7 +163,11 @@ func editFilePost(ctx *context.Context, form auth.EditRepoFileForm, isNewFile bo
|
||||
branchName = form.NewBranchName
|
||||
}
|
||||
|
||||
form.TreePath = strings.Trim(path.Clean("/"+form.TreePath), " /")
|
||||
form.TreePath = cleanUploadFileName(form.TreePath)
|
||||
if len(form.TreePath) == 0 {
|
||||
ctx.Error(500, "Upload file name is invalid")
|
||||
return
|
||||
}
|
||||
treeNames, treePaths := getParentTreeFields(form.TreePath)
|
||||
|
||||
ctx.Data["TreePath"] = form.TreePath
|
||||
@@ -373,6 +377,13 @@ func DeleteFile(ctx *context.Context) {
|
||||
func DeleteFilePost(ctx *context.Context, form auth.DeleteRepoFileForm) {
|
||||
ctx.Data["PageIsDelete"] = true
|
||||
ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
|
||||
|
||||
ctx.Repo.TreePath = cleanUploadFileName(ctx.Repo.TreePath)
|
||||
if len(ctx.Repo.TreePath) == 0 {
|
||||
ctx.Error(500, "Delete file name is invalid")
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Data["TreePath"] = ctx.Repo.TreePath
|
||||
canCommit := renderCommitRights(ctx)
|
||||
|
||||
@@ -477,7 +488,12 @@ func UploadFilePost(ctx *context.Context, form auth.UploadRepoFileForm) {
|
||||
branchName = form.NewBranchName
|
||||
}
|
||||
|
||||
form.TreePath = strings.Trim(path.Clean("/"+form.TreePath), " /")
|
||||
form.TreePath = cleanUploadFileName(form.TreePath)
|
||||
if len(form.TreePath) == 0 {
|
||||
ctx.Error(500, "Upload file name is invalid")
|
||||
return
|
||||
}
|
||||
|
||||
treeNames, treePaths := getParentTreeFields(form.TreePath)
|
||||
if len(treeNames) == 0 {
|
||||
// We must at least have one element for user to input.
|
||||
@@ -559,6 +575,17 @@ func UploadFilePost(ctx *context.Context, form auth.UploadRepoFileForm) {
|
||||
ctx.Redirect(ctx.Repo.RepoLink + "/src/branch/" + branchName + "/" + form.TreePath)
|
||||
}
|
||||
|
||||
func cleanUploadFileName(name string) string {
|
||||
name = strings.TrimLeft(name, "./\\")
|
||||
name = strings.Replace(name, "../", "", -1)
|
||||
name = strings.Replace(name, "..\\", "", -1)
|
||||
name = strings.TrimPrefix(path.Clean(name), ".git/")
|
||||
if name == ".git" {
|
||||
return ""
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
// UploadFileToServer upload file to server file dir not git
|
||||
func UploadFileToServer(ctx *context.Context) {
|
||||
file, header, err := ctx.Req.FormFile("file")
|
||||
@@ -591,7 +618,13 @@ func UploadFileToServer(ctx *context.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
upload, err := models.NewUpload(header.Filename, buf, file)
|
||||
name := cleanUploadFileName(header.Filename)
|
||||
if len(name) == 0 {
|
||||
ctx.Error(500, "Upload file name is invalid")
|
||||
return
|
||||
}
|
||||
|
||||
upload, err := models.NewUpload(name, buf, file)
|
||||
if err != nil {
|
||||
ctx.Error(500, fmt.Sprintf("NewUpload: %v", err))
|
||||
return
|
||||
|
30
routers/repo/editor_test.go
Normal file
30
routers/repo/editor_test.go
Normal file
@@ -0,0 +1,30 @@
|
||||
// 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 repo
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCleanUploadName(t *testing.T) {
|
||||
models.PrepareTestEnv(t)
|
||||
|
||||
var kases = map[string]string{
|
||||
".git/refs/master": "git/refs/master",
|
||||
"/root/abc": "root/abc",
|
||||
"./../../abc": "abc",
|
||||
"a/../.git": "a/.git",
|
||||
"a/../../../abc": "a/abc",
|
||||
"../../../acd": "acd",
|
||||
"../../.git/abc": "git/abc",
|
||||
"..\\..\\.git/abc": "git/abc",
|
||||
}
|
||||
for k, v := range kases {
|
||||
assert.EqualValues(t, v, cleanUploadFileName(k))
|
||||
}
|
||||
}
|
@@ -73,7 +73,6 @@ func findWikiRepoCommit(ctx *context.Context) (*git.Repository, *git.Commit, err
|
||||
|
||||
commit, err := wikiRepo.GetBranchCommit("master")
|
||||
if err != nil {
|
||||
ctx.ServerError("GetBranchCommit", err)
|
||||
return wikiRepo, nil, err
|
||||
}
|
||||
return wikiRepo, commit, nil
|
||||
@@ -111,6 +110,9 @@ func wikiContentsByName(ctx *context.Context, commit *git.Commit, wikiName strin
|
||||
func renderWikiPage(ctx *context.Context, isViewPage bool) (*git.Repository, *git.TreeEntry) {
|
||||
wikiRepo, commit, err := findWikiRepoCommit(ctx)
|
||||
if err != nil {
|
||||
if !git.IsErrNotExist(err) {
|
||||
ctx.ServerError("GetBranchCommit", err)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
|
@@ -51,8 +51,6 @@
|
||||
{{end}}
|
||||
{{if .RequireTribute}}
|
||||
<script src="{{AppSubUrl}}/vendor/plugins/tribute/tribute.min.js"></script>
|
||||
|
||||
{{if .Assignees}}
|
||||
<script>
|
||||
var issuesTribute = new Tribute({
|
||||
values: [
|
||||
@@ -73,7 +71,6 @@
|
||||
})
|
||||
issuesTribute.attach(document.getElementById('content'))
|
||||
</script>
|
||||
{{end}}
|
||||
<script>
|
||||
var emojiTribute = new Tribute({
|
||||
collection: [{
|
||||
|
@@ -204,7 +204,7 @@
|
||||
<a class="preview item" data-url="{{AppSubUrl}}/api/v1/markdown" data-context="{{$.RepoLink}}">{{$.i18n.Tr "preview"}}</a>
|
||||
</div>
|
||||
<div class="ui bottom attached active write tab segment">
|
||||
<textarea tabindex="1" id="content" name="content"></textarea>
|
||||
<textarea tabindex="1" name="content"></textarea>
|
||||
</div>
|
||||
<div class="ui bottom attached tab preview segment markdown">
|
||||
{{$.i18n.Tr "loading"}}
|
||||
|
@@ -115,7 +115,7 @@
|
||||
<a class="preview item" data-url="{{AppSubUrl}}/api/v1/markdown" data-context="{{$.RepoLink}}">{{$.i18n.Tr "preview"}}</a>
|
||||
</div>
|
||||
<div class="ui bottom attached active write tab segment">
|
||||
<textarea tabindex="1" id="content" name="content"></textarea>
|
||||
<textarea tabindex="1" name="content"></textarea>
|
||||
</div>
|
||||
<div class="ui bottom attached tab preview segment markdown">
|
||||
{{$.i18n.Tr "loading"}}
|
||||
|
@@ -338,7 +338,7 @@
|
||||
</div>
|
||||
</div>
|
||||
{{if .CanCreateIssueDependencies}}
|
||||
<input type="hidden" id="repolink" value="{{$.RepoLink}}">
|
||||
<input type="hidden" id="repolink" value="{{$.RepoRelPath}}">
|
||||
<!-- I know, there is probably a better way to do this -->
|
||||
<input type="hidden" id="issueIndex" value="{{.Issue.Index}}"/>
|
||||
|
||||
|
@@ -38,7 +38,7 @@
|
||||
{{else}}
|
||||
<span class="text grey"><i class="octicon octicon-primitive-dot"></i></span>
|
||||
{{end}}
|
||||
<a href="{{$.BaseLink}}/settings/hooks/{{.ID}}">{{.URL}}</a>
|
||||
<a class="dont-break-out" href="{{$.BaseLink}}/settings/hooks/{{.ID}}">{{.URL}}</a>
|
||||
<div class="ui right">
|
||||
<span class="text blue"><a href="{{$.BaseLink}}/settings/hooks/{{.ID}}"><i class="fa fa-pencil"></i></a></span>
|
||||
<span class="text red"><a class="delete-button" data-url="{{$.Link}}/delete" data-id="{{.ID}}"><i class="fa fa-times"></i></a></span>
|
||||
|
Reference in New Issue
Block a user