378 lines
8.7 KiB
Go
378 lines
8.7 KiB
Go
package srun
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"github.com/Mmx233/tool"
|
|
log "github.com/sirupsen/logrus"
|
|
"io"
|
|
"math/rand"
|
|
"net/http"
|
|
"net/url"
|
|
"regexp"
|
|
"strings"
|
|
"time"
|
|
"unsafe"
|
|
)
|
|
|
|
type Api struct {
|
|
BaseUrl string
|
|
Client *http.Client
|
|
// 禁用自动重定向
|
|
NoDirect *http.Client
|
|
|
|
CustomHeader map[string]interface{}
|
|
}
|
|
|
|
type ApiConfig struct {
|
|
Https bool
|
|
Domain string
|
|
Client *http.Client
|
|
CustomHeader map[string]interface{}
|
|
}
|
|
|
|
func (a *Api) Init(conf *ApiConfig) {
|
|
a.BaseUrl = "http"
|
|
if conf.Https {
|
|
a.BaseUrl += "s"
|
|
}
|
|
a.BaseUrl = a.BaseUrl + "://" + conf.Domain + "/"
|
|
|
|
a.CustomHeader = conf.CustomHeader
|
|
|
|
// 初始化 http client
|
|
a.Client = conf.Client
|
|
copyClient := *conf.Client
|
|
a.NoDirect = ©Client
|
|
a.NoDirect.CheckRedirect = func(_ *http.Request, _ []*http.Request) error {
|
|
return http.ErrUseLastResponse
|
|
}
|
|
}
|
|
|
|
func (a *Api) request(path string, query map[string]interface{}) (map[string]interface{}, error) {
|
|
log.Debugln("HTTP GET", a.BaseUrl+path)
|
|
callback := fmt.Sprintf("jQuery%s_%d", tool.NewRand(rand.NewSource(time.Now().UnixNano())).WithLetters("123456789").String(21), time.Now().UnixMilli())
|
|
if query == nil {
|
|
query = make(map[string]interface{}, 2)
|
|
}
|
|
query["callback"] = callback
|
|
query["_"] = fmt.Sprint(time.Now().UnixMilli())
|
|
httpTool := tool.NewHttpTool(a.Client)
|
|
req, err := httpTool.GenReq("GET", &tool.DoHttpReq{
|
|
Url: a.BaseUrl + path,
|
|
Query: query,
|
|
Header: a.CustomHeader,
|
|
})
|
|
if err != nil {
|
|
log.Debugln(err)
|
|
return nil, err
|
|
}
|
|
|
|
resp, err := httpTool.Client.Do(req)
|
|
if err != nil {
|
|
log.Debugln(err)
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
data, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
log.Debugln(err)
|
|
return nil, err
|
|
}
|
|
res := string(data)
|
|
|
|
log.Debugln(res)
|
|
res = strings.TrimPrefix(res, callback+"(")
|
|
res = strings.TrimSuffix(res, ")")
|
|
|
|
var r map[string]interface{}
|
|
return r, json.Unmarshal([]byte(res), &r)
|
|
}
|
|
|
|
func (a *Api) GetUserInfo() (map[string]interface{}, error) {
|
|
return a.request("cgi-bin/rad_user_info", nil)
|
|
}
|
|
|
|
func (a *Api) _JoinRedirectLocation(addr *url.URL, loc string) (*url.URL, error) {
|
|
if loc == "" {
|
|
return nil, errors.New("目标跳转地址缺失")
|
|
}
|
|
if strings.HasPrefix(loc, "/") {
|
|
addr.Path = strings.TrimPrefix(loc, "/")
|
|
return addr, nil
|
|
} else {
|
|
return url.Parse(loc)
|
|
}
|
|
}
|
|
|
|
type _FollowRedirectConfig struct {
|
|
// 覆盖响应处理逻辑,设置后 onNextAddr 无效
|
|
onResponse func(res *http.Response) (next *url.URL, err error)
|
|
// 获取到下一个请求地址时触发
|
|
onNextAddr func(addr *url.URL) error
|
|
}
|
|
|
|
func (a *Api) _FollowRedirect(addr *url.URL, conf _FollowRedirectConfig) (*url.URL, error) {
|
|
addrCopy := *addr
|
|
addr = &addrCopy
|
|
for {
|
|
log.Debugln("HTTP GET", addr)
|
|
req, err := http.NewRequest("GET", addr.String(), nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for k, v := range a.CustomHeader {
|
|
req.Header.Set(k, fmt.Sprint(v))
|
|
}
|
|
res, err := a.NoDirect.Do(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if conf.onResponse != nil {
|
|
var nextAddr *url.URL
|
|
nextAddr, err = conf.onResponse(res)
|
|
if err != nil {
|
|
return nil, err
|
|
} else if nextAddr == nil {
|
|
break
|
|
}
|
|
addr = nextAddr
|
|
continue
|
|
}
|
|
_, _ = io.Copy(io.Discard, res.Body)
|
|
_ = res.Body.Close()
|
|
if res.StatusCode < 300 {
|
|
break
|
|
} else if res.StatusCode < 400 {
|
|
addr, err = a._JoinRedirectLocation(addr, res.Header.Get("location"))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if conf.onNextAddr != nil {
|
|
if err = conf.onNextAddr(addr); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
} else {
|
|
return nil, fmt.Errorf("server return http status %d", res.StatusCode)
|
|
}
|
|
}
|
|
return addr, nil
|
|
}
|
|
|
|
func (a *Api) _SearchAcid(query url.Values) (string, bool) {
|
|
addr := query.Get(`ac_id`)
|
|
return addr, addr != ""
|
|
}
|
|
|
|
func (a *Api) DetectEnc() (string, error) {
|
|
log.Debugln("HTTP GET", a.BaseUrl)
|
|
res, err := a.Client.Get(a.BaseUrl)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer res.Body.Close()
|
|
if res.StatusCode == 200 {
|
|
indexHtml, err := io.ReadAll(res.Body)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
jsReg, err := regexp.Compile(`(?i)<script src="\.?(.+[./]portal[0-9]*\.js)(\?.*)?">`)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
jsPathMatch := jsReg.FindSubmatch(indexHtml)
|
|
if len(jsPathMatch) == 3 {
|
|
jsPathBytes := jsPathMatch[1]
|
|
jsPath := unsafe.String(unsafe.SliceData(jsPathBytes), len(jsPathBytes))
|
|
jsUrl, err := url.Parse(a.BaseUrl)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
jsUrl.Path = jsPath
|
|
jsAddr := jsUrl.String()
|
|
log.Debugln("HTTP GET", jsAddr)
|
|
jsRes, err := a.Client.Get(jsAddr)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer jsRes.Body.Close()
|
|
if jsRes.StatusCode == 200 {
|
|
jsContent, err := io.ReadAll(jsRes.Body)
|
|
if err == nil {
|
|
reg, err := regexp.Compile(`var enc = (.*?)[,;]`)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
encMatch := reg.FindSubmatch(jsContent)
|
|
if len(encMatch) == 2 {
|
|
encBytes := encMatch[1]
|
|
encStr := unsafe.String(unsafe.SliceData(encBytes), len(encBytes))
|
|
encSplit := strings.Split(encStr, "+")
|
|
for i, v := range encSplit {
|
|
encSplit[i] = strings.Trim(strings.TrimSpace(v), "'\"")
|
|
}
|
|
enc := strings.Join(encSplit, "")
|
|
return enc, nil
|
|
}
|
|
}
|
|
} else {
|
|
_, _ = io.Copy(io.Discard, jsRes.Body)
|
|
}
|
|
}
|
|
} else {
|
|
_, _ = io.Copy(io.Discard, res.Body)
|
|
}
|
|
return "", ErrEnvCannotFound
|
|
}
|
|
|
|
// DetectAcid err 为 nil 时 acid 一定存在
|
|
func (a *Api) DetectAcid() (string, error) {
|
|
// 从入口地址 url query 寻找 acid
|
|
baseUrl, err := url.Parse(a.BaseUrl)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
var AcidFound = errors.New("acid found")
|
|
var acid string
|
|
finalAddr, err := a._FollowRedirect(baseUrl, _FollowRedirectConfig{
|
|
onNextAddr: func(addr *url.URL) error {
|
|
var ok bool
|
|
acid, ok = a._SearchAcid(addr.Query())
|
|
if ok {
|
|
return AcidFound
|
|
}
|
|
return nil
|
|
},
|
|
})
|
|
if err != nil {
|
|
if errors.Is(err, AcidFound) {
|
|
return acid, nil
|
|
}
|
|
return "", err
|
|
}
|
|
|
|
// 从 html 寻找 acid
|
|
log.Debugln("HTTP GET", finalAddr.String())
|
|
res, err := a.Client.Get(a.BaseUrl)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer res.Body.Close()
|
|
if res.StatusCode == 200 {
|
|
var indexHtml []byte
|
|
indexHtml, err = io.ReadAll(res.Body)
|
|
if err == nil {
|
|
var reg *regexp.Regexp
|
|
reg, err = regexp.Compile(`"ac_id".*?value="(.+)"`)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
result := reg.FindSubmatch(indexHtml)
|
|
if len(result) == 2 {
|
|
return string(result[1]), nil
|
|
}
|
|
}
|
|
} else {
|
|
_, _ = io.Copy(io.Discard, res.Body)
|
|
}
|
|
|
|
return "", ErrAcidCannotFound
|
|
}
|
|
|
|
// Reality acid 可能为空字符串
|
|
func (a *Api) Reality(addr string, getAcid bool) (acid string, online bool, err error) {
|
|
startUrl, err := url.Parse(addr)
|
|
if err != nil {
|
|
return "", false, err
|
|
}
|
|
var AlreadyOnline = errors.New("already online")
|
|
var finalUrl *url.URL
|
|
finalUrl, err = a._FollowRedirect(startUrl, _FollowRedirectConfig{
|
|
onResponse: func(res *http.Response) (next *url.URL, err error) {
|
|
defer res.Body.Close()
|
|
if res.StatusCode < 300 {
|
|
var body []byte
|
|
body, err = io.ReadAll(res.Body)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
var reg *regexp.Regexp
|
|
reg, err = regexp.Compile(`<script>top\.self\.location\.href='(.*)'</script>`)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
result := reg.FindSubmatch(body)
|
|
if len(result) == 2 {
|
|
nextBytes := result[1]
|
|
nextAddr := unsafe.String(unsafe.SliceData(nextBytes), len(nextBytes))
|
|
next, err = url.Parse(nextAddr)
|
|
}
|
|
} else if res.StatusCode < 400 {
|
|
next, err = a._JoinRedirectLocation(res.Request.URL, res.Header.Get("location"))
|
|
}
|
|
if getAcid && next != nil {
|
|
acid, _ = a._SearchAcid(next.Query())
|
|
}
|
|
return
|
|
},
|
|
})
|
|
if err != nil {
|
|
if errors.Is(err, AlreadyOnline) {
|
|
online = true
|
|
err = nil
|
|
return
|
|
}
|
|
return
|
|
}
|
|
online = finalUrl.Host == startUrl.Host
|
|
return
|
|
}
|
|
|
|
type LoginRequest struct {
|
|
Username string
|
|
Password string
|
|
AcID string
|
|
IP string
|
|
Info string
|
|
ChkSum string
|
|
N string
|
|
Type string
|
|
OS string
|
|
Name string
|
|
DoubleStack string
|
|
}
|
|
|
|
func (a *Api) Login(req *LoginRequest) (map[string]interface{}, error) {
|
|
return a.request(
|
|
"cgi-bin/srun_portal",
|
|
map[string]interface{}{
|
|
"action": "login",
|
|
"username": req.Username,
|
|
"password": req.Password,
|
|
"ac_id": req.AcID,
|
|
"ip": req.IP,
|
|
"info": req.Info,
|
|
"chksum": req.ChkSum,
|
|
"n": req.N,
|
|
"type": req.Type,
|
|
"os": req.OS,
|
|
"name": req.Name,
|
|
"double_stack": req.DoubleStack,
|
|
})
|
|
}
|
|
|
|
func (a *Api) GetChallenge(username, ip string) (map[string]interface{}, error) {
|
|
return a.request(
|
|
"cgi-bin/get_challenge",
|
|
map[string]interface{}{
|
|
"username": username,
|
|
"ip": ip,
|
|
})
|
|
}
|