forgejo/routers/api/v1/repo/file.go

1015 lines
28 KiB
Go
Raw Normal View History

2014-11-17 01:27:04 +00:00
// Copyright 2014 The Gogs Authors. All rights reserved.
// Copyright 2018 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
2014-11-17 01:27:04 +00:00
package repo
2014-11-17 02:32:26 +00:00
import (
"bytes"
"encoding/base64"
"errors"
"fmt"
"io"
"net/http"
"path"
"strings"
"time"
"code.gitea.io/gitea/models"
git_model "code.gitea.io/gitea/models/git"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/httpcache"
"code.gitea.io/gitea/modules/lfs"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage"
api "code.gitea.io/gitea/modules/structs"
Move macaron to chi (#14293) Use [chi](https://github.com/go-chi/chi) instead of the forked [macaron](https://gitea.com/macaron/macaron). Since macaron and chi have conflicts with session share, this big PR becomes a have-to thing. According my previous idea, we can replace macaron step by step but I'm wrong. :( Below is a list of big changes on this PR. - [x] Define `context.ResponseWriter` interface with an implementation `context.Response`. - [x] Use chi instead of macaron, and also a customize `Route` to wrap chi so that the router usage is similar as before. - [x] Create different routers for `web`, `api`, `internal` and `install` so that the codes will be more clear and no magic . - [x] Use https://github.com/unrolled/render instead of macaron's internal render - [x] Use https://github.com/NYTimes/gziphandler instead of https://gitea.com/macaron/gzip - [x] Use https://gitea.com/go-chi/session which is a modified version of https://gitea.com/macaron/session and removed `nodb` support since it will not be maintained. **BREAK** - [x] Use https://gitea.com/go-chi/captcha which is a modified version of https://gitea.com/macaron/captcha - [x] Use https://gitea.com/go-chi/cache which is a modified version of https://gitea.com/macaron/cache - [x] Use https://gitea.com/go-chi/binding which is a modified version of https://gitea.com/macaron/binding - [x] Use https://github.com/go-chi/cors instead of https://gitea.com/macaron/cors - [x] Dropped https://gitea.com/macaron/i18n and make a new one in `code.gitea.io/gitea/modules/translation` - [x] Move validation form structs from `code.gitea.io/gitea/modules/auth` to `code.gitea.io/gitea/modules/forms` to avoid dependency cycle. - [x] Removed macaron log service because it's not need any more. **BREAK** - [x] All form structs have to be get by `web.GetForm(ctx)` in the route function but not as a function parameter on routes definition. - [x] Move Git HTTP protocol implementation to use routers directly. - [x] Fix the problem that chi routes don't support trailing slash but macaron did. - [x] `/api/v1/swagger` now will be redirect to `/api/swagger` but not render directly so that `APIContext` will not create a html render. Notices: - Chi router don't support request with trailing slash - Integration test `TestUserHeatmap` maybe mysql version related. It's failed on my macOS(mysql 5.7.29 installed via brew) but succeed on CI. Co-authored-by: 6543 <6543@obermui.de>
2021-01-26 15:36:53 +00:00
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/common"
"code.gitea.io/gitea/services/context"
archiver_service "code.gitea.io/gitea/services/repository/archiver"
files_service "code.gitea.io/gitea/services/repository/files"
2014-11-17 02:32:26 +00:00
)
[BRANDING] add X-Forgejo-* headers (cherry picked from commit 0a3388f93f53f53cce79ebcc194d67979cbe61cc) (cherry picked from commit 7eba0a440a3138443d0b7461560f9f6f1c46b256) (cherry picked from commit eb9646c7ef0b1a9e73bf22806acbc2caeaee7ff3) (cherry picked from commit f1972578f5cb72ad9e1ef1ded9aa324b1d669864) Conflicts: (cherry picked from commit 7f96222fb4b4a773193a11d4ba76f449013c30ef) (cherry picked from commit e3c7c9fe7b5809cdb75f1eb629c82b27986f7e98) (cherry picked from commit 84fdead90242e68493efb96a7fee1e3b5fd417e9) (cherry picked from commit 85148e11961b86269b7cda30294a4cdc817787d0) (cherry picked from commit c0086bd70d39d9a1075fa624cdbfdf5ef26f7a2c) (cherry picked from commit d1e31ef31817e5e811f6bea7aee5582383d53b12) (cherry picked from commit 681d3ed5c43d45f802dc13c58c5b821da938374d) (cherry picked from commit 76a3001f5bc5a064054514c179ba14008952e914) (cherry picked from commit a55a9567d36f6b60557df3df3a0801c0fccec963) (cherry picked from commit aa7adc167d4e4ee7879b88fe799f2afca2fd5531) (cherry picked from commit d5354cb52c26ab0090388d019efe61770e7ec452) (cherry picked from commit 472c48999699420d15aaa7c61a42db8136221616) (cherry picked from commit dc816d065b715f2bfdc7fa68fa2d254f3d481b35) (cherry picked from commit 4795f9ea85eac21c70db1159914a7b7b035b7374) (cherry picked from commit ddd4ae53433d4adab823aa974c0f1c39d0394414) (cherry picked from commit 0e95f2a36b1c0f480f94fef85d341792c7a7b759) (cherry picked from commit 47a41a4e7b3c5d3cf247dafef17a64415069f171) (cherry picked from commit d4416fcd3ee0f1d74c2b03fbe40c15b83f80b56d) (cherry picked from commit f279d153b61677e2a766859c7a7688b0327e7c9a) (cherry picked from commit 959f908ffdc9dd2fb07aefdf7cb85ba34f9071a3) (cherry picked from commit 82df95310176a083abb9eaa51de9e9754d4f72c6) (cherry picked from commit 8f8c9fd9e3deca0af0f087fc44fcd271e8440a7b) (cherry picked from commit d4a0db7706f30a394192c6023072ad5185a7d600) (cherry picked from commit 44594d623942128072bd86a20b5b90603517005e) (cherry picked from commit 62b1de579ebfe326340b218cc9a980b705b8e6a2) (cherry picked from commit 507abee353f120247276fe9165f09976421f926d) (cherry picked from commit 8c36ac42c7091661b2f3fec70fc88a74043628fc) (cherry picked from commit 72f74067f4c781e1c4936f0da2653a35a6f1c82b) (cherry picked from commit 8e5a9e187b50a6eb8523c6dda4ce1a23c08a0b5a) (cherry picked from commit cff8f43c7e4ab86a468ea49f7b945412d9911151) (cherry picked from commit 493cc93e6d4b738a5b81d1a0db831bf9f430e9de) (cherry picked from commit c94352b4d460e631c223b45516e42765357bf67d) (cherry picked from commit 3f622c51a70200bf67d209d0cec78db2f25b65db) (cherry picked from commit 84c62ad447a94ebd5d1906e41c29efd3a0816c70) (cherry picked from commit ba186c8ee40e95cd4f40b6205d673dfe20528825) (cherry picked from commit 4238ef291dffb68b350aa2f410e934d52cb0a1eb) (cherry picked from commit 3ef1bd61b9a45271e69c1529875e84b379073318) (cherry picked from commit f304086bb693d3a0a58871ba8cb1354ba290695c) (cherry picked from commit 64a2edabcb7bc9e3961ea45f83cf6488e4cd99f2) (cherry picked from commit 6accea6ba7f2b0f98b6bef5b1483ee39ad68edba) (cherry picked from commit 2296e93a42939af90dee718bc8ff691009238c47) (cherry picked from commit 2bf0be3b1bff62923dd04e1a02956ced1eda97af) (cherry picked from commit fb4652b197ec64280689e6d065492f916f7e26f3) (cherry picked from commit 7d462cf353ef87007025bbd76aebef429be10562) (cherry picked from commit f5319660ddecea208163b5bcd37444322e571b51)
2023-01-14 07:16:30 +00:00
const (
giteaObjectTypeHeader = "X-Gitea-Object-Type"
forgejoObjectTypeHeader = "X-Forgejo-Object-Type"
)
2016-11-24 07:04:31 +00:00
// GetRawFile get a file by path on a repository
func GetRawFile(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/raw/{filepath} repository repoGetRawFile
// ---
// summary: Get a file from a repository
// produces:
// - application/octet-stream
// parameters:
// - name: owner
// in: path
// description: owner of the repo
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo
// type: string
// required: true
// - name: filepath
// in: path
// description: filepath of the file to get
// type: string
// required: true
// - name: ref
// in: query
// description: "The name of the commit/branch/tag. Default the repositorys default branch (usually master)"
// type: string
// required: false
// responses:
// 200:
// description: Returns raw file content.
// schema:
// type: file
// "404":
// "$ref": "#/responses/notFound"
if ctx.Repo.Repository.IsEmpty {
2019-03-19 02:29:43 +00:00
ctx.NotFound()
return
}
blob, entry, lastModified := getBlobForEntry(ctx)
if ctx.Written() {
return
}
ctx.RespHeader().Set(giteaObjectTypeHeader, string(files_service.GetObjectTypeFromTreeEntry(entry)))
[BRANDING] add X-Forgejo-* headers (cherry picked from commit 0a3388f93f53f53cce79ebcc194d67979cbe61cc) (cherry picked from commit 7eba0a440a3138443d0b7461560f9f6f1c46b256) (cherry picked from commit eb9646c7ef0b1a9e73bf22806acbc2caeaee7ff3) (cherry picked from commit f1972578f5cb72ad9e1ef1ded9aa324b1d669864) Conflicts: (cherry picked from commit 7f96222fb4b4a773193a11d4ba76f449013c30ef) (cherry picked from commit e3c7c9fe7b5809cdb75f1eb629c82b27986f7e98) (cherry picked from commit 84fdead90242e68493efb96a7fee1e3b5fd417e9) (cherry picked from commit 85148e11961b86269b7cda30294a4cdc817787d0) (cherry picked from commit c0086bd70d39d9a1075fa624cdbfdf5ef26f7a2c) (cherry picked from commit d1e31ef31817e5e811f6bea7aee5582383d53b12) (cherry picked from commit 681d3ed5c43d45f802dc13c58c5b821da938374d) (cherry picked from commit 76a3001f5bc5a064054514c179ba14008952e914) (cherry picked from commit a55a9567d36f6b60557df3df3a0801c0fccec963) (cherry picked from commit aa7adc167d4e4ee7879b88fe799f2afca2fd5531) (cherry picked from commit d5354cb52c26ab0090388d019efe61770e7ec452) (cherry picked from commit 472c48999699420d15aaa7c61a42db8136221616) (cherry picked from commit dc816d065b715f2bfdc7fa68fa2d254f3d481b35) (cherry picked from commit 4795f9ea85eac21c70db1159914a7b7b035b7374) (cherry picked from commit ddd4ae53433d4adab823aa974c0f1c39d0394414) (cherry picked from commit 0e95f2a36b1c0f480f94fef85d341792c7a7b759) (cherry picked from commit 47a41a4e7b3c5d3cf247dafef17a64415069f171) (cherry picked from commit d4416fcd3ee0f1d74c2b03fbe40c15b83f80b56d) (cherry picked from commit f279d153b61677e2a766859c7a7688b0327e7c9a) (cherry picked from commit 959f908ffdc9dd2fb07aefdf7cb85ba34f9071a3) (cherry picked from commit 82df95310176a083abb9eaa51de9e9754d4f72c6) (cherry picked from commit 8f8c9fd9e3deca0af0f087fc44fcd271e8440a7b) (cherry picked from commit d4a0db7706f30a394192c6023072ad5185a7d600) (cherry picked from commit 44594d623942128072bd86a20b5b90603517005e) (cherry picked from commit 62b1de579ebfe326340b218cc9a980b705b8e6a2) (cherry picked from commit 507abee353f120247276fe9165f09976421f926d) (cherry picked from commit 8c36ac42c7091661b2f3fec70fc88a74043628fc) (cherry picked from commit 72f74067f4c781e1c4936f0da2653a35a6f1c82b) (cherry picked from commit 8e5a9e187b50a6eb8523c6dda4ce1a23c08a0b5a) (cherry picked from commit cff8f43c7e4ab86a468ea49f7b945412d9911151) (cherry picked from commit 493cc93e6d4b738a5b81d1a0db831bf9f430e9de) (cherry picked from commit c94352b4d460e631c223b45516e42765357bf67d) (cherry picked from commit 3f622c51a70200bf67d209d0cec78db2f25b65db) (cherry picked from commit 84c62ad447a94ebd5d1906e41c29efd3a0816c70) (cherry picked from commit ba186c8ee40e95cd4f40b6205d673dfe20528825) (cherry picked from commit 4238ef291dffb68b350aa2f410e934d52cb0a1eb) (cherry picked from commit 3ef1bd61b9a45271e69c1529875e84b379073318) (cherry picked from commit f304086bb693d3a0a58871ba8cb1354ba290695c) (cherry picked from commit 64a2edabcb7bc9e3961ea45f83cf6488e4cd99f2) (cherry picked from commit 6accea6ba7f2b0f98b6bef5b1483ee39ad68edba) (cherry picked from commit 2296e93a42939af90dee718bc8ff691009238c47) (cherry picked from commit 2bf0be3b1bff62923dd04e1a02956ced1eda97af) (cherry picked from commit fb4652b197ec64280689e6d065492f916f7e26f3) (cherry picked from commit 7d462cf353ef87007025bbd76aebef429be10562) (cherry picked from commit f5319660ddecea208163b5bcd37444322e571b51)
2023-01-14 07:16:30 +00:00
ctx.RespHeader().Set(forgejoObjectTypeHeader, string(files_service.GetObjectTypeFromTreeEntry(entry)))
if err := common.ServeBlob(ctx.Base, ctx.Repo.TreePath, blob, lastModified); err != nil {
ctx.Error(http.StatusInternalServerError, "ServeBlob", err)
}
}
// GetRawFileOrLFS get a file by repo's path, redirecting to LFS if necessary.
func GetRawFileOrLFS(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/media/{filepath} repository repoGetRawFileOrLFS
// ---
// summary: Get a file or it's LFS object from a repository
// produces:
// - application/octet-stream
// parameters:
// - name: owner
// in: path
// description: owner of the repo
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo
// type: string
// required: true
// - name: filepath
// in: path
// description: filepath of the file to get
// type: string
// required: true
// - name: ref
// in: query
// description: "The name of the commit/branch/tag. Default the repositorys default branch (usually master)"
// type: string
// required: false
// responses:
// 200:
// description: Returns raw file content.
// schema:
// type: file
// "404":
// "$ref": "#/responses/notFound"
if ctx.Repo.Repository.IsEmpty {
ctx.NotFound()
return
}
blob, entry, lastModified := getBlobForEntry(ctx)
if ctx.Written() {
return
}
ctx.RespHeader().Set(giteaObjectTypeHeader, string(files_service.GetObjectTypeFromTreeEntry(entry)))
[BRANDING] add X-Forgejo-* headers (cherry picked from commit 0a3388f93f53f53cce79ebcc194d67979cbe61cc) (cherry picked from commit 7eba0a440a3138443d0b7461560f9f6f1c46b256) (cherry picked from commit eb9646c7ef0b1a9e73bf22806acbc2caeaee7ff3) (cherry picked from commit f1972578f5cb72ad9e1ef1ded9aa324b1d669864) Conflicts: (cherry picked from commit 7f96222fb4b4a773193a11d4ba76f449013c30ef) (cherry picked from commit e3c7c9fe7b5809cdb75f1eb629c82b27986f7e98) (cherry picked from commit 84fdead90242e68493efb96a7fee1e3b5fd417e9) (cherry picked from commit 85148e11961b86269b7cda30294a4cdc817787d0) (cherry picked from commit c0086bd70d39d9a1075fa624cdbfdf5ef26f7a2c) (cherry picked from commit d1e31ef31817e5e811f6bea7aee5582383d53b12) (cherry picked from commit 681d3ed5c43d45f802dc13c58c5b821da938374d) (cherry picked from commit 76a3001f5bc5a064054514c179ba14008952e914) (cherry picked from commit a55a9567d36f6b60557df3df3a0801c0fccec963) (cherry picked from commit aa7adc167d4e4ee7879b88fe799f2afca2fd5531) (cherry picked from commit d5354cb52c26ab0090388d019efe61770e7ec452) (cherry picked from commit 472c48999699420d15aaa7c61a42db8136221616) (cherry picked from commit dc816d065b715f2bfdc7fa68fa2d254f3d481b35) (cherry picked from commit 4795f9ea85eac21c70db1159914a7b7b035b7374) (cherry picked from commit ddd4ae53433d4adab823aa974c0f1c39d0394414) (cherry picked from commit 0e95f2a36b1c0f480f94fef85d341792c7a7b759) (cherry picked from commit 47a41a4e7b3c5d3cf247dafef17a64415069f171) (cherry picked from commit d4416fcd3ee0f1d74c2b03fbe40c15b83f80b56d) (cherry picked from commit f279d153b61677e2a766859c7a7688b0327e7c9a) (cherry picked from commit 959f908ffdc9dd2fb07aefdf7cb85ba34f9071a3) (cherry picked from commit 82df95310176a083abb9eaa51de9e9754d4f72c6) (cherry picked from commit 8f8c9fd9e3deca0af0f087fc44fcd271e8440a7b) (cherry picked from commit d4a0db7706f30a394192c6023072ad5185a7d600) (cherry picked from commit 44594d623942128072bd86a20b5b90603517005e) (cherry picked from commit 62b1de579ebfe326340b218cc9a980b705b8e6a2) (cherry picked from commit 507abee353f120247276fe9165f09976421f926d) (cherry picked from commit 8c36ac42c7091661b2f3fec70fc88a74043628fc) (cherry picked from commit 72f74067f4c781e1c4936f0da2653a35a6f1c82b) (cherry picked from commit 8e5a9e187b50a6eb8523c6dda4ce1a23c08a0b5a) (cherry picked from commit cff8f43c7e4ab86a468ea49f7b945412d9911151) (cherry picked from commit 493cc93e6d4b738a5b81d1a0db831bf9f430e9de) (cherry picked from commit c94352b4d460e631c223b45516e42765357bf67d) (cherry picked from commit 3f622c51a70200bf67d209d0cec78db2f25b65db) (cherry picked from commit 84c62ad447a94ebd5d1906e41c29efd3a0816c70) (cherry picked from commit ba186c8ee40e95cd4f40b6205d673dfe20528825) (cherry picked from commit 4238ef291dffb68b350aa2f410e934d52cb0a1eb) (cherry picked from commit 3ef1bd61b9a45271e69c1529875e84b379073318) (cherry picked from commit f304086bb693d3a0a58871ba8cb1354ba290695c) (cherry picked from commit 64a2edabcb7bc9e3961ea45f83cf6488e4cd99f2) (cherry picked from commit 6accea6ba7f2b0f98b6bef5b1483ee39ad68edba) (cherry picked from commit 2296e93a42939af90dee718bc8ff691009238c47) (cherry picked from commit 2bf0be3b1bff62923dd04e1a02956ced1eda97af) (cherry picked from commit fb4652b197ec64280689e6d065492f916f7e26f3) (cherry picked from commit 7d462cf353ef87007025bbd76aebef429be10562) (cherry picked from commit f5319660ddecea208163b5bcd37444322e571b51)
2023-01-14 07:16:30 +00:00
ctx.RespHeader().Set(forgejoObjectTypeHeader, string(files_service.GetObjectTypeFromTreeEntry(entry)))
// LFS Pointer files are at most 1024 bytes - so any blob greater than 1024 bytes cannot be an LFS file
if blob.Size() > 1024 {
// First handle caching for the blob
if httpcache.HandleGenericETagTimeCache(ctx.Req, ctx.Resp, `"`+blob.ID.String()+`"`, lastModified) {
return
}
// OK not cached - serve!
if err := common.ServeBlob(ctx.Base, ctx.Repo.TreePath, blob, lastModified); err != nil {
ctx.ServerError("ServeBlob", err)
}
return
}
// OK, now the blob is known to have at most 1024 bytes we can simply read this in one go (This saves reading it twice)
dataRc, err := blob.DataAsync()
if err != nil {
ctx.ServerError("DataAsync", err)
return
}
// FIXME: code from #19689, what if the file is large ... OOM ...
buf, err := io.ReadAll(dataRc)
if err != nil {
_ = dataRc.Close()
ctx.ServerError("DataAsync", err)
return
}
if err := dataRc.Close(); err != nil {
log.Error("Error whilst closing blob %s reader in %-v. Error: %v", blob.ID, ctx.Repo.Repository, err)
}
// Check if the blob represents a pointer
pointer, _ := lfs.ReadPointer(bytes.NewReader(buf))
// if it's not a pointer, just serve the data directly
if !pointer.IsValid() {
// First handle caching for the blob
if httpcache.HandleGenericETagTimeCache(ctx.Req, ctx.Resp, `"`+blob.ID.String()+`"`, lastModified) {
return
}
// OK not cached - serve!
common.ServeContentByReader(ctx.Base, ctx.Repo.TreePath, blob.Size(), bytes.NewReader(buf))
return
}
// Now check if there is a MetaObject for this pointer
meta, err := git_model.GetLFSMetaObjectByOid(ctx, ctx.Repo.Repository.ID, pointer.Oid)
// If there isn't one, just serve the data directly
if err == git_model.ErrLFSObjectNotExist {
// Handle caching for the blob SHA (not the LFS object OID)
if httpcache.HandleGenericETagTimeCache(ctx.Req, ctx.Resp, `"`+blob.ID.String()+`"`, lastModified) {
return
}
common.ServeContentByReader(ctx.Base, ctx.Repo.TreePath, blob.Size(), bytes.NewReader(buf))
return
} else if err != nil {
ctx.ServerError("GetLFSMetaObjectByOid", err)
return
}
// Handle caching for the LFS object OID
if httpcache.HandleGenericETagCache(ctx.Req, ctx.Resp, `"`+pointer.Oid+`"`) {
return
}
if setting.LFS.Storage.MinioConfig.ServeDirect {
// If we have a signed url (S3, object storage), redirect to this directly.
Fix `missing signature key` error when pulling Docker images with `SERVE_DIRECT` enabled (#32365) Fix #28121 I did some tests and found that the `missing signature key` error is caused by an incorrect `Content-Type` header. Gitea correctly sets the `Content-Type` header when serving files. https://github.com/go-gitea/gitea/blob/348d1d0f322ca57c459acd902f54821d687ca804/routers/api/packages/container/container.go#L712-L717 However, when `SERVE_DIRECT` is enabled, the `Content-Type` header may be set to an incorrect value by the storage service. To fix this issue, we can use query parameters to override response header values. https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObject.html <img width="600px" src="https://github.com/user-attachments/assets/f2ff90f0-f1df-46f9-9680-b8120222c555" /> In this PR, I introduced a new parameter to the `URL` method to support additional parameters. ``` URL(path, name string, reqParams url.Values) (*url.URL, error) ``` --- Most S3-like services support specifying the content type when storing objects. However, Gitea always use `application/octet-stream`. Therefore, I believe we also need to improve the `Save` method to support storing objects with the correct content type. https://github.com/go-gitea/gitea/blob/b7fb20e73e63b8edc9b90c52073e248bef428fcc/modules/storage/minio.go#L214-L221 (cherry picked from commit 0690cb076bf63f71988a709f62a9c04660b51a4f) Conflicts: - modules/storage/azureblob.go Dropped the change, as we do not support Azure blob storage. - modules/storage/helper.go Resolved by adjusting their `discardStorage` to our `DiscardStorage` - routers/api/actions/artifacts.go routers/api/actions/artifactsv4.go routers/web/repo/actions/view.go routers/web/repo/download.go Resolved the conflicts by manually adding the new `nil` parameter to the `storage.Attachments.URL()` calls. Originally conflicted due to differences in the if expression above these calls.
2024-10-31 15:28:25 +00:00
u, err := storage.LFS.URL(pointer.RelativePath(), blob.Name(), nil)
if u != nil && err == nil {
ctx.Redirect(u.String())
return
}
}
lfsDataRc, err := lfs.ReadMetaObject(meta.Pointer)
if err != nil {
ctx.ServerError("ReadMetaObject", err)
return
}
defer lfsDataRc.Close()
common.ServeContentByReadSeeker(ctx.Base, ctx.Repo.TreePath, lastModified, lfsDataRc)
}
func getBlobForEntry(ctx *context.APIContext) (blob *git.Blob, entry *git.TreeEntry, lastModified *time.Time) {
entry, err := ctx.Repo.Commit.GetTreeEntryByPath(ctx.Repo.TreePath)
2014-11-17 02:32:26 +00:00
if err != nil {
if git.IsErrNotExist(err) {
2019-03-19 02:29:43 +00:00
ctx.NotFound()
2014-11-17 02:32:26 +00:00
} else {
ctx.Error(http.StatusInternalServerError, "GetTreeEntryByPath", err)
2014-11-17 02:32:26 +00:00
}
return nil, nil, nil
2014-11-17 02:32:26 +00:00
}
if entry.IsDir() || entry.IsSubModule() {
ctx.NotFound("getBlobForEntry", nil)
return nil, nil, nil
2014-11-17 02:32:26 +00:00
}
info, _, err := git.Entries([]*git.TreeEntry{entry}).GetCommitsInfo(ctx, ctx.Repo.Commit, path.Dir("/" + ctx.Repo.TreePath)[1:])
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetCommitsInfo", err)
return nil, nil, nil
}
if len(info) == 1 {
// Not Modified
lastModified = &info[0].Commit.Committer.When
}
blob = entry.Blob()
return blob, entry, lastModified
2014-11-17 02:32:26 +00:00
}
2015-09-02 13:54:35 +00:00
2016-11-24 07:04:31 +00:00
// GetArchive get archive of a repository
func GetArchive(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/archive/{archive} repository repoGetArchive
// ---
// summary: Get an archive of a repository
// produces:
// - application/octet-stream
// - application/zip
// - application/gzip
// parameters:
// - name: owner
// in: path
// description: owner of the repo
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo
// type: string
// required: true
// - name: archive
// in: path
// description: the git reference for download with attached archive format (e.g. master.zip)
// type: string
// required: true
// responses:
// 200:
// description: success
// "404":
// "$ref": "#/responses/notFound"
if ctx.Repo.GitRepo == nil {
gitRepo, err := gitrepo.OpenRepository(ctx, ctx.Repo.Repository)
if err != nil {
ctx.Error(http.StatusInternalServerError, "OpenRepository", err)
return
}
ctx.Repo.GitRepo = gitRepo
defer gitRepo.Close()
2015-09-02 13:54:35 +00:00
}
archiveDownload(ctx)
}
func archiveDownload(ctx *context.APIContext) {
uri := ctx.Params("*")
2024-04-02 14:34:57 +00:00
aReq, err := archiver_service.NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, uri)
if err != nil {
if errors.Is(err, archiver_service.ErrUnknownArchiveFormat{}) {
ctx.Error(http.StatusBadRequest, "unknown archive format", err)
} else if errors.Is(err, archiver_service.RepoRefNotFoundError{}) {
ctx.Error(http.StatusNotFound, "unrecognized reference", err)
} else {
ctx.ServerError("archiver_service.NewRequest", err)
}
return
}
archiver, err := aReq.Await(ctx)
if err != nil {
ctx.ServerError("archiver.Await", err)
return
}
download(ctx, aReq.GetArchiveName(), archiver)
}
func download(ctx *context.APIContext, archiveName string, archiver *repo_model.RepoArchiver) {
downloadName := ctx.Repo.Repository.Name + "-" + archiveName
Add an immutable tarball link to archive download headers for Nix This allows `nix flake metadata` and nix in general to lock a *branch* tarball link in a manner that causes it to fetch the correct commit even if the branch is updated with a newer version. For further context, Nix flakes are a feature that, among other things, allows for "inputs" that are "github:someuser/somerepo", "https://some-tarball-service/some-tarball.tar.gz", "sourcehut:~meow/nya" or similar. This feature allows our users to fetch tarballs of git-based inputs to their builds rather than using git to fetch them, saving significant download time. There is presently no gitea or forgejo specific fetcher in Nix, and we don't particularly wish to have one. Ideally (as a developer on a Nix implementation myself) we could just use the generic tarball fetcher and not add specific forgejo support, but to do so, we need additional metadata to know which commit a given *branch* tarball represents, which is the purpose of the Link header added here. The result of this patch is that a Nix user can specify `inputs.something.url = "https://forgejo-host/some/project/archive/main.tar.gz"` in flake.nix and get a link to some concrete tarball for the actual commit in the lock file, then when they run `nix flake update` in the future, they will get the latest commit in that branch. Example of it working locally: » nix flake metadata --refresh 'http://localhost:3000/api/v1/repos/jade/cats/archive/main.tar.gz?dir=configs/nix' Resolved URL: http://localhost:3000/api/v1/repos/jade/cats/archive/main.tar.gz?dir=configs/nix Locked URL: http://localhost:3000/api/v1/repos/jade/cats/archive/804ede182b6b66469b23ea4d21eece52766b7a06.tar.gz?dir=configs /nix&narHash=sha256-yP7KkDVfuixZzs0fsqhSETXFC0y8m6nmPLw2GrAMxKQ%3D Description: Computers with the nixos Path: /nix/store/s856c6yqghyan4v0zy6jj19ksv0q22nx-source Revision: 804ede182b6b66469b23ea4d21eece52766b7a06 Last modified: 2024-05-02 00:48:32 For details on the header value, see: https://github.com/nixos/nix/blob/56763ff918eb308db23080e560ed2ea3e00c80a7/doc/manual/src/protocols/tarball-fetcher.md
2024-05-03 00:51:26 +00:00
// Add nix format link header so tarballs lock correctly:
// https://github.com/nixos/nix/blob/56763ff918eb308db23080e560ed2ea3e00c80a7/doc/manual/src/protocols/tarball-fetcher.md
ctx.Resp.Header().Add("Link", fmt.Sprintf("<%s/archive/%s.tar.gz?rev=%s>; rel=\"immutable\"",
ctx.Repo.Repository.APIURL(),
archiver.CommitID, archiver.CommitID))
rPath := archiver.RelativePath()
if setting.RepoArchive.Storage.MinioConfig.ServeDirect {
// If we have a signed url (S3, object storage), redirect to this directly.
Fix `missing signature key` error when pulling Docker images with `SERVE_DIRECT` enabled (#32365) Fix #28121 I did some tests and found that the `missing signature key` error is caused by an incorrect `Content-Type` header. Gitea correctly sets the `Content-Type` header when serving files. https://github.com/go-gitea/gitea/blob/348d1d0f322ca57c459acd902f54821d687ca804/routers/api/packages/container/container.go#L712-L717 However, when `SERVE_DIRECT` is enabled, the `Content-Type` header may be set to an incorrect value by the storage service. To fix this issue, we can use query parameters to override response header values. https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObject.html <img width="600px" src="https://github.com/user-attachments/assets/f2ff90f0-f1df-46f9-9680-b8120222c555" /> In this PR, I introduced a new parameter to the `URL` method to support additional parameters. ``` URL(path, name string, reqParams url.Values) (*url.URL, error) ``` --- Most S3-like services support specifying the content type when storing objects. However, Gitea always use `application/octet-stream`. Therefore, I believe we also need to improve the `Save` method to support storing objects with the correct content type. https://github.com/go-gitea/gitea/blob/b7fb20e73e63b8edc9b90c52073e248bef428fcc/modules/storage/minio.go#L214-L221 (cherry picked from commit 0690cb076bf63f71988a709f62a9c04660b51a4f) Conflicts: - modules/storage/azureblob.go Dropped the change, as we do not support Azure blob storage. - modules/storage/helper.go Resolved by adjusting their `discardStorage` to our `DiscardStorage` - routers/api/actions/artifacts.go routers/api/actions/artifactsv4.go routers/web/repo/actions/view.go routers/web/repo/download.go Resolved the conflicts by manually adding the new `nil` parameter to the `storage.Attachments.URL()` calls. Originally conflicted due to differences in the if expression above these calls.
2024-10-31 15:28:25 +00:00
u, err := storage.RepoArchives.URL(rPath, downloadName, nil)
if u != nil && err == nil {
ctx.Redirect(u.String())
return
}
}
// If we have matched and access to release or issue
fr, err := storage.RepoArchives.Open(rPath)
if err != nil {
ctx.ServerError("Open", err)
return
}
defer fr.Close()
contentType := ""
switch archiver.Type {
case git.ZIP:
contentType = "application/zip"
case git.TARGZ:
// Per RFC6713.
contentType = "application/gzip"
}
ctx.ServeContent(fr, &context.ServeHeaderOptions{
ContentType: contentType,
Filename: downloadName,
LastModified: archiver.CreatedUnix.AsLocalTime(),
})
2015-09-02 13:54:35 +00:00
}
2016-08-30 23:18:40 +00:00
2016-11-24 07:04:31 +00:00
// GetEditorconfig get editor config of a repository
2016-08-30 23:18:40 +00:00
func GetEditorconfig(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/editorconfig/{filepath} repository repoGetEditorConfig
// ---
// summary: Get the EditorConfig definitions of a file in a repository
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: owner of the repo
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo
// type: string
// required: true
// - name: filepath
// in: path
// description: filepath of file to get
// type: string
// required: true
// - name: ref
// in: query
// description: "The name of the commit/branch/tag. Default the repositorys default branch (usually master)"
// type: string
// required: false
// responses:
// 200:
// description: success
// "404":
// "$ref": "#/responses/notFound"
ec, _, err := ctx.Repo.GetEditorconfig(ctx.Repo.Commit)
2016-08-30 23:18:40 +00:00
if err != nil {
if git.IsErrNotExist(err) {
2019-03-19 02:29:43 +00:00
ctx.NotFound(err)
2016-08-30 23:18:40 +00:00
} else {
ctx.Error(http.StatusInternalServerError, "GetEditorconfig", err)
2016-08-30 23:18:40 +00:00
}
return
}
fileName := ctx.Params("filename")
2019-10-17 00:15:02 +00:00
def, err := ec.GetDefinitionForFilename(fileName)
2016-08-30 23:18:40 +00:00
if def == nil {
2019-03-19 02:29:43 +00:00
ctx.NotFound(err)
2016-08-30 23:18:40 +00:00
return
}
ctx.JSON(http.StatusOK, def)
}
// canWriteFiles returns true if repository is editable and user has proper access level.
func canWriteFiles(ctx *context.APIContext, branch string) bool {
return ctx.Repo.CanWriteToBranch(ctx, ctx.Doer, branch) &&
!ctx.Repo.Repository.IsMirror &&
!ctx.Repo.Repository.IsArchived
}
// canReadFiles returns true if repository is readable and user has proper access level.
func canReadFiles(r *context.Repository) bool {
return r.Permission.CanRead(unit.TypeCode)
}
func base64Reader(s string) (io.ReadSeeker, error) {
b, err := base64.StdEncoding.DecodeString(s)
if err != nil {
return nil, err
}
return bytes.NewReader(b), nil
}
// ChangeFiles handles API call for modifying multiple files
func ChangeFiles(ctx *context.APIContext) {
// swagger:operation POST /repos/{owner}/{repo}/contents repository repoChangeFiles
// ---
// summary: Modify multiple files in a repository
// consumes:
// - application/json
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: owner of the repo
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo
// type: string
// required: true
// - name: body
// in: body
// required: true
// schema:
// "$ref": "#/definitions/ChangeFilesOptions"
// responses:
// "201":
// "$ref": "#/responses/FilesResponse"
// "403":
// "$ref": "#/responses/error"
// "404":
// "$ref": "#/responses/notFound"
feat(quota): Quota enforcement The previous commit laid out the foundation of the quota engine, this one builds on top of it, and implements the actual enforcement. Enforcement happens at the route decoration level, whenever possible. In case of the API, when over quota, a 413 error is returned, with an appropriate JSON payload. In case of web routes, a 413 HTML page is rendered with similar information. This implementation is for a **soft quota**: quota usage is checked before an operation is to be performed, and the operation is *only* denied if the user is already over quota. This makes it possible to go over quota, but has the significant advantage of being practically implementable within the current Forgejo architecture. The goal of enforcement is to deny actions that can make the user go over quota, and allow the rest. As such, deleting things should - in almost all cases - be possible. A prime exemption is deleting files via the web ui: that creates a new commit, which in turn increases repo size, thus, is denied if the user is over quota. Limitations ----------- Because we generally work at a route decorator level, and rarely look *into* the operation itself, `size:repos:public` and `size:repos:private` are not enforced at this level, the engine enforces against `size:repos:all`. This will be improved in the future. AGit does not play very well with this system, because AGit PRs count toward the repo they're opened against, while in the GitHub-style fork + pull model, it counts against the fork. This too, can be improved in the future. There's very little done on the UI side to guard against going over quota. What this patch implements, is enforcement, not prevention. The UI will still let you *try* operations that *will* result in a denial. Signed-off-by: Gergely Nagy <forgejo@gergo.csillger.hu>
2024-07-06 08:30:16 +00:00
// "413":
// "$ref": "#/responses/quotaExceeded"
// "422":
// "$ref": "#/responses/error"
// "423":
// "$ref": "#/responses/repoArchivedError"
apiOpts := web.GetForm(ctx).(*api.ChangeFilesOptions)
if apiOpts.BranchName == "" {
apiOpts.BranchName = ctx.Repo.Repository.DefaultBranch
}
var files []*files_service.ChangeRepoFile
for _, file := range apiOpts.Files {
contentReader, err := base64Reader(file.ContentBase64)
if err != nil {
ctx.Error(http.StatusUnprocessableEntity, "Invalid base64 content", err)
return
}
changeRepoFile := &files_service.ChangeRepoFile{
Operation: file.Operation,
TreePath: file.Path,
FromTreePath: file.FromPath,
ContentReader: contentReader,
SHA: file.SHA,
}
files = append(files, changeRepoFile)
}
opts := &files_service.ChangeRepoFilesOptions{
Files: files,
Message: apiOpts.Message,
OldBranch: apiOpts.BranchName,
NewBranch: apiOpts.NewBranchName,
Committer: &files_service.IdentityOptions{
Name: apiOpts.Committer.Name,
Email: apiOpts.Committer.Email,
},
Author: &files_service.IdentityOptions{
Name: apiOpts.Author.Name,
Email: apiOpts.Author.Email,
},
Dates: &files_service.CommitDateOptions{
Author: apiOpts.Dates.Author,
Committer: apiOpts.Dates.Committer,
},
Signoff: apiOpts.Signoff,
}
if opts.Dates.Author.IsZero() {
opts.Dates.Author = time.Now()
}
if opts.Dates.Committer.IsZero() {
opts.Dates.Committer = time.Now()
}
if opts.Message == "" {
opts.Message = changeFilesCommitMessage(ctx, files)
}
if filesResponse, err := createOrUpdateFiles(ctx, opts); err != nil {
handleCreateOrUpdateFileError(ctx, err)
} else {
ctx.JSON(http.StatusCreated, filesResponse)
}
}
// CreateFile handles API call for creating a file
Move macaron to chi (#14293) Use [chi](https://github.com/go-chi/chi) instead of the forked [macaron](https://gitea.com/macaron/macaron). Since macaron and chi have conflicts with session share, this big PR becomes a have-to thing. According my previous idea, we can replace macaron step by step but I'm wrong. :( Below is a list of big changes on this PR. - [x] Define `context.ResponseWriter` interface with an implementation `context.Response`. - [x] Use chi instead of macaron, and also a customize `Route` to wrap chi so that the router usage is similar as before. - [x] Create different routers for `web`, `api`, `internal` and `install` so that the codes will be more clear and no magic . - [x] Use https://github.com/unrolled/render instead of macaron's internal render - [x] Use https://github.com/NYTimes/gziphandler instead of https://gitea.com/macaron/gzip - [x] Use https://gitea.com/go-chi/session which is a modified version of https://gitea.com/macaron/session and removed `nodb` support since it will not be maintained. **BREAK** - [x] Use https://gitea.com/go-chi/captcha which is a modified version of https://gitea.com/macaron/captcha - [x] Use https://gitea.com/go-chi/cache which is a modified version of https://gitea.com/macaron/cache - [x] Use https://gitea.com/go-chi/binding which is a modified version of https://gitea.com/macaron/binding - [x] Use https://github.com/go-chi/cors instead of https://gitea.com/macaron/cors - [x] Dropped https://gitea.com/macaron/i18n and make a new one in `code.gitea.io/gitea/modules/translation` - [x] Move validation form structs from `code.gitea.io/gitea/modules/auth` to `code.gitea.io/gitea/modules/forms` to avoid dependency cycle. - [x] Removed macaron log service because it's not need any more. **BREAK** - [x] All form structs have to be get by `web.GetForm(ctx)` in the route function but not as a function parameter on routes definition. - [x] Move Git HTTP protocol implementation to use routers directly. - [x] Fix the problem that chi routes don't support trailing slash but macaron did. - [x] `/api/v1/swagger` now will be redirect to `/api/swagger` but not render directly so that `APIContext` will not create a html render. Notices: - Chi router don't support request with trailing slash - Integration test `TestUserHeatmap` maybe mysql version related. It's failed on my macOS(mysql 5.7.29 installed via brew) but succeed on CI. Co-authored-by: 6543 <6543@obermui.de>
2021-01-26 15:36:53 +00:00
func CreateFile(ctx *context.APIContext) {
// swagger:operation POST /repos/{owner}/{repo}/contents/{filepath} repository repoCreateFile
// ---
// summary: Create a file in a repository
// consumes:
// - application/json
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: owner of the repo
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo
// type: string
// required: true
// - name: filepath
// in: path
// description: path of the file to create
// type: string
// required: true
// - name: body
// in: body
// required: true
// schema:
// "$ref": "#/definitions/CreateFileOptions"
// responses:
// "201":
// "$ref": "#/responses/FileResponse"
// "403":
// "$ref": "#/responses/error"
// "404":
// "$ref": "#/responses/notFound"
feat(quota): Quota enforcement The previous commit laid out the foundation of the quota engine, this one builds on top of it, and implements the actual enforcement. Enforcement happens at the route decoration level, whenever possible. In case of the API, when over quota, a 413 error is returned, with an appropriate JSON payload. In case of web routes, a 413 HTML page is rendered with similar information. This implementation is for a **soft quota**: quota usage is checked before an operation is to be performed, and the operation is *only* denied if the user is already over quota. This makes it possible to go over quota, but has the significant advantage of being practically implementable within the current Forgejo architecture. The goal of enforcement is to deny actions that can make the user go over quota, and allow the rest. As such, deleting things should - in almost all cases - be possible. A prime exemption is deleting files via the web ui: that creates a new commit, which in turn increases repo size, thus, is denied if the user is over quota. Limitations ----------- Because we generally work at a route decorator level, and rarely look *into* the operation itself, `size:repos:public` and `size:repos:private` are not enforced at this level, the engine enforces against `size:repos:all`. This will be improved in the future. AGit does not play very well with this system, because AGit PRs count toward the repo they're opened against, while in the GitHub-style fork + pull model, it counts against the fork. This too, can be improved in the future. There's very little done on the UI side to guard against going over quota. What this patch implements, is enforcement, not prevention. The UI will still let you *try* operations that *will* result in a denial. Signed-off-by: Gergely Nagy <forgejo@gergo.csillger.hu>
2024-07-06 08:30:16 +00:00
// "413":
// "$ref": "#/responses/quotaExceeded"
// "422":
// "$ref": "#/responses/error"
// "423":
// "$ref": "#/responses/repoArchivedError"
Move macaron to chi (#14293) Use [chi](https://github.com/go-chi/chi) instead of the forked [macaron](https://gitea.com/macaron/macaron). Since macaron and chi have conflicts with session share, this big PR becomes a have-to thing. According my previous idea, we can replace macaron step by step but I'm wrong. :( Below is a list of big changes on this PR. - [x] Define `context.ResponseWriter` interface with an implementation `context.Response`. - [x] Use chi instead of macaron, and also a customize `Route` to wrap chi so that the router usage is similar as before. - [x] Create different routers for `web`, `api`, `internal` and `install` so that the codes will be more clear and no magic . - [x] Use https://github.com/unrolled/render instead of macaron's internal render - [x] Use https://github.com/NYTimes/gziphandler instead of https://gitea.com/macaron/gzip - [x] Use https://gitea.com/go-chi/session which is a modified version of https://gitea.com/macaron/session and removed `nodb` support since it will not be maintained. **BREAK** - [x] Use https://gitea.com/go-chi/captcha which is a modified version of https://gitea.com/macaron/captcha - [x] Use https://gitea.com/go-chi/cache which is a modified version of https://gitea.com/macaron/cache - [x] Use https://gitea.com/go-chi/binding which is a modified version of https://gitea.com/macaron/binding - [x] Use https://github.com/go-chi/cors instead of https://gitea.com/macaron/cors - [x] Dropped https://gitea.com/macaron/i18n and make a new one in `code.gitea.io/gitea/modules/translation` - [x] Move validation form structs from `code.gitea.io/gitea/modules/auth` to `code.gitea.io/gitea/modules/forms` to avoid dependency cycle. - [x] Removed macaron log service because it's not need any more. **BREAK** - [x] All form structs have to be get by `web.GetForm(ctx)` in the route function but not as a function parameter on routes definition. - [x] Move Git HTTP protocol implementation to use routers directly. - [x] Fix the problem that chi routes don't support trailing slash but macaron did. - [x] `/api/v1/swagger` now will be redirect to `/api/swagger` but not render directly so that `APIContext` will not create a html render. Notices: - Chi router don't support request with trailing slash - Integration test `TestUserHeatmap` maybe mysql version related. It's failed on my macOS(mysql 5.7.29 installed via brew) but succeed on CI. Co-authored-by: 6543 <6543@obermui.de>
2021-01-26 15:36:53 +00:00
apiOpts := web.GetForm(ctx).(*api.CreateFileOptions)
if apiOpts.BranchName == "" {
apiOpts.BranchName = ctx.Repo.Repository.DefaultBranch
}
contentReader, err := base64Reader(apiOpts.ContentBase64)
if err != nil {
ctx.Error(http.StatusUnprocessableEntity, "Invalid base64 content", err)
return
}
opts := &files_service.ChangeRepoFilesOptions{
Files: []*files_service.ChangeRepoFile{
{
Operation: "create",
TreePath: ctx.Params("*"),
ContentReader: contentReader,
},
},
Message: apiOpts.Message,
OldBranch: apiOpts.BranchName,
NewBranch: apiOpts.NewBranchName,
Committer: &files_service.IdentityOptions{
Name: apiOpts.Committer.Name,
Email: apiOpts.Committer.Email,
},
Author: &files_service.IdentityOptions{
Name: apiOpts.Author.Name,
Email: apiOpts.Author.Email,
},
Dates: &files_service.CommitDateOptions{
Author: apiOpts.Dates.Author,
Committer: apiOpts.Dates.Committer,
},
Signoff: apiOpts.Signoff,
}
if opts.Dates.Author.IsZero() {
opts.Dates.Author = time.Now()
}
if opts.Dates.Committer.IsZero() {
opts.Dates.Committer = time.Now()
}
if opts.Message == "" {
opts.Message = changeFilesCommitMessage(ctx, opts.Files)
}
if filesResponse, err := createOrUpdateFiles(ctx, opts); err != nil {
handleCreateOrUpdateFileError(ctx, err)
} else {
fileResponse := files_service.GetFileResponseFromFilesResponse(filesResponse, 0)
ctx.JSON(http.StatusCreated, fileResponse)
}
}
// UpdateFile handles API call for updating a file
Move macaron to chi (#14293) Use [chi](https://github.com/go-chi/chi) instead of the forked [macaron](https://gitea.com/macaron/macaron). Since macaron and chi have conflicts with session share, this big PR becomes a have-to thing. According my previous idea, we can replace macaron step by step but I'm wrong. :( Below is a list of big changes on this PR. - [x] Define `context.ResponseWriter` interface with an implementation `context.Response`. - [x] Use chi instead of macaron, and also a customize `Route` to wrap chi so that the router usage is similar as before. - [x] Create different routers for `web`, `api`, `internal` and `install` so that the codes will be more clear and no magic . - [x] Use https://github.com/unrolled/render instead of macaron's internal render - [x] Use https://github.com/NYTimes/gziphandler instead of https://gitea.com/macaron/gzip - [x] Use https://gitea.com/go-chi/session which is a modified version of https://gitea.com/macaron/session and removed `nodb` support since it will not be maintained. **BREAK** - [x] Use https://gitea.com/go-chi/captcha which is a modified version of https://gitea.com/macaron/captcha - [x] Use https://gitea.com/go-chi/cache which is a modified version of https://gitea.com/macaron/cache - [x] Use https://gitea.com/go-chi/binding which is a modified version of https://gitea.com/macaron/binding - [x] Use https://github.com/go-chi/cors instead of https://gitea.com/macaron/cors - [x] Dropped https://gitea.com/macaron/i18n and make a new one in `code.gitea.io/gitea/modules/translation` - [x] Move validation form structs from `code.gitea.io/gitea/modules/auth` to `code.gitea.io/gitea/modules/forms` to avoid dependency cycle. - [x] Removed macaron log service because it's not need any more. **BREAK** - [x] All form structs have to be get by `web.GetForm(ctx)` in the route function but not as a function parameter on routes definition. - [x] Move Git HTTP protocol implementation to use routers directly. - [x] Fix the problem that chi routes don't support trailing slash but macaron did. - [x] `/api/v1/swagger` now will be redirect to `/api/swagger` but not render directly so that `APIContext` will not create a html render. Notices: - Chi router don't support request with trailing slash - Integration test `TestUserHeatmap` maybe mysql version related. It's failed on my macOS(mysql 5.7.29 installed via brew) but succeed on CI. Co-authored-by: 6543 <6543@obermui.de>
2021-01-26 15:36:53 +00:00
func UpdateFile(ctx *context.APIContext) {
// swagger:operation PUT /repos/{owner}/{repo}/contents/{filepath} repository repoUpdateFile
// ---
// summary: Update a file in a repository
// consumes:
// - application/json
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: owner of the repo
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo
// type: string
// required: true
// - name: filepath
// in: path
// description: path of the file to update
// type: string
// required: true
// - name: body
// in: body
// required: true
// schema:
// "$ref": "#/definitions/UpdateFileOptions"
// responses:
// "200":
// "$ref": "#/responses/FileResponse"
// "403":
// "$ref": "#/responses/error"
// "404":
// "$ref": "#/responses/notFound"
feat(quota): Quota enforcement The previous commit laid out the foundation of the quota engine, this one builds on top of it, and implements the actual enforcement. Enforcement happens at the route decoration level, whenever possible. In case of the API, when over quota, a 413 error is returned, with an appropriate JSON payload. In case of web routes, a 413 HTML page is rendered with similar information. This implementation is for a **soft quota**: quota usage is checked before an operation is to be performed, and the operation is *only* denied if the user is already over quota. This makes it possible to go over quota, but has the significant advantage of being practically implementable within the current Forgejo architecture. The goal of enforcement is to deny actions that can make the user go over quota, and allow the rest. As such, deleting things should - in almost all cases - be possible. A prime exemption is deleting files via the web ui: that creates a new commit, which in turn increases repo size, thus, is denied if the user is over quota. Limitations ----------- Because we generally work at a route decorator level, and rarely look *into* the operation itself, `size:repos:public` and `size:repos:private` are not enforced at this level, the engine enforces against `size:repos:all`. This will be improved in the future. AGit does not play very well with this system, because AGit PRs count toward the repo they're opened against, while in the GitHub-style fork + pull model, it counts against the fork. This too, can be improved in the future. There's very little done on the UI side to guard against going over quota. What this patch implements, is enforcement, not prevention. The UI will still let you *try* operations that *will* result in a denial. Signed-off-by: Gergely Nagy <forgejo@gergo.csillger.hu>
2024-07-06 08:30:16 +00:00
// "413":
// "$ref": "#/responses/quotaExceeded"
// "422":
// "$ref": "#/responses/error"
// "423":
// "$ref": "#/responses/repoArchivedError"
Move macaron to chi (#14293) Use [chi](https://github.com/go-chi/chi) instead of the forked [macaron](https://gitea.com/macaron/macaron). Since macaron and chi have conflicts with session share, this big PR becomes a have-to thing. According my previous idea, we can replace macaron step by step but I'm wrong. :( Below is a list of big changes on this PR. - [x] Define `context.ResponseWriter` interface with an implementation `context.Response`. - [x] Use chi instead of macaron, and also a customize `Route` to wrap chi so that the router usage is similar as before. - [x] Create different routers for `web`, `api`, `internal` and `install` so that the codes will be more clear and no magic . - [x] Use https://github.com/unrolled/render instead of macaron's internal render - [x] Use https://github.com/NYTimes/gziphandler instead of https://gitea.com/macaron/gzip - [x] Use https://gitea.com/go-chi/session which is a modified version of https://gitea.com/macaron/session and removed `nodb` support since it will not be maintained. **BREAK** - [x] Use https://gitea.com/go-chi/captcha which is a modified version of https://gitea.com/macaron/captcha - [x] Use https://gitea.com/go-chi/cache which is a modified version of https://gitea.com/macaron/cache - [x] Use https://gitea.com/go-chi/binding which is a modified version of https://gitea.com/macaron/binding - [x] Use https://github.com/go-chi/cors instead of https://gitea.com/macaron/cors - [x] Dropped https://gitea.com/macaron/i18n and make a new one in `code.gitea.io/gitea/modules/translation` - [x] Move validation form structs from `code.gitea.io/gitea/modules/auth` to `code.gitea.io/gitea/modules/forms` to avoid dependency cycle. - [x] Removed macaron log service because it's not need any more. **BREAK** - [x] All form structs have to be get by `web.GetForm(ctx)` in the route function but not as a function parameter on routes definition. - [x] Move Git HTTP protocol implementation to use routers directly. - [x] Fix the problem that chi routes don't support trailing slash but macaron did. - [x] `/api/v1/swagger` now will be redirect to `/api/swagger` but not render directly so that `APIContext` will not create a html render. Notices: - Chi router don't support request with trailing slash - Integration test `TestUserHeatmap` maybe mysql version related. It's failed on my macOS(mysql 5.7.29 installed via brew) but succeed on CI. Co-authored-by: 6543 <6543@obermui.de>
2021-01-26 15:36:53 +00:00
apiOpts := web.GetForm(ctx).(*api.UpdateFileOptions)
if ctx.Repo.Repository.IsEmpty {
ctx.Error(http.StatusUnprocessableEntity, "RepoIsEmpty", fmt.Errorf("repo is empty"))
return
}
if apiOpts.BranchName == "" {
apiOpts.BranchName = ctx.Repo.Repository.DefaultBranch
}
contentReader, err := base64Reader(apiOpts.ContentBase64)
if err != nil {
ctx.Error(http.StatusUnprocessableEntity, "Invalid base64 content", err)
return
}
opts := &files_service.ChangeRepoFilesOptions{
Files: []*files_service.ChangeRepoFile{
{
Operation: "update",
ContentReader: contentReader,
SHA: apiOpts.SHA,
FromTreePath: apiOpts.FromPath,
TreePath: ctx.Params("*"),
},
},
Message: apiOpts.Message,
OldBranch: apiOpts.BranchName,
NewBranch: apiOpts.NewBranchName,
Committer: &files_service.IdentityOptions{
Name: apiOpts.Committer.Name,
Email: apiOpts.Committer.Email,
},
Author: &files_service.IdentityOptions{
Name: apiOpts.Author.Name,
Email: apiOpts.Author.Email,
},
Dates: &files_service.CommitDateOptions{
Author: apiOpts.Dates.Author,
Committer: apiOpts.Dates.Committer,
},
Signoff: apiOpts.Signoff,
}
if opts.Dates.Author.IsZero() {
opts.Dates.Author = time.Now()
}
if opts.Dates.Committer.IsZero() {
opts.Dates.Committer = time.Now()
}
if opts.Message == "" {
opts.Message = changeFilesCommitMessage(ctx, opts.Files)
}
if filesResponse, err := createOrUpdateFiles(ctx, opts); err != nil {
handleCreateOrUpdateFileError(ctx, err)
} else {
fileResponse := files_service.GetFileResponseFromFilesResponse(filesResponse, 0)
ctx.JSON(http.StatusOK, fileResponse)
}
}
func handleCreateOrUpdateFileError(ctx *context.APIContext, err error) {
if models.IsErrUserCannotCommit(err) || models.IsErrFilePathProtected(err) {
ctx.Error(http.StatusForbidden, "Access", err)
return
}
if git_model.IsErrBranchAlreadyExists(err) || models.IsErrFilenameInvalid(err) || models.IsErrSHADoesNotMatch(err) ||
models.IsErrFilePathInvalid(err) || models.IsErrRepoFileAlreadyExists(err) {
ctx.Error(http.StatusUnprocessableEntity, "Invalid", err)
return
}
if git_model.IsErrBranchNotExist(err) || git.IsErrBranchNotExist(err) {
ctx.Error(http.StatusNotFound, "BranchDoesNotExist", err)
return
}
ctx.Error(http.StatusInternalServerError, "UpdateFile", err)
}
// Called from both CreateFile or UpdateFile to handle both
func createOrUpdateFiles(ctx *context.APIContext, opts *files_service.ChangeRepoFilesOptions) (*api.FilesResponse, error) {
if !canWriteFiles(ctx, opts.OldBranch) {
return nil, repo_model.ErrUserDoesNotHaveAccessToRepo{
UserID: ctx.Doer.ID,
RepoName: ctx.Repo.Repository.LowerName,
}
}
return files_service.ChangeRepoFiles(ctx, ctx.Repo.Repository, ctx.Doer, opts)
}
// format commit message if empty
func changeFilesCommitMessage(ctx *context.APIContext, files []*files_service.ChangeRepoFile) string {
var (
createFiles []string
updateFiles []string
deleteFiles []string
)
for _, file := range files {
switch file.Operation {
case "create":
createFiles = append(createFiles, file.TreePath)
case "update":
updateFiles = append(updateFiles, file.TreePath)
case "delete":
deleteFiles = append(deleteFiles, file.TreePath)
}
}
message := ""
if len(createFiles) != 0 {
message += ctx.Locale.TrString("repo.editor.add", strings.Join(createFiles, ", ")+"\n")
}
if len(updateFiles) != 0 {
message += ctx.Locale.TrString("repo.editor.update", strings.Join(updateFiles, ", ")+"\n")
}
if len(deleteFiles) != 0 {
message += ctx.Locale.TrString("repo.editor.delete", strings.Join(deleteFiles, ", "))
}
return strings.Trim(message, "\n")
}
// DeleteFile Delete a file in a repository
Move macaron to chi (#14293) Use [chi](https://github.com/go-chi/chi) instead of the forked [macaron](https://gitea.com/macaron/macaron). Since macaron and chi have conflicts with session share, this big PR becomes a have-to thing. According my previous idea, we can replace macaron step by step but I'm wrong. :( Below is a list of big changes on this PR. - [x] Define `context.ResponseWriter` interface with an implementation `context.Response`. - [x] Use chi instead of macaron, and also a customize `Route` to wrap chi so that the router usage is similar as before. - [x] Create different routers for `web`, `api`, `internal` and `install` so that the codes will be more clear and no magic . - [x] Use https://github.com/unrolled/render instead of macaron's internal render - [x] Use https://github.com/NYTimes/gziphandler instead of https://gitea.com/macaron/gzip - [x] Use https://gitea.com/go-chi/session which is a modified version of https://gitea.com/macaron/session and removed `nodb` support since it will not be maintained. **BREAK** - [x] Use https://gitea.com/go-chi/captcha which is a modified version of https://gitea.com/macaron/captcha - [x] Use https://gitea.com/go-chi/cache which is a modified version of https://gitea.com/macaron/cache - [x] Use https://gitea.com/go-chi/binding which is a modified version of https://gitea.com/macaron/binding - [x] Use https://github.com/go-chi/cors instead of https://gitea.com/macaron/cors - [x] Dropped https://gitea.com/macaron/i18n and make a new one in `code.gitea.io/gitea/modules/translation` - [x] Move validation form structs from `code.gitea.io/gitea/modules/auth` to `code.gitea.io/gitea/modules/forms` to avoid dependency cycle. - [x] Removed macaron log service because it's not need any more. **BREAK** - [x] All form structs have to be get by `web.GetForm(ctx)` in the route function but not as a function parameter on routes definition. - [x] Move Git HTTP protocol implementation to use routers directly. - [x] Fix the problem that chi routes don't support trailing slash but macaron did. - [x] `/api/v1/swagger` now will be redirect to `/api/swagger` but not render directly so that `APIContext` will not create a html render. Notices: - Chi router don't support request with trailing slash - Integration test `TestUserHeatmap` maybe mysql version related. It's failed on my macOS(mysql 5.7.29 installed via brew) but succeed on CI. Co-authored-by: 6543 <6543@obermui.de>
2021-01-26 15:36:53 +00:00
func DeleteFile(ctx *context.APIContext) {
// swagger:operation DELETE /repos/{owner}/{repo}/contents/{filepath} repository repoDeleteFile
// ---
// summary: Delete a file in a repository
// consumes:
// - application/json
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: owner of the repo
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo
// type: string
// required: true
// - name: filepath
// in: path
// description: path of the file to delete
// type: string
// required: true
// - name: body
// in: body
// required: true
// schema:
// "$ref": "#/definitions/DeleteFileOptions"
// responses:
// "200":
// "$ref": "#/responses/FileDeleteResponse"
// "400":
// "$ref": "#/responses/error"
// "403":
// "$ref": "#/responses/error"
// "404":
// "$ref": "#/responses/error"
feat(quota): Quota enforcement The previous commit laid out the foundation of the quota engine, this one builds on top of it, and implements the actual enforcement. Enforcement happens at the route decoration level, whenever possible. In case of the API, when over quota, a 413 error is returned, with an appropriate JSON payload. In case of web routes, a 413 HTML page is rendered with similar information. This implementation is for a **soft quota**: quota usage is checked before an operation is to be performed, and the operation is *only* denied if the user is already over quota. This makes it possible to go over quota, but has the significant advantage of being practically implementable within the current Forgejo architecture. The goal of enforcement is to deny actions that can make the user go over quota, and allow the rest. As such, deleting things should - in almost all cases - be possible. A prime exemption is deleting files via the web ui: that creates a new commit, which in turn increases repo size, thus, is denied if the user is over quota. Limitations ----------- Because we generally work at a route decorator level, and rarely look *into* the operation itself, `size:repos:public` and `size:repos:private` are not enforced at this level, the engine enforces against `size:repos:all`. This will be improved in the future. AGit does not play very well with this system, because AGit PRs count toward the repo they're opened against, while in the GitHub-style fork + pull model, it counts against the fork. This too, can be improved in the future. There's very little done on the UI side to guard against going over quota. What this patch implements, is enforcement, not prevention. The UI will still let you *try* operations that *will* result in a denial. Signed-off-by: Gergely Nagy <forgejo@gergo.csillger.hu>
2024-07-06 08:30:16 +00:00
// "413":
// "$ref": "#/responses/quotaExceeded"
// "423":
// "$ref": "#/responses/repoArchivedError"
Move macaron to chi (#14293) Use [chi](https://github.com/go-chi/chi) instead of the forked [macaron](https://gitea.com/macaron/macaron). Since macaron and chi have conflicts with session share, this big PR becomes a have-to thing. According my previous idea, we can replace macaron step by step but I'm wrong. :( Below is a list of big changes on this PR. - [x] Define `context.ResponseWriter` interface with an implementation `context.Response`. - [x] Use chi instead of macaron, and also a customize `Route` to wrap chi so that the router usage is similar as before. - [x] Create different routers for `web`, `api`, `internal` and `install` so that the codes will be more clear and no magic . - [x] Use https://github.com/unrolled/render instead of macaron's internal render - [x] Use https://github.com/NYTimes/gziphandler instead of https://gitea.com/macaron/gzip - [x] Use https://gitea.com/go-chi/session which is a modified version of https://gitea.com/macaron/session and removed `nodb` support since it will not be maintained. **BREAK** - [x] Use https://gitea.com/go-chi/captcha which is a modified version of https://gitea.com/macaron/captcha - [x] Use https://gitea.com/go-chi/cache which is a modified version of https://gitea.com/macaron/cache - [x] Use https://gitea.com/go-chi/binding which is a modified version of https://gitea.com/macaron/binding - [x] Use https://github.com/go-chi/cors instead of https://gitea.com/macaron/cors - [x] Dropped https://gitea.com/macaron/i18n and make a new one in `code.gitea.io/gitea/modules/translation` - [x] Move validation form structs from `code.gitea.io/gitea/modules/auth` to `code.gitea.io/gitea/modules/forms` to avoid dependency cycle. - [x] Removed macaron log service because it's not need any more. **BREAK** - [x] All form structs have to be get by `web.GetForm(ctx)` in the route function but not as a function parameter on routes definition. - [x] Move Git HTTP protocol implementation to use routers directly. - [x] Fix the problem that chi routes don't support trailing slash but macaron did. - [x] `/api/v1/swagger` now will be redirect to `/api/swagger` but not render directly so that `APIContext` will not create a html render. Notices: - Chi router don't support request with trailing slash - Integration test `TestUserHeatmap` maybe mysql version related. It's failed on my macOS(mysql 5.7.29 installed via brew) but succeed on CI. Co-authored-by: 6543 <6543@obermui.de>
2021-01-26 15:36:53 +00:00
apiOpts := web.GetForm(ctx).(*api.DeleteFileOptions)
if !canWriteFiles(ctx, apiOpts.BranchName) {
ctx.Error(http.StatusForbidden, "DeleteFile", repo_model.ErrUserDoesNotHaveAccessToRepo{
UserID: ctx.Doer.ID,
RepoName: ctx.Repo.Repository.LowerName,
})
return
}
if apiOpts.BranchName == "" {
apiOpts.BranchName = ctx.Repo.Repository.DefaultBranch
}
opts := &files_service.ChangeRepoFilesOptions{
Files: []*files_service.ChangeRepoFile{
{
Operation: "delete",
SHA: apiOpts.SHA,
TreePath: ctx.Params("*"),
},
},
Message: apiOpts.Message,
OldBranch: apiOpts.BranchName,
NewBranch: apiOpts.NewBranchName,
Committer: &files_service.IdentityOptions{
Name: apiOpts.Committer.Name,
Email: apiOpts.Committer.Email,
},
Author: &files_service.IdentityOptions{
Name: apiOpts.Author.Name,
Email: apiOpts.Author.Email,
},
Dates: &files_service.CommitDateOptions{
Author: apiOpts.Dates.Author,
Committer: apiOpts.Dates.Committer,
},
Signoff: apiOpts.Signoff,
}
if opts.Dates.Author.IsZero() {
opts.Dates.Author = time.Now()
}
if opts.Dates.Committer.IsZero() {
opts.Dates.Committer = time.Now()
}
if opts.Message == "" {
opts.Message = changeFilesCommitMessage(ctx, opts.Files)
}
if filesResponse, err := files_service.ChangeRepoFiles(ctx, ctx.Repo.Repository, ctx.Doer, opts); err != nil {
if git.IsErrBranchNotExist(err) || models.IsErrRepoFileDoesNotExist(err) || git.IsErrNotExist(err) {
ctx.Error(http.StatusNotFound, "DeleteFile", err)
return
} else if git_model.IsErrBranchAlreadyExists(err) ||
models.IsErrFilenameInvalid(err) ||
models.IsErrSHADoesNotMatch(err) ||
models.IsErrCommitIDDoesNotMatch(err) ||
models.IsErrSHAOrCommitIDNotProvided(err) {
ctx.Error(http.StatusBadRequest, "DeleteFile", err)
return
} else if models.IsErrUserCannotCommit(err) {
ctx.Error(http.StatusForbidden, "DeleteFile", err)
return
}
ctx.Error(http.StatusInternalServerError, "DeleteFile", err)
} else {
fileResponse := files_service.GetFileResponseFromFilesResponse(filesResponse, 0)
ctx.JSON(http.StatusOK, fileResponse) // FIXME on APIv2: return http.StatusNoContent
}
}
// GetContents Get the metadata and contents (if a file) of an entry in a repository, or a list of entries if a dir
func GetContents(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/contents/{filepath} repository repoGetContents
// ---
// summary: Gets the metadata and contents (if a file) of an entry in a repository, or a list of entries if a dir
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: owner of the repo
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo
// type: string
// required: true
// - name: filepath
// in: path
// description: path of the dir, file, symlink or submodule in the repo
// type: string
// required: true
// - name: ref
// in: query
// description: "The name of the commit/branch/tag. Default the repositorys default branch (usually master)"
// type: string
// required: false
// responses:
// "200":
// "$ref": "#/responses/ContentsResponse"
// "404":
// "$ref": "#/responses/notFound"
if !canReadFiles(ctx.Repo) {
ctx.Error(http.StatusInternalServerError, "GetContentsOrList", repo_model.ErrUserDoesNotHaveAccessToRepo{
UserID: ctx.Doer.ID,
RepoName: ctx.Repo.Repository.LowerName,
})
return
}
treePath := ctx.Params("*")
ref := ctx.FormTrim("ref")
if fileList, err := files_service.GetContentsOrList(ctx, ctx.Repo.Repository, treePath, ref); err != nil {
if git.IsErrNotExist(err) {
ctx.NotFound("GetContentsOrList", err)
return
}
ctx.Error(http.StatusInternalServerError, "GetContentsOrList", err)
} else {
ctx.JSON(http.StatusOK, fileList)
}
2016-08-30 23:18:40 +00:00
}
// GetContentsList Get the metadata of all the entries of the root dir
func GetContentsList(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/contents repository repoGetContentsList
// ---
// summary: Gets the metadata of all the entries of the root dir
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: owner of the repo
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo
// type: string
// required: true
// - name: ref
// in: query
// description: "The name of the commit/branch/tag. Default the repositorys default branch (usually master)"
// type: string
// required: false
// responses:
// "200":
// "$ref": "#/responses/ContentsListResponse"
// "404":
// "$ref": "#/responses/notFound"
// same as GetContents(), this function is here because swagger fails if path is empty in GetContents() interface
GetContents(ctx)
}