Attachments: Add extension support, allow all types for releases (#12465)
* Attachments: Add extension support, allow all types for releases - Add support for file extensions, matching the `accept` attribute of `<input type="file">` - Add support for type wildcard mime types, e.g. `image/*` - Create repository.release.ALLOWED_TYPES setting (default unrestricted) - Change default for attachment.ALLOWED_TYPES to a list of extensions - Split out POST /attachments into two endpoints for issue/pr and releases to prevent circumvention of allowed types check Fixes: https://github.com/go-gitea/gitea/pull/10172 Fixes: https://github.com/go-gitea/gitea/issues/7266 Fixes: https://github.com/go-gitea/gitea/pull/12460 Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#Unique_file_type_specifiers * rename function * extract GET routes out of RepoMustNotBeArchived Co-authored-by: Lauris BH <lauris@nix.lv>
This commit is contained in:
parent
67a5573310
commit
cda44750cb
|
@ -88,7 +88,7 @@ LOCAL_COPY_PATH = tmp/local-repo
|
||||||
ENABLED = true
|
ENABLED = true
|
||||||
; Path for uploads. Defaults to `data/tmp/uploads` (tmp gets deleted on gitea restart)
|
; Path for uploads. Defaults to `data/tmp/uploads` (tmp gets deleted on gitea restart)
|
||||||
TEMP_PATH = data/tmp/uploads
|
TEMP_PATH = data/tmp/uploads
|
||||||
; One or more allowed types, e.g. image/jpeg|image/png. Nothing means any file type
|
; Comma-separated list of allowed file extensions (`.zip`), mime types (`text/plain`) or wildcard type (`image/*`, `audio/*`, `video/*`). Empty value or `*/*` allows all types.
|
||||||
ALLOWED_TYPES =
|
ALLOWED_TYPES =
|
||||||
; Max size of each file in megabytes. Defaults to 3MB
|
; Max size of each file in megabytes. Defaults to 3MB
|
||||||
FILE_MAX_SIZE = 3
|
FILE_MAX_SIZE = 3
|
||||||
|
@ -117,6 +117,10 @@ DEFAULT_MERGE_MESSAGE_OFFICIAL_APPROVERS_ONLY=true
|
||||||
; List of reasons why a Pull Request or Issue can be locked
|
; List of reasons why a Pull Request or Issue can be locked
|
||||||
LOCK_REASONS=Too heated,Off-topic,Resolved,Spam
|
LOCK_REASONS=Too heated,Off-topic,Resolved,Spam
|
||||||
|
|
||||||
|
[repository.release]
|
||||||
|
; Comma-separated list of allowed file extensions (`.zip`), mime types (`text/plain`) or wildcard type (`image/*`, `audio/*`, `video/*`). Empty value or `*/*` allows all types.
|
||||||
|
ALLOWED_TYPES =
|
||||||
|
|
||||||
[repository.signing]
|
[repository.signing]
|
||||||
; GPG key to use to sign commits, Defaults to the default - that is the value of git config --get user.signingkey
|
; GPG key to use to sign commits, Defaults to the default - that is the value of git config --get user.signingkey
|
||||||
; run in the context of the RUN_USER
|
; run in the context of the RUN_USER
|
||||||
|
@ -766,11 +770,10 @@ DISABLE_GRAVATAR = false
|
||||||
ENABLE_FEDERATED_AVATAR = false
|
ENABLE_FEDERATED_AVATAR = false
|
||||||
|
|
||||||
[attachment]
|
[attachment]
|
||||||
; Whether attachments are enabled. Defaults to `true`
|
; Whether issue and pull request attachments are enabled. Defaults to `true`
|
||||||
ENABLED = true
|
ENABLED = true
|
||||||
|
; Comma-separated list of allowed file extensions (`.zip`), mime types (`text/plain`) or wildcard type (`image/*`, `audio/*`, `video/*`). Empty value or `*/*` allows all types.
|
||||||
; One or more allowed types, e.g. "image/jpeg|image/png". Use "*/*" for all types.
|
ALLOWED_TYPES = .docx,.gif,.gz,.jpeg,.jpg,.log,.pdf,.png,.pptx,.txt,.xlsx,.zip
|
||||||
ALLOWED_TYPES = image/jpeg|image/png|application/zip|application/gzip
|
|
||||||
; Max size of each file. Defaults to 4MB
|
; Max size of each file. Defaults to 4MB
|
||||||
MAX_SIZE = 4
|
MAX_SIZE = 4
|
||||||
; Max number of files per upload. Defaults to 5
|
; Max number of files per upload. Defaults to 5
|
||||||
|
|
|
@ -101,6 +101,18 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
|
||||||
|
|
||||||
- `LOCK_REASONS`: **Too heated,Off-topic,Resolved,Spam**: A list of reasons why a Pull Request or Issue can be locked
|
- `LOCK_REASONS`: **Too heated,Off-topic,Resolved,Spam**: A list of reasons why a Pull Request or Issue can be locked
|
||||||
|
|
||||||
|
### Repository - Upload (`repository.upload`)
|
||||||
|
|
||||||
|
- `ENABLED`: **true**: Whether repository file uploads are enabled
|
||||||
|
- `TEMP_PATH`: **data/tmp/uploads**: Path for uploads (tmp gets deleted on gitea restart)
|
||||||
|
- `ALLOWED_TYPES`: **\<empty\>**: Comma-separated list of allowed file extensions (`.zip`), mime types (`text/plain`) or wildcard type (`image/*`, `audio/*`, `video/*`). Empty value or `*/*` allows all types.
|
||||||
|
- `FILE_MAX_SIZE`: **3**: Max size of each file in megabytes.
|
||||||
|
- `MAX_FILES`: **5**: Max number of files per upload
|
||||||
|
|
||||||
|
### Repository - Release (`repository.release`)
|
||||||
|
|
||||||
|
- `ALLOWED_TYPES`: **\<empty\>**: Comma-separated list of allowed file extensions (`.zip`), mime types (`text/plain`) or wildcard type (`image/*`, `audio/*`, `video/*`). Empty value or `*/*` allows all types.
|
||||||
|
|
||||||
### Repository - Signing (`repository.signing`)
|
### Repository - Signing (`repository.signing`)
|
||||||
|
|
||||||
- `SIGNING_KEY`: **default**: \[none, KEYID, default \]: Key to sign with.
|
- `SIGNING_KEY`: **default**: \[none, KEYID, default \]: Key to sign with.
|
||||||
|
@ -560,11 +572,10 @@ Default templates for project boards:
|
||||||
- `PROJECT_BOARD_BASIC_KANBAN_TYPE`: **To Do, In Progress, Done**
|
- `PROJECT_BOARD_BASIC_KANBAN_TYPE`: **To Do, In Progress, Done**
|
||||||
- `PROJECT_BOARD_BUG_TRIAGE_TYPE`: **Needs Triage, High Priority, Low Priority, Closed**
|
- `PROJECT_BOARD_BUG_TRIAGE_TYPE`: **Needs Triage, High Priority, Low Priority, Closed**
|
||||||
|
|
||||||
## Attachment (`attachment`)
|
## Issue and pull request attachments (`attachment`)
|
||||||
|
|
||||||
- `ENABLED`: **true**: Enable this to allow uploading attachments.
|
- `ENABLED`: **true**: Whether issue and pull request attachments are enabled.
|
||||||
- `ALLOWED_TYPES`: **see app.example.ini**: Allowed MIME types, e.g. `image/jpeg|image/png`.
|
- `ALLOWED_TYPES`: **.docx,.gif,.gz,.jpeg,.jpg,.log,.pdf,.png,.pptx,.txt,.xlsx,.zip**: Comma-separated list of allowed file extensions (`.zip`), mime types (`text/plain`) or wildcard type (`image/*`, `audio/*`, `video/*`). Empty value or `*/*` allows all types.
|
||||||
Use `*/*` for all types.
|
|
||||||
- `MAX_SIZE`: **4**: Maximum size (MB).
|
- `MAX_SIZE`: **4**: Maximum size (MB).
|
||||||
- `MAX_FILES`: **5**: Maximum number of attachments that can be uploaded at once.
|
- `MAX_FILES`: **5**: Maximum number of attachments that can be uploaded at once.
|
||||||
- `STORAGE_TYPE`: **local**: Storage type for attachments, `local` for local disk or `minio` for s3 compatible object storage service, default is `local` or other name defined with `[storage.xxx]`
|
- `STORAGE_TYPE`: **local**: Storage type for attachments, `local` for local disk or `minio` for s3 compatible object storage service, default is `local` or other name defined with `[storage.xxx]`
|
||||||
|
|
|
@ -43,7 +43,7 @@ func createAttachment(t *testing.T, session *TestSession, repoURL, filename stri
|
||||||
|
|
||||||
csrf := GetCSRF(t, session, repoURL)
|
csrf := GetCSRF(t, session, repoURL)
|
||||||
|
|
||||||
req := NewRequestWithBody(t, "POST", "/attachments", body)
|
req := NewRequestWithBody(t, "POST", repoURL+"/issues/attachments", body)
|
||||||
req.Header.Add("X-Csrf-Token", csrf)
|
req.Header.Add("X-Csrf-Token", csrf)
|
||||||
req.Header.Add("Content-Type", writer.FormDataContentType())
|
req.Header.Add("Content-Type", writer.FormDataContentType())
|
||||||
resp := session.MakeRequest(t, req, expectedStatus)
|
resp := session.MakeRequest(t, req, expectedStatus)
|
||||||
|
|
|
@ -5,18 +5,14 @@
|
||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/aes"
|
|
||||||
"crypto/cipher"
|
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"crypto/rand"
|
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"crypto/subtle"
|
"crypto/subtle"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/generate"
|
"code.gitea.io/gitea/modules/generate"
|
||||||
|
"code.gitea.io/gitea/modules/secret"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
|
|
||||||
|
@ -67,8 +63,8 @@ func (t *TwoFactor) getEncryptionKey() []byte {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetSecret sets the 2FA secret.
|
// SetSecret sets the 2FA secret.
|
||||||
func (t *TwoFactor) SetSecret(secret string) error {
|
func (t *TwoFactor) SetSecret(secretString string) error {
|
||||||
secretBytes, err := aesEncrypt(t.getEncryptionKey(), []byte(secret))
|
secretBytes, err := secret.AesEncrypt(t.getEncryptionKey(), []byte(secretString))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -82,51 +78,14 @@ func (t *TwoFactor) ValidateTOTP(passcode string) (bool, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
secret, err := aesDecrypt(t.getEncryptionKey(), decodedStoredSecret)
|
secretBytes, err := secret.AesDecrypt(t.getEncryptionKey(), decodedStoredSecret)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
secretStr := string(secret)
|
secretStr := string(secretBytes)
|
||||||
return totp.Validate(passcode, secretStr), nil
|
return totp.Validate(passcode, secretStr), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// aesEncrypt encrypts text and given key with AES.
|
|
||||||
func aesEncrypt(key, text []byte) ([]byte, error) {
|
|
||||||
block, err := aes.NewCipher(key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
b := base64.StdEncoding.EncodeToString(text)
|
|
||||||
ciphertext := make([]byte, aes.BlockSize+len(b))
|
|
||||||
iv := ciphertext[:aes.BlockSize]
|
|
||||||
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
cfb := cipher.NewCFBEncrypter(block, iv)
|
|
||||||
cfb.XORKeyStream(ciphertext[aes.BlockSize:], []byte(b))
|
|
||||||
return ciphertext, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// aesDecrypt decrypts text and given key with AES.
|
|
||||||
func aesDecrypt(key, text []byte) ([]byte, error) {
|
|
||||||
block, err := aes.NewCipher(key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(text) < aes.BlockSize {
|
|
||||||
return nil, errors.New("ciphertext too short")
|
|
||||||
}
|
|
||||||
iv := text[:aes.BlockSize]
|
|
||||||
text = text[aes.BlockSize:]
|
|
||||||
cfb := cipher.NewCFBDecrypter(block, iv)
|
|
||||||
cfb.XORKeyStream(text, text)
|
|
||||||
data, err := base64.StdEncoding.DecodeString(string(text))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewTwoFactor creates a new two-factor authentication token.
|
// NewTwoFactor creates a new two-factor authentication token.
|
||||||
func NewTwoFactor(t *TwoFactor) error {
|
func NewTwoFactor(t *TwoFactor) error {
|
||||||
_, err := x.Insert(t)
|
_, err := x.Insert(t)
|
||||||
|
|
|
@ -5,8 +5,14 @@
|
||||||
package secret
|
package secret
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
"crypto/sha256"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
// New creats a new secret
|
// New creats a new secret
|
||||||
|
@ -31,3 +37,65 @@ func randomString(len int64) (string, error) {
|
||||||
b, err := randomBytes(len)
|
b, err := randomBytes(len)
|
||||||
return base64.URLEncoding.EncodeToString(b), err
|
return base64.URLEncoding.EncodeToString(b), err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AesEncrypt encrypts text and given key with AES.
|
||||||
|
func AesEncrypt(key, text []byte) ([]byte, error) {
|
||||||
|
block, err := aes.NewCipher(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
b := base64.StdEncoding.EncodeToString(text)
|
||||||
|
ciphertext := make([]byte, aes.BlockSize+len(b))
|
||||||
|
iv := ciphertext[:aes.BlockSize]
|
||||||
|
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cfb := cipher.NewCFBEncrypter(block, iv)
|
||||||
|
cfb.XORKeyStream(ciphertext[aes.BlockSize:], []byte(b))
|
||||||
|
return ciphertext, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AesDecrypt decrypts text and given key with AES.
|
||||||
|
func AesDecrypt(key, text []byte) ([]byte, error) {
|
||||||
|
block, err := aes.NewCipher(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(text) < aes.BlockSize {
|
||||||
|
return nil, errors.New("ciphertext too short")
|
||||||
|
}
|
||||||
|
iv := text[:aes.BlockSize]
|
||||||
|
text = text[aes.BlockSize:]
|
||||||
|
cfb := cipher.NewCFBDecrypter(block, iv)
|
||||||
|
cfb.XORKeyStream(text, text)
|
||||||
|
data, err := base64.StdEncoding.DecodeString(string(text))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncryptSecret encrypts a string with given key into a hex string
|
||||||
|
func EncryptSecret(key string, str string) (string, error) {
|
||||||
|
keyHash := sha256.Sum256([]byte(key))
|
||||||
|
plaintext := []byte(str)
|
||||||
|
ciphertext, err := AesEncrypt(keyHash[:], plaintext)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return hex.EncodeToString(ciphertext), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecryptSecret decrypts a previously encrypted hex string
|
||||||
|
func DecryptSecret(key string, cipherhex string) (string, error) {
|
||||||
|
keyHash := sha256.Sum256([]byte(key))
|
||||||
|
ciphertext, err := hex.DecodeString(cipherhex)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
plaintext, err := AesDecrypt(keyHash[:], ciphertext)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(plaintext), nil
|
||||||
|
}
|
||||||
|
|
|
@ -20,3 +20,16 @@ func TestNew(t *testing.T) {
|
||||||
// check if secrets
|
// check if secrets
|
||||||
assert.NotEqual(t, result, result2)
|
assert.NotEqual(t, result, result2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEncryptDecrypt(t *testing.T) {
|
||||||
|
var hex string
|
||||||
|
var str string
|
||||||
|
|
||||||
|
hex, _ = EncryptSecret("foo", "baz")
|
||||||
|
str, _ = DecryptSecret("foo", hex)
|
||||||
|
assert.Equal(t, str, "baz")
|
||||||
|
|
||||||
|
hex, _ = EncryptSecret("bar", "baz")
|
||||||
|
str, _ = DecryptSecret("foo", hex)
|
||||||
|
assert.NotEqual(t, str, "baz")
|
||||||
|
}
|
||||||
|
|
|
@ -6,7 +6,6 @@ package setting
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
)
|
)
|
||||||
|
@ -65,7 +64,7 @@ func newAttachmentService() {
|
||||||
Attachment.Minio.BasePath = sec.Key("MINIO_BASE_PATH").MustString("attachments/")
|
Attachment.Minio.BasePath = sec.Key("MINIO_BASE_PATH").MustString("attachments/")
|
||||||
}
|
}
|
||||||
|
|
||||||
Attachment.AllowedTypes = strings.Replace(sec.Key("ALLOWED_TYPES").MustString("image/jpeg,image/png,application/zip,application/gzip"), "|", ",", -1)
|
Attachment.AllowedTypes = sec.Key("ALLOWED_TYPES").MustString(".docx,.gif,.gz,.jpeg,.jpg,.log,.pdf,.png,.pptx,.txt,.xlsx,.zip")
|
||||||
Attachment.MaxSize = sec.Key("MAX_SIZE").MustInt64(4)
|
Attachment.MaxSize = sec.Key("MAX_SIZE").MustInt64(4)
|
||||||
Attachment.MaxFiles = sec.Key("MAX_FILES").MustInt(5)
|
Attachment.MaxFiles = sec.Key("MAX_FILES").MustInt(5)
|
||||||
Attachment.Enabled = sec.Key("ENABLED").MustBool(true)
|
Attachment.Enabled = sec.Key("ENABLED").MustBool(true)
|
||||||
|
|
|
@ -58,7 +58,7 @@ var (
|
||||||
Upload struct {
|
Upload struct {
|
||||||
Enabled bool
|
Enabled bool
|
||||||
TempPath string
|
TempPath string
|
||||||
AllowedTypes []string `delim:"|"`
|
AllowedTypes string
|
||||||
FileMaxSize int64
|
FileMaxSize int64
|
||||||
MaxFiles int
|
MaxFiles int
|
||||||
} `ini:"-"`
|
} `ini:"-"`
|
||||||
|
@ -85,6 +85,10 @@ var (
|
||||||
LockReasons []string
|
LockReasons []string
|
||||||
} `ini:"repository.issue"`
|
} `ini:"repository.issue"`
|
||||||
|
|
||||||
|
Release struct {
|
||||||
|
AllowedTypes string
|
||||||
|
} `ini:"repository.release"`
|
||||||
|
|
||||||
Signing struct {
|
Signing struct {
|
||||||
SigningKey string
|
SigningKey string
|
||||||
SigningName string
|
SigningName string
|
||||||
|
@ -165,13 +169,13 @@ var (
|
||||||
Upload: struct {
|
Upload: struct {
|
||||||
Enabled bool
|
Enabled bool
|
||||||
TempPath string
|
TempPath string
|
||||||
AllowedTypes []string `delim:"|"`
|
AllowedTypes string
|
||||||
FileMaxSize int64
|
FileMaxSize int64
|
||||||
MaxFiles int
|
MaxFiles int
|
||||||
}{
|
}{
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
TempPath: "data/tmp/uploads",
|
TempPath: "data/tmp/uploads",
|
||||||
AllowedTypes: []string{},
|
AllowedTypes: "",
|
||||||
FileMaxSize: 3,
|
FileMaxSize: 3,
|
||||||
MaxFiles: 5,
|
MaxFiles: 5,
|
||||||
},
|
},
|
||||||
|
@ -213,6 +217,12 @@ var (
|
||||||
LockReasons: strings.Split("Too heated,Off-topic,Spam,Resolved", ","),
|
LockReasons: strings.Split("Too heated,Off-topic,Spam,Resolved", ","),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
Release: struct {
|
||||||
|
AllowedTypes string
|
||||||
|
}{
|
||||||
|
AllowedTypes: "",
|
||||||
|
},
|
||||||
|
|
||||||
// Signing settings
|
// Signing settings
|
||||||
Signing: struct {
|
Signing: struct {
|
||||||
SigningKey string
|
SigningKey string
|
||||||
|
|
|
@ -1,46 +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 upload
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ErrFileTypeForbidden not allowed file type error
|
|
||||||
type ErrFileTypeForbidden struct {
|
|
||||||
Type string
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsErrFileTypeForbidden checks if an error is a ErrFileTypeForbidden.
|
|
||||||
func IsErrFileTypeForbidden(err error) bool {
|
|
||||||
_, ok := err.(ErrFileTypeForbidden)
|
|
||||||
return ok
|
|
||||||
}
|
|
||||||
|
|
||||||
func (err ErrFileTypeForbidden) Error() string {
|
|
||||||
return fmt.Sprintf("File type is not allowed: %s", err.Type)
|
|
||||||
}
|
|
||||||
|
|
||||||
// VerifyAllowedContentType validates a file is allowed to be uploaded.
|
|
||||||
func VerifyAllowedContentType(buf []byte, allowedTypes []string) error {
|
|
||||||
fileType := http.DetectContentType(buf)
|
|
||||||
|
|
||||||
for _, t := range allowedTypes {
|
|
||||||
t := strings.Trim(t, " ")
|
|
||||||
|
|
||||||
if t == "*/*" || t == fileType ||
|
|
||||||
// Allow directives after type, like 'text/plain; charset=utf-8'
|
|
||||||
strings.HasPrefix(fileType, t+";") {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("Attachment with type %s blocked from upload", fileType)
|
|
||||||
return ErrFileTypeForbidden{Type: fileType}
|
|
||||||
}
|
|
|
@ -1,47 +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 upload
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"compress/gzip"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestUpload(t *testing.T) {
|
|
||||||
testContent := []byte(`This is a plain text file.`)
|
|
||||||
var b bytes.Buffer
|
|
||||||
w := gzip.NewWriter(&b)
|
|
||||||
w.Write(testContent)
|
|
||||||
w.Close()
|
|
||||||
|
|
||||||
kases := []struct {
|
|
||||||
data []byte
|
|
||||||
allowedTypes []string
|
|
||||||
err error
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
data: testContent,
|
|
||||||
allowedTypes: []string{"text/plain"},
|
|
||||||
err: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
data: testContent,
|
|
||||||
allowedTypes: []string{"application/x-gzip"},
|
|
||||||
err: ErrFileTypeForbidden{"text/plain; charset=utf-8"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
data: b.Bytes(),
|
|
||||||
allowedTypes: []string{"application/x-gzip"},
|
|
||||||
err: nil,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, kase := range kases {
|
|
||||||
assert.Equal(t, kase.err, VerifyAllowedContentType(kase.data, kase.allowedTypes))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,94 @@
|
||||||
|
// 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 upload
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"path"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/context"
|
||||||
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrFileTypeForbidden not allowed file type error
|
||||||
|
type ErrFileTypeForbidden struct {
|
||||||
|
Type string
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsErrFileTypeForbidden checks if an error is a ErrFileTypeForbidden.
|
||||||
|
func IsErrFileTypeForbidden(err error) bool {
|
||||||
|
_, ok := err.(ErrFileTypeForbidden)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err ErrFileTypeForbidden) Error() string {
|
||||||
|
return "This file extension or type is not allowed to be uploaded."
|
||||||
|
}
|
||||||
|
|
||||||
|
var mimeTypeSuffixRe = regexp.MustCompile(`;.*$`)
|
||||||
|
var wildcardTypeRe = regexp.MustCompile(`^[a-z]+/\*$`)
|
||||||
|
|
||||||
|
// Verify validates whether a file is allowed to be uploaded.
|
||||||
|
func Verify(buf []byte, fileName string, allowedTypesStr string) error {
|
||||||
|
allowedTypesStr = strings.ReplaceAll(allowedTypesStr, "|", ",") // compat for old config format
|
||||||
|
|
||||||
|
allowedTypes := []string{}
|
||||||
|
for _, entry := range strings.Split(allowedTypesStr, ",") {
|
||||||
|
entry = strings.ToLower(strings.TrimSpace(entry))
|
||||||
|
if entry != "" {
|
||||||
|
allowedTypes = append(allowedTypes, entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(allowedTypes) == 0 {
|
||||||
|
return nil // everything is allowed
|
||||||
|
}
|
||||||
|
|
||||||
|
fullMimeType := http.DetectContentType(buf)
|
||||||
|
mimeType := strings.TrimSpace(mimeTypeSuffixRe.ReplaceAllString(fullMimeType, ""))
|
||||||
|
extension := strings.ToLower(path.Ext(fileName))
|
||||||
|
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#Unique_file_type_specifiers
|
||||||
|
for _, allowEntry := range allowedTypes {
|
||||||
|
if allowEntry == "*/*" {
|
||||||
|
return nil // everything allowed
|
||||||
|
} else if strings.HasPrefix(allowEntry, ".") && allowEntry == extension {
|
||||||
|
return nil // extension is allowed
|
||||||
|
} else if mimeType == allowEntry {
|
||||||
|
return nil // mime type is allowed
|
||||||
|
} else if wildcardTypeRe.MatchString(allowEntry) && strings.HasPrefix(mimeType, allowEntry[:len(allowEntry)-1]) {
|
||||||
|
return nil // wildcard match, e.g. image/*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Attachment with type %s blocked from upload", fullMimeType)
|
||||||
|
return ErrFileTypeForbidden{Type: fullMimeType}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddUploadContext renders template values for dropzone
|
||||||
|
func AddUploadContext(ctx *context.Context, uploadType string) {
|
||||||
|
if uploadType == "release" {
|
||||||
|
ctx.Data["UploadUrl"] = ctx.Repo.RepoLink + "/releases/attachments"
|
||||||
|
ctx.Data["UploadRemoveUrl"] = ctx.Repo.RepoLink + "/releases/attachments/remove"
|
||||||
|
ctx.Data["UploadAccepts"] = strings.Replace(setting.Repository.Release.AllowedTypes, "|", ",", -1)
|
||||||
|
ctx.Data["UploadMaxFiles"] = setting.Attachment.MaxFiles
|
||||||
|
ctx.Data["UploadMaxSize"] = setting.Attachment.MaxSize
|
||||||
|
} else if uploadType == "comment" {
|
||||||
|
ctx.Data["UploadUrl"] = ctx.Repo.RepoLink + "/issues/attachments"
|
||||||
|
ctx.Data["UploadRemoveUrl"] = ctx.Repo.RepoLink + "/issues/attachments/remove"
|
||||||
|
ctx.Data["UploadAccepts"] = strings.Replace(setting.Attachment.AllowedTypes, "|", ",", -1)
|
||||||
|
ctx.Data["UploadMaxFiles"] = setting.Attachment.MaxFiles
|
||||||
|
ctx.Data["UploadMaxSize"] = setting.Attachment.MaxSize
|
||||||
|
} else if uploadType == "repo" {
|
||||||
|
ctx.Data["UploadUrl"] = ctx.Repo.RepoLink + "/upload-file"
|
||||||
|
ctx.Data["UploadRemoveUrl"] = ctx.Repo.RepoLink + "/upload-remove"
|
||||||
|
ctx.Data["UploadAccepts"] = strings.Replace(setting.Repository.Upload.AllowedTypes, "|", ",", -1)
|
||||||
|
ctx.Data["UploadMaxFiles"] = setting.Repository.Upload.MaxFiles
|
||||||
|
ctx.Data["UploadMaxSize"] = setting.Repository.Upload.FileMaxSize
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,195 @@
|
||||||
|
// 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 upload
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"compress/gzip"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUpload(t *testing.T) {
|
||||||
|
testContent := []byte(`This is a plain text file.`)
|
||||||
|
var b bytes.Buffer
|
||||||
|
w := gzip.NewWriter(&b)
|
||||||
|
w.Write(testContent)
|
||||||
|
w.Close()
|
||||||
|
|
||||||
|
kases := []struct {
|
||||||
|
data []byte
|
||||||
|
fileName string
|
||||||
|
allowedTypes string
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
data: testContent,
|
||||||
|
fileName: "test.txt",
|
||||||
|
allowedTypes: "",
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: testContent,
|
||||||
|
fileName: "dir/test.txt",
|
||||||
|
allowedTypes: "",
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: testContent,
|
||||||
|
fileName: "../../../test.txt",
|
||||||
|
allowedTypes: "",
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: testContent,
|
||||||
|
fileName: "test.txt",
|
||||||
|
allowedTypes: "",
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: testContent,
|
||||||
|
fileName: "test.txt",
|
||||||
|
allowedTypes: ",",
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: testContent,
|
||||||
|
fileName: "test.txt",
|
||||||
|
allowedTypes: "|",
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: testContent,
|
||||||
|
fileName: "test.txt",
|
||||||
|
allowedTypes: "*/*",
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: testContent,
|
||||||
|
fileName: "test.txt",
|
||||||
|
allowedTypes: "*/*,",
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: testContent,
|
||||||
|
fileName: "test.txt",
|
||||||
|
allowedTypes: "*/*|",
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: testContent,
|
||||||
|
fileName: "test.txt",
|
||||||
|
allowedTypes: "text/plain",
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: testContent,
|
||||||
|
fileName: "dir/test.txt",
|
||||||
|
allowedTypes: "text/plain",
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: testContent,
|
||||||
|
fileName: "/dir.txt/test.js",
|
||||||
|
allowedTypes: ".js",
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: testContent,
|
||||||
|
fileName: "test.txt",
|
||||||
|
allowedTypes: " text/plain ",
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: testContent,
|
||||||
|
fileName: "test.txt",
|
||||||
|
allowedTypes: ".txt",
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: testContent,
|
||||||
|
fileName: "test.txt",
|
||||||
|
allowedTypes: " .txt,.js",
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: testContent,
|
||||||
|
fileName: "test.txt",
|
||||||
|
allowedTypes: " .txt|.js",
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: testContent,
|
||||||
|
fileName: "../../test.txt",
|
||||||
|
allowedTypes: " .txt|.js",
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: testContent,
|
||||||
|
fileName: "test.txt",
|
||||||
|
allowedTypes: " .txt ,.js ",
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: testContent,
|
||||||
|
fileName: "test.txt",
|
||||||
|
allowedTypes: "text/plain, .txt",
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: testContent,
|
||||||
|
fileName: "test.txt",
|
||||||
|
allowedTypes: "text/*",
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: testContent,
|
||||||
|
fileName: "test.txt",
|
||||||
|
allowedTypes: "text/*,.js",
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: testContent,
|
||||||
|
fileName: "test.txt",
|
||||||
|
allowedTypes: "text/**",
|
||||||
|
err: ErrFileTypeForbidden{"text/plain; charset=utf-8"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: testContent,
|
||||||
|
fileName: "test.txt",
|
||||||
|
allowedTypes: "application/x-gzip",
|
||||||
|
err: ErrFileTypeForbidden{"text/plain; charset=utf-8"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: testContent,
|
||||||
|
fileName: "test.txt",
|
||||||
|
allowedTypes: ".zip",
|
||||||
|
err: ErrFileTypeForbidden{"text/plain; charset=utf-8"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: testContent,
|
||||||
|
fileName: "test.txt",
|
||||||
|
allowedTypes: ".zip,.txtx",
|
||||||
|
err: ErrFileTypeForbidden{"text/plain; charset=utf-8"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: testContent,
|
||||||
|
fileName: "test.txt",
|
||||||
|
allowedTypes: ".zip|.txtx",
|
||||||
|
err: ErrFileTypeForbidden{"text/plain; charset=utf-8"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: b.Bytes(),
|
||||||
|
fileName: "test.txt",
|
||||||
|
allowedTypes: "application/x-gzip",
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, kase := range kases {
|
||||||
|
assert.Equal(t, kase.err, Verify(kase.data, kase.fileName, kase.allowedTypes))
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,7 +6,6 @@ package repo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/models"
|
"code.gitea.io/gitea/models"
|
||||||
"code.gitea.io/gitea/modules/context"
|
"code.gitea.io/gitea/modules/context"
|
||||||
|
@ -182,7 +181,7 @@ func CreateReleaseAttachment(ctx *context.APIContext) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the filetype is allowed by the settings
|
// Check if the filetype is allowed by the settings
|
||||||
err = upload.VerifyAllowedContentType(buf, strings.Split(setting.Attachment.AllowedTypes, ","))
|
err = upload.Verify(buf, header.Filename, setting.Repository.Release.AllowedTypes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(http.StatusBadRequest, "DetectContentType", err)
|
ctx.Error(http.StatusBadRequest, "DetectContentType", err)
|
||||||
return
|
return
|
||||||
|
|
|
@ -7,7 +7,6 @@ package repo
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/models"
|
"code.gitea.io/gitea/models"
|
||||||
"code.gitea.io/gitea/modules/context"
|
"code.gitea.io/gitea/modules/context"
|
||||||
|
@ -17,16 +16,18 @@ import (
|
||||||
"code.gitea.io/gitea/modules/upload"
|
"code.gitea.io/gitea/modules/upload"
|
||||||
)
|
)
|
||||||
|
|
||||||
func renderAttachmentSettings(ctx *context.Context) {
|
// UploadIssueAttachment response for Issue/PR attachments
|
||||||
ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled
|
func UploadIssueAttachment(ctx *context.Context) {
|
||||||
ctx.Data["AttachmentStoreType"] = setting.Attachment.Storage.Type
|
uploadAttachment(ctx, setting.Attachment.AllowedTypes)
|
||||||
ctx.Data["AttachmentAllowedTypes"] = setting.Attachment.AllowedTypes
|
|
||||||
ctx.Data["AttachmentMaxSize"] = setting.Attachment.MaxSize
|
|
||||||
ctx.Data["AttachmentMaxFiles"] = setting.Attachment.MaxFiles
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UploadAttachment response for uploading issue's attachment
|
// UploadReleaseAttachment response for uploading release attachments
|
||||||
func UploadAttachment(ctx *context.Context) {
|
func UploadReleaseAttachment(ctx *context.Context) {
|
||||||
|
uploadAttachment(ctx, setting.Repository.Release.AllowedTypes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UploadAttachment response for uploading attachments
|
||||||
|
func uploadAttachment(ctx *context.Context, allowedTypes string) {
|
||||||
if !setting.Attachment.Enabled {
|
if !setting.Attachment.Enabled {
|
||||||
ctx.Error(404, "attachment is not enabled")
|
ctx.Error(404, "attachment is not enabled")
|
||||||
return
|
return
|
||||||
|
@ -45,7 +46,7 @@ func UploadAttachment(ctx *context.Context) {
|
||||||
buf = buf[:n]
|
buf = buf[:n]
|
||||||
}
|
}
|
||||||
|
|
||||||
err = upload.VerifyAllowedContentType(buf, strings.Split(setting.Attachment.AllowedTypes, ","))
|
err = upload.Verify(buf, header.Filename, allowedTypes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(400, err.Error())
|
ctx.Error(400, err.Error())
|
||||||
return
|
return
|
||||||
|
|
|
@ -17,6 +17,7 @@ import (
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/upload"
|
||||||
"code.gitea.io/gitea/services/gitdiff"
|
"code.gitea.io/gitea/services/gitdiff"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -578,7 +579,8 @@ func CompareDiff(ctx *context.Context) {
|
||||||
ctx.Data["RequireSimpleMDE"] = true
|
ctx.Data["RequireSimpleMDE"] = true
|
||||||
ctx.Data["PullRequestWorkInProgressPrefixes"] = setting.Repository.PullRequest.WorkInProgressPrefixes
|
ctx.Data["PullRequestWorkInProgressPrefixes"] = setting.Repository.PullRequest.WorkInProgressPrefixes
|
||||||
setTemplateIfExists(ctx, pullRequestTemplateKey, nil, pullRequestTemplateCandidates)
|
setTemplateIfExists(ctx, pullRequestTemplateKey, nil, pullRequestTemplateCandidates)
|
||||||
renderAttachmentSettings(ctx)
|
ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled
|
||||||
|
upload.AddUploadContext(ctx, "comment")
|
||||||
|
|
||||||
ctx.Data["HasIssuesOrPullsWritePermission"] = ctx.Repo.CanWrite(models.UnitTypePullRequests)
|
ctx.Data["HasIssuesOrPullsWritePermission"] = ctx.Repo.CanWrite(models.UnitTypePullRequests)
|
||||||
|
|
||||||
|
|
|
@ -494,18 +494,12 @@ func DeleteFilePost(ctx *context.Context, form auth.DeleteRepoFileForm) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderUploadSettings(ctx *context.Context) {
|
|
||||||
ctx.Data["RequireTribute"] = true
|
|
||||||
ctx.Data["RequireSimpleMDE"] = true
|
|
||||||
ctx.Data["UploadAllowedTypes"] = strings.Join(setting.Repository.Upload.AllowedTypes, ",")
|
|
||||||
ctx.Data["UploadMaxSize"] = setting.Repository.Upload.FileMaxSize
|
|
||||||
ctx.Data["UploadMaxFiles"] = setting.Repository.Upload.MaxFiles
|
|
||||||
}
|
|
||||||
|
|
||||||
// UploadFile render upload file page
|
// UploadFile render upload file page
|
||||||
func UploadFile(ctx *context.Context) {
|
func UploadFile(ctx *context.Context) {
|
||||||
ctx.Data["PageIsUpload"] = true
|
ctx.Data["PageIsUpload"] = true
|
||||||
renderUploadSettings(ctx)
|
ctx.Data["RequireTribute"] = true
|
||||||
|
ctx.Data["RequireSimpleMDE"] = true
|
||||||
|
upload.AddUploadContext(ctx, "repo")
|
||||||
canCommit := renderCommitRights(ctx)
|
canCommit := renderCommitRights(ctx)
|
||||||
treePath := cleanUploadFileName(ctx.Repo.TreePath)
|
treePath := cleanUploadFileName(ctx.Repo.TreePath)
|
||||||
if treePath != ctx.Repo.TreePath {
|
if treePath != ctx.Repo.TreePath {
|
||||||
|
@ -538,7 +532,9 @@ func UploadFile(ctx *context.Context) {
|
||||||
// UploadFilePost response for uploading file
|
// UploadFilePost response for uploading file
|
||||||
func UploadFilePost(ctx *context.Context, form auth.UploadRepoFileForm) {
|
func UploadFilePost(ctx *context.Context, form auth.UploadRepoFileForm) {
|
||||||
ctx.Data["PageIsUpload"] = true
|
ctx.Data["PageIsUpload"] = true
|
||||||
renderUploadSettings(ctx)
|
ctx.Data["RequireTribute"] = true
|
||||||
|
ctx.Data["RequireSimpleMDE"] = true
|
||||||
|
upload.AddUploadContext(ctx, "repo")
|
||||||
canCommit := renderCommitRights(ctx)
|
canCommit := renderCommitRights(ctx)
|
||||||
|
|
||||||
oldBranchName := ctx.Repo.BranchName
|
oldBranchName := ctx.Repo.BranchName
|
||||||
|
@ -704,12 +700,10 @@ func UploadFileToServer(ctx *context.Context) {
|
||||||
buf = buf[:n]
|
buf = buf[:n]
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(setting.Repository.Upload.AllowedTypes) > 0 {
|
err = upload.Verify(buf, header.Filename, setting.Repository.Upload.AllowedTypes)
|
||||||
err = upload.VerifyAllowedContentType(buf, setting.Repository.Upload.AllowedTypes)
|
if err != nil {
|
||||||
if err != nil {
|
ctx.Error(400, err.Error())
|
||||||
ctx.Error(400, err.Error())
|
return
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
name := cleanUploadFileName(header.Filename)
|
name := cleanUploadFileName(header.Filename)
|
||||||
|
|
|
@ -26,6 +26,7 @@ import (
|
||||||
"code.gitea.io/gitea/modules/markup/markdown"
|
"code.gitea.io/gitea/modules/markup/markdown"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
|
"code.gitea.io/gitea/modules/upload"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
comment_service "code.gitea.io/gitea/services/comments"
|
comment_service "code.gitea.io/gitea/services/comments"
|
||||||
issue_service "code.gitea.io/gitea/services/issue"
|
issue_service "code.gitea.io/gitea/services/issue"
|
||||||
|
@ -573,6 +574,8 @@ func NewIssue(ctx *context.Context) {
|
||||||
body := ctx.Query("body")
|
body := ctx.Query("body")
|
||||||
ctx.Data["BodyQuery"] = body
|
ctx.Data["BodyQuery"] = body
|
||||||
ctx.Data["IsProjectsEnabled"] = ctx.Repo.CanRead(models.UnitTypeProjects)
|
ctx.Data["IsProjectsEnabled"] = ctx.Repo.CanRead(models.UnitTypeProjects)
|
||||||
|
ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled
|
||||||
|
upload.AddUploadContext(ctx, "comment")
|
||||||
|
|
||||||
milestoneID := ctx.QueryInt64("milestone")
|
milestoneID := ctx.QueryInt64("milestone")
|
||||||
if milestoneID > 0 {
|
if milestoneID > 0 {
|
||||||
|
@ -599,8 +602,6 @@ func NewIssue(ctx *context.Context) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
renderAttachmentSettings(ctx)
|
|
||||||
|
|
||||||
RetrieveRepoMetas(ctx, ctx.Repo.Repository, false)
|
RetrieveRepoMetas(ctx, ctx.Repo.Repository, false)
|
||||||
setTemplateIfExists(ctx, issueTemplateKey, context.IssueTemplateDirCandidates, IssueTemplateCandidates)
|
setTemplateIfExists(ctx, issueTemplateKey, context.IssueTemplateDirCandidates, IssueTemplateCandidates)
|
||||||
if ctx.Written() {
|
if ctx.Written() {
|
||||||
|
@ -731,7 +732,8 @@ func NewIssuePost(ctx *context.Context, form auth.CreateIssueForm) {
|
||||||
ctx.Data["RequireSimpleMDE"] = true
|
ctx.Data["RequireSimpleMDE"] = true
|
||||||
ctx.Data["ReadOnly"] = false
|
ctx.Data["ReadOnly"] = false
|
||||||
ctx.Data["PullRequestWorkInProgressPrefixes"] = setting.Repository.PullRequest.WorkInProgressPrefixes
|
ctx.Data["PullRequestWorkInProgressPrefixes"] = setting.Repository.PullRequest.WorkInProgressPrefixes
|
||||||
renderAttachmentSettings(ctx)
|
ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled
|
||||||
|
upload.AddUploadContext(ctx, "comment")
|
||||||
|
|
||||||
var (
|
var (
|
||||||
repo = ctx.Repo.Repository
|
repo = ctx.Repo.Repository
|
||||||
|
@ -880,8 +882,8 @@ func ViewIssue(ctx *context.Context) {
|
||||||
ctx.Data["RequireTribute"] = true
|
ctx.Data["RequireTribute"] = true
|
||||||
ctx.Data["RequireSimpleMDE"] = true
|
ctx.Data["RequireSimpleMDE"] = true
|
||||||
ctx.Data["IsProjectsEnabled"] = ctx.Repo.CanRead(models.UnitTypeProjects)
|
ctx.Data["IsProjectsEnabled"] = ctx.Repo.CanRead(models.UnitTypeProjects)
|
||||||
|
ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled
|
||||||
renderAttachmentSettings(ctx)
|
upload.AddUploadContext(ctx, "comment")
|
||||||
|
|
||||||
if err = issue.LoadAttributes(); err != nil {
|
if err = issue.LoadAttributes(); err != nil {
|
||||||
ctx.ServerError("LoadAttributes", err)
|
ctx.ServerError("LoadAttributes", err)
|
||||||
|
|
|
@ -24,6 +24,7 @@ import (
|
||||||
"code.gitea.io/gitea/modules/notification"
|
"code.gitea.io/gitea/modules/notification"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/structs"
|
"code.gitea.io/gitea/modules/structs"
|
||||||
|
"code.gitea.io/gitea/modules/upload"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
"code.gitea.io/gitea/routers/utils"
|
"code.gitea.io/gitea/routers/utils"
|
||||||
"code.gitea.io/gitea/services/gitdiff"
|
"code.gitea.io/gitea/services/gitdiff"
|
||||||
|
@ -892,7 +893,8 @@ func CompareAndPullRequestPost(ctx *context.Context, form auth.CreateIssueForm)
|
||||||
ctx.Data["IsDiffCompare"] = true
|
ctx.Data["IsDiffCompare"] = true
|
||||||
ctx.Data["RequireHighlightJS"] = true
|
ctx.Data["RequireHighlightJS"] = true
|
||||||
ctx.Data["PullRequestWorkInProgressPrefixes"] = setting.Repository.PullRequest.WorkInProgressPrefixes
|
ctx.Data["PullRequestWorkInProgressPrefixes"] = setting.Repository.PullRequest.WorkInProgressPrefixes
|
||||||
renderAttachmentSettings(ctx)
|
ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled
|
||||||
|
upload.AddUploadContext(ctx, "comment")
|
||||||
|
|
||||||
var (
|
var (
|
||||||
repo = ctx.Repo.Repository
|
repo = ctx.Repo.Repository
|
||||||
|
|
|
@ -16,6 +16,7 @@ import (
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/markup/markdown"
|
"code.gitea.io/gitea/modules/markup/markdown"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/upload"
|
||||||
releaseservice "code.gitea.io/gitea/services/release"
|
releaseservice "code.gitea.io/gitea/services/release"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -192,7 +193,8 @@ func NewRelease(ctx *context.Context) {
|
||||||
ctx.Data["Title"] = ctx.Tr("repo.release.new_release")
|
ctx.Data["Title"] = ctx.Tr("repo.release.new_release")
|
||||||
ctx.Data["PageIsReleaseList"] = true
|
ctx.Data["PageIsReleaseList"] = true
|
||||||
ctx.Data["tag_target"] = ctx.Repo.Repository.DefaultBranch
|
ctx.Data["tag_target"] = ctx.Repo.Repository.DefaultBranch
|
||||||
renderAttachmentSettings(ctx)
|
ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled
|
||||||
|
upload.AddUploadContext(ctx, "release")
|
||||||
ctx.HTML(200, tplReleaseNew)
|
ctx.HTML(200, tplReleaseNew)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -278,7 +280,8 @@ func EditRelease(ctx *context.Context) {
|
||||||
ctx.Data["Title"] = ctx.Tr("repo.release.edit_release")
|
ctx.Data["Title"] = ctx.Tr("repo.release.edit_release")
|
||||||
ctx.Data["PageIsReleaseList"] = true
|
ctx.Data["PageIsReleaseList"] = true
|
||||||
ctx.Data["PageIsEditRelease"] = true
|
ctx.Data["PageIsEditRelease"] = true
|
||||||
renderAttachmentSettings(ctx)
|
ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled
|
||||||
|
upload.AddUploadContext(ctx, "release")
|
||||||
|
|
||||||
tagName := ctx.Params("*")
|
tagName := ctx.Params("*")
|
||||||
rel, err := models.GetRelease(ctx.Repo.Repository.ID, tagName)
|
rel, err := models.GetRelease(ctx.Repo.Repository.ID, tagName)
|
||||||
|
|
|
@ -512,11 +512,6 @@ func RegisterRoutes(m *macaron.Macaron) {
|
||||||
m.Get("/attachments/:uuid", repo.GetAttachment)
|
m.Get("/attachments/:uuid", repo.GetAttachment)
|
||||||
}, ignSignIn)
|
}, ignSignIn)
|
||||||
|
|
||||||
m.Group("/attachments", func() {
|
|
||||||
m.Post("", repo.UploadAttachment)
|
|
||||||
m.Post("/delete", repo.DeleteAttachment)
|
|
||||||
}, reqSignIn)
|
|
||||||
|
|
||||||
m.Group("/:username", func() {
|
m.Group("/:username", func() {
|
||||||
m.Post("/action/:action", user.Action)
|
m.Post("/action/:action", user.Action)
|
||||||
}, reqSignIn)
|
}, reqSignIn)
|
||||||
|
@ -754,8 +749,11 @@ func RegisterRoutes(m *macaron.Macaron) {
|
||||||
m.Post("/reactions/:action", bindIgnErr(auth.ReactionForm{}), repo.ChangeIssueReaction)
|
m.Post("/reactions/:action", bindIgnErr(auth.ReactionForm{}), repo.ChangeIssueReaction)
|
||||||
m.Post("/lock", reqRepoIssueWriter, bindIgnErr(auth.IssueLockForm{}), repo.LockIssue)
|
m.Post("/lock", reqRepoIssueWriter, bindIgnErr(auth.IssueLockForm{}), repo.LockIssue)
|
||||||
m.Post("/unlock", reqRepoIssueWriter, repo.UnlockIssue)
|
m.Post("/unlock", reqRepoIssueWriter, repo.UnlockIssue)
|
||||||
m.Get("/attachments", repo.GetIssueAttachments)
|
|
||||||
}, context.RepoMustNotBeArchived())
|
}, context.RepoMustNotBeArchived())
|
||||||
|
m.Group("/:index", func() {
|
||||||
|
m.Get("/attachments", repo.GetIssueAttachments)
|
||||||
|
m.Get("/attachments/:uuid", repo.GetAttachment)
|
||||||
|
})
|
||||||
|
|
||||||
m.Post("/labels", reqRepoIssuesOrPullsWriter, repo.UpdateIssueLabel)
|
m.Post("/labels", reqRepoIssuesOrPullsWriter, repo.UpdateIssueLabel)
|
||||||
m.Post("/milestone", reqRepoIssuesOrPullsWriter, repo.UpdateIssueMilestone)
|
m.Post("/milestone", reqRepoIssuesOrPullsWriter, repo.UpdateIssueMilestone)
|
||||||
|
@ -764,13 +762,17 @@ func RegisterRoutes(m *macaron.Macaron) {
|
||||||
m.Post("/request_review", reqRepoIssuesOrPullsReader, repo.UpdatePullReviewRequest)
|
m.Post("/request_review", reqRepoIssuesOrPullsReader, repo.UpdatePullReviewRequest)
|
||||||
m.Post("/status", reqRepoIssuesOrPullsWriter, repo.UpdateIssueStatus)
|
m.Post("/status", reqRepoIssuesOrPullsWriter, repo.UpdateIssueStatus)
|
||||||
m.Post("/resolve_conversation", reqRepoIssuesOrPullsReader, repo.UpdateResolveConversation)
|
m.Post("/resolve_conversation", reqRepoIssuesOrPullsReader, repo.UpdateResolveConversation)
|
||||||
|
m.Post("/attachments", repo.UploadIssueAttachment)
|
||||||
|
m.Post("/attachments/remove", repo.DeleteAttachment)
|
||||||
}, context.RepoMustNotBeArchived())
|
}, context.RepoMustNotBeArchived())
|
||||||
m.Group("/comments/:id", func() {
|
m.Group("/comments/:id", func() {
|
||||||
m.Post("", repo.UpdateCommentContent)
|
m.Post("", repo.UpdateCommentContent)
|
||||||
m.Post("/delete", repo.DeleteComment)
|
m.Post("/delete", repo.DeleteComment)
|
||||||
m.Post("/reactions/:action", bindIgnErr(auth.ReactionForm{}), repo.ChangeCommentReaction)
|
m.Post("/reactions/:action", bindIgnErr(auth.ReactionForm{}), repo.ChangeCommentReaction)
|
||||||
m.Get("/attachments", repo.GetCommentAttachments)
|
|
||||||
}, context.RepoMustNotBeArchived())
|
}, context.RepoMustNotBeArchived())
|
||||||
|
m.Group("/comments/:id", func() {
|
||||||
|
m.Get("/attachments", repo.GetCommentAttachments)
|
||||||
|
})
|
||||||
m.Group("/labels", func() {
|
m.Group("/labels", func() {
|
||||||
m.Post("/new", bindIgnErr(auth.CreateLabelForm{}), repo.NewLabel)
|
m.Post("/new", bindIgnErr(auth.CreateLabelForm{}), repo.NewLabel)
|
||||||
m.Post("/edit", bindIgnErr(auth.CreateLabelForm{}), repo.UpdateLabel)
|
m.Post("/edit", bindIgnErr(auth.CreateLabelForm{}), repo.UpdateLabel)
|
||||||
|
@ -826,11 +828,14 @@ func RegisterRoutes(m *macaron.Macaron) {
|
||||||
m.Get("/", repo.Releases)
|
m.Get("/", repo.Releases)
|
||||||
m.Get("/tag/*", repo.SingleRelease)
|
m.Get("/tag/*", repo.SingleRelease)
|
||||||
m.Get("/latest", repo.LatestRelease)
|
m.Get("/latest", repo.LatestRelease)
|
||||||
|
m.Get("/attachments/:uuid", repo.GetAttachment)
|
||||||
}, repo.MustBeNotEmpty, context.RepoRefByType(context.RepoRefTag))
|
}, repo.MustBeNotEmpty, context.RepoRefByType(context.RepoRefTag))
|
||||||
m.Group("/releases", func() {
|
m.Group("/releases", func() {
|
||||||
m.Get("/new", repo.NewRelease)
|
m.Get("/new", repo.NewRelease)
|
||||||
m.Post("/new", bindIgnErr(auth.NewReleaseForm{}), repo.NewReleasePost)
|
m.Post("/new", bindIgnErr(auth.NewReleaseForm{}), repo.NewReleasePost)
|
||||||
m.Post("/delete", repo.DeleteRelease)
|
m.Post("/delete", repo.DeleteRelease)
|
||||||
|
m.Post("/attachments", repo.UploadReleaseAttachment)
|
||||||
|
m.Post("/attachments/remove", repo.DeleteAttachment)
|
||||||
}, reqSignIn, repo.MustBeNotEmpty, context.RepoMustNotBeArchived(), reqRepoReleaseWriter, context.RepoRef())
|
}, reqSignIn, repo.MustBeNotEmpty, context.RepoMustNotBeArchived(), reqRepoReleaseWriter, context.RepoRef())
|
||||||
m.Group("/releases", func() {
|
m.Group("/releases", func() {
|
||||||
m.Get("/edit/*", repo.EditRelease)
|
m.Get("/edit/*", repo.EditRelease)
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="files"></div>
|
<div class="files"></div>
|
||||||
<div class="ui dropzone" id="dropzone" data-upload-url="{{.RepoLink}}/upload-file" data-remove-url="{{.RepoLink}}/upload-remove" data-csrf="{{.CsrfToken}}" data-accepts="{{.UploadAllowedTypes}}" data-max-file="{{.UploadMaxFiles}}" data-max-size="{{.UploadMaxSize}}" data-default-message="{{.i18n.Tr "dropzone.default_message"}}" data-invalid-input-type="{{.i18n.Tr "dropzone.invalid_input_type"}}" data-file-too-big="{{.i18n.Tr "dropzone.file_too_big"}}" data-remove-file="{{.i18n.Tr "dropzone.remove_file"}}"></div>
|
{{template "repo/upload" .}}
|
||||||
</div>
|
</div>
|
||||||
{{template "repo/editor/commit_form" .}}
|
{{template "repo/editor/commit_form" .}}
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -12,8 +12,8 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{if .IsAttachmentEnabled}}
|
{{if .IsAttachmentEnabled}}
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="files"></div>
|
<div class="files"></div>
|
||||||
<div class="ui dropzone" id="dropzone" data-upload-url="{{AppSubUrl}}/attachments" data-accepts="{{.AttachmentAllowedTypes}}" data-max-file="{{.AttachmentMaxFiles}}" data-max-size="{{.AttachmentMaxSize}}" data-default-message="{{.i18n.Tr "dropzone.default_message"}}" data-invalid-input-type="{{.i18n.Tr "dropzone.invalid_input_type"}}" data-file-too-big="{{.i18n.Tr "dropzone.file_too_big"}}" data-remove-file="{{.i18n.Tr "dropzone.remove_file"}}"></div>
|
{{template "repo/upload" .}}
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
|
@ -197,19 +197,10 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{if .IsAttachmentEnabled}}
|
{{if .IsAttachmentEnabled}}
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="comment-files"></div>
|
<div class="comment-files"></div>
|
||||||
<div class="ui dropzone" id="comment-dropzone"
|
{{template "repo/upload" .}}
|
||||||
data-upload-url="{{AppSubUrl}}/attachments"
|
|
||||||
data-remove-url="{{AppSubUrl}}/attachments/delete"
|
|
||||||
data-csrf="{{.CsrfToken}}" data-accepts="{{.AttachmentAllowedTypes}}"
|
|
||||||
data-max-file="{{.AttachmentMaxFiles}}" data-max-size="{{.AttachmentMaxSize}}"
|
|
||||||
data-default-message="{{.i18n.Tr "dropzone.default_message"}}"
|
|
||||||
data-invalid-input-type="{{.i18n.Tr "dropzone.invalid_input_type"}}"
|
|
||||||
data-file-too-big="{{.i18n.Tr "dropzone.file_too_big"}}"
|
|
||||||
data-remove-file="{{.i18n.Tr "dropzone.remove_file"}}">
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
{{end}}
|
{{end}}
|
||||||
<div class="field footer">
|
<div class="field footer">
|
||||||
<div class="text right edit">
|
<div class="text right edit">
|
||||||
|
|
|
@ -49,10 +49,10 @@
|
||||||
<textarea name="content">{{.content}}</textarea>
|
<textarea name="content">{{.content}}</textarea>
|
||||||
</div>
|
</div>
|
||||||
{{if .IsAttachmentEnabled}}
|
{{if .IsAttachmentEnabled}}
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="files"></div>
|
<div class="files"></div>
|
||||||
<div class="ui dropzone" id="dropzone" data-upload-url="{{AppSubUrl}}/attachments" data-accepts="{{.AttachmentAllowedTypes}}" data-max-file="{{.AttachmentMaxFiles}}" data-max-size="{{.AttachmentMaxSize}}" data-default-message="{{.i18n.Tr "dropzone.default_message"}}" data-invalid-input-type="{{.i18n.Tr "dropzone.invalid_input_type"}}" data-file-too-big="{{.i18n.Tr "dropzone.file_too_big"}}" data-remove-file="{{.i18n.Tr "dropzone.remove_file"}}"></div>
|
{{template "repo/upload" .}}
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
<div class="ui container">
|
<div class="ui container">
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
<div
|
||||||
|
class="ui dropzone"
|
||||||
|
id="dropzone"
|
||||||
|
data-upload-url="{{.UploadUrl}}"
|
||||||
|
data-remove-url="{{.UploadRemoveUrl}}"
|
||||||
|
data-accepts="{{.UploadAccepts}}"
|
||||||
|
data-max-file="{{.UploadMaxFiles}}"
|
||||||
|
data-max-size="{{.UploadMaxSize}}"
|
||||||
|
data-default-message="{{.i18n.Tr "dropzone.default_message"}}"
|
||||||
|
data-invalid-input-type="{{.i18n.Tr "dropzone.invalid_input_type"}}"
|
||||||
|
data-file-too-big="{{.i18n.Tr "dropzone.file_too_big"}}"
|
||||||
|
data-remove-file="{{.i18n.Tr "dropzone.remove_file"}}"
|
||||||
|
></div>
|
|
@ -326,7 +326,7 @@ function uploadFile(file, callback) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
xhr.open('post', `${AppSubUrl}/attachments`, true);
|
xhr.open('post', $('#dropzone').data('upload-url'), true);
|
||||||
xhr.setRequestHeader('X-Csrf-Token', csrf);
|
xhr.setRequestHeader('X-Csrf-Token', csrf);
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('file', file, file.name);
|
formData.append('file', file, file.name);
|
||||||
|
@ -902,7 +902,7 @@ async function initRepository() {
|
||||||
headers: {'X-Csrf-Token': csrf},
|
headers: {'X-Csrf-Token': csrf},
|
||||||
maxFiles: $dropzone.data('max-file'),
|
maxFiles: $dropzone.data('max-file'),
|
||||||
maxFilesize: $dropzone.data('max-size'),
|
maxFilesize: $dropzone.data('max-size'),
|
||||||
acceptedFiles: ($dropzone.data('accepts') === '*/*') ? null : $dropzone.data('accepts'),
|
acceptedFiles: (['*/*', ''].includes($dropzone.data('accepts'))) ? null : $dropzone.data('accepts'),
|
||||||
addRemoveLinks: true,
|
addRemoveLinks: true,
|
||||||
dictDefaultMessage: $dropzone.data('default-message'),
|
dictDefaultMessage: $dropzone.data('default-message'),
|
||||||
dictInvalidFileType: $dropzone.data('invalid-input-type'),
|
dictInvalidFileType: $dropzone.data('invalid-input-type'),
|
||||||
|
@ -923,10 +923,10 @@ async function initRepository() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$(`#${filenameDict[file.name].uuid}`).remove();
|
$(`#${filenameDict[file.name].uuid}`).remove();
|
||||||
if ($dropzone.data('remove-url') && $dropzone.data('csrf') && !filenameDict[file.name].submitted) {
|
if ($dropzone.data('remove-url') && !filenameDict[file.name].submitted) {
|
||||||
$.post($dropzone.data('remove-url'), {
|
$.post($dropzone.data('remove-url'), {
|
||||||
file: filenameDict[file.name].uuid,
|
file: filenameDict[file.name].uuid,
|
||||||
_csrf: $dropzone.data('csrf')
|
_csrf: csrf,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -2323,7 +2323,7 @@ $(document).ready(async () => {
|
||||||
headers: {'X-Csrf-Token': csrf},
|
headers: {'X-Csrf-Token': csrf},
|
||||||
maxFiles: $dropzone.data('max-file'),
|
maxFiles: $dropzone.data('max-file'),
|
||||||
maxFilesize: $dropzone.data('max-size'),
|
maxFilesize: $dropzone.data('max-size'),
|
||||||
acceptedFiles: ($dropzone.data('accepts') === '*/*') ? null : $dropzone.data('accepts'),
|
acceptedFiles: (['*/*', ''].includes($dropzone.data('accepts'))) ? null : $dropzone.data('accepts'),
|
||||||
addRemoveLinks: true,
|
addRemoveLinks: true,
|
||||||
dictDefaultMessage: $dropzone.data('default-message'),
|
dictDefaultMessage: $dropzone.data('default-message'),
|
||||||
dictInvalidFileType: $dropzone.data('invalid-input-type'),
|
dictInvalidFileType: $dropzone.data('invalid-input-type'),
|
||||||
|
@ -2340,10 +2340,10 @@ $(document).ready(async () => {
|
||||||
if (file.name in filenameDict) {
|
if (file.name in filenameDict) {
|
||||||
$(`#${filenameDict[file.name]}`).remove();
|
$(`#${filenameDict[file.name]}`).remove();
|
||||||
}
|
}
|
||||||
if ($dropzone.data('remove-url') && $dropzone.data('csrf')) {
|
if ($dropzone.data('remove-url')) {
|
||||||
$.post($dropzone.data('remove-url'), {
|
$.post($dropzone.data('remove-url'), {
|
||||||
file: filenameDict[file.name],
|
file: filenameDict[file.name],
|
||||||
_csrf: $dropzone.data('csrf')
|
_csrf: csrf
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue