2023-02-28 20:25:15 +07:00
/ *
* SPDX - License - Identifier : AGPL - 3.0 - only
2024-01-04 16:28:16 +07:00
* Copyright ( c ) 2022 - 2024 , daeuniverse Organization < dae @ v2raya . org >
2023-02-28 20:25:15 +07:00
* /
package subscription
2023-01-28 00:50:21 +07:00
import (
2023-02-09 19:17:45 +07:00
"bufio"
"bytes"
2023-01-28 00:50:21 +07:00
"encoding/json"
"fmt"
"io"
"net"
"net/http"
"net/url"
2023-02-09 19:17:45 +07:00
"os"
"path/filepath"
2023-01-28 00:50:21 +07:00
"strconv"
"strings"
2023-04-23 12:27:29 +07:00
"github.com/daeuniverse/dae/common"
2023-07-23 22:52:27 +07:00
"github.com/daeuniverse/dae/config"
2023-04-23 12:27:29 +07:00
"github.com/sirupsen/logrus"
2023-01-28 00:50:21 +07:00
)
type sip008 struct {
Version int ` json:"version" `
Servers [ ] sip008Server ` json:"servers" `
BytesUsed int64 ` json:"bytes_used" `
BytesRemaining int64 ` json:"bytes_remaining" `
}
type sip008Server struct {
Id string ` json:"id" `
Remarks string ` json:"remarks" `
Server string ` json:"server" `
ServerPort int ` json:"server_port" `
Password string ` json:"password" `
Method string ` json:"method" `
Plugin string ` json:"plugin" `
PluginOpts string ` json:"plugin_opts" `
}
2023-02-28 20:25:15 +07:00
func ResolveSubscriptionAsBase64 ( log * logrus . Logger , b [ ] byte ) ( nodes [ ] string ) {
2023-01-28 00:50:21 +07:00
log . Debugln ( "Try to resolve as base64" )
// base64 decode
raw , e := common . Base64StdDecode ( string ( b ) )
if e != nil {
raw , _ = common . Base64UrlDecode ( string ( b ) )
}
// Simply check and preprocess.
lines := strings . Split ( raw , "\n" )
for _ , line := range lines {
line = strings . TrimSpace ( line )
if line == "" {
continue
}
protocol , suffix , _ := strings . Cut ( line , "://" )
if len ( protocol ) == 0 || len ( suffix ) == 0 {
continue
}
nodes = append ( nodes , line )
}
return nodes
}
2023-02-28 20:25:15 +07:00
func ResolveSubscriptionAsSIP008 ( log * logrus . Logger , b [ ] byte ) ( nodes [ ] string , err error ) {
2023-01-28 00:50:21 +07:00
log . Debugln ( "Try to resolve as sip008" )
var sip sip008
err = json . Unmarshal ( b , & sip )
if err != nil {
return nil , fmt . Errorf ( "failed to unmarshal json to sip008" )
}
if sip . Version != 1 || sip . Servers == nil {
return nil , fmt . Errorf ( "does not seems like a standard sip008 subscription" )
}
for _ , server := range sip . Servers {
u := url . URL {
Scheme : "ss" ,
User : url . UserPassword ( server . Method , server . Password ) ,
Host : net . JoinHostPort ( server . Server , strconv . Itoa ( server . ServerPort ) ) ,
RawQuery : url . Values { "plugin" : [ ] string { server . PluginOpts } } . Encode ( ) ,
Fragment : server . Remarks ,
}
nodes = append ( nodes , u . String ( ) )
}
return nodes , nil
}
2023-02-28 20:25:15 +07:00
func ResolveFile ( u * url . URL , configDir string ) ( b [ ] byte , err error ) {
2023-02-09 19:17:45 +07:00
if u . Host == "" {
return nil , fmt . Errorf ( "not support absolute path" )
}
/// Relative location.
2023-02-09 19:54:06 +07:00
// Make sure path is secure.
2023-02-09 19:17:45 +07:00
path := filepath . Join ( configDir , u . Host , u . Path )
2023-02-09 22:17:49 +07:00
if err = common . EnsureFileInSubDir ( path , configDir ) ; err != nil {
2023-02-09 19:17:45 +07:00
return nil , err
}
2023-02-10 10:04:16 +07:00
/// Read and resolve.
2023-02-09 19:17:45 +07:00
f , err := os . Open ( path )
if err != nil {
return nil , err
}
2023-08-28 22:09:50 +07:00
defer f . Close ( )
2023-02-09 22:17:49 +07:00
// Check file access.
fi , err := f . Stat ( )
if err != nil {
return nil , err
}
if fi . IsDir ( ) {
return nil , fmt . Errorf ( "subscription file cannot be a directory: %v" , path )
}
if fi . Mode ( ) & 0037 > 0 {
return nil , fmt . Errorf ( "permissions %04o for '%v' are too open; requires the file is NOT writable by the same group and NOT accessible by others; suggest 0640 or 0600" , fi . Mode ( ) & 0777 , path )
}
2023-02-09 19:17:45 +07:00
// Resolve the first line instruction.
fReader := bufio . NewReader ( f )
b , err = fReader . Peek ( 1 )
if err != nil {
return nil , err
}
if string ( b [ 0 ] ) == "@" {
// Instruction line. But not support yet.
_ , _ , err = fReader . ReadLine ( )
if err != nil {
return nil , err
}
}
b , err = io . ReadAll ( fReader )
if err != nil {
return nil , err
}
return bytes . TrimSpace ( b ) , err
}
2023-06-04 10:38:05 +07:00
func ResolveSubscription ( log * logrus . Logger , client * http . Client , configDir string , subscription string ) ( tag string , nodes [ ] string , err error ) {
2023-02-10 10:04:16 +07:00
/// Get tag.
2023-02-12 10:33:12 +07:00
tag , subscription = common . GetTagFromLinkLikePlaintext ( subscription )
2023-02-10 10:04:16 +07:00
/// Parse url.
2023-02-09 11:26:44 +07:00
u , err := url . Parse ( subscription )
if err != nil {
2023-02-10 10:04:16 +07:00
return tag , nil , fmt . Errorf ( "failed to parse subscription \"%v\": %w" , subscription , err )
2023-02-09 11:26:44 +07:00
}
2023-02-09 19:17:45 +07:00
log . Debugf ( "ResolveSubscription: %v" , subscription )
var (
b [ ] byte
2023-07-23 22:52:27 +07:00
req * http . Request
2023-02-09 19:17:45 +07:00
resp * http . Response
)
2023-02-09 11:26:44 +07:00
switch u . Scheme {
case "file" :
2023-02-28 20:25:15 +07:00
b , err = ResolveFile ( u , configDir )
2023-02-09 19:17:45 +07:00
if err != nil {
2023-02-10 10:04:16 +07:00
return "" , nil , err
2023-02-09 19:17:45 +07:00
}
goto resolve
2023-02-09 11:26:44 +07:00
default :
}
2023-07-23 22:52:27 +07:00
req , err = http . NewRequest ( "GET" , subscription , nil )
if err != nil {
return "" , nil , err
}
req . Header . Set ( "User-Agent" , fmt . Sprintf ( "dae/%v (like v2rayA/1.0 WebRequestHelper) (like v2rayN/1.0 WebRequestHelper)" , config . Version ) )
2023-07-24 20:54:20 +07:00
resp , err = client . Do ( req )
if err != nil {
return "" , nil , err
}
2023-01-28 00:50:21 +07:00
defer resp . Body . Close ( )
2023-02-09 19:17:45 +07:00
b , err = io . ReadAll ( resp . Body )
2023-01-28 00:50:21 +07:00
if err != nil {
2023-02-10 10:04:16 +07:00
return "" , nil , err
2023-01-28 00:50:21 +07:00
}
2023-02-09 19:17:45 +07:00
resolve :
2023-02-28 20:25:15 +07:00
if nodes , err = ResolveSubscriptionAsSIP008 ( log , b ) ; err == nil {
2023-02-10 10:04:16 +07:00
return tag , nodes , nil
2023-01-28 00:50:21 +07:00
} else {
log . Debugln ( err )
}
2023-02-28 20:25:15 +07:00
return tag , ResolveSubscriptionAsBase64 ( log , b ) , nil
2023-01-28 00:50:21 +07:00
}