v2.0.0.0000
This commit is contained in:
592
src/internal/authentication/authentication.go
Executable file
592
src/internal/authentication/authentication.go
Executable file
@@ -0,0 +1,592 @@
|
||||
package authentication
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"crypto/hmac"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
|
||||
"time"
|
||||
//"fmt"
|
||||
//"log"
|
||||
)
|
||||
|
||||
const tokenLength = 40
|
||||
const saltLength = 20
|
||||
const idLength = 10
|
||||
|
||||
var tokenValidity int
|
||||
var database string
|
||||
|
||||
var databaseFile = "authentication.json"
|
||||
|
||||
var data = make(map[string]interface{})
|
||||
var tokens = make(map[string]interface{})
|
||||
|
||||
var initAuthentication = false
|
||||
|
||||
// Cookie : cookie
|
||||
type Cookie struct {
|
||||
Name string
|
||||
Value string
|
||||
Path string
|
||||
Domain string
|
||||
Expires time.Time
|
||||
RawExpires string
|
||||
}
|
||||
|
||||
// Framework examples
|
||||
|
||||
/*
|
||||
func main() {
|
||||
var err error
|
||||
|
||||
var checkErr = func(err error) {
|
||||
log.Println(err)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
err = Init("", 10) // Path to save the data, Validity of tokens in minutes | (error)
|
||||
if err != nil {
|
||||
checkErr(err)
|
||||
}
|
||||
|
||||
|
||||
err = CreateDefaultUser("admin", "123")
|
||||
if err != nil {
|
||||
checkErr(err)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
err = CreateNewUser("xteve", "xteve") // Username, Password | (error)
|
||||
if err != nil {
|
||||
checkErr(err)
|
||||
}
|
||||
|
||||
|
||||
|
||||
err, token := UserAuthentication("xteve", "xteve") // Username, Password | (error, token)
|
||||
if err != nil {
|
||||
checkErr(err)
|
||||
} else {
|
||||
fmt.Println("UserAuthentication()")
|
||||
fmt.Println("Token:", token)
|
||||
fmt.Println("---")
|
||||
}
|
||||
|
||||
err, newToken := CheckTheValidityOfTheToken(token) // Current token | (error, new token)
|
||||
if err != nil {
|
||||
checkErr(err)
|
||||
} else {
|
||||
fmt.Println("CheckTheValidityOfTheToken()")
|
||||
fmt.Println("New Token:", newToken)
|
||||
fmt.Println("---")
|
||||
}
|
||||
|
||||
err, userID := GetUserID(newToken) // Current token | (error, user id)
|
||||
if err != nil {
|
||||
checkErr(err)
|
||||
} else {
|
||||
fmt.Println("GetUserID()")
|
||||
fmt.Println("User ID:", userID)
|
||||
fmt.Println("---")
|
||||
}
|
||||
|
||||
|
||||
var userData = make(map[string]interface{})
|
||||
userData["type"] = "Administrator"
|
||||
err = WriteUserData(userID, userData) // User id, user data | (error)
|
||||
if err != nil {
|
||||
checkErr(err)
|
||||
}
|
||||
|
||||
err, userData = ReadUserData(userID) // User id | (error, userData)
|
||||
if err != nil {
|
||||
checkErr(err)
|
||||
} else {
|
||||
fmt.Println("ReadUserData()")
|
||||
fmt.Println("User data:", userData)
|
||||
fmt.Println("---")
|
||||
}
|
||||
|
||||
err = RemoveUser(userID)
|
||||
if err != nil {
|
||||
checkErr(err)
|
||||
}
|
||||
|
||||
}
|
||||
*/
|
||||
|
||||
// Init : databasePath = Path to authentication.json
|
||||
func Init(databasePath string, validity int) (err error) {
|
||||
database = filepath.Dir(databasePath) + string(os.PathSeparator) + databaseFile
|
||||
|
||||
// Check if the database already exists
|
||||
if _, err = os.Stat(database); os.IsNotExist(err) {
|
||||
// Create an empty database
|
||||
var defaults = make(map[string]interface{})
|
||||
defaults["dbVersion"] = "1.0"
|
||||
defaults["hash"] = "sha256"
|
||||
defaults["users"] = make(map[string]interface{})
|
||||
|
||||
if saveDatabase(defaults) != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Loading the database
|
||||
err = loadDatabase()
|
||||
|
||||
// Set Token Validity
|
||||
tokenValidity = validity
|
||||
initAuthentication = true
|
||||
return
|
||||
}
|
||||
|
||||
// CreateDefaultUser = created efault user
|
||||
func CreateDefaultUser(username, password string) (err error) {
|
||||
|
||||
err = checkInit()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var users = data["users"].(map[string]interface{})
|
||||
// Check if the default user exists
|
||||
if len(users) > 0 {
|
||||
err = createError(001)
|
||||
return
|
||||
}
|
||||
|
||||
var defaults = defaultsForNewUser(username, password)
|
||||
users[defaults["_id"].(string)] = defaults
|
||||
saveDatabase(data)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// CreateNewUser : create new user
|
||||
func CreateNewUser(username, password string) (userID string, err error) {
|
||||
|
||||
err = checkInit()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var checkIfTheUserAlreadyExists = func(username string, userData map[string]interface{}) (err error) {
|
||||
var salt = userData["_salt"].(string)
|
||||
var loginUsername = userData["_username"].(string)
|
||||
|
||||
if SHA256(username, salt) == loginUsername {
|
||||
err = createError(020)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var users = data["users"].(map[string]interface{})
|
||||
for _, userData := range users {
|
||||
err = checkIfTheUserAlreadyExists(username, userData.(map[string]interface{}))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var defaults = defaultsForNewUser(username, password)
|
||||
userID = defaults["_id"].(string)
|
||||
users[userID] = defaults
|
||||
|
||||
saveDatabase(data)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// UserAuthentication : user authentication
|
||||
func UserAuthentication(username, password string) (token string, err error) {
|
||||
|
||||
err = checkInit()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var login = func(username, password string, loginData map[string]interface{}) (err error) {
|
||||
err = createError(010)
|
||||
|
||||
var salt = loginData["_salt"].(string)
|
||||
var loginUsername = loginData["_username"].(string)
|
||||
var loginPassword = loginData["_password"].(string)
|
||||
|
||||
if SHA256(username, salt) == loginUsername {
|
||||
if SHA256(password, salt) == loginPassword {
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var users = data["users"].(map[string]interface{})
|
||||
for id, loginData := range users {
|
||||
err = login(username, password, loginData.(map[string]interface{}))
|
||||
if err == nil {
|
||||
token = setToken(id, "-")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// CheckTheValidityOfTheToken : check token
|
||||
func CheckTheValidityOfTheToken(token string) (newToken string, err error) {
|
||||
|
||||
err = checkInit()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = createError(011)
|
||||
|
||||
if v, ok := tokens[token]; ok {
|
||||
var expires = v.(map[string]interface{})["expires"].(time.Time)
|
||||
var userID = v.(map[string]interface{})["id"].(string)
|
||||
|
||||
if expires.Sub(time.Now().Local()) < 0 {
|
||||
return
|
||||
}
|
||||
|
||||
newToken = setToken(userID, token)
|
||||
|
||||
err = nil
|
||||
|
||||
} else {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetUserID : get user ID
|
||||
func GetUserID(token string) (userID string, err error) {
|
||||
|
||||
err = checkInit()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = createError(002)
|
||||
|
||||
if v, ok := tokens[token]; ok {
|
||||
var expires = v.(map[string]interface{})["expires"].(time.Time)
|
||||
userID = v.(map[string]interface{})["id"].(string)
|
||||
|
||||
if expires.Sub(time.Now().Local()) < 0 {
|
||||
return
|
||||
}
|
||||
|
||||
err = nil
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// WriteUserData : save user date
|
||||
func WriteUserData(userID string, userData map[string]interface{}) (err error) {
|
||||
|
||||
err = checkInit()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = createError(030)
|
||||
|
||||
if v, ok := data["users"].(map[string]interface{})[userID].(map[string]interface{}); ok {
|
||||
|
||||
v["data"] = userData
|
||||
err = saveDatabase(data)
|
||||
|
||||
} else {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ReadUserData : load user date
|
||||
func ReadUserData(userID string) (userData map[string]interface{}, err error) {
|
||||
|
||||
err = checkInit()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = createError(031)
|
||||
|
||||
if v, ok := data["users"].(map[string]interface{})[userID].(map[string]interface{}); ok {
|
||||
userData = v["data"].(map[string]interface{})
|
||||
err = nil
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// RemoveUser : remove user
|
||||
func RemoveUser(userID string) (err error) {
|
||||
|
||||
err = checkInit()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = createError(032)
|
||||
|
||||
if _, ok := data["users"].(map[string]interface{})[userID]; ok {
|
||||
|
||||
delete(data["users"].(map[string]interface{}), userID)
|
||||
err = saveDatabase(data)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// SetDefaultUserData : set default user data
|
||||
func SetDefaultUserData(defaults map[string]interface{}) (err error) {
|
||||
|
||||
allUserData, err := GetAllUserData()
|
||||
|
||||
for _, d := range allUserData {
|
||||
var data = d.(map[string]interface{})["data"].(map[string]interface{})
|
||||
var userID = d.(map[string]interface{})["_id"].(string)
|
||||
|
||||
for k, v := range defaults {
|
||||
if _, ok := data[k]; ok {
|
||||
// Key exist
|
||||
} else {
|
||||
data[k] = v
|
||||
}
|
||||
}
|
||||
err = WriteUserData(userID, data)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ChangeCredentials : change credentials
|
||||
func ChangeCredentials(userID, username, password string) (err error) {
|
||||
err = checkInit()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = createError(032)
|
||||
|
||||
if userData, ok := data["users"].(map[string]interface{})[userID]; ok {
|
||||
//var userData = tmp.(map[string]interface{})
|
||||
var salt = userData.(map[string]interface{})["_salt"].(string)
|
||||
|
||||
if len(username) > 0 {
|
||||
userData.(map[string]interface{})["_username"] = SHA256(username, salt)
|
||||
}
|
||||
|
||||
if len(password) > 0 {
|
||||
userData.(map[string]interface{})["_password"] = SHA256(password, salt)
|
||||
}
|
||||
|
||||
err = saveDatabase(data)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetAllUserData : get all user data
|
||||
func GetAllUserData() (allUserData map[string]interface{}, err error) {
|
||||
|
||||
err = checkInit()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(data) == 0 {
|
||||
var defaults = make(map[string]interface{})
|
||||
defaults["dbVersion"] = "1.0"
|
||||
defaults["hash"] = "sha256"
|
||||
defaults["users"] = make(map[string]interface{})
|
||||
saveDatabase(defaults)
|
||||
data = defaults
|
||||
}
|
||||
|
||||
allUserData = data["users"].(map[string]interface{})
|
||||
return
|
||||
}
|
||||
|
||||
// CheckTheValidityOfTheTokenFromHTTPHeader : get token from HTTP header
|
||||
func CheckTheValidityOfTheTokenFromHTTPHeader(w http.ResponseWriter, r *http.Request) (writer http.ResponseWriter, newToken string, err error) {
|
||||
err = createError(011)
|
||||
for _, cookie := range r.Cookies() {
|
||||
if cookie.Name == "Token" {
|
||||
var token string
|
||||
token, err = CheckTheValidityOfTheToken(cookie.Value)
|
||||
//fmt.Println("T", token, err)
|
||||
writer = SetCookieToken(w, token)
|
||||
newToken = token
|
||||
}
|
||||
}
|
||||
//fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Framework tools
|
||||
|
||||
func checkInit() (err error) {
|
||||
if initAuthentication == false {
|
||||
err = createError(000)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func saveDatabase(tmpMap interface{}) (err error) {
|
||||
|
||||
jsonString, err := json.MarshalIndent(tmpMap, "", " ")
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(database, []byte(jsonString), 0600)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func loadDatabase() (err error) {
|
||||
jsonString, err := ioutil.ReadFile(database)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = json.Unmarshal([]byte(jsonString), &data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// SHA256 : password + salt = sha256 string
|
||||
func SHA256(secret, salt string) string {
|
||||
key := []byte(secret)
|
||||
h := hmac.New(sha256.New, key)
|
||||
h.Write([]byte("_remote_db"))
|
||||
return base64.StdEncoding.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
|
||||
func randomString(n int) string {
|
||||
const alphanum = "-AbCdEfGhIjKlMnOpQrStUvWxYz0123456789aBcDeFgHiJkLmNoPqRsTuVwXyZ_"
|
||||
|
||||
var bytes = make([]byte, n)
|
||||
rand.Read(bytes)
|
||||
for i, b := range bytes {
|
||||
bytes[i] = alphanum[b%byte(len(alphanum))]
|
||||
}
|
||||
return string(bytes)
|
||||
}
|
||||
|
||||
func randomID(n int) string {
|
||||
const alphanum = "ABCDEFGHJKLMNOPQRSTUVWXYZ0123456789"
|
||||
|
||||
var bytes = make([]byte, n)
|
||||
rand.Read(bytes)
|
||||
for i, b := range bytes {
|
||||
bytes[i] = alphanum[b%byte(len(alphanum))]
|
||||
}
|
||||
return string(bytes)
|
||||
}
|
||||
|
||||
func createError(errCode int) (err error) {
|
||||
var errMsg string
|
||||
switch errCode {
|
||||
case 000:
|
||||
errMsg = "Authentication has not yet been initialized"
|
||||
case 001:
|
||||
errMsg = "Default user already exists"
|
||||
case 002:
|
||||
errMsg = "No user id found for this token"
|
||||
case 010:
|
||||
errMsg = "User authentication failed"
|
||||
case 011:
|
||||
errMsg = "Session has expired"
|
||||
case 020:
|
||||
errMsg = "User already exists"
|
||||
case 030:
|
||||
errMsg = "User data could not be saved"
|
||||
case 031:
|
||||
errMsg = "User data could not be read"
|
||||
case 032:
|
||||
errMsg = "User ID was not found"
|
||||
}
|
||||
|
||||
err = errors.New(errMsg)
|
||||
return
|
||||
}
|
||||
|
||||
func defaultsForNewUser(username, password string) map[string]interface{} {
|
||||
var defaults = make(map[string]interface{})
|
||||
var salt = randomString(saltLength)
|
||||
defaults["_username"] = SHA256(username, salt)
|
||||
defaults["_password"] = SHA256(password, salt)
|
||||
defaults["_salt"] = salt
|
||||
defaults["_id"] = "id-" + randomID(idLength)
|
||||
//defaults["_one.time.token"] = randomString(tokenLength)
|
||||
defaults["data"] = make(map[string]interface{})
|
||||
|
||||
return defaults
|
||||
}
|
||||
|
||||
func setToken(id, oldToken string) (newToken string) {
|
||||
delete(tokens, oldToken)
|
||||
|
||||
loopToken:
|
||||
newToken = randomString(tokenLength)
|
||||
if _, ok := tokens[newToken]; ok {
|
||||
goto loopToken
|
||||
}
|
||||
|
||||
var tmp = make(map[string]interface{})
|
||||
tmp["id"] = id
|
||||
tmp["expires"] = time.Now().Local().Add(time.Minute * time.Duration(tokenValidity))
|
||||
|
||||
tokens[newToken] = tmp
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func mapToJSON(tmpMap interface{}) string {
|
||||
jsonString, err := json.MarshalIndent(tmpMap, "", " ")
|
||||
if err != nil {
|
||||
return "{}"
|
||||
}
|
||||
return string(jsonString)
|
||||
}
|
||||
|
||||
// SetCookieToken : set cookie
|
||||
func SetCookieToken(w http.ResponseWriter, token string) http.ResponseWriter {
|
||||
expiration := time.Now().Add(time.Minute * time.Duration(tokenValidity))
|
||||
cookie := http.Cookie{Name: "Token", Value: token, Expires: expiration}
|
||||
http.SetCookie(w, &cookie)
|
||||
return w
|
||||
}
|
||||
84
src/internal/m3u-parser/m3u-parser_test.go
Normal file
84
src/internal/m3u-parser/m3u-parser_test.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package m3u
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type M3UStream struct {
|
||||
GroupTitle string `json:"group-title,required"`
|
||||
Name string `json:"name,required"`
|
||||
TvgID string `json:"tvg-id,required"`
|
||||
TvgLogo string `json:"tvg-logo,required"`
|
||||
TvgName string `json:"tvg-name,required"`
|
||||
URL string `json:"url,required"`
|
||||
UUIDKey string `json:"_uuid.key,omitempty"`
|
||||
UUIDValue string `json:"_uuid.value,omitempty"`
|
||||
}
|
||||
|
||||
func TestStream1(t *testing.T) {
|
||||
|
||||
var file = "test_list_1.m3u"
|
||||
var content, err = ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
streams, err := MakeInterfaceFromM3U(content)
|
||||
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
err = checkStream(streams)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
fmt.Println("Streams:", len(streams))
|
||||
t.Log(streams)
|
||||
|
||||
}
|
||||
|
||||
func checkStream(streamInterface []interface{}) (err error) {
|
||||
|
||||
for i, s := range streamInterface {
|
||||
|
||||
var stream = s.(map[string]string)
|
||||
var m3uStream M3UStream
|
||||
|
||||
jsonString, err := json.MarshalIndent(stream, "", " ")
|
||||
|
||||
if err == nil {
|
||||
|
||||
err = json.Unmarshal(jsonString, &m3uStream)
|
||||
if err == nil {
|
||||
|
||||
log.Print(fmt.Sprintf("Stream: %d", i))
|
||||
log.Print(fmt.Sprintf("Name*: %s", m3uStream.Name))
|
||||
log.Print(fmt.Sprintf("URL*: %s", m3uStream.URL))
|
||||
log.Print(fmt.Sprintf("tvg-name: %s", m3uStream.TvgName))
|
||||
log.Print(fmt.Sprintf("tvg-id**: %s", m3uStream.TvgID))
|
||||
log.Print(fmt.Sprintf("tvg-logo: %s", m3uStream.TvgLogo))
|
||||
log.Print(fmt.Sprintf("group-title**: %s", m3uStream.GroupTitle))
|
||||
|
||||
if len(m3uStream.UUIDKey) > 0 {
|
||||
log.Print(fmt.Sprintf("UUID key***: %s", m3uStream.UUIDKey))
|
||||
log.Print(fmt.Sprintf("UUID value: %s", m3uStream.UUIDValue))
|
||||
} else {
|
||||
log.Print(fmt.Sprintf("UUID key: false"))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
log.Println(fmt.Sprintf("- - - - - (*: Required) | (**: Nice to have) | (***: Love it) - - - - -"))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
7
src/internal/m3u-parser/test_list_1.m3u
Normal file
7
src/internal/m3u-parser/test_list_1.m3u
Normal file
@@ -0,0 +1,7 @@
|
||||
#EXTM3U url-tvg="http://example.com/file.xml" x-tvg-url="http://example.com/xteve.xml"
|
||||
#EXTINF:0 channelID="1" tvg-chno="1" tvg-name="Channel.1" tvg-id="tvg.id.1" tvg-logo="https://example/logo.png" group-title="Group 1",Channel 1
|
||||
http://example.com/stream/1
|
||||
|
||||
#EXTINF:0 channelID="2" tvg-chno="2" tvg-name="Channel.2" tvg-id="tvg.id.2" tvg-logo="https://example/logo.png" group-title="Group 2",Channel 2
|
||||
#123
|
||||
http://example.com/stream/2
|
||||
267
src/internal/m3u-parser/xteve_m3uParser.go
Executable file
267
src/internal/m3u-parser/xteve_m3uParser.go
Executable file
@@ -0,0 +1,267 @@
|
||||
package m3u
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// MakeInterfaceFromM3U :
|
||||
func MakeInterfaceFromM3U(byteStream []byte) (allChannels []interface{}, err error) {
|
||||
|
||||
var content = string(byteStream)
|
||||
var channelName string
|
||||
|
||||
var parseMetaData = func(channel string) (stream map[string]string) {
|
||||
|
||||
stream = make(map[string]string)
|
||||
var exceptForParameter = `[a-z-A-Z=]*(".*?")`
|
||||
var exceptForChannelName = `,([^\n]*|,[^\r]*)`
|
||||
|
||||
var lines = strings.Split(strings.Replace(channel, "\r\n", "\n", -1), "\n")
|
||||
|
||||
// Zeilen mit # und leerer Zeilen entfernen
|
||||
for i := len(lines) - 1; i >= 0; i-- {
|
||||
|
||||
if len(lines[i]) == 0 || lines[i][0:1] == "#" {
|
||||
lines = append(lines[:i], lines[i+1:]...)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if len(lines) >= 2 {
|
||||
|
||||
for _, line := range lines {
|
||||
|
||||
_, err := url.ParseRequestURI(line)
|
||||
|
||||
switch err {
|
||||
|
||||
case nil:
|
||||
stream["url"] = strings.Trim(line, "\r\n")
|
||||
|
||||
default:
|
||||
|
||||
var value string
|
||||
// Alle Parameter parsen
|
||||
var p = regexp.MustCompile(exceptForParameter)
|
||||
var streamParameter = p.FindAllString(line, -1)
|
||||
|
||||
for _, p := range streamParameter {
|
||||
|
||||
line = strings.Replace(line, p, "", 1)
|
||||
|
||||
p = strings.Replace(p, `"`, "", -1)
|
||||
var parameter = strings.Split(p, "=")
|
||||
|
||||
if len(parameter) == 2 {
|
||||
|
||||
// TVG Key als Kleinbuchstaben speichern
|
||||
switch strings.Contains(parameter[0], "tvg") {
|
||||
|
||||
case true:
|
||||
stream[strings.ToLower(parameter[0])] = parameter[1]
|
||||
case false:
|
||||
stream[parameter[0]] = parameter[1]
|
||||
|
||||
}
|
||||
|
||||
// URL's nicht an die Filterfunktion übergeben
|
||||
if !strings.Contains(parameter[1], "://") && len(parameter[1]) > 0 {
|
||||
value = value + parameter[1] + " "
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Kanalnamen parsen
|
||||
n := regexp.MustCompile(exceptForChannelName)
|
||||
var name = n.FindAllString(line, 1)
|
||||
|
||||
if len(name) > 0 {
|
||||
channelName = name[0]
|
||||
channelName = strings.Replace(channelName, `,`, "", 1)
|
||||
channelName = strings.TrimRight(channelName, "\r\n")
|
||||
channelName = strings.TrimRight(channelName, " ")
|
||||
}
|
||||
|
||||
if len(channelName) == 0 {
|
||||
|
||||
if v, ok := stream["tvg-name"]; ok {
|
||||
channelName = v
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
channelName = strings.TrimRight(channelName, " ")
|
||||
|
||||
// Kanäle ohne Namen werden augelassen
|
||||
if len(channelName) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
stream["name"] = channelName
|
||||
value = value + channelName
|
||||
|
||||
stream["_values"] = value
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Nach eindeutiger ID im Stream suchen
|
||||
for key, value := range stream {
|
||||
|
||||
if !strings.Contains(strings.ToLower(key), "tvg-id") {
|
||||
|
||||
if strings.Contains(strings.ToLower(key), "id") {
|
||||
|
||||
stream["_uuid.key"] = key
|
||||
stream["_uuid.value"] = value
|
||||
//os.Exit(0)
|
||||
break
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
//fmt.Println(content)
|
||||
|
||||
if strings.Contains(content, "#EXTM3U") {
|
||||
|
||||
var channels = strings.Split(content, "#EXTINF")
|
||||
|
||||
channels = append(channels[:0], channels[1:]...)
|
||||
|
||||
for _, channel := range channels {
|
||||
|
||||
var stream = parseMetaData(channel)
|
||||
|
||||
if len(stream) > 0 && stream != nil {
|
||||
allChannels = append(allChannels, stream)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
err = errors.New("No valid m3u file")
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// MakeInterfaceFromM3U2 :
|
||||
func MakeInterfaceFromM3U2(byteStream []byte) (allChannels []interface{}, err error) {
|
||||
var content = string(byteStream)
|
||||
//var allChannels = make([]interface{}, 0)
|
||||
|
||||
var channels = strings.Split(content, "#EXTINF")
|
||||
|
||||
var parseMetaData = func(metaData string) map[string]string {
|
||||
var values string // Save all values in a key
|
||||
var channel = make(map[string]string)
|
||||
|
||||
var exceptForParameter = `[a-z-A-Z=]*(".*?")`
|
||||
//var exceptForChannelName = `(,[^.$\n]*|,[^.$\r]*)`
|
||||
var exceptForChannelName = `(,[^\n]*|,[^\r]*)`
|
||||
|
||||
var exceptForStreamingURL = `(\n.*?\n|\r.*?\r|\n.*?\z|\r.*?\z)`
|
||||
//var exceptForStreamingURL = `^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?`
|
||||
|
||||
// Parse all parameters
|
||||
p := regexp.MustCompile(exceptForParameter)
|
||||
var parameter = p.FindAllString(metaData, -1)
|
||||
//fmt.Println(parameter)
|
||||
for _, i := range parameter {
|
||||
var remove = i
|
||||
i = strings.Replace(i, `"`, "", -1)
|
||||
if strings.Contains(i, "=") {
|
||||
var item = strings.Split(i, "=")
|
||||
switch strings.Contains(item[0], "tvg") {
|
||||
case true:
|
||||
channel[strings.ToLower(item[0])] = item[1]
|
||||
case false:
|
||||
channel[item[0]] = item[1]
|
||||
}
|
||||
|
||||
switch strings.Contains(item[1], "://") {
|
||||
case false:
|
||||
values = values + item[1] + " "
|
||||
}
|
||||
|
||||
}
|
||||
metaData = strings.Replace(metaData, remove, "", 1)
|
||||
}
|
||||
|
||||
// Parse channel name (after the comma)
|
||||
n := regexp.MustCompile(exceptForChannelName)
|
||||
var name = n.FindAllString(metaData, 1)
|
||||
//name[len(name) - 1] = strings.Replace(name[len(name) - 1], `\r`, "", -1)
|
||||
|
||||
var channelName string
|
||||
if len(name) == 0 {
|
||||
if v, ok := channel["tvg-name"]; ok {
|
||||
channelName = v
|
||||
}
|
||||
} else {
|
||||
channelName = name[len(name)-1][1:len(name[len(name)-1])]
|
||||
}
|
||||
|
||||
channelName = strings.Replace(channelName, `"`, "", -1)
|
||||
|
||||
var replacer = strings.NewReplacer("\n", "", "\r", "")
|
||||
channel["name"] = replacer.Replace(channelName)
|
||||
|
||||
values = values + channelName + " "
|
||||
|
||||
// Parse streaming URL
|
||||
u := regexp.MustCompile(exceptForStreamingURL)
|
||||
var streamingURL = u.FindAllString(metaData, -1)
|
||||
var url = strings.Replace(streamingURL[0], "\n", "", -1)
|
||||
url = strings.Replace(url, "\r", "", -1)
|
||||
url = strings.Trim(url, "\r\n")
|
||||
channel["url"] = url
|
||||
|
||||
channel["_values"] = values
|
||||
|
||||
// Search for a unique ID
|
||||
|
||||
for key, value := range channel {
|
||||
if !strings.Contains(strings.ToLower(key), "tvg-id") {
|
||||
if strings.Contains(strings.ToLower(key), "id") {
|
||||
channel["_uuid.key"] = key
|
||||
channel["_uuid.value"] = value
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return channel
|
||||
}
|
||||
|
||||
if strings.Contains(channels[0], "#EXTM3U") {
|
||||
|
||||
for _, thisStream := range channels {
|
||||
if !strings.Contains(thisStream, "#EXTM3U") {
|
||||
var channel = parseMetaData(thisStream)
|
||||
allChannels = append(allChannels, channel)
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
err = errors.New("No valid m3u file")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
129
src/internal/up2date/client/client.go
Executable file
129
src/internal/up2date/client/client.go
Executable file
@@ -0,0 +1,129 @@
|
||||
package up2date
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ClientInfo : Information about the key (NAME OS, ARCH, UUID, KEY)
|
||||
type ClientInfo struct {
|
||||
Arch string `json:"arch,required"`
|
||||
Branch string `json:"branch,required"`
|
||||
CMD string `json:"cmd,omitempty"`
|
||||
Name string `json:"name,required"`
|
||||
OS string `json:"os,required"`
|
||||
URL string `json:"url,required"`
|
||||
|
||||
Response ServerResponse `json:"response,omitempty"`
|
||||
}
|
||||
|
||||
//ServerResponse : Response from server after client request
|
||||
type ServerResponse struct {
|
||||
Status bool `json:"status,omitempty"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
UpdateBIN string `json:"update.url.bin,omitempty"`
|
||||
UpdateZIP string `json:"update.url.zip,omitempty"`
|
||||
Filename string `json:"filename.bin,omitempty"`
|
||||
}
|
||||
|
||||
// Updater : Client infos
|
||||
var Updater ClientInfo
|
||||
|
||||
// UpdateURL : URL for the new binary
|
||||
var UpdateURL string
|
||||
|
||||
// Init : Init
|
||||
func Init() {
|
||||
Updater.OS = runtime.GOOS
|
||||
Updater.Arch = runtime.GOARCH
|
||||
}
|
||||
|
||||
// GetVersion : Information about the latest version
|
||||
func GetVersion() (err error) {
|
||||
|
||||
Updater.CMD = "getVersion"
|
||||
err = serverRequest()
|
||||
return
|
||||
}
|
||||
|
||||
func serverRequest() (err error) {
|
||||
|
||||
var serverResponse ServerResponse
|
||||
jsonByte, err := json.MarshalIndent(Updater, "", " ")
|
||||
if err == nil {
|
||||
|
||||
// Serververbindung prüfen
|
||||
u, err := url.Parse(Updater.URL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var server = u.Host
|
||||
|
||||
timeout := time.Duration(1 * time.Second)
|
||||
_, err = net.DialTimeout("tcp", server, timeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check redirect 301 <---> 308
|
||||
redirect, err := http.NewRequest("POST", Updater.URL, nil)
|
||||
|
||||
client := &http.Client{}
|
||||
client.CheckRedirect = func(redirect *http.Request, via []*http.Request) error {
|
||||
return errors.New("Redirect")
|
||||
}
|
||||
|
||||
resp, err := client.Do(redirect)
|
||||
|
||||
if err != nil {
|
||||
// Redirect
|
||||
if resp.StatusCode >= 301 && resp.StatusCode <= 308 { //status code 301 <---> 308
|
||||
Updater.URL = resp.Header.Get("Location")
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// ---
|
||||
|
||||
req, err := http.NewRequest("POST", Updater.URL, bytes.NewBuffer(jsonByte))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
client = &http.Client{}
|
||||
resp, err = client.Do(req)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
//fmt.Println(resp.StatusCode, Updater.URL, Updater.CMD)
|
||||
err = fmt.Errorf(fmt.Sprintf("%d: %s (%s)", resp.StatusCode, http.StatusText(resp.StatusCode), Updater.URL))
|
||||
return err
|
||||
}
|
||||
|
||||
Updater.CMD = ""
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, _ := ioutil.ReadAll(resp.Body)
|
||||
|
||||
err = json.Unmarshal(body, &serverResponse)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
Updater.Response = serverResponse
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
271
src/internal/up2date/client/update.go
Executable file
271
src/internal/up2date/client/update.go
Executable file
@@ -0,0 +1,271 @@
|
||||
package up2date
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/kardianos/osext"
|
||||
)
|
||||
|
||||
// DoUpdate : Update binary
|
||||
func DoUpdate(fileType, filenameBIN string) (err error) {
|
||||
|
||||
var url string
|
||||
switch fileType {
|
||||
case "bin":
|
||||
url = Updater.Response.UpdateBIN
|
||||
case "zip":
|
||||
url = Updater.Response.UpdateZIP
|
||||
}
|
||||
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
filenameBIN = filenameBIN + ".exe"
|
||||
}
|
||||
|
||||
if len(url) > 0 {
|
||||
log.Println("["+strings.ToUpper(fileType)+"]", "New version ("+Updater.Name+"):", Updater.Response.Version)
|
||||
|
||||
// Download new binary
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
log.Println("["+strings.ToUpper(fileType)+"]", "Download new version...")
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
log.Println("["+strings.ToUpper(fileType)+"]", "Download new version...OK")
|
||||
return fmt.Errorf("bad status: %s", resp.Status)
|
||||
}
|
||||
|
||||
// Change binary filename to .filename
|
||||
binary, err := osext.Executable()
|
||||
var filename = getFilenameFromPath(binary)
|
||||
var path = getPlatformPath(binary)
|
||||
var oldBinary = path + "_old_" + filename
|
||||
var newBinary = binary
|
||||
|
||||
// ZIP
|
||||
var tmpFolder = path + "tmp"
|
||||
var tmpFile = tmpFolder + string(os.PathSeparator) + filenameBIN
|
||||
|
||||
//fmt.Println(binary, path+"."+filename)
|
||||
os.Rename(newBinary, oldBinary)
|
||||
|
||||
// Save the new binary with the old file name
|
||||
out, err := os.Create(binary)
|
||||
if err != nil {
|
||||
restorOldBinary(oldBinary, newBinary)
|
||||
return err
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
// Write the body to file
|
||||
|
||||
_, err = io.Copy(out, resp.Body)
|
||||
if err != nil {
|
||||
restorOldBinary(oldBinary, newBinary)
|
||||
return err
|
||||
}
|
||||
|
||||
// Update as a ZIP file
|
||||
if fileType == "zip" {
|
||||
|
||||
log.Println("["+strings.ToUpper(fileType)+"]", "Update file:", filenameBIN)
|
||||
log.Println("["+strings.ToUpper(fileType)+"]", "Unzip ZIP file...")
|
||||
err = extractZIP(binary, tmpFolder)
|
||||
|
||||
binary = newBinary
|
||||
|
||||
if err != nil {
|
||||
|
||||
log.Println("["+strings.ToUpper(fileType)+"]", "Unzip ZIP file...ERROR")
|
||||
|
||||
restorOldBinary(oldBinary, newBinary)
|
||||
|
||||
return err
|
||||
} else {
|
||||
|
||||
log.Println("["+strings.ToUpper(fileType)+"]", "Unzip ZIP file...OK")
|
||||
log.Println("["+strings.ToUpper(fileType)+"]", "Copy binary file...")
|
||||
|
||||
err = copyFile(tmpFile, binary)
|
||||
if err == nil {
|
||||
log.Println("["+strings.ToUpper(fileType)+"]", "Copy binary file...OK")
|
||||
} else {
|
||||
|
||||
log.Println("["+strings.ToUpper(fileType)+"]", "Copy binary file...ERROR")
|
||||
restorOldBinary(oldBinary, newBinary)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
os.RemoveAll(tmpFolder)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Set the permission
|
||||
err = os.Chmod(binary, 0755)
|
||||
|
||||
// Close the new file !Windows
|
||||
out.Close()
|
||||
|
||||
log.Println("["+strings.ToUpper(fileType)+"]", "Update Successful")
|
||||
|
||||
// Restart binary (Windows)
|
||||
if runtime.GOOS == "windows" {
|
||||
|
||||
bin, err := os.Executable()
|
||||
|
||||
if err != nil {
|
||||
restorOldBinary(oldBinary, newBinary)
|
||||
return err
|
||||
}
|
||||
|
||||
var pid = os.Getpid()
|
||||
var process, _ = os.FindProcess(pid)
|
||||
|
||||
if proc, err := start(bin); err == nil {
|
||||
|
||||
os.RemoveAll(oldBinary)
|
||||
process.Kill()
|
||||
proc.Wait()
|
||||
|
||||
} else {
|
||||
restorOldBinary(oldBinary, newBinary)
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// Restart binary (Linux and UNIX)
|
||||
file, _ := osext.Executable()
|
||||
os.RemoveAll(oldBinary)
|
||||
err = syscall.Exec(file, os.Args, os.Environ())
|
||||
if err != nil {
|
||||
restorOldBinary(oldBinary, newBinary)
|
||||
log.Fatal(err)
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func start(args ...string) (p *os.Process, err error) {
|
||||
|
||||
if args[0], err = exec.LookPath(args[0]); err == nil {
|
||||
//fmt.Println(args[0])
|
||||
var procAttr os.ProcAttr
|
||||
procAttr.Files = []*os.File{os.Stdin, os.Stdout, os.Stderr}
|
||||
p, err := os.StartProcess(args[0], args, &procAttr)
|
||||
|
||||
if err == nil {
|
||||
return p, nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func restorOldBinary(oldBinary, newBinary string) {
|
||||
os.RemoveAll(newBinary)
|
||||
os.Rename(oldBinary, newBinary)
|
||||
}
|
||||
|
||||
func getPlatformFile(filename string) string {
|
||||
|
||||
path, file := filepath.Split(filename)
|
||||
var newPath = filepath.Dir(path)
|
||||
var newFileName = newPath + string(os.PathSeparator) + file
|
||||
|
||||
return newFileName
|
||||
}
|
||||
|
||||
func getFilenameFromPath(path string) string {
|
||||
|
||||
file := filepath.Base(path)
|
||||
|
||||
return file
|
||||
}
|
||||
|
||||
func getPlatformPath(path string) string {
|
||||
|
||||
var newPath = filepath.Dir(path) + string(os.PathSeparator)
|
||||
|
||||
return newPath
|
||||
}
|
||||
|
||||
func copyFile(src, dst string) (err error) {
|
||||
in, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer in.Close()
|
||||
|
||||
out, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
_, err = io.Copy(out, in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return out.Close()
|
||||
}
|
||||
|
||||
func extractZIP(archive, target string) (err error) {
|
||||
|
||||
reader, err := zip.OpenReader(archive)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(target, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, file := range reader.File {
|
||||
|
||||
path := filepath.Join(target, file.Name)
|
||||
if file.FileInfo().IsDir() {
|
||||
os.MkdirAll(path, file.Mode())
|
||||
continue
|
||||
}
|
||||
|
||||
fileReader, err := file.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fileReader.Close()
|
||||
|
||||
targetFile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer targetFile.Close()
|
||||
|
||||
if _, err := io.Copy(targetFile, fileReader); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
Reference in New Issue
Block a user