Files
siyuan/kernel/mobile/kernel.go

352 lines
9.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// SiYuan - Refactor your thinking
// Copyright (c) 2020-present, b3log.org
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package mobile
import (
"fmt"
"net/http"
"net/url"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/88250/gulu"
"github.com/88250/lute/ast"
"github.com/siyuan-note/filelock"
"github.com/siyuan-note/httpclient"
"github.com/siyuan-note/logging"
"github.com/siyuan-note/siyuan/kernel/cache"
"github.com/siyuan-note/siyuan/kernel/job"
"github.com/siyuan-note/siyuan/kernel/model"
"github.com/siyuan-note/siyuan/kernel/server"
"github.com/siyuan-note/siyuan/kernel/sql"
"github.com/siyuan-note/siyuan/kernel/util"
_ "golang.org/x/mobile/bind"
)
// VerifyAppStoreTransaction 用于验证苹果 App Store 交易。
//
// accountToken UUID:
//
// 6ba7b810-9dad-11d1-0001-377616491562
// 6ba7b810-9dad-11d1-{Cloud Region}00{User ID},中间的 00 为保留位
//
// 返回码:
//
// 0验证通过
// -1云端区域无效
// -2服务器通讯失败需要重试
// -3非 iOS 设备
// -4账号未登录
// -5账号状态异常
// -6参数错误
// -7校验 accountToken 失败
// -8校验 transaction 失败
// -9未知的商品
func VerifyAppStoreTransaction(accountToken, transactionID string) (retCode int) {
retCode = -2
retMsg := "unknown error"
accountToken = strings.TrimSpace(accountToken)
transactionID = strings.TrimSpace(transactionID)
if "" == accountToken || "" == transactionID {
retCode = -6
retMsg = "invalid parameters"
logging.LogErrorf(retMsg)
return
}
if 36 != len(accountToken) {
retCode = -6
retMsg = fmt.Sprintf("invalid accountToken [%s]", accountToken)
logging.LogErrorf(retMsg)
return
}
if util.ContainerIOS != util.Container {
retCode = -3
retMsg = fmt.Sprintf("invalid container [%s]", util.Container)
logging.LogErrorf(retMsg)
return
}
user := model.Conf.GetUser()
if nil == user || "" == user.UserToken {
retCode = -4
retMsg = "account not logged in"
logging.LogErrorf(retMsg)
return
}
cloudRegionArg := accountToken[19:20]
if "0" != cloudRegionArg && "1" != cloudRegionArg {
retCode = -1
retMsg = fmt.Sprintf("invalid cloud region [%s]", cloudRegionArg)
logging.LogErrorf(retMsg)
return
}
cloudRegion, _ := strconv.Atoi(cloudRegionArg)
if util.CurrentCloudRegion != cloudRegion {
retCode = -1
retMsg = fmt.Sprintf("invalid cloud region [cloudRegionArg=%s, currentRegion=%d]", cloudRegionArg, util.CurrentCloudRegion)
logging.LogErrorf(retMsg)
return
}
userID := strings.ReplaceAll(accountToken[22:], "-", "")
if user.UserId != userID {
retCode = -5
retMsg = fmt.Sprintf("invalid user [userID=%s, accountToken=%s]", user.UserId, accountToken)
logging.LogErrorf(retMsg)
return
}
verifyURL := util.GetCloudServer() + "/apis/siyuan/verifyAppStoreTransaction"
result := gulu.Ret.NewResult()
request := httpclient.NewCloudRequest30s()
resp, reqErr := request.SetSuccessResult(result).SetCookies(&http.Cookie{Name: "symphony", Value: user.UserToken}).
SetBody(map[string]string{"transactionId": transactionID, "accountToken": accountToken, "userId": userID}).Post(verifyURL)
if nil != reqErr {
retCode = -2
retMsg = fmt.Sprintf("verify app store transaction failed: %s", reqErr)
logging.LogErrorf(retMsg)
return
}
if http.StatusUnauthorized == resp.StatusCode || http.StatusForbidden == resp.StatusCode {
retCode = -4
retMsg = fmt.Sprintf("verify app store transaction failed [sc=%d]", resp.StatusCode)
logging.LogErrorf(retMsg)
return
}
if http.StatusOK != resp.StatusCode {
retCode = -2
retMsg = fmt.Sprintf("verify app store transaction failed [sc=%d]", resp.StatusCode)
logging.LogErrorf(retMsg)
return
}
if -1 == result.Code {
retCode = -5
retMsg = fmt.Sprintf("verify app store transaction failed [code=%d, msg=%s]", result.Code, result.Msg)
logging.LogErrorf(retMsg)
return
}
if -3 == result.Code {
retCode = -6
retMsg = fmt.Sprintf("verify app store transaction failed [code=%d, msg=%s]", result.Code, result.Msg)
logging.LogErrorf(retMsg)
return
}
if -2 == result.Code {
retCode = -8
retMsg = fmt.Sprintf("verify app store transaction failed [code=%d, msg=%s]", result.Code, result.Msg)
logging.LogErrorf(retMsg)
return
}
if -4 == result.Code {
retCode = -8
retMsg = fmt.Sprintf("verify app store transaction failed [code=%d, msg=%s]", result.Code, result.Msg)
logging.LogErrorf(retMsg)
return
}
if -5 == result.Code {
retCode = -7
retMsg = fmt.Sprintf("verify app store transaction failed [code=%d, msg=%s]", result.Code, result.Msg)
logging.LogErrorf(retMsg)
return
}
if -6 == result.Code {
retCode = -9
retMsg = fmt.Sprintf("verify app store transaction failed [code=%d, msg=%s]", result.Code, result.Msg)
logging.LogErrorf(retMsg)
return
}
if -64 == result.Code {
retCode = -2
retMsg = fmt.Sprintf("verify app store transaction failed [code=%d, msg=%s]", result.Code, result.Msg)
logging.LogErrorf(retMsg)
return
}
if 0 != result.Code {
retCode = -2
retMsg = fmt.Sprintf("verify app store transaction failed [code=%d, msg=%s]", result.Code, result.Msg)
logging.LogErrorf(retMsg)
return
}
retCode = 0
retMsg = fmt.Sprintf("verify app store transaction [%s] success", transactionID)
logging.LogInfof(retMsg)
return
}
func StartKernelFast(container, appDir, workspaceBaseDir, localIPs string) {
go server.Serve(true, model.Conf.CookieKey)
}
func StartKernel(container, appDir, workspaceBaseDir, timezoneID, localIPs, lang, osVer string) {
SetTimezone(container, appDir, timezoneID)
util.Mode = "prod"
util.MobileOSVer = osVer
util.LocalIPs = strings.Split(localIPs, ",")
util.BootMobile(container, appDir, workspaceBaseDir, lang)
model.InitConf()
go server.Serve(false, model.Conf.CookieKey)
go func() {
model.InitAppearance()
sql.InitDatabase(false)
sql.InitHistoryDatabase(false)
sql.InitAssetContentDatabase(false)
sql.SetCaseSensitive(model.Conf.Search.CaseSensitive)
sql.SetIndexAssetPath(model.Conf.Search.IndexAssetPath)
model.BootSyncData()
model.InitBoxes()
model.LoadFlashcards()
util.LoadAssetsTexts()
util.SetBooted()
util.PushClearAllMsg()
job.StartCron()
go model.AutoGenerateFileHistory()
go cache.LoadAssets()
}()
}
func Language(num int) string {
return model.Conf.Language(num)
}
func ShowMsg(msg string, timeout int) {
util.PushMsg(msg, timeout)
}
func IsHttpServing() bool {
return util.HttpServing
}
func SetHttpServerPort(port int) {
filelock.AndroidServerPort = port
}
func GetCurrentWorkspacePath() string {
return util.WorkspaceDir
}
func GetAssetAbsPath(asset string) (ret string) {
ret, err := model.GetAssetAbsPath(asset)
if err != nil {
logging.LogErrorf("get asset [%s] abs path failed: %s", asset, err)
ret = asset
}
return
}
func GetMimeTypeByExt(ext string) string {
return util.GetMimeTypeByExt(ext)
}
func SetTimezone(container, appDir, timezoneID string) {
if "ios" == container {
os.Setenv("ZONEINFO", filepath.Join(appDir, "app", "zoneinfo.zip"))
}
z, err := time.LoadLocation(strings.TrimSpace(timezoneID))
if err != nil {
fmt.Printf("load location failed: %s\n", err)
time.Local = time.FixedZone("CST", 8*3600)
return
}
time.Local = z
}
func DisableFeature(feature string) {
util.DisableFeature(feature)
}
func FilepathBase(path string) string {
return filepath.Base(path)
}
func FilterUploadFileName(name string) string {
return util.FilterUploadFileName(name)
}
func AssetName(name string) string {
return util.AssetName(name, ast.NewNodeID())
}
func HTML2Markdown(html string) string {
return util.NewLute().HTML2Md(html)
}
func Unzip(zipFilePath, destination string) {
if err := gulu.Zip.Unzip(zipFilePath, destination); nil != err {
logging.LogErrorf("unzip [%s] failed: %s", zipFilePath, err)
panic(err)
}
}
// GetExportFilePath 解析导出文件绝对路径,绕过 HTTP 层以避免锁屏密码拦截。
// exportPath 格式为 "/export/xxx.zip" 或 "assets/xxx"。
// 返回文件在磁盘上的绝对路径,以便原生端分块拷贝,避免大文件内存溢出。
// 解析失败返回空字符串。
func GetExportFilePath(exportPath string) (ret string) {
var absPath string
if strings.HasPrefix(exportPath, "/export/") {
fileName := strings.TrimPrefix(exportPath, "/export/")
if decoded, err := url.PathUnescape(fileName); err == nil {
fileName = decoded
}
fileName = filepath.Clean(fileName)
if strings.HasPrefix(fileName, "..") {
logging.LogWarnf("get export file path [%s] blocked: path traversal attempt [%s]", exportPath, fileName)
return
}
absPath = filepath.Join(util.TempDir, "export", fileName)
exportBaseDir := filepath.Join(util.TempDir, "export")
if !gulu.File.IsSubPath(exportBaseDir, absPath) {
logging.LogWarnf("get export file path [%s] blocked: path [%s] is outside export base dir [%s]", exportPath, absPath, exportBaseDir)
return
}
} else if strings.HasPrefix(exportPath, "assets/") {
var err error
absPath, err = model.GetAssetAbsPath(exportPath)
if nil != err {
logging.LogErrorf("get asset abs path [%s] failed: %s", exportPath, err)
return
}
} else {
logging.LogWarnf("get export file path [%s] failed: unsupported path prefix", exportPath)
return
}
if "" == absPath {
logging.LogWarnf("get export file path [%s] failed: resolved to empty abs path", exportPath)
return
}
return absPath
}
func Exit() {
os.Exit(logging.ExitCodeOk)
}