diff --git a/client-ui/main.go b/client-ui/main.go index dd7c3f4..824c662 100644 --- a/client-ui/main.go +++ b/client-ui/main.go @@ -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, diff --git a/client-ui/qr_login_ui.go b/client-ui/qr_login_ui.go new file mode 100644 index 0000000..d4d7d83 --- /dev/null +++ b/client-ui/qr_login_ui.go @@ -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 + } + } +} diff --git a/plugins/vpn/qr_code.go b/plugins/vpn/qr_code.go new file mode 100644 index 0000000..2341e11 --- /dev/null +++ b/plugins/vpn/qr_code.go @@ -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 = ` + 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 +} diff --git a/plugins/vpn/qr_code_test.go b/plugins/vpn/qr_code_test.go new file mode 100644 index 0000000..ac6cd8c --- /dev/null +++ b/plugins/vpn/qr_code_test.go @@ -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) +}