Skip to content

Commit

Permalink
feat(vpn-qr-code): feature of loading QR code image for QR code login
Browse files Browse the repository at this point in the history
  • Loading branch information
genshen committed May 28, 2024
1 parent 75e16af commit 00a24a8
Show file tree
Hide file tree
Showing 4 changed files with 198 additions and 0 deletions.
9 changes: 9 additions & 0 deletions client-ui/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,15 @@ func main() {
{Text: "password", Widget: uiVpnPassword},
}},
),
container.NewTabItemWithIcon(
"QR Code",
theme.MoreHorizontalIcon(),
container.NewVBox(
LoadQRImage(),
widget.NewLabel("QR code scanned"),
widget.NewButton("Load/Reload QR Code", func() {}),
),
),
)
tabs.SetTabLocation(container.TabLocationTop)
selectCopyProxyCommand := container.NewBorder(nil, nil, nil, nil,
Expand Down
25 changes: 25 additions & 0 deletions client-ui/qr_login_ui.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package main

import (
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/storage"
"github.com/genshen/wssocks-plugin-ustb/plugins/vpn"
log "github.com/sirupsen/logrus"
)

// LoadQRImage shows QR image for login
func LoadQRImage() *canvas.Image {
if qrCodeImgUrl, err := vpn.ParseQRCodeImgUrl(); err != nil {
log.Println(err) // todo
return nil
} else {
if uri, err := storage.ParseURI(qrCodeImgUrl); err != nil {
log.Println(err) // todo:
return nil
} else {
image := canvas.NewImageFromURI(uri)
image.FillMode = canvas.ImageFillOriginal
return image
}
}
}
142 changes: 142 additions & 0 deletions plugins/vpn/qr_code.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package vpn

import (
"bufio"
"bytes"
"errors"
"fmt"
"gopkg.in/yaml.v3"
"net/http"
"net/url"
"strings"
)

const LoadImgUrl = "https://n.ustb.edu.cn/login/"
const FindQrcodeUrlRegex = `"ustb-qrcode",`
const FindQrcodeImgTagRegex = `<img`

// QRCodeImgLoaderConfig is the config to generate an url of sis.ustb.edu.cn for requesting qr code image.
// here we treat js as yaml (because the source string is not standard json, we can not parse it as a json)
type QRCodeImgLoaderConfig struct {
Id string `yaml:"id"`
ApiUrl string `yaml:"api_url"`
AppID string `yaml:"appid"`
ReturnUrl string `yaml:"return_url"`
RandToken string `yaml:"rand_token"`
}

func (q *QRCodeImgLoaderConfig) GenUrl() (string, error) {
if q.ApiUrl == "" {
return "", errors.New("api url is empty")
}
if q.AppID == "" {
return "", errors.New("app id is empty")
}
if q.ReturnUrl == "" {
return "", errors.New("return url is empty")
}
if q.RandToken == "" {
return "", errors.New("rand token is empty")
}
// todo encodeURI for return_url
return fmt.Sprintf("%s?appid=%s&return_url=%s&rand_token=%s&embed_flag=1", q.ApiUrl, q.AppID, q.ReturnUrl, q.RandToken), nil
}

// ParseQRCodeImgUrl uses ParseQRCodeHtmlUrl to get the html url and parse the html file to get final image url
func ParseQRCodeImgUrl() (string, error) {
htmlUrl, err := ParseQRCodeHtmlUrl()
if err != nil {
return "", err
}
htmlUri, err := url.Parse(htmlUrl)
if err != nil {
return "", err
}

// make a http request
response, err := http.Get(htmlUrl)
if err != nil {
}

defer response.Body.Close()

scanner := bufio.NewScanner(response.Body)
imgUrl := ""
for scanner.Scan() {
line := scanner.Text()
if strings.Contains(line, FindQrcodeImgTagRegex) { // first line to match start
// line e.g. <img id="qrimg" src="/connect/qrimg?sid=3894c5568dd1ef0f6434f426297a678d" height="90%" border="0">
subStr := strings.SplitN(line, "\"", 5)
if len(subStr) != 5 {
return "", errors.New("invalid format in qr image url parsing")
} else {
imgUrl = subStr[3]
break
}
}
}

if err := scanner.Err(); err != nil {
return "", err
}

// use htmlUri's host, schema
return fmt.Sprintf("%s://%s%s", htmlUri.Scheme, htmlUri.Host, imgUrl), nil
}

func ParseQRCodeHtmlUrl() (string, error) {
// parse the html of `LOAD_IMG_URL` to get following object text:
//{
// id: "ustb-qrcode",
// api_url: "",
// appid: "",
// return_url: "",
// rand_token: "",
// width: "",
// height: ""
//}
response, err := http.Get(LoadImgUrl)
if err != nil {
}

defer response.Body.Close()

scanner := bufio.NewScanner(response.Body)
var findQrMatchStart = false
var qrConfigBuffer bytes.Buffer
qrConfigBuffer.WriteString("{")
for scanner.Scan() {
line := scanner.Text()
if strings.Contains(line, FindQrcodeUrlRegex) { // first line to match start
findQrMatchStart = true // start match
}
// only start matched and end not matched, we can add text to buffer.
if findQrMatchStart {
qrConfigBuffer.WriteString(line)
if strings.Contains(line, "}") {
break
}
}
}

if err := scanner.Err(); err != nil {
return "", err
}

fmt.Println("parsed qr code config:", qrConfigBuffer.String())
var qrImgUrlConfig QRCodeImgLoaderConfig
if err := yaml.Unmarshal(qrConfigBuffer.Bytes(), &qrImgUrlConfig); err != nil {
return "", err
}

if qrUrl, err := qrImgUrlConfig.GenUrl(); err != nil {
return "", err
} else {
return qrUrl, nil
}
}

// wait qr state and get auth code
func WaitQrState(imgUrl *url.URL) {
// get https://sis.ustb.edu.cn/connect/state?sid=bf1a027b75d6e21b351f81cdc1b739a2
}
22 changes: 22 additions & 0 deletions plugins/vpn/qr_code_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package vpn

import (
"fmt"
"testing"
)

func TestQRCodeHtmlUrl(t *testing.T) {
u, err := ParseQRCodeHtmlUrl()
if err != nil {
t.Error("error in loading qr code html url:", err)
}
fmt.Println(u)
}

func TestQRCodeImgUrl(t *testing.T) {
u, err := ParseQRCodeImgUrl()
if err != nil {
t.Error("error in loading qr code img url:", err)
}
fmt.Println(u)
}

0 comments on commit 00a24a8

Please sign in to comment.