mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-11-29 09:31:11 -05:00
Only serve attachments when linked to issue/release and if accessible by user (#9340)
* test: add current attachement responses * refactor: check if attachement is linked and accessible by user * chore: clean TODO * fix: typo attachement -> attachment * revert un-needed go.sum change * refactor: move models logic to models * fix TestCreateIssueAttachment which was wrongly successful * fix unit tests with unittype added * fix unit tests with changes * use a valid uuid format for pgsql int. test * test: add unit test TestLinkedRepository * refactor: allow uploader to access unlinked attachement * add missing blank line * refactor: move to a separate function repo.GetAttachment * typo * test: remove err test return * refactor: use repo perm for access checking generally + 404 for all reject
This commit is contained in:
parent
6a5a2f493a
commit
8b24073713
10 changed files with 279 additions and 124 deletions
|
@ -1,88 +0,0 @@
|
||||||
// Copyright 2019 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 integrations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"image"
|
|
||||||
"image/png"
|
|
||||||
"io"
|
|
||||||
"mime/multipart"
|
|
||||||
"net/http"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/test"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func generateImg() bytes.Buffer {
|
|
||||||
// Generate image
|
|
||||||
myImage := image.NewRGBA(image.Rect(0, 0, 32, 32))
|
|
||||||
var buff bytes.Buffer
|
|
||||||
png.Encode(&buff, myImage)
|
|
||||||
return buff
|
|
||||||
}
|
|
||||||
|
|
||||||
func createAttachment(t *testing.T, session *TestSession, repoURL, filename string, buff bytes.Buffer, expectedStatus int) string {
|
|
||||||
body := &bytes.Buffer{}
|
|
||||||
|
|
||||||
//Setup multi-part
|
|
||||||
writer := multipart.NewWriter(body)
|
|
||||||
part, err := writer.CreateFormFile("file", filename)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
_, err = io.Copy(part, &buff)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
err = writer.Close()
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
csrf := GetCSRF(t, session, repoURL)
|
|
||||||
|
|
||||||
req := NewRequestWithBody(t, "POST", "/attachments", body)
|
|
||||||
req.Header.Add("X-Csrf-Token", csrf)
|
|
||||||
req.Header.Add("Content-Type", writer.FormDataContentType())
|
|
||||||
resp := session.MakeRequest(t, req, expectedStatus)
|
|
||||||
|
|
||||||
if expectedStatus != http.StatusOK {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
var obj map[string]string
|
|
||||||
DecodeJSON(t, resp, &obj)
|
|
||||||
return obj["uuid"]
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCreateAnonymousAttachment(t *testing.T) {
|
|
||||||
prepareTestEnv(t)
|
|
||||||
session := emptyTestSession(t)
|
|
||||||
createAttachment(t, session, "user2/repo1", "image.png", generateImg(), http.StatusFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCreateIssueAttachement(t *testing.T) {
|
|
||||||
prepareTestEnv(t)
|
|
||||||
const repoURL = "user2/repo1"
|
|
||||||
session := loginUser(t, "user2")
|
|
||||||
uuid := createAttachment(t, session, repoURL, "image.png", generateImg(), http.StatusOK)
|
|
||||||
|
|
||||||
req := NewRequest(t, "GET", repoURL+"/issues/new")
|
|
||||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
|
||||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
|
||||||
|
|
||||||
link, exists := htmlDoc.doc.Find("form").Attr("action")
|
|
||||||
assert.True(t, exists, "The template has changed")
|
|
||||||
|
|
||||||
postData := map[string]string{
|
|
||||||
"_csrf": htmlDoc.GetCSRF(),
|
|
||||||
"title": "New Issue With Attachement",
|
|
||||||
"content": "some content",
|
|
||||||
"files[0]": uuid,
|
|
||||||
}
|
|
||||||
|
|
||||||
req = NewRequestWithValues(t, "POST", link, postData)
|
|
||||||
resp = session.MakeRequest(t, req, http.StatusFound)
|
|
||||||
test.RedirectURL(resp) // check that redirect URL exists
|
|
||||||
|
|
||||||
//Validate that attachement is available
|
|
||||||
req = NewRequest(t, "GET", "/attachments/"+uuid)
|
|
||||||
session.MakeRequest(t, req, http.StatusOK)
|
|
||||||
}
|
|
137
integrations/attachment_test.go
Normal file
137
integrations/attachment_test.go
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
// Copyright 2019 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 integrations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"image"
|
||||||
|
"image/png"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"mime/multipart"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models"
|
||||||
|
"code.gitea.io/gitea/modules/test"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func generateImg() bytes.Buffer {
|
||||||
|
// Generate image
|
||||||
|
myImage := image.NewRGBA(image.Rect(0, 0, 32, 32))
|
||||||
|
var buff bytes.Buffer
|
||||||
|
png.Encode(&buff, myImage)
|
||||||
|
return buff
|
||||||
|
}
|
||||||
|
|
||||||
|
func createAttachment(t *testing.T, session *TestSession, repoURL, filename string, buff bytes.Buffer, expectedStatus int) string {
|
||||||
|
body := &bytes.Buffer{}
|
||||||
|
|
||||||
|
//Setup multi-part
|
||||||
|
writer := multipart.NewWriter(body)
|
||||||
|
part, err := writer.CreateFormFile("file", filename)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
_, err = io.Copy(part, &buff)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
err = writer.Close()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
csrf := GetCSRF(t, session, repoURL)
|
||||||
|
|
||||||
|
req := NewRequestWithBody(t, "POST", "/attachments", body)
|
||||||
|
req.Header.Add("X-Csrf-Token", csrf)
|
||||||
|
req.Header.Add("Content-Type", writer.FormDataContentType())
|
||||||
|
resp := session.MakeRequest(t, req, expectedStatus)
|
||||||
|
|
||||||
|
if expectedStatus != http.StatusOK {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
var obj map[string]string
|
||||||
|
DecodeJSON(t, resp, &obj)
|
||||||
|
return obj["uuid"]
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateAnonymousAttachment(t *testing.T) {
|
||||||
|
prepareTestEnv(t)
|
||||||
|
session := emptyTestSession(t)
|
||||||
|
createAttachment(t, session, "user2/repo1", "image.png", generateImg(), http.StatusFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateIssueAttachment(t *testing.T) {
|
||||||
|
prepareTestEnv(t)
|
||||||
|
const repoURL = "user2/repo1"
|
||||||
|
session := loginUser(t, "user2")
|
||||||
|
uuid := createAttachment(t, session, repoURL, "image.png", generateImg(), http.StatusOK)
|
||||||
|
|
||||||
|
req := NewRequest(t, "GET", repoURL+"/issues/new")
|
||||||
|
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||||
|
|
||||||
|
link, exists := htmlDoc.doc.Find("form").Attr("action")
|
||||||
|
assert.True(t, exists, "The template has changed")
|
||||||
|
|
||||||
|
postData := map[string]string{
|
||||||
|
"_csrf": htmlDoc.GetCSRF(),
|
||||||
|
"title": "New Issue With Attachment",
|
||||||
|
"content": "some content",
|
||||||
|
"files": uuid,
|
||||||
|
}
|
||||||
|
|
||||||
|
req = NewRequestWithValues(t, "POST", link, postData)
|
||||||
|
resp = session.MakeRequest(t, req, http.StatusFound)
|
||||||
|
test.RedirectURL(resp) // check that redirect URL exists
|
||||||
|
|
||||||
|
//Validate that attachment is available
|
||||||
|
req = NewRequest(t, "GET", "/attachments/"+uuid)
|
||||||
|
session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetAttachment(t *testing.T) {
|
||||||
|
prepareTestEnv(t)
|
||||||
|
adminSession := loginUser(t, "user1")
|
||||||
|
user2Session := loginUser(t, "user2")
|
||||||
|
user8Session := loginUser(t, "user8")
|
||||||
|
emptySession := emptyTestSession(t)
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
uuid string
|
||||||
|
createFile bool
|
||||||
|
session *TestSession
|
||||||
|
want int
|
||||||
|
}{
|
||||||
|
{"LinkedIssueUUID", "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", true, user2Session, http.StatusOK},
|
||||||
|
{"LinkedCommentUUID", "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a17", true, user2Session, http.StatusOK},
|
||||||
|
{"linked_release_uuid", "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a19", true, user2Session, http.StatusOK},
|
||||||
|
{"NotExistingUUID", "b0eebc99-9c0b-4ef8-bb6d-6bb9bd380a18", false, user2Session, http.StatusNotFound},
|
||||||
|
{"FileMissing", "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a18", false, user2Session, http.StatusInternalServerError},
|
||||||
|
{"NotLinked", "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a20", true, user2Session, http.StatusNotFound},
|
||||||
|
{"NotLinkedAccessibleByUploader", "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a20", true, user8Session, http.StatusOK},
|
||||||
|
{"PublicByNonLogged", "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", true, emptySession, http.StatusOK},
|
||||||
|
{"PrivateByNonLogged", "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a12", true, emptySession, http.StatusNotFound},
|
||||||
|
{"PrivateAccessibleByAdmin", "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a12", true, adminSession, http.StatusOK},
|
||||||
|
{"PrivateAccessibleByUser", "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a12", true, user2Session, http.StatusOK},
|
||||||
|
{"RepoNotAccessibleByUser", "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a12", true, user8Session, http.StatusNotFound},
|
||||||
|
{"OrgNotAccessibleByUser", "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a21", true, user8Session, http.StatusNotFound},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
//Write empty file to be available for response
|
||||||
|
if tc.createFile {
|
||||||
|
localPath := models.AttachmentLocalPath(tc.uuid)
|
||||||
|
err := os.MkdirAll(path.Dir(localPath), os.ModePerm)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
err = ioutil.WriteFile(localPath, []byte("hello world"), 0644)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
//Actual test
|
||||||
|
req := NewRequest(t, "GET", "/attachments/"+tc.uuid)
|
||||||
|
tc.session.MakeRequest(t, req, tc.want)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -71,6 +71,26 @@ func (a *Attachment) DownloadURL() string {
|
||||||
return fmt.Sprintf("%sattachments/%s", setting.AppURL, a.UUID)
|
return fmt.Sprintf("%sattachments/%s", setting.AppURL, a.UUID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LinkedRepository returns the linked repo if any
|
||||||
|
func (a *Attachment) LinkedRepository() (*Repository, UnitType, error) {
|
||||||
|
if a.IssueID != 0 {
|
||||||
|
iss, err := GetIssueByID(a.IssueID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, UnitTypeIssues, err
|
||||||
|
}
|
||||||
|
repo, err := GetRepositoryByID(iss.RepoID)
|
||||||
|
return repo, UnitTypeIssues, err
|
||||||
|
} else if a.ReleaseID != 0 {
|
||||||
|
rel, err := GetReleaseByID(a.ReleaseID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, UnitTypeReleases, err
|
||||||
|
}
|
||||||
|
repo, err := GetRepositoryByID(rel.RepoID)
|
||||||
|
return repo, UnitTypeReleases, err
|
||||||
|
}
|
||||||
|
return nil, -1, nil
|
||||||
|
}
|
||||||
|
|
||||||
// NewAttachment creates a new attachment object.
|
// NewAttachment creates a new attachment object.
|
||||||
func NewAttachment(attach *Attachment, buf []byte, file io.Reader) (_ *Attachment, err error) {
|
func NewAttachment(attach *Attachment, buf []byte, file io.Reader) (_ *Attachment, err error) {
|
||||||
attach.UUID = gouuid.NewV4().String()
|
attach.UUID = gouuid.NewV4().String()
|
||||||
|
|
|
@ -61,7 +61,7 @@ func TestGetByCommentOrIssueID(t *testing.T) {
|
||||||
// count of attachments from issue ID
|
// count of attachments from issue ID
|
||||||
attachments, err := GetAttachmentsByIssueID(1)
|
attachments, err := GetAttachmentsByIssueID(1)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, 2, len(attachments))
|
assert.Equal(t, 1, len(attachments))
|
||||||
|
|
||||||
attachments, err = GetAttachmentsByCommentID(1)
|
attachments, err = GetAttachmentsByCommentID(1)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
@ -73,7 +73,7 @@ func TestDeleteAttachments(t *testing.T) {
|
||||||
|
|
||||||
count, err := DeleteAttachmentsByIssue(4, false)
|
count, err := DeleteAttachmentsByIssue(4, false)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, 1, count)
|
assert.Equal(t, 2, count)
|
||||||
|
|
||||||
count, err = DeleteAttachmentsByComment(2, false)
|
count, err = DeleteAttachmentsByComment(2, false)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
@ -128,3 +128,31 @@ func TestGetAttachmentsByUUIDs(t *testing.T) {
|
||||||
assert.Equal(t, int64(1), attachList[0].IssueID)
|
assert.Equal(t, int64(1), attachList[0].IssueID)
|
||||||
assert.Equal(t, int64(5), attachList[1].IssueID)
|
assert.Equal(t, int64(5), attachList[1].IssueID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLinkedRepository(t *testing.T) {
|
||||||
|
assert.NoError(t, PrepareTestDatabase())
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
attachID int64
|
||||||
|
expectedRepo *Repository
|
||||||
|
expectedUnitType UnitType
|
||||||
|
}{
|
||||||
|
{"LinkedIssue", 1, &Repository{ID: 1}, UnitTypeIssues},
|
||||||
|
{"LinkedComment", 3, &Repository{ID: 1}, UnitTypeIssues},
|
||||||
|
{"LinkedRelease", 9, &Repository{ID: 1}, UnitTypeReleases},
|
||||||
|
{"Notlinked", 10, nil, -1},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
attach, err := GetAttachmentByID(tc.attachID)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
repo, unitType, err := attach.LinkedRepository()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
if tc.expectedRepo != nil {
|
||||||
|
assert.Equal(t, tc.expectedRepo.ID, repo.ID)
|
||||||
|
}
|
||||||
|
assert.Equal(t, tc.expectedUnitType, unitType)
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
-
|
-
|
||||||
id: 2
|
id: 2
|
||||||
uuid: a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a12
|
uuid: a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a12
|
||||||
issue_id: 1
|
issue_id: 4
|
||||||
comment_id: 0
|
comment_id: 0
|
||||||
name: attach2
|
name: attach2
|
||||||
download_count: 1
|
download_count: 1
|
||||||
|
@ -81,6 +81,15 @@
|
||||||
-
|
-
|
||||||
id: 10
|
id: 10
|
||||||
uuid: a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a20
|
uuid: a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a20
|
||||||
|
uploader_id: 8
|
||||||
|
name: attach1
|
||||||
|
download_count: 0
|
||||||
|
created_unix: 946684800
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 11
|
||||||
|
uuid: a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a21
|
||||||
|
release_id: 2
|
||||||
name: attach1
|
name: attach1
|
||||||
download_count: 0
|
download_count: 0
|
||||||
created_unix: 946684800
|
created_unix: 946684800
|
|
@ -12,3 +12,18 @@
|
||||||
is_prerelease: false
|
is_prerelease: false
|
||||||
is_tag: false
|
is_tag: false
|
||||||
created_unix: 946684800
|
created_unix: 946684800
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 2
|
||||||
|
repo_id: 40
|
||||||
|
publisher_id: 2
|
||||||
|
tag_name: "v1.1"
|
||||||
|
lower_tag_name: "v1.1"
|
||||||
|
target: "master"
|
||||||
|
title: "testing-release"
|
||||||
|
sha1: "65f1bf27bc3bf70f64657658635e66094edbcb4d"
|
||||||
|
num_commits: 10
|
||||||
|
is_draft: false
|
||||||
|
is_prerelease: false
|
||||||
|
is_tag: false
|
||||||
|
created_unix: 946684800
|
||||||
|
|
|
@ -473,3 +473,9 @@
|
||||||
type: 7
|
type: 7
|
||||||
config: "{\"ExternalTrackerURL\":\"https://tracker.com\",\"ExternalTrackerFormat\":\"https://tracker.com/{user}/{repo}/issues/{index}\",\"ExternalTrackerStyle\":\"alphanumeric\"}"
|
config: "{\"ExternalTrackerURL\":\"https://tracker.com\",\"ExternalTrackerFormat\":\"https://tracker.com/{user}/{repo}/issues/{index}\",\"ExternalTrackerStyle\":\"alphanumeric\"}"
|
||||||
created_unix: 946684810
|
created_unix: 946684810
|
||||||
|
-
|
||||||
|
id: 69
|
||||||
|
repo_id: 2
|
||||||
|
type: 2
|
||||||
|
config: "{}"
|
||||||
|
created_unix: 946684810
|
|
@ -6,6 +6,8 @@ package repo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models"
|
"code.gitea.io/gitea/models"
|
||||||
|
@ -85,3 +87,57 @@ func DeleteAttachment(ctx *context.Context) {
|
||||||
"uuid": attach.UUID,
|
"uuid": attach.UUID,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetAttachment serve attachements
|
||||||
|
func GetAttachment(ctx *context.Context) {
|
||||||
|
attach, err := models.GetAttachmentByUUID(ctx.Params(":uuid"))
|
||||||
|
if err != nil {
|
||||||
|
if models.IsErrAttachmentNotExist(err) {
|
||||||
|
ctx.Error(404)
|
||||||
|
} else {
|
||||||
|
ctx.ServerError("GetAttachmentByUUID", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
repository, unitType, err := attach.LinkedRepository()
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("LinkedRepository", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if repository == nil { //If not linked
|
||||||
|
if !(ctx.IsSigned && attach.UploaderID == ctx.User.ID) { //We block if not the uploader
|
||||||
|
ctx.Error(http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else { //If we have the repository we check access
|
||||||
|
perm, err := models.GetUserRepoPermission(repository, ctx.User)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !perm.CanRead(unitType) {
|
||||||
|
ctx.Error(http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//If we have matched and access to release or issue
|
||||||
|
fr, err := os.Open(attach.LocalPath())
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("Open", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer fr.Close()
|
||||||
|
|
||||||
|
if err := attach.IncreaseDownloadCount(); err != nil {
|
||||||
|
ctx.ServerError("Update", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = ServeData(ctx, attach.Name, fr); err != nil {
|
||||||
|
ctx.ServerError("ServeData", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -8,7 +8,6 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
"path"
|
"path"
|
||||||
"text/template"
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
|
@ -474,34 +473,7 @@ func RegisterRoutes(m *macaron.Macaron) {
|
||||||
m.Get("/following", user.Following)
|
m.Get("/following", user.Following)
|
||||||
})
|
})
|
||||||
|
|
||||||
m.Get("/attachments/:uuid", func(ctx *context.Context) {
|
m.Get("/attachments/:uuid", repo.GetAttachment)
|
||||||
attach, err := models.GetAttachmentByUUID(ctx.Params(":uuid"))
|
|
||||||
if err != nil {
|
|
||||||
if models.IsErrAttachmentNotExist(err) {
|
|
||||||
ctx.Error(404)
|
|
||||||
} else {
|
|
||||||
ctx.ServerError("GetAttachmentByUUID", err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fr, err := os.Open(attach.LocalPath())
|
|
||||||
if err != nil {
|
|
||||||
ctx.ServerError("Open", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer fr.Close()
|
|
||||||
|
|
||||||
if err := attach.IncreaseDownloadCount(); err != nil {
|
|
||||||
ctx.ServerError("Update", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = repo.ServeData(ctx, attach.Name, fr); err != nil {
|
|
||||||
ctx.ServerError("ServeData", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}, ignSignIn)
|
}, ignSignIn)
|
||||||
|
|
||||||
m.Group("/attachments", func() {
|
m.Group("/attachments", func() {
|
||||||
|
|
|
@ -26,10 +26,10 @@ func TestIssues(t *testing.T) {
|
||||||
Issues(ctx)
|
Issues(ctx)
|
||||||
assert.EqualValues(t, http.StatusOK, ctx.Resp.Status())
|
assert.EqualValues(t, http.StatusOK, ctx.Resp.Status())
|
||||||
|
|
||||||
assert.EqualValues(t, map[int64]int64{1: 1}, ctx.Data["Counts"])
|
assert.EqualValues(t, map[int64]int64{1: 1, 2: 1}, ctx.Data["Counts"])
|
||||||
assert.EqualValues(t, true, ctx.Data["IsShowClosed"])
|
assert.EqualValues(t, true, ctx.Data["IsShowClosed"])
|
||||||
assert.Len(t, ctx.Data["Issues"], 1)
|
assert.Len(t, ctx.Data["Issues"], 1)
|
||||||
assert.Len(t, ctx.Data["Repos"], 1)
|
assert.Len(t, ctx.Data["Repos"], 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMilestones(t *testing.T) {
|
func TestMilestones(t *testing.T) {
|
||||||
|
|
Loading…
Reference in a new issue