Added Https service. Fix for #188 How enable https? (#312)

* https service added on port 8091 default

* https port check

* format

* readme

* readme

* readme

* readme

---------

Co-authored-by: evfedoto <evfedoto@cisco.com>
Co-authored-by: nikk <tsynik@gmail.com>
This commit is contained in:
Evgeni
2023-11-13 00:50:11 +01:00
committed by GitHub
parent 792f87380d
commit a91e6eb11b
23 changed files with 46811 additions and 2120 deletions

5
.gitignore vendored
View File

@@ -35,3 +35,8 @@ toolchains/
/toolchain/
/test/
/server/web/pages/template/pages/msx
server/config.db
/server/web/pages/template/pages/
server/web/pages/template/route.go
server/server.pem
server/server.key

View File

@@ -4,7 +4,7 @@ TorrServer, stream torrent to http
### Installation
Just download server from releases and exec file\
https://github.com/YouROK/TorrServer/releases \
After open browser link http://127.0.0.1:8090 \
Then open the browser link http://127.0.0.1:8090 or https://127.0.0.1:8091 if the server was started with --ssl option \
On linux systems you may need to set the environment variable before run \
***export GODEBUG=madvdontneed=1***
@@ -38,11 +38,19 @@ path/to/Android/sdk/ndk/ver/toolchains/llvm/prebuilt/platform
#
### Server args:
#### Usage
TorrServer-darwin-arm64 [--port PORT] [--path PATH] [--logpath LOGPATH] [--weblogpath WEBLOGPATH] [--rdb] [--httpauth] [--dontkill] [--ui] [--torrentsdir TORRENTSDIR] [--torrentaddr TORRENTADDR] [--pubipv4 PUBIPV4] [--pubipv6 PUBIPV6] [--searchwa]
TorrServer-darwin-arm64 [--port PORT] [--ssl] [--sslport PORT] [--sslcert PATH] [--sslkey PATH] [--path PATH] [--logpath LOGPATH] [--weblogpath WEBLOGPATH] [--rdb] [--httpauth] [--dontkill] [--ui] [--torrentsdir TORRENTSDIR] [--torrentaddr TORRENTADDR] [--pubipv4 PUBIPV4] [--pubipv6 PUBIPV6] [--searchwa]
#### Options
* --port PORT, -p PORT
* web server port, default 8090
* --ssl
* enables https
* --sslport PORT
* web server ssl port, If not set, will be set to default 8091 or taken from db(if stored previously). Accepted if --ssl enabled.
* --sslcert PATH
* path to ssl cert file. If not set, will be taken from db(if stored previously) or default self-signed certificate/key will be generated. Accepted if --ssl enabled.
* --sslkey PATH
* path to ssl key file. If not set, will be taken from db(if stored previously) or default self-signed certificate/key will be generated. Accepted if --ssl enabled.
* --path PATH, -d PATH
* database dir path
* --logpath LOGPATH, -l LOGPATH

View File

@@ -23,6 +23,10 @@ import (
type args struct {
Port string `arg:"-p" help:"web server port, default 8090"`
Ssl bool `help:"enables https"`
SslPort string `help:"web server ssl port, If not set, will be set to default 8091 or taken from db(if stored previously). Accepted if --ssl enabled."`
SslCert string `help:"path to ssl cert file. If not set, will be taken from db(if stored previously) or default self-signed certificate/key will be generated. Accepted if --ssl enabled."`
SslKey string `help:"path to ssl key file. If not set, will be taken from db(if stored previously) or default self-signed certificate/key will be generated. Accepted if --ssl enabled."`
Path string `arg:"-d" help:"database dir path"`
LogPath string `arg:"-l" help:"server log file path"`
WebLogPath string `arg:"-w" help:"web access log file path"`
@@ -56,6 +60,10 @@ func main() {
params.Port = "8090"
}
if params.SslPort == "" {
params.SslPort = "8091"
}
settings.Path = params.Path
settings.HttpAuth = params.HttpAuth
log.Init(params.LogPath, params.WebLogPath)
@@ -71,7 +79,11 @@ func main() {
if params.UI {
go func() {
time.Sleep(time.Second)
browser.OpenURL("http://127.0.0.1:" + params.Port)
if params.Ssl {
browser.OpenURL("https://127.0.0.1:" + params.SslPort)
} else {
browser.OpenURL("http://127.0.0.1:" + params.Port)
}
}()
}
@@ -91,7 +103,7 @@ func main() {
go watchTDir(params.TorrentsDir)
}
server.Start(params.Port, params.RDB, params.SearchWA)
server.Start(params.Port, params.SslPort, params.SslCert, params.SslKey, params.Ssl, params.RDB, params.SearchWA)
log.TLogln(server.WaitServer())
log.Close()
time.Sleep(time.Second * 3)

View File

@@ -236,13 +236,23 @@ func loadTorrent(path, host string) (ret []interface{}) {
func getLink(host, path string) string {
if !strings.HasPrefix(host, "http") {
host = "http://" + host
if settings.Ssl {
host = "https://" + host
} else {
host = "http://" + host
}
}
pos := strings.LastIndex(host, ":")
if pos > 7 {
host = host[:pos]
}
return host + ":" + settings.Port + "/" + path
if settings.Ssl {
return host + ":" + settings.SslPort + "/" + path
} else {
return host + ":" + settings.Port + "/" + path
}
}
func getObjFromTorrent(path, parent, host string, torr *torr.Torrent, file *state.TorrentFileStat) (ret interface{}) {

View File

@@ -10,8 +10,40 @@ import (
"server/web"
)
func Start(port string, roSets, searchWA bool) {
func Start(port, sslport, sslCert, sslKey string, sslEnabled, roSets, searchWA bool) {
settings.InitSets(roSets, searchWA)
//// https checks
// check if ssl enabled
settings.Ssl = sslEnabled
settings.BTsets.Ssl = sslEnabled
if sslEnabled {
// set settings ssl enabled
if sslport == "" {
if settings.BTsets.SslPort == "" {
settings.BTsets.SslPort = "8091"
}
} else {
settings.BTsets.SslPort = sslport
}
// check if ssl cert and key files exist
if sslCert != "" && sslKey != "" {
// set settings ssl cert and key files
settings.BTsets.SslCert = sslCert
settings.BTsets.SslKey = sslKey
}
log.TLogln("Check web ssl port", settings.BTsets.SslPort)
l, err := net.Listen("tcp", ":"+settings.BTsets.SslPort)
if l != nil {
l.Close()
}
if err != nil {
log.TLogln("Port", settings.BTsets.SslPort, "already in use! Please set different port for HTTP. Abort")
os.Exit(1)
}
}
// http checks
if port == "" {
port = "8090"
}
@@ -21,13 +53,16 @@ func Start(port string, roSets, searchWA bool) {
l.Close()
}
if err != nil {
log.TLogln("Port", port, "already in use! Abort")
log.TLogln("Port", port, "already in use! Please set different sslport for HTTPS. Abort")
os.Exit(1)
} else {
go cleanCache()
settings.Port = port
web.Start(port)
}
// set settings http and https ports. Start web server.
go cleanCache()
settings.Port = port
settings.SslPort = settings.BTsets.SslPort
web.Start()
}
func cleanCache() {

View File

@@ -46,6 +46,12 @@ type BTSets struct {
UploadRateLimit int // in kb, 0 - inf
ConnectionsLimit int
PeersListenPort int
//Https
Ssl bool
SslPort string
SslCert string
SslKey string
}
func (v *BTSets) String() string {

View File

@@ -11,6 +11,8 @@ var (
tdb *TDB
Path string
Port string
SslPort string
Ssl bool
ReadOnly bool
HttpAuth bool
SearchWA bool

View File

@@ -71,6 +71,9 @@ func (t *Torrent) Preload(index int, size int64) {
if ffprobe.Exists() {
link := "http://127.0.0.1:" + settings.Port + "/play/" + t.Hash().HexString() + "/" + strconv.Itoa(index)
if settings.Ssl {
link = "https://127.0.0.1:" + settings.SslPort + "/play/" + t.Hash().HexString() + "/" + strconv.Itoa(index)
}
if data, err := ffprobe.ProbeUrl(link); err == nil {
t.BitRate = data.Format.BitRate
t.DurationSeconds = data.Format.DurationSeconds

View File

@@ -21,6 +21,9 @@ func ffp(c *gin.Context) {
}
link := "http://127.0.0.1:" + sets.Port + "/play/" + hash + "/" + indexStr
if sets.Ssl {
link = "https://127.0.0.1:" + sets.SslPort + "/play/" + hash + "/" + indexStr
}
data, err := ffprobe.ProbeUrl(link)
if err != nil {

View File

@@ -118,20 +118,20 @@ var Mstile150x150png []byte
//go:embed pages/site.webmanifest
var Sitewebmanifest []byte
//go:embed pages/static/js/2.84d4a004.chunk.js
var Staticjs284d4a004chunkjs []byte
//go:embed pages/static/js/2.0d7c02d7.chunk.js
var Staticjs20d7c02d7chunkjs []byte
//go:embed pages/static/js/2.84d4a004.chunk.js.LICENSE.txt
var Staticjs284d4a004chunkjsLICENSEtxt []byte
//go:embed pages/static/js/2.0d7c02d7.chunk.js.LICENSE.txt
var Staticjs20d7c02d7chunkjsLICENSEtxt []byte
//go:embed pages/static/js/2.84d4a004.chunk.js.map
var Staticjs284d4a004chunkjsmap []byte
//go:embed pages/static/js/2.0d7c02d7.chunk.js.map
var Staticjs20d7c02d7chunkjsmap []byte
//go:embed pages/static/js/main.cc15501f.chunk.js
var Staticjsmaincc15501fchunkjs []byte
//go:embed pages/static/js/main.7efc8add.chunk.js
var Staticjsmain7efc8addchunkjs []byte
//go:embed pages/static/js/main.cc15501f.chunk.js.map
var Staticjsmaincc15501fchunkjsmap []byte
//go:embed pages/static/js/main.7efc8add.chunk.js.map
var Staticjsmain7efc8addchunkjsmap []byte
//go:embed pages/static/js/runtime-main.64d07802.js
var Staticjsruntimemain64d07802js []byte

View File

@@ -1,17 +1,17 @@
{
"files": {
"main.js": "/static/js/main.cc15501f.chunk.js",
"main.js.map": "/static/js/main.cc15501f.chunk.js.map",
"main.js": "/static/js/main.7efc8add.chunk.js",
"main.js.map": "/static/js/main.7efc8add.chunk.js.map",
"runtime-main.js": "/static/js/runtime-main.64d07802.js",
"runtime-main.js.map": "/static/js/runtime-main.64d07802.js.map",
"static/js/2.84d4a004.chunk.js": "/static/js/2.84d4a004.chunk.js",
"static/js/2.84d4a004.chunk.js.map": "/static/js/2.84d4a004.chunk.js.map",
"static/js/2.0d7c02d7.chunk.js": "/static/js/2.0d7c02d7.chunk.js",
"static/js/2.0d7c02d7.chunk.js.map": "/static/js/2.0d7c02d7.chunk.js.map",
"index.html": "/index.html",
"static/js/2.84d4a004.chunk.js.LICENSE.txt": "/static/js/2.84d4a004.chunk.js.LICENSE.txt"
"static/js/2.0d7c02d7.chunk.js.LICENSE.txt": "/static/js/2.0d7c02d7.chunk.js.LICENSE.txt"
},
"entrypoints": [
"static/js/runtime-main.64d07802.js",
"static/js/2.84d4a004.chunk.js",
"static/js/main.cc15501f.chunk.js"
"static/js/2.0d7c02d7.chunk.js",
"static/js/main.7efc8add.chunk.js"
]
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,94 +0,0 @@
/*
object-assign
(c) Sindre Sorhus
@license MIT
*/
/*!
* The buffer module from node.js, for the browser.
*
* @author Feross Aboukhadijeh <http://feross.org>
* @license MIT
*/
/*! blob-to-buffer. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */
/*! https://mths.be/punycode v1.4.1 by @mathias */
/*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh <https://feross.org/opensource> */
/*! magnet-uri. MIT License. WebTorrent LLC <https://webtorrent.io/opensource> */
/*! parse-torrent. MIT License. WebTorrent LLC <https://webtorrent.io/opensource> */
/*! queue-microtask. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */
/*! safe-buffer. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */
/*! simple-concat. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */
/*! simple-get. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */
/**
* A better abstraction over CSS.
*
* @copyright Oleg Isonen (Slobodskoi) / Isonen 2014-present
* @website https://github.com/cssinjs/jss
* @license MIT
*/
/** @license React v0.20.2
* scheduler.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/** @license React v16.13.1
* react-is.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/** @license React v17.0.2
* react-dom.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/** @license React v17.0.2
* react-is.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/** @license React v17.0.2
* react-jsx-runtime.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/** @license React v17.0.2
* react.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -161,24 +161,24 @@ func RouteWebPages(route *gin.RouterGroup) {
c.Data(200, "application/manifest+json", Sitewebmanifest)
})
route.GET("/static/js/2.84d4a004.chunk.js", func(c *gin.Context) {
c.Data(200, "application/javascript; charset=utf-8", Staticjs284d4a004chunkjs)
route.GET("/static/js/2.0d7c02d7.chunk.js", func(c *gin.Context) {
c.Data(200, "application/javascript; charset=utf-8", Staticjs20d7c02d7chunkjs)
})
route.GET("/static/js/2.84d4a004.chunk.js.LICENSE.txt", func(c *gin.Context) {
c.Data(200, "text/plain; charset=utf-8", Staticjs284d4a004chunkjsLICENSEtxt)
route.GET("/static/js/2.0d7c02d7.chunk.js.LICENSE.txt", func(c *gin.Context) {
c.Data(200, "text/plain; charset=utf-8", Staticjs20d7c02d7chunkjsLICENSEtxt)
})
route.GET("/static/js/2.84d4a004.chunk.js.map", func(c *gin.Context) {
c.Data(200, "application/json", Staticjs284d4a004chunkjsmap)
route.GET("/static/js/2.0d7c02d7.chunk.js.map", func(c *gin.Context) {
c.Data(200, "application/json", Staticjs20d7c02d7chunkjsmap)
})
route.GET("/static/js/main.cc15501f.chunk.js", func(c *gin.Context) {
c.Data(200, "application/javascript; charset=utf-8", Staticjsmaincc15501fchunkjs)
route.GET("/static/js/main.7efc8add.chunk.js", func(c *gin.Context) {
c.Data(200, "application/javascript; charset=utf-8", Staticjsmain7efc8addchunkjs)
})
route.GET("/static/js/main.cc15501f.chunk.js.map", func(c *gin.Context) {
c.Data(200, "application/json", Staticjsmaincc15501fchunkjsmap)
route.GET("/static/js/main.7efc8add.chunk.js.map", func(c *gin.Context) {
c.Data(200, "application/json", Staticjsmain7efc8addchunkjsmap)
})
route.GET("/static/js/runtime-main.64d07802.js", func(c *gin.Context) {

View File

@@ -22,6 +22,7 @@ import (
"server/web/auth"
"server/web/blocker"
"server/web/pages"
"server/web/sslcerts"
)
var (
@@ -29,7 +30,7 @@ var (
waitChan = make(chan error)
)
func Start(port string) {
func Start() {
log.TLogln("Start TorrServer " + version.Version + " torrent " + version.GetTorrentVersion())
ips := getLocalIps()
if len(ips) > 0 {
@@ -71,8 +72,36 @@ func Start(port string) {
if settings.BTsets.EnableDLNA {
dlna.Start()
}
log.TLogln("Start web server at port", port)
waitChan <- route.Run(":" + port)
log.TLogln(settings.BTsets)
//check if https enabled
if settings.Ssl {
//if no cert and key files set in db/settings, generate new self-signed cert and key files
if settings.BTsets.SslCert == "" || settings.BTsets.SslKey == "" {
settings.BTsets.SslCert, settings.BTsets.SslKey = sslcerts.MakeCertKeyFiles(ips)
log.TLogln("Saving path to ssl cert and key in db", settings.BTsets.SslCert, settings.BTsets.SslKey)
settings.SetBTSets(settings.BTsets)
}
//verify if cert and key files are valid
err = sslcerts.VerifyCertKeyFiles(settings.BTsets.SslCert, settings.BTsets.SslKey, settings.SslPort)
//if not valid, generate new self-signed cert and key files
if err != nil {
log.TLogln("Error checking certificate and private key files:", err)
settings.BTsets.SslCert, settings.BTsets.SslKey = sslcerts.MakeCertKeyFiles(ips)
log.TLogln("Saving path to ssl cert and key in db", settings.BTsets.SslCert, settings.BTsets.SslKey)
settings.SetBTSets(settings.BTsets)
}
go func() {
log.TLogln("Starting https server at port", settings.SslPort)
waitChan <- route.RunTLS(":"+settings.SslPort, settings.BTsets.SslCert, settings.BTsets.SslKey)
}()
}
go func() {
log.TLogln("Start http server at port", settings.Port)
waitChan <- route.Run(":" + settings.Port)
}()
}
func Wait() error {

View File

@@ -0,0 +1,144 @@
package sslcerts
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"errors"
"math/big"
"net"
"os"
"path/filepath"
"server/log"
"time"
)
func generateSelfSignedCert(ips []string) ([]byte, []byte, error) {
priv, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
if err != nil {
return nil, nil, err
}
notBefore := time.Now()
notAfter := notBefore.Add(365 * 24 * time.Hour) // Valid for 1 year
serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
if err != nil {
return nil, nil, err
}
netIps := make([]net.IP, 0)
if len(ips) != 0 {
for _, ip := range ips {
netIps = append(netIps, net.ParseIP(ip))
}
}
template := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
Organization: []string{"Torrserver"},
},
NotBefore: notBefore,
NotAfter: notAfter,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
DNSNames: []string{"localhost"},
IPAddresses: netIps,
}
certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
if err != nil {
return nil, nil, err
}
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
privBytes, err := x509.MarshalECPrivateKey(priv)
if err != nil {
return nil, nil, err
}
privPEM := pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: privBytes})
return certPEM, privPEM, nil
}
func MakeCertKeyFiles(ips []string) (string, string) {
certPEM, privPEM, err := generateSelfSignedCert(ips)
if err != nil {
log.TLogln("Error generating certificate:", err)
os.Exit(1)
}
certFile, err := os.Create("server.pem")
if err != nil {
log.TLogln("Error creating certificate file:", err)
os.Exit(1)
}
defer certFile.Close()
privFile, err := os.Create("server.key")
if err != nil {
log.TLogln("Error creating private key file:", err)
os.Exit(1)
}
defer privFile.Close()
_, err = certFile.Write(certPEM)
if err != nil {
log.TLogln("Error writing certificate file:", err)
os.Exit(1)
}
_, err = privFile.Write(privPEM)
if err != nil {
log.TLogln("Error writing private key file:", err)
os.Exit(1)
}
log.TLogln("Self-signed certificate and private key generated successfully.")
return getAbsPath("server.pem"), getAbsPath("server.key")
}
func getAbsPath(fileName string) string {
filePath, err := filepath.Abs(fileName)
if err != nil {
log.TLogln("Error getting absolute path:", err)
os.Exit(1)
}
return filePath
}
func VerifyCertKeyFiles(certFile, keyFile, port string) error {
// Load the certificate and key
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
return err
}
// Check if the certificate chain is expired
for _, cert := range cert.Certificate {
x509Cert, err := x509.ParseCertificate(cert)
if err != nil {
return err
}
if x509Cert.NotAfter.Before(time.Now()) {
return errors.New("certificate has expired")
}
}
// Create a TLS configuration
config := tls.Config{
Certificates: []tls.Certificate{cert},
}
// Create a listener to check the certificate and key
ln, err := tls.Listen("tcp", ":"+port, &config)
if err != nil {
return err
}
defer ln.Close()
log.TLogln("Certificate and key are valid.")
return nil
}

44278
web/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -56,7 +56,7 @@ const Table = memo(
return (
(season === selectedSeason || !seasonAmount?.length) && (
<tr key={id} className={isViewed ? 'viewed-file-row' : null}>
<td data-label='viewed' className={isViewed ? 'viewed-file-indicator' : null} />
<td data-label='viewed' aria-label='viewed' className={isViewed ? 'viewed-file-indicator' : null} />
<td data-label='name'>{shouldDisplayFullFileName ? path : title}</td>
{fileHasSeasonText && seasonAmount?.length === 1 && <td data-label='season'>{season}</td>}
{fileHasEpisodeText && <td data-label='episode'>{episode}</td>}

File diff suppressed because it is too large Load Diff