New upload API (#897)
* New upload API (WIP) * update * Last fix * Adding changes from update.go to api.go * fix bracket * fix import
Cette révision appartient à :
Parent
cdd84db36e
révision
b61fe55f82
|
@ -2,6 +2,7 @@ package router
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html"
|
||||
"net/http"
|
||||
|
@ -16,6 +17,7 @@ import (
|
|||
"github.com/NyaaPantsu/nyaa/service/api"
|
||||
"github.com/NyaaPantsu/nyaa/service/torrent"
|
||||
"github.com/NyaaPantsu/nyaa/service/upload"
|
||||
"github.com/NyaaPantsu/nyaa/service/user"
|
||||
"github.com/NyaaPantsu/nyaa/util"
|
||||
"github.com/NyaaPantsu/nyaa/util/log"
|
||||
"github.com/NyaaPantsu/nyaa/util/search"
|
||||
|
@ -135,10 +137,14 @@ func APIViewHeadHandler(w http.ResponseWriter, r *http.Request) {
|
|||
// APIUploadHandler : Controller for uploading a torrent with api
|
||||
func APIUploadHandler(w http.ResponseWriter, r *http.Request) {
|
||||
token := r.Header.Get("Authorization")
|
||||
user := model.User{}
|
||||
db.ORM.Where("api_token = ?", token).First(&user) //i don't like this
|
||||
user, _, _, err := userService.RetrieveUserByAPIToken(token)
|
||||
defer r.Body.Close()
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, "Error API token doesn't exist", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if !uploadService.IsUploadEnabled(user) {
|
||||
http.Error(w, "Error uploads are disabled", http.StatusBadRequest)
|
||||
return
|
||||
|
@ -150,139 +156,124 @@ func APIUploadHandler(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
upload := apiService.TorrentRequest{}
|
||||
var filesize int64
|
||||
|
||||
contentType := r.Header.Get("Content-Type")
|
||||
if contentType == "application/json" {
|
||||
|
||||
d := json.NewDecoder(r.Body)
|
||||
if err := d.Decode(&upload); err != nil {
|
||||
decodeError := fmt.Errorf("Unable to decode upload data: %s", err).Error()
|
||||
http.Error(w, decodeError, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
err, code := upload.ValidateUpload()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), code)
|
||||
return
|
||||
}
|
||||
} else if strings.HasPrefix(contentType, "multipart/form-data") {
|
||||
upload.Name = r.FormValue("name")
|
||||
upload.Category, _ = strconv.Atoi(r.FormValue("category"))
|
||||
upload.SubCategory, _ = strconv.Atoi(r.FormValue("sub_category"))
|
||||
upload.Description = r.FormValue("description")
|
||||
upload.Remake, _ = strconv.ParseBool(r.FormValue("remake"))
|
||||
upload.WebsiteLink = r.FormValue("website_link")
|
||||
|
||||
var err error
|
||||
var code int
|
||||
|
||||
filesize, err, code = upload.ValidateMultipartUpload(r)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), code)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if contentType != "application/json" && !strings.HasPrefix(contentType, "multipart/form-data") && r.Header.Get("Content-Type") != "application/x-www-form-urlencoded" {
|
||||
// TODO What should we do here ? upload is empty so we shouldn't
|
||||
// create a torrent from it
|
||||
err := fmt.Errorf("Please provide either of Content-Type: application/json header or multipart/form-data").Error()
|
||||
http.Error(w, err, http.StatusInternalServerError)
|
||||
http.Error(w, errors.New("Please provide either of Content-Type: application/json header or multipart/form-data").Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
// As long as the right content-type is sent, formValue is smart enough to parse it
|
||||
err = upload.ExtractInfo(r)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
var sameTorrents int
|
||||
|
||||
db.ORM.Model(&model.Torrent{}).Where("torrent_hash = ?", upload.Hash).Count(&sameTorrents)
|
||||
status := model.TorrentStatusNormal
|
||||
if upload.Remake { // overrides trusted
|
||||
status = model.TorrentStatusRemake
|
||||
} else if user.IsTrusted() {
|
||||
status = model.TorrentStatusTrusted
|
||||
}
|
||||
|
||||
if sameTorrents == 0 {
|
||||
torrent := model.Torrent{
|
||||
Name: upload.Name,
|
||||
Category: upload.Category,
|
||||
SubCategory: upload.SubCategory,
|
||||
Status: model.TorrentStatusNormal,
|
||||
Hash: upload.Hash,
|
||||
Date: time.Now(),
|
||||
Filesize: filesize,
|
||||
Description: upload.Description,
|
||||
UploaderID: user.ID,
|
||||
Uploader: &user,
|
||||
WebsiteLink: upload.WebsiteLink,
|
||||
err = torrentService.ExistOrDelete(upload.Infohash, user)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
torrent := model.Torrent{
|
||||
Name: upload.Name,
|
||||
Category: upload.CategoryID,
|
||||
SubCategory: upload.SubCategoryID,
|
||||
Status: status,
|
||||
Hidden: upload.Hidden,
|
||||
Hash: upload.Infohash,
|
||||
Date: time.Now(),
|
||||
Filesize: upload.Filesize,
|
||||
Description: upload.Description,
|
||||
WebsiteLink: upload.WebsiteLink,
|
||||
UploaderID: user.ID}
|
||||
torrent.ParseTrackers(upload.Trackers)
|
||||
db.ORM.Create(&torrent)
|
||||
|
||||
if db.ElasticSearchClient != nil {
|
||||
err := torrent.AddToESIndex(db.ElasticSearchClient)
|
||||
if err == nil {
|
||||
log.Infof("Successfully added torrent to ES index.")
|
||||
} else {
|
||||
log.Errorf("Unable to add torrent to ES index: %s", err)
|
||||
}
|
||||
|
||||
if upload.Remake {
|
||||
torrent.Status = model.TorrentStatusRemake
|
||||
} else if user.IsTrusted() {
|
||||
torrent.Status = model.TorrentStatusTrusted
|
||||
}
|
||||
|
||||
db.ORM.Create(&torrent)
|
||||
|
||||
if db.ElasticSearchClient != nil {
|
||||
err := torrent.AddToESIndex(db.ElasticSearchClient)
|
||||
if err == nil {
|
||||
log.Infof("Successfully added torrent to ES index.")
|
||||
} else {
|
||||
log.Errorf("Unable to add torrent to ES index: %s", err)
|
||||
}
|
||||
}
|
||||
/*if err != nil {
|
||||
util.SendError(w, err, 500)
|
||||
return
|
||||
}*/
|
||||
fmt.Printf("%+v\n", torrent)
|
||||
} else {
|
||||
http.Error(w, "torrent already exists", http.StatusBadRequest)
|
||||
return
|
||||
log.Errorf("Unable to create elasticsearch client: %s", err)
|
||||
}
|
||||
|
||||
torrentService.NewTorrentEvent(Router, user, &torrent)
|
||||
// add filelist to files db, if we have one
|
||||
if len(upload.FileList) > 0 {
|
||||
for _, uploadedFile := range upload.FileList {
|
||||
file := model.File{TorrentID: torrent.ID, Filesize: upload.Filesize}
|
||||
err := file.SetPath(uploadedFile.Path)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
db.ORM.Create(&file)
|
||||
}
|
||||
}
|
||||
/*if err != nil {
|
||||
util.SendError(w, err, 500)
|
||||
return
|
||||
}*/
|
||||
fmt.Printf("%+v\n", torrent)
|
||||
}
|
||||
|
||||
// APIUpdateHandler : Controller for updating a torrent with api
|
||||
// FIXME Impossible to update a torrent uploaded by user 0
|
||||
func APIUpdateHandler(w http.ResponseWriter, r *http.Request) {
|
||||
token := r.Header.Get("Authorization")
|
||||
user := model.User{}
|
||||
db.ORM.Where("api_token = ?", token).First(&user) //i don't like this
|
||||
user, _, _, err := userService.RetrieveUserByAPIToken(token)
|
||||
defer r.Body.Close()
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, "Error API token doesn't exist", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if !uploadService.IsUploadEnabled(user) {
|
||||
http.Error(w, "Error uploads are disabled", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
contentType := r.Header.Get("Content-Type")
|
||||
if contentType == "application/json" {
|
||||
if user.ID == 0 {
|
||||
http.Error(w, apiService.ErrAPIKey.Error(), http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
update := apiService.UpdateRequest{}
|
||||
d := json.NewDecoder(r.Body)
|
||||
if err := d.Decode(&update); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
id := update.ID
|
||||
torrent := model.Torrent{}
|
||||
db.ORM.Where("torrent_id = ?", id).First(&torrent)
|
||||
if torrent.ID == 0 {
|
||||
http.Error(w, apiService.ErrTorrentID.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if torrent.UploaderID != 0 && torrent.UploaderID != user.ID { //&& user.Status != mod
|
||||
http.Error(w, apiService.ErrRights.Error(), http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
err, code := update.Update.ValidateUpdate()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), code)
|
||||
return
|
||||
}
|
||||
update.UpdateTorrent(&torrent)
|
||||
|
||||
torrentService.UpdateTorrent(torrent)
|
||||
if user.ID == 0 {
|
||||
http.Error(w, apiService.ErrAPIKey.Error(), http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
contentType := r.Header.Get("Content-Type")
|
||||
if contentType != "application/json" && !strings.HasPrefix(contentType, "multipart/form-data") && r.Header.Get("Content-Type") != "application/x-www-form-urlencoded" {
|
||||
// TODO What should we do here ? upload is empty so we shouldn't
|
||||
// create a torrent from it
|
||||
http.Error(w, errors.New("Please provide either of Content-Type: application/json header or multipart/form-data").Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
update := apiService.UpdateRequest{}
|
||||
err = update.Update.ExtractEditInfo(r)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
torrent := model.Torrent{}
|
||||
db.ORM.Where("torrent_id = ?", r.FormValue("id")).First(&torrent)
|
||||
if torrent.ID == 0 {
|
||||
http.Error(w, apiService.ErrTorrentID.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if torrent.UploaderID != 0 && torrent.UploaderID != user.ID { //&& user.Status != mod
|
||||
http.Error(w, apiService.ErrRights.Error(), http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
update.UpdateTorrent(&torrent, user)
|
||||
torrentService.UpdateTorrent(torrent)
|
||||
}
|
||||
|
||||
// APISearchHandler : Controller for searching with api
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
"github.com/NyaaPantsu/nyaa/db"
|
||||
"github.com/NyaaPantsu/nyaa/model"
|
||||
"github.com/NyaaPantsu/nyaa/service"
|
||||
"github.com/NyaaPantsu/nyaa/service/api"
|
||||
"github.com/NyaaPantsu/nyaa/service/comment"
|
||||
"github.com/NyaaPantsu/nyaa/service/report"
|
||||
"github.com/NyaaPantsu/nyaa/service/torrent"
|
||||
|
@ -271,7 +272,7 @@ func TorrentEditModPanel(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
defer r.Body.Close()
|
||||
torrentJSON := torrent.ToJSON()
|
||||
uploadForm := newUploadForm()
|
||||
uploadForm := apiService.NewTorrentRequest()
|
||||
uploadForm.Name = torrentJSON.Name
|
||||
uploadForm.Category = torrentJSON.Category + "_" + torrentJSON.SubCategory
|
||||
uploadForm.Status = torrentJSON.Status
|
||||
|
@ -285,7 +286,7 @@ func TorrentEditModPanel(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
// TorrentPostEditModPanel : Controller for editing a torrent after POST request
|
||||
func TorrentPostEditModPanel(w http.ResponseWriter, r *http.Request) {
|
||||
var uploadForm uploadForm
|
||||
var uploadForm apiService.TorrentRequest
|
||||
defer r.Body.Close()
|
||||
id := r.URL.Query().Get("id")
|
||||
messages := msg.GetMessages(r)
|
||||
|
|
313
router/upload.go
313
router/upload.go
|
@ -1,313 +0,0 @@
|
|||
package router
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/NyaaPantsu/nyaa/cache"
|
||||
"github.com/NyaaPantsu/nyaa/config"
|
||||
"github.com/NyaaPantsu/nyaa/service/upload"
|
||||
"github.com/NyaaPantsu/nyaa/util"
|
||||
"github.com/NyaaPantsu/nyaa/util/categories"
|
||||
"github.com/NyaaPantsu/nyaa/util/metainfo"
|
||||
"github.com/zeebo/bencode"
|
||||
)
|
||||
|
||||
// Use this, because we seem to avoid using models, and we would need
|
||||
// the torrent ID to create the File in the DB
|
||||
type uploadedFile struct {
|
||||
Path []string
|
||||
Filesize int64
|
||||
}
|
||||
|
||||
// uploadForm serializing HTTP form for torrent upload
|
||||
type uploadForm struct {
|
||||
Name string
|
||||
Magnet string
|
||||
Category string
|
||||
Remake bool
|
||||
Description string
|
||||
Status int
|
||||
Hidden bool
|
||||
CaptchaID string
|
||||
WebsiteLink string
|
||||
|
||||
Infohash string
|
||||
CategoryID int
|
||||
SubCategoryID int
|
||||
Filesize int64
|
||||
Filepath string
|
||||
FileList []uploadedFile
|
||||
Trackers []string
|
||||
}
|
||||
|
||||
// TODO: these should be in another package (?)
|
||||
|
||||
// form names
|
||||
const uploadFormName = "name"
|
||||
const uploadFormTorrent = "torrent"
|
||||
const uploadFormMagnet = "magnet"
|
||||
const uploadFormCategory = "c"
|
||||
const uploadFormRemake = "remake"
|
||||
const uploadFormDescription = "desc"
|
||||
const uploadFormWebsiteLink = "website_link"
|
||||
const uploadFormStatus = "status"
|
||||
const uploadFormHidden = "hidden"
|
||||
|
||||
// error indicating that you can't send both a magnet link and torrent
|
||||
var errTorrentPlusMagnet = errors.New("Upload either a torrent file or magnet link, not both")
|
||||
|
||||
// error indicating a torrent is private
|
||||
var errPrivateTorrent = errors.New("Torrent is private")
|
||||
|
||||
// error indicating a problem with its trackers
|
||||
var errTrackerProblem = errors.New("Torrent does not have any (working) trackers: " + config.Conf.WebAddress.Nyaa + "/faq#trackers")
|
||||
|
||||
// error indicating a torrent's name is invalid
|
||||
var errInvalidTorrentName = errors.New("Torrent name is invalid")
|
||||
|
||||
// error indicating a torrent's description is invalid
|
||||
var errInvalidTorrentDescription = errors.New("Torrent description is invalid")
|
||||
|
||||
// error indicating a torrent's website link is invalid
|
||||
var errInvalidWebsiteLink = errors.New("Website url or IRC link is invalid")
|
||||
|
||||
// error indicating a torrent's category is invalid
|
||||
var errInvalidTorrentCategory = errors.New("Torrent category is invalid")
|
||||
|
||||
// var p = bluemonday.UGCPolicy()
|
||||
|
||||
/**
|
||||
uploadForm.ExtractInfo takes an http request and computes all fields for this form
|
||||
*/
|
||||
func (f *uploadForm) ExtractInfo(r *http.Request) error {
|
||||
|
||||
f.Name = r.FormValue(uploadFormName)
|
||||
f.Category = r.FormValue(uploadFormCategory)
|
||||
f.Description = r.FormValue(uploadFormDescription)
|
||||
f.WebsiteLink = r.FormValue(uploadFormWebsiteLink)
|
||||
f.Status, _ = strconv.Atoi(r.FormValue(uploadFormStatus))
|
||||
f.Magnet = r.FormValue(uploadFormMagnet)
|
||||
f.Remake = r.FormValue(uploadFormRemake) == "on"
|
||||
f.Hidden = r.FormValue(uploadFormHidden) == "on"
|
||||
|
||||
// trim whitespace
|
||||
f.Name = strings.TrimSpace(f.Name)
|
||||
f.Description = util.Sanitize(strings.TrimSpace(f.Description), "default")
|
||||
f.WebsiteLink = strings.TrimSpace(f.WebsiteLink)
|
||||
f.Magnet = strings.TrimSpace(f.Magnet)
|
||||
cache.Impl.ClearAll()
|
||||
defer r.Body.Close()
|
||||
if len(f.Description) > 500 {
|
||||
return errInvalidTorrentDescription
|
||||
}
|
||||
|
||||
catsSplit := strings.Split(f.Category, "_")
|
||||
// need this to prevent out of index panics
|
||||
if len(catsSplit) == 2 {
|
||||
CatID, err := strconv.Atoi(catsSplit[0])
|
||||
if err != nil {
|
||||
return errInvalidTorrentCategory
|
||||
}
|
||||
SubCatID, err := strconv.Atoi(catsSplit[1])
|
||||
if err != nil {
|
||||
return errInvalidTorrentCategory
|
||||
}
|
||||
|
||||
if !categories.CategoryExists(f.Category) {
|
||||
return errInvalidTorrentCategory
|
||||
}
|
||||
|
||||
f.CategoryID = CatID
|
||||
f.SubCategoryID = SubCatID
|
||||
} else {
|
||||
return errInvalidTorrentCategory
|
||||
}
|
||||
if f.WebsiteLink != "" {
|
||||
// WebsiteLink
|
||||
urlRegexp, _ := regexp.Compile(`^(https?:\/\/|ircs?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$`)
|
||||
if !urlRegexp.MatchString(f.WebsiteLink) {
|
||||
return errInvalidWebsiteLink
|
||||
}
|
||||
}
|
||||
|
||||
// first: parse torrent file (if any) to fill missing information
|
||||
tfile, _, err := r.FormFile(uploadFormTorrent)
|
||||
if err == nil {
|
||||
var torrent metainfo.TorrentFile
|
||||
|
||||
// decode torrent
|
||||
_, seekErr := tfile.Seek(0, io.SeekStart)
|
||||
if seekErr != nil {
|
||||
return seekErr
|
||||
}
|
||||
err = bencode.NewDecoder(tfile).Decode(&torrent)
|
||||
if err != nil {
|
||||
return metainfo.ErrInvalidTorrentFile
|
||||
}
|
||||
|
||||
// check a few things
|
||||
if torrent.IsPrivate() {
|
||||
return errPrivateTorrent
|
||||
}
|
||||
trackers := torrent.GetAllAnnounceURLS()
|
||||
f.Trackers = uploadService.CheckTrackers(trackers)
|
||||
if len(f.Trackers) == 0 {
|
||||
return errTrackerProblem
|
||||
}
|
||||
|
||||
// Name
|
||||
if len(f.Name) == 0 {
|
||||
f.Name = torrent.TorrentName()
|
||||
}
|
||||
|
||||
// Magnet link: if a file is provided it should be empty
|
||||
if len(f.Magnet) != 0 {
|
||||
return errTorrentPlusMagnet
|
||||
}
|
||||
|
||||
_, seekErr = tfile.Seek(0, io.SeekStart)
|
||||
if seekErr != nil {
|
||||
return seekErr
|
||||
}
|
||||
infohash, err := metainfo.DecodeInfohash(tfile)
|
||||
if err != nil {
|
||||
return metainfo.ErrInvalidTorrentFile
|
||||
}
|
||||
f.Infohash = infohash
|
||||
f.Magnet = util.InfoHashToMagnet(infohash, f.Name, trackers...)
|
||||
|
||||
// extract filesize
|
||||
f.Filesize = int64(torrent.TotalSize())
|
||||
|
||||
// extract filelist
|
||||
fileInfos := torrent.Info.GetFiles()
|
||||
for _, fileInfo := range fileInfos {
|
||||
f.FileList = append(f.FileList, uploadedFile{
|
||||
Path: fileInfo.Path,
|
||||
Filesize: int64(fileInfo.Length),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// No torrent file provided
|
||||
magnetURL, err := url.Parse(string(f.Magnet)) //?
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
xt := magnetURL.Query().Get("xt")
|
||||
if !strings.HasPrefix(xt, "urn:btih:") {
|
||||
return errors.New("Incorrect magnet")
|
||||
}
|
||||
xt = strings.SplitAfter(xt, ":")[2]
|
||||
f.Infohash = strings.ToUpper(strings.Split(xt, "&")[0])
|
||||
isBase32, err := regexp.MatchString("^[2-7A-Z]{32}$", f.Infohash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !isBase32 {
|
||||
isBase16, err := regexp.MatchString("^[0-9A-F]{40}$", f.Infohash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !isBase16 {
|
||||
return errors.New("Incorrect hash")
|
||||
}
|
||||
}
|
||||
// TODO: Get Trackers from magnet URL
|
||||
f.Filesize = 0
|
||||
f.Filepath = ""
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// then actually check that we have everything we need
|
||||
if len(f.Name) == 0 {
|
||||
return errInvalidTorrentName
|
||||
}
|
||||
|
||||
// after data has been checked & extracted, write it to disk
|
||||
if len(config.Conf.Torrents.FileStorage) > 0 {
|
||||
err := writeTorrentToDisk(tfile, f.Infohash+".torrent", &f.Filepath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
f.Filepath = ""
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *uploadForm) ExtractEditInfo(r *http.Request) error {
|
||||
f.Name = r.FormValue(uploadFormName)
|
||||
f.Category = r.FormValue(uploadFormCategory)
|
||||
f.WebsiteLink = r.FormValue(uploadFormWebsiteLink)
|
||||
f.Description = r.FormValue(uploadFormDescription)
|
||||
f.Hidden = r.FormValue(uploadFormHidden) == "on"
|
||||
f.Status, _ = strconv.Atoi(r.FormValue(uploadFormStatus))
|
||||
|
||||
// trim whitespace
|
||||
f.Name = strings.TrimSpace(f.Name)
|
||||
f.Description = util.Sanitize(strings.TrimSpace(f.Description), "default")
|
||||
defer r.Body.Close()
|
||||
|
||||
catsSplit := strings.Split(f.Category, "_")
|
||||
// need this to prevent out of index panics
|
||||
if len(catsSplit) == 2 {
|
||||
CatID, err := strconv.Atoi(catsSplit[0])
|
||||
if err != nil {
|
||||
return errInvalidTorrentCategory
|
||||
}
|
||||
SubCatID, err := strconv.Atoi(catsSplit[1])
|
||||
if err != nil {
|
||||
return errInvalidTorrentCategory
|
||||
}
|
||||
|
||||
if !categories.CategoryExists(f.Category) {
|
||||
return errInvalidTorrentCategory
|
||||
}
|
||||
|
||||
f.CategoryID = CatID
|
||||
f.SubCategoryID = SubCatID
|
||||
} else {
|
||||
return errInvalidTorrentCategory
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeTorrentToDisk(file multipart.File, name string, fullpath *string) error {
|
||||
_, seekErr := file.Seek(0, io.SeekStart)
|
||||
if seekErr != nil {
|
||||
return seekErr
|
||||
}
|
||||
b, err := ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*fullpath = fmt.Sprintf("%s%c%s", config.Conf.Torrents.FileStorage, os.PathSeparator, name)
|
||||
return ioutil.WriteFile(*fullpath, b, 0644)
|
||||
}
|
||||
|
||||
// newUploadForm creates a new upload form given parameters as list
|
||||
func newUploadForm(params ...string) (uploadForm uploadForm) {
|
||||
if len(params) > 1 {
|
||||
uploadForm.Category = params[0]
|
||||
} else {
|
||||
uploadForm.Category = "3_12"
|
||||
}
|
||||
if len(params) > 2 {
|
||||
uploadForm.Description = params[1]
|
||||
} else {
|
||||
uploadForm.Description = "Description"
|
||||
}
|
||||
return
|
||||
}
|
|
@ -1,30 +1,26 @@
|
|||
package router
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/NyaaPantsu/nyaa/config"
|
||||
"github.com/NyaaPantsu/nyaa/db"
|
||||
"github.com/NyaaPantsu/nyaa/model"
|
||||
"github.com/NyaaPantsu/nyaa/service/api"
|
||||
"github.com/NyaaPantsu/nyaa/service/captcha"
|
||||
"github.com/NyaaPantsu/nyaa/service/notifier"
|
||||
"github.com/NyaaPantsu/nyaa/service/torrent"
|
||||
"github.com/NyaaPantsu/nyaa/service/upload"
|
||||
"github.com/NyaaPantsu/nyaa/service/user"
|
||||
"github.com/NyaaPantsu/nyaa/service/user/permission"
|
||||
"github.com/NyaaPantsu/nyaa/util/log"
|
||||
msg "github.com/NyaaPantsu/nyaa/util/messages"
|
||||
"github.com/NyaaPantsu/nyaa/util/publicSettings"
|
||||
)
|
||||
|
||||
// UploadHandler : Main Controller for uploading a torrent
|
||||
func UploadHandler(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
user := getUser(r)
|
||||
if !uploadService.IsUploadEnabled(*user) {
|
||||
if !uploadService.IsUploadEnabled(user) {
|
||||
http.Error(w, "Error uploads are disabled", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
@ -38,7 +34,7 @@ func UploadHandler(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
// UploadPostHandler : Controller for uploading a torrent, after POST request, redirect or makes error in messages
|
||||
func UploadPostHandler(w http.ResponseWriter, r *http.Request) {
|
||||
var uploadForm uploadForm
|
||||
var uploadForm apiService.TorrentRequest
|
||||
defer r.Body.Close()
|
||||
user := getUser(r)
|
||||
messages := msg.GetMessages(r) // new util for errors and infos
|
||||
|
@ -62,14 +58,9 @@ func UploadPostHandler(w http.ResponseWriter, r *http.Request) {
|
|||
status = model.TorrentStatusTrusted
|
||||
}
|
||||
|
||||
torrentIndb := model.Torrent{}
|
||||
db.ORM.Unscoped().Model(&model.Torrent{}).Where("torrent_hash = ?", uploadForm.Infohash).First(&torrentIndb)
|
||||
if torrentIndb.ID > 0 {
|
||||
if userPermission.CurrentUserIdentical(user, torrentIndb.UploaderID) && torrentIndb.IsDeleted() && !torrentIndb.IsBlocked() { // if torrent is not locked and is deleted and the user is the actual owner
|
||||
torrentService.DefinitelyDeleteTorrent(strconv.Itoa(int(torrentIndb.ID)))
|
||||
} else {
|
||||
messages.AddError("errors", "Torrent already in database !")
|
||||
}
|
||||
err = torrentService.ExistOrDelete(uploadForm.Infohash, user)
|
||||
if err != nil {
|
||||
messages.AddError("errors", err.Error())
|
||||
}
|
||||
|
||||
if !messages.HasErrors() {
|
||||
|
@ -98,21 +89,7 @@ func UploadPostHandler(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
}
|
||||
|
||||
url, err := Router.Get("view_torrent").URL("id", strconv.FormatUint(uint64(torrent.ID), 10))
|
||||
|
||||
if user.ID > 0 && config.Conf.Users.DefaultUserSettings["new_torrent"] { // If we are a member and notifications for new torrents are enabled
|
||||
userService.GetFollowers(user) // We populate the liked field for users
|
||||
if len(user.Followers) > 0 { // If we are followed by at least someone
|
||||
for _, follower := range user.Followers {
|
||||
follower.ParseSettings() // We need to call it before checking settings
|
||||
if follower.Settings.Get("new_torrent") {
|
||||
T, _, _ := publicSettings.TfuncAndLanguageWithFallback(follower.Language, follower.Language) // We need to send the notification to every user in their language
|
||||
|
||||
notifierService.NotifyUser(&follower, torrent.Identifier(), fmt.Sprintf(T("new_torrent_uploaded"), torrent.Name, user.Username), url.String(), follower.Settings.Get("new_torrent_email"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
torrentService.NewTorrentEvent(Router, user, &torrent)
|
||||
|
||||
// add filelist to files db, if we have one
|
||||
if len(uploadForm.FileList) > 0 {
|
||||
|
@ -126,6 +103,7 @@ func UploadPostHandler(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
}
|
||||
|
||||
url, err := Router.Get("view_torrent").URL("id", strconv.FormatUint(uint64(torrent.ID), 10))
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
|
@ -139,7 +117,7 @@ func UploadGetHandler(w http.ResponseWriter, r *http.Request) {
|
|||
defer r.Body.Close()
|
||||
messages := msg.GetMessages(r) // new util for errors and infos
|
||||
|
||||
var uploadForm uploadForm
|
||||
var uploadForm apiService.TorrentRequest
|
||||
_ = uploadForm.ExtractInfo(r)
|
||||
user := getUser(r)
|
||||
if userPermission.NeedsCaptcha(user) {
|
||||
|
|
|
@ -14,6 +14,7 @@ import (
|
|||
"github.com/NyaaPantsu/nyaa/db"
|
||||
"github.com/NyaaPantsu/nyaa/model"
|
||||
"github.com/NyaaPantsu/nyaa/service"
|
||||
"github.com/NyaaPantsu/nyaa/service/api"
|
||||
"github.com/NyaaPantsu/nyaa/service/captcha"
|
||||
"github.com/NyaaPantsu/nyaa/service/notifier"
|
||||
"github.com/NyaaPantsu/nyaa/service/report"
|
||||
|
@ -172,7 +173,7 @@ func TorrentEditUserPanel(w http.ResponseWriter, r *http.Request) {
|
|||
messages := msg.GetMessages(r)
|
||||
currentUser := getUser(r)
|
||||
if userPermission.CurrentOrAdmin(currentUser, torrent.UploaderID) {
|
||||
uploadForm := newUploadForm()
|
||||
uploadForm := apiService.NewTorrentRequest()
|
||||
uploadForm.Name = torrent.Name
|
||||
uploadForm.Category = strconv.Itoa(torrent.Category) + "_" + strconv.Itoa(torrent.SubCategory)
|
||||
uploadForm.Remake = torrent.Status == model.TorrentStatusRemake
|
||||
|
@ -190,7 +191,7 @@ func TorrentEditUserPanel(w http.ResponseWriter, r *http.Request) {
|
|||
// TorrentPostEditUserPanel : Controller for editing a user torrent by a user, after post request
|
||||
func TorrentPostEditUserPanel(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
var uploadForm uploadForm
|
||||
var uploadForm apiService.TorrentRequest
|
||||
id := r.URL.Query().Get("id")
|
||||
messages := msg.GetMessages(r)
|
||||
torrent, _ := torrentService.GetTorrentByID(id)
|
||||
|
@ -202,7 +203,6 @@ func TorrentPostEditUserPanel(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
if !messages.HasErrors() {
|
||||
status := model.TorrentStatusNormal
|
||||
uploadForm.Remake = r.FormValue(uploadFormRemake) == "on"
|
||||
if uploadForm.Remake { // overrides trusted
|
||||
status = model.TorrentStatusRemake
|
||||
} else if currentUser.IsTrusted() {
|
||||
|
|
|
@ -3,22 +3,63 @@ package apiService
|
|||
import (
|
||||
"encoding/base32"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/NyaaPantsu/nyaa/cache"
|
||||
"github.com/NyaaPantsu/nyaa/config"
|
||||
"github.com/NyaaPantsu/nyaa/model"
|
||||
"github.com/NyaaPantsu/nyaa/service"
|
||||
"github.com/NyaaPantsu/nyaa/service/upload"
|
||||
"github.com/NyaaPantsu/nyaa/util"
|
||||
"github.com/NyaaPantsu/nyaa/util/categories"
|
||||
"github.com/NyaaPantsu/nyaa/util/metainfo"
|
||||
"github.com/zeebo/bencode"
|
||||
)
|
||||
|
||||
// form names
|
||||
const uploadFormName = "name"
|
||||
const uploadFormTorrent = "torrent"
|
||||
const uploadFormMagnet = "magnet"
|
||||
const uploadFormCategory = "c"
|
||||
const uploadFormRemake = "remake"
|
||||
const uploadFormDescription = "desc"
|
||||
const uploadFormWebsiteLink = "website_link"
|
||||
const uploadFormStatus = "status"
|
||||
const uploadFormHidden = "hidden"
|
||||
|
||||
// error indicating that you can't send both a magnet link and torrent
|
||||
var errTorrentPlusMagnet = errors.New("Upload either a torrent file or magnet link, not both")
|
||||
|
||||
// error indicating a torrent is private
|
||||
var errPrivateTorrent = errors.New("Torrent is private")
|
||||
|
||||
// error indicating a problem with its trackers
|
||||
var errTrackerProblem = errors.New("Torrent does not have any (working) trackers: " + config.Conf.WebAddress.Nyaa + "/faq#trackers")
|
||||
|
||||
// error indicating a torrent's name is invalid
|
||||
var errInvalidTorrentName = errors.New("Torrent name is invalid")
|
||||
|
||||
// error indicating a torrent's description is invalid
|
||||
var errInvalidTorrentDescription = errors.New("Torrent description is invalid")
|
||||
|
||||
// error indicating a torrent's website link is invalid
|
||||
var errInvalidWebsiteLink = errors.New("Website url or IRC link is invalid")
|
||||
|
||||
// error indicating a torrent's category is invalid
|
||||
var errInvalidTorrentCategory = errors.New("Torrent category is invalid")
|
||||
|
||||
type torrentsQuery struct {
|
||||
Category int `json:"category"`
|
||||
SubCategory int `json:"sub_category"`
|
||||
|
@ -34,17 +75,34 @@ type TorrentsRequest struct {
|
|||
MaxPerPage int `json:"limit"`
|
||||
}
|
||||
|
||||
// Use this, because we seem to avoid using models, and we would need
|
||||
// the torrent ID to create the File in the DB
|
||||
type uploadedFile struct {
|
||||
Path []string `json:"path"`
|
||||
Filesize int64 `json:"filesize"`
|
||||
}
|
||||
|
||||
// TorrentRequest struct
|
||||
//accept torrent files?
|
||||
// Same json name as the constant!
|
||||
type TorrentRequest struct {
|
||||
Name string `json:"name"`
|
||||
Category int `json:"category"`
|
||||
SubCategory int `json:"sub_category"`
|
||||
Magnet string `json:"magnet"`
|
||||
Hash string `json:"hash"`
|
||||
Description string `json:"description"`
|
||||
Remake bool `json:"remake"`
|
||||
WebsiteLink string `json:"website_link"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Magnet string `json:"magnet,omitempty"`
|
||||
Category string `json:"c"`
|
||||
Remake bool `json:"remake,omitempty"`
|
||||
Description string `json:"desc,omitempty"`
|
||||
Status int `json:"status,omitempty"`
|
||||
Hidden bool `json:"hidden,omitempty"`
|
||||
CaptchaID string `json:"-"`
|
||||
WebsiteLink string `json:"website_link,omitempty"`
|
||||
SubCategory int `json:"sub_category,omitempty"`
|
||||
|
||||
Infohash string `json:"hash,omitempty"`
|
||||
CategoryID int `json:"-"`
|
||||
SubCategoryID int `json:"-"`
|
||||
Filesize int64 `json:"filesize,omitempty"`
|
||||
Filepath string `json:"-"`
|
||||
FileList []uploadedFile `json:"filelist,omitempty"`
|
||||
Trackers []string `json:"trackers,omitempty"`
|
||||
}
|
||||
|
||||
// UpdateRequest struct
|
||||
|
@ -73,16 +131,24 @@ func (r *TorrentsRequest) ToParams() serviceBase.WhereParams {
|
|||
return res
|
||||
}
|
||||
|
||||
func validateName(r *TorrentRequest) (error, int) {
|
||||
/*if len(r.Name) < 100 { //isn't this too much?
|
||||
return ErrShortName, http.StatusNotAcceptable
|
||||
}*/
|
||||
return nil, http.StatusOK
|
||||
func (r *TorrentRequest) validateName() error {
|
||||
// then actually check that we have everything we need
|
||||
if len(r.Name) == 0 {
|
||||
return errInvalidTorrentName
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *TorrentRequest) validateDescription() error {
|
||||
if len(r.Description) > 500 {
|
||||
return errInvalidTorrentDescription
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO Check category is within accepted range
|
||||
func validateCategory(r *TorrentRequest) (error, int) {
|
||||
if r.Category == 0 {
|
||||
if r.CategoryID == 0 {
|
||||
return ErrCategory, http.StatusNotAcceptable
|
||||
}
|
||||
return nil, http.StatusOK
|
||||
|
@ -107,157 +173,265 @@ func validateWebsiteLink(r *TorrentRequest) (error, int) {
|
|||
return nil, http.StatusOK
|
||||
}
|
||||
|
||||
func validateMagnet(r *TorrentRequest) (error, int) {
|
||||
func (r *TorrentRequest) validateMagnet() error {
|
||||
magnetURL, err := url.Parse(string(r.Magnet)) //?
|
||||
if err != nil {
|
||||
return err, http.StatusInternalServerError
|
||||
return err
|
||||
}
|
||||
xt := magnetURL.Query().Get("xt")
|
||||
if !strings.HasPrefix(xt, "urn:btih:") {
|
||||
return ErrMagnet, http.StatusNotAcceptable
|
||||
return ErrMagnet
|
||||
}
|
||||
xt = strings.SplitAfter(xt, ":")[2]
|
||||
r.Hash = strings.ToUpper(strings.Split(xt, "&")[0])
|
||||
fmt.Println(r.Hash)
|
||||
return nil, http.StatusOK
|
||||
r.Infohash = strings.ToUpper(strings.Split(xt, "&")[0])
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateHash(r *TorrentRequest) (error, int) {
|
||||
r.Hash = strings.ToUpper(r.Hash)
|
||||
isBase32, err := regexp.MatchString("^[2-7A-Z]{32}$", r.Hash)
|
||||
func (r *TorrentRequest) validateHash() error {
|
||||
isBase32, err := regexp.MatchString("^[2-7A-Z]{32}$", r.Infohash)
|
||||
if err != nil {
|
||||
return err, http.StatusInternalServerError
|
||||
return err
|
||||
}
|
||||
if !isBase32 {
|
||||
isBase16, err := regexp.MatchString("^[0-9A-F]{40}$", r.Hash)
|
||||
isBase16, err := regexp.MatchString("^[0-9A-F]{40}$", r.Infohash)
|
||||
if err != nil {
|
||||
return err, http.StatusInternalServerError
|
||||
return err
|
||||
}
|
||||
if !isBase16 {
|
||||
return ErrHash, http.StatusNotAcceptable
|
||||
return ErrHash
|
||||
}
|
||||
} else {
|
||||
//convert to base16
|
||||
data, err := base32.StdEncoding.DecodeString(r.Hash)
|
||||
data, err := base32.StdEncoding.DecodeString(r.Infohash)
|
||||
if err != nil {
|
||||
return err, http.StatusInternalServerError
|
||||
return err
|
||||
}
|
||||
hash16 := make([]byte, hex.EncodedLen(len(data)))
|
||||
hex.Encode(hash16, data)
|
||||
r.Hash = strings.ToUpper(string(hash16))
|
||||
r.Infohash = strings.ToUpper(string(hash16))
|
||||
}
|
||||
return nil, http.StatusOK
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateUpload : Check if an upload is valid
|
||||
func (r *TorrentRequest) ValidateUpload() (err error, code int) {
|
||||
validators := []func(r *TorrentRequest) (error, int){
|
||||
validateName,
|
||||
validateCategory,
|
||||
validateSubCategory,
|
||||
validateMagnet,
|
||||
validateHash,
|
||||
validateWebsiteLink,
|
||||
// ExtractEditInfo : takes an http request and computes all fields for this form
|
||||
func (r *TorrentRequest) ExtractEditInfo(req *http.Request) error {
|
||||
err := r.ExtractBasicValue(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i, validator := range validators {
|
||||
if r.Hash != "" && i == 3 {
|
||||
continue
|
||||
}
|
||||
err, code = validator(r)
|
||||
defer req.Body.Close()
|
||||
|
||||
err = r.ExtractCategory(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExtractCategory : takes an http request and computes category field for this form
|
||||
func (r *TorrentRequest) ExtractCategory(req *http.Request) error {
|
||||
catsSplit := strings.Split(r.Category, "_")
|
||||
// need this to prevent out of index panics
|
||||
if len(catsSplit) == 2 {
|
||||
CatID, err := strconv.Atoi(catsSplit[0])
|
||||
if err != nil {
|
||||
break
|
||||
return errInvalidTorrentCategory
|
||||
}
|
||||
SubCatID, err := strconv.Atoi(catsSplit[1])
|
||||
if err != nil {
|
||||
return errInvalidTorrentCategory
|
||||
}
|
||||
|
||||
if !categories.CategoryExists(r.Category) {
|
||||
return errInvalidTorrentCategory
|
||||
}
|
||||
|
||||
r.CategoryID = CatID
|
||||
r.SubCategoryID = SubCatID
|
||||
} else {
|
||||
return errInvalidTorrentCategory
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExtractBasicValue : takes an http request and computes all basic fields for this form
|
||||
func (r *TorrentRequest) ExtractBasicValue(req *http.Request) error {
|
||||
if strings.HasPrefix(req.Header.Get("Content-type"), "multipart/form-data") || req.Header.Get("Content-Type") == "application/x-www-form-urlencoded" { // Multipart
|
||||
if strings.HasPrefix(req.Header.Get("Content-type"), "multipart/form-data") { // We parse the multipart form
|
||||
err := req.ParseMultipartForm(15485760)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
r.Name = req.FormValue(uploadFormName)
|
||||
r.Category = req.FormValue(uploadFormCategory)
|
||||
r.WebsiteLink = req.FormValue(uploadFormWebsiteLink)
|
||||
r.Description = req.FormValue(uploadFormDescription)
|
||||
r.Hidden = req.FormValue(uploadFormHidden) == "on"
|
||||
r.Status, _ = strconv.Atoi(req.FormValue(uploadFormStatus))
|
||||
r.Remake = req.FormValue(uploadFormRemake) == "on"
|
||||
r.Magnet = req.FormValue(uploadFormMagnet)
|
||||
} else { // JSON (no file upload then)
|
||||
decoder := json.NewDecoder(req.Body)
|
||||
err := decoder.Decode(&r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return err, code
|
||||
// trim whitespace
|
||||
r.Name = strings.TrimSpace(r.Name)
|
||||
r.Description = util.Sanitize(strings.TrimSpace(r.Description), "default")
|
||||
r.WebsiteLink = strings.TrimSpace(r.WebsiteLink)
|
||||
r.Magnet = strings.TrimSpace(r.Magnet)
|
||||
|
||||
// then actually check that we have everything we need
|
||||
err := r.validateName()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = r.validateDescription()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if r.WebsiteLink != "" {
|
||||
// WebsiteLink
|
||||
urlRegexp, _ := regexp.Compile(`^(https?:\/\/|ircs?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$`)
|
||||
if !urlRegexp.MatchString(r.WebsiteLink) {
|
||||
return errInvalidWebsiteLink
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExtractInfo : takes an http request and computes all fields for this form
|
||||
func (r *TorrentRequest) ExtractInfo(req *http.Request) error {
|
||||
err := r.ExtractBasicValue(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cache.Impl.ClearAll()
|
||||
defer req.Body.Close()
|
||||
|
||||
err = r.ExtractCategory(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tfile, err := r.ValidateMultipartUpload(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// after data has been checked & extracted, write it to disk
|
||||
if len(config.Conf.Torrents.FileStorage) > 0 {
|
||||
err := writeTorrentToDisk(tfile, r.Infohash+".torrent", &r.Filepath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
r.Filepath = ""
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateMultipartUpload : Check if multipart upload is valid
|
||||
func (r *TorrentRequest) ValidateMultipartUpload(req *http.Request) (int64, error, int) {
|
||||
tfile, _, err := req.FormFile("torrent")
|
||||
func (r *TorrentRequest) ValidateMultipartUpload(req *http.Request) (multipart.File, error) {
|
||||
// first: parse torrent file (if any) to fill missing information
|
||||
tfile, _, err := req.FormFile(uploadFormTorrent)
|
||||
if err == nil {
|
||||
var torrent metainfo.TorrentFile
|
||||
|
||||
// decode torrent
|
||||
if _, err = tfile.Seek(0, io.SeekStart); err != nil {
|
||||
return 0, err, http.StatusInternalServerError
|
||||
_, seekErr := tfile.Seek(0, io.SeekStart)
|
||||
if seekErr != nil {
|
||||
return tfile, seekErr
|
||||
}
|
||||
if err = bencode.NewDecoder(tfile).Decode(&torrent); err != nil {
|
||||
return 0, err, http.StatusInternalServerError
|
||||
err = bencode.NewDecoder(tfile).Decode(&torrent)
|
||||
if err != nil {
|
||||
return tfile, metainfo.ErrInvalidTorrentFile
|
||||
}
|
||||
|
||||
// check a few things
|
||||
if torrent.IsPrivate() {
|
||||
return 0, errors.New("private torrents not allowed"), http.StatusNotAcceptable
|
||||
return tfile, errPrivateTorrent
|
||||
}
|
||||
trackers := torrent.GetAllAnnounceURLS()
|
||||
trackers = uploadService.CheckTrackers(trackers)
|
||||
if len(trackers) == 0 {
|
||||
return 0, errors.New("tracker(s) not allowed"), http.StatusNotAcceptable
|
||||
r.Trackers = uploadService.CheckTrackers(trackers)
|
||||
if len(r.Trackers) == 0 {
|
||||
return tfile, errTrackerProblem
|
||||
}
|
||||
if r.Name == "" {
|
||||
|
||||
// Name
|
||||
if len(r.Name) == 0 {
|
||||
r.Name = torrent.TorrentName()
|
||||
}
|
||||
|
||||
_, err = tfile.Seek(0, io.SeekStart)
|
||||
if err != nil {
|
||||
return 0, err, http.StatusInternalServerError
|
||||
// Magnet link: if a file is provided it should be empty
|
||||
if len(r.Magnet) != 0 {
|
||||
return tfile, errTorrentPlusMagnet
|
||||
}
|
||||
|
||||
_, seekErr = tfile.Seek(0, io.SeekStart)
|
||||
if seekErr != nil {
|
||||
return tfile, seekErr
|
||||
}
|
||||
infohash, err := metainfo.DecodeInfohash(tfile)
|
||||
if err != nil {
|
||||
return 0, err, http.StatusInternalServerError
|
||||
return tfile, metainfo.ErrInvalidTorrentFile
|
||||
}
|
||||
r.Hash = infohash
|
||||
r.Infohash = infohash
|
||||
r.Magnet = util.InfoHashToMagnet(infohash, r.Name, trackers...)
|
||||
|
||||
// extract filesize
|
||||
filesize := int64(torrent.TotalSize())
|
||||
err, code := r.ValidateUpload()
|
||||
return filesize, err, code
|
||||
}
|
||||
return 0, err, http.StatusInternalServerError
|
||||
}
|
||||
r.Filesize = int64(torrent.TotalSize())
|
||||
|
||||
// ValidateUpdate : Check if an update is valid
|
||||
func (r *TorrentRequest) ValidateUpdate() (err error, code int) {
|
||||
validators := []func(r *TorrentRequest) (error, int){
|
||||
validateName,
|
||||
validateCategory,
|
||||
validateSubCategory,
|
||||
validateMagnet,
|
||||
validateHash,
|
||||
validateWebsiteLink,
|
||||
}
|
||||
|
||||
//don't update not requested values
|
||||
//rewrite with reflect?
|
||||
for i, validator := range validators {
|
||||
if (r.Name == "" && i == 0) || (r.Category == 0 && i == 1) ||
|
||||
(r.SubCategory == 0 && i == 2) ||
|
||||
(r.Hash != "" || r.Magnet == "" && i == 3) || (r.Hash == "" && i == 4) {
|
||||
continue
|
||||
// extract filelist
|
||||
fileInfos := torrent.Info.GetFiles()
|
||||
for _, fileInfo := range fileInfos {
|
||||
r.FileList = append(r.FileList, uploadedFile{
|
||||
Path: fileInfo.Path,
|
||||
Filesize: int64(fileInfo.Length),
|
||||
})
|
||||
}
|
||||
err, code = validator(r)
|
||||
} else {
|
||||
err = r.validateMagnet()
|
||||
if err != nil {
|
||||
break
|
||||
return tfile, err
|
||||
}
|
||||
}
|
||||
err = r.validateHash()
|
||||
if err != nil {
|
||||
return tfile, err
|
||||
}
|
||||
// TODO: Get Trackers from magnet URL
|
||||
r.Filesize = 0
|
||||
r.Filepath = ""
|
||||
|
||||
return err, code
|
||||
return tfile, nil
|
||||
}
|
||||
return tfile, err
|
||||
}
|
||||
|
||||
// UpdateTorrent : Update torrent model
|
||||
//rewrite with reflect ?
|
||||
func (r *UpdateRequest) UpdateTorrent(t *model.Torrent) {
|
||||
func (r *UpdateRequest) UpdateTorrent(t *model.Torrent, currentUser *model.User) {
|
||||
if r.Update.Name != "" {
|
||||
t.Name = r.Update.Name
|
||||
}
|
||||
if r.Update.Hash != "" {
|
||||
t.Hash = r.Update.Hash
|
||||
if r.Update.Infohash != "" {
|
||||
t.Hash = r.Update.Infohash
|
||||
}
|
||||
if r.Update.Category != 0 {
|
||||
t.Category = r.Update.Category
|
||||
if r.Update.CategoryID != 0 {
|
||||
t.Category = r.Update.CategoryID
|
||||
}
|
||||
if r.Update.SubCategory != 0 {
|
||||
t.SubCategory = r.Update.SubCategory
|
||||
if r.Update.SubCategoryID != 0 {
|
||||
t.SubCategory = r.Update.SubCategoryID
|
||||
}
|
||||
if r.Update.Description != "" {
|
||||
t.Description = r.Update.Description
|
||||
|
@ -265,4 +439,39 @@ func (r *UpdateRequest) UpdateTorrent(t *model.Torrent) {
|
|||
if r.Update.WebsiteLink != "" {
|
||||
t.WebsiteLink = r.Update.WebsiteLink
|
||||
}
|
||||
status := model.TorrentStatusNormal
|
||||
if r.Update.Remake { // overrides trusted
|
||||
status = model.TorrentStatusRemake
|
||||
} else if currentUser.IsTrusted() {
|
||||
status = model.TorrentStatusTrusted
|
||||
}
|
||||
t.Status = status
|
||||
}
|
||||
|
||||
func writeTorrentToDisk(file multipart.File, name string, fullpath *string) error {
|
||||
_, seekErr := file.Seek(0, io.SeekStart)
|
||||
if seekErr != nil {
|
||||
return seekErr
|
||||
}
|
||||
b, err := ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*fullpath = fmt.Sprintf("%s%c%s", config.Conf.Torrents.FileStorage, os.PathSeparator, name)
|
||||
return ioutil.WriteFile(*fullpath, b, 0644)
|
||||
}
|
||||
|
||||
// NewTorrentRequest : creates a new torrent request struc with some default value
|
||||
func NewTorrentRequest(params ...string) (torrentRequest TorrentRequest) {
|
||||
if len(params) > 1 {
|
||||
torrentRequest.Category = params[0]
|
||||
} else {
|
||||
torrentRequest.Category = "3_12"
|
||||
}
|
||||
if len(params) > 2 {
|
||||
torrentRequest.Description = params[1]
|
||||
} else {
|
||||
torrentRequest.Description = "Description"
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package torrentService
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
@ -10,8 +11,13 @@ import (
|
|||
"github.com/NyaaPantsu/nyaa/db"
|
||||
"github.com/NyaaPantsu/nyaa/model"
|
||||
"github.com/NyaaPantsu/nyaa/service"
|
||||
"github.com/NyaaPantsu/nyaa/service/notifier"
|
||||
"github.com/NyaaPantsu/nyaa/service/user"
|
||||
"github.com/NyaaPantsu/nyaa/service/user/permission"
|
||||
"github.com/NyaaPantsu/nyaa/util"
|
||||
"github.com/NyaaPantsu/nyaa/util/log"
|
||||
"github.com/NyaaPantsu/nyaa/util/publicSettings"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
/* Function to interact with Models
|
||||
|
@ -296,3 +302,41 @@ func GetDeletedTorrents(parameters *serviceBase.WhereParams, orderBy string, lim
|
|||
torrents, count, err = getTorrentsOrderBy(parameters, orderBy, limit, offset, true, true, true)
|
||||
return
|
||||
}
|
||||
|
||||
// ExistOrDelete : Check if a torrent exist with the same hash and if it can be replaced, it is replaced
|
||||
func ExistOrDelete(hash string, user *model.User) error {
|
||||
torrentIndb := model.Torrent{}
|
||||
db.ORM.Unscoped().Model(&model.Torrent{}).Where("torrent_hash = ?", hash).First(&torrentIndb)
|
||||
if torrentIndb.ID > 0 {
|
||||
if userPermission.CurrentUserIdentical(user, torrentIndb.UploaderID) && torrentIndb.IsDeleted() && !torrentIndb.IsBlocked() { // if torrent is not locked and is deleted and the user is the actual owner
|
||||
_, err := DefinitelyDeleteTorrent(strconv.Itoa(int(torrentIndb.ID)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return errors.New("Torrent already in database")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewTorrentEvent : Should be called when you create a new torrent
|
||||
func NewTorrentEvent(router *mux.Router, user *model.User, torrent *model.Torrent) error {
|
||||
url, err := router.Get("view_torrent").URL("id", strconv.FormatUint(uint64(torrent.ID), 10))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if user.ID > 0 && config.Conf.Users.DefaultUserSettings["new_torrent"] { // If we are a member and notifications for new torrents are enabled
|
||||
userService.GetFollowers(user) // We populate the liked field for users
|
||||
if len(user.Followers) > 0 { // If we are followed by at least someone
|
||||
for _, follower := range user.Followers {
|
||||
follower.ParseSettings() // We need to call it before checking settings
|
||||
if follower.Settings.Get("new_torrent") {
|
||||
T, _, _ := publicSettings.TfuncAndLanguageWithFallback(follower.Language, follower.Language) // We need to send the notification to every user in their language
|
||||
notifierService.NotifyUser(&follower, torrent.Identifier(), fmt.Sprintf(T("new_torrent_uploaded"), torrent.Name, user.Username), url.String(), follower.Settings.Get("new_torrent_email"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@ func CheckTrackers(trackers []string) []string {
|
|||
}
|
||||
|
||||
// IsUploadEnabled : Check if upload is enabled in config
|
||||
func IsUploadEnabled(u model.User) bool {
|
||||
func IsUploadEnabled(u *model.User) bool {
|
||||
if config.Conf.Torrents.UploadsDisabled {
|
||||
if config.Conf.Torrents.AdminsAreStillAllowedTo && u.IsModerator() {
|
||||
return true
|
||||
|
|
|
@ -269,32 +269,37 @@ func RetrieveCurrentUser(r *http.Request) (model.User, int, error) {
|
|||
}
|
||||
|
||||
// RetrieveUserByEmail retrieves a user by an email
|
||||
func RetrieveUserByEmail(email string) (*model.PublicUser, string, int, error) {
|
||||
func RetrieveUserByEmail(email string) (*model.User, string, int, error) {
|
||||
var user model.User
|
||||
if db.ORM.Unscoped().Where("email = ?", email).First(&user).RecordNotFound() {
|
||||
return &model.PublicUser{User: &user}, email, http.StatusNotFound, errors.New("user not found")
|
||||
return &user, email, http.StatusNotFound, errors.New("user not found")
|
||||
}
|
||||
return &model.PublicUser{User: &user}, email, http.StatusOK, nil
|
||||
return &user, email, http.StatusOK, nil
|
||||
}
|
||||
|
||||
// RetrieveUserByAPIToken retrieves a user by an API token
|
||||
func RetrieveUserByAPIToken(apiToken string) (*model.User, string, int, error) {
|
||||
var user model.User
|
||||
if db.ORM.Unscoped().Where("api_token = ?", apiToken).First(&user).RecordNotFound() {
|
||||
return &user, apiToken, http.StatusNotFound, errors.New("user not found")
|
||||
}
|
||||
return &user, apiToken, http.StatusOK, nil
|
||||
}
|
||||
|
||||
// RetrieveUsersByEmail retrieves users by an email
|
||||
func RetrieveUsersByEmail(email string) []*model.PublicUser {
|
||||
func RetrieveUsersByEmail(email string) []*model.User {
|
||||
var users []*model.User
|
||||
var userArr []*model.PublicUser
|
||||
db.ORM.Where("email = ?", email).Find(&users)
|
||||
for _, user := range users {
|
||||
userArr = append(userArr, &model.PublicUser{User: user})
|
||||
}
|
||||
return userArr
|
||||
return users
|
||||
}
|
||||
|
||||
// RetrieveUserByUsername retrieves a user by username.
|
||||
func RetrieveUserByUsername(username string) (*model.PublicUser, string, int, error) {
|
||||
func RetrieveUserByUsername(username string) (*model.User, string, int, error) {
|
||||
var user model.User
|
||||
if db.ORM.Where("username = ?", username).First(&user).RecordNotFound() {
|
||||
return &model.PublicUser{User: &user}, username, http.StatusNotFound, errors.New("user not found")
|
||||
return &user, username, http.StatusNotFound, errors.New("user not found")
|
||||
}
|
||||
return &model.PublicUser{User: &user}, username, http.StatusOK, nil
|
||||
return &user, username, http.StatusOK, nil
|
||||
}
|
||||
|
||||
// RetrieveOldUploadsByUsername retrieves olduploads by username
|
||||
|
|
Référencer dans un nouveau ticket