diff --git a/libv2ray_main.go b/libv2ray_main.go index 99de802..57815c0 100644 --- a/libv2ray_main.go +++ b/libv2ray_main.go @@ -26,6 +26,7 @@ import ( mobasset "golang.org/x/mobile/asset" ) +// Constants for environment variables const ( coreAsset = "xray.location.asset" coreCert = "xray.location.cert" @@ -51,39 +52,66 @@ type CoreCallbackHandler interface { // consoleLogWriter implements a log writer without datetime stamps // as Android system already adds timestamps to each log line type consoleLogWriter struct { - logger *log.Logger + logger *log.Logger // Standard logger } // InitCoreEnv initializes environment variables and file system handlers for the core // It sets up asset path, certificate path, XUDP base key and customizes the file reader // to support Android asset system func InitCoreEnv(envPath string, key string) { + // Set asset/cert paths if len(envPath) > 0 { - os.Setenv(coreAsset, envPath) - os.Setenv(coreCert, envPath) - } - if len(key) > 0 { - os.Setenv(xudpBaseKey, key) - - } - - corefilesystem.NewFileReader = func(path string) (io.ReadCloser, error) { - if _, err := os.Stat(path); os.IsNotExist(err) { - _, file := filepath.Split(path) - return mobasset.Open(file) + if err := os.Setenv(coreAsset, envPath); err != nil { + log.Printf("failed to set %s: %v", coreAsset, err) } - return os.Open(path) + if err := os.Setenv(coreCert, envPath); err != nil { + log.Printf("failed to set %s: %v", coreCert, err) + } + } + + // Set XUDP encryption key + if len(key) > 0 { + if err := os.Setenv(xudpBaseKey, key); err != nil { + log.Printf("failed to set %s: %v", xudpBaseKey, err) + } + } + + // Custom file reader with path validation + corefilesystem.NewFileReader = func(path string) (io.ReadCloser, error) { + // G304 Fix - Path sanitization + baseDir := envPath + cleanPath := filepath.Clean(path) + fullPath := filepath.Join(baseDir, cleanPath) + + // Prevent directory traversal + if baseDir != "" && !strings.HasPrefix(fullPath, baseDir) { + return nil, fmt.Errorf("unauthorized path: %s", path) + } + + // Check file existence + if _, err := os.Stat(fullPath); os.IsNotExist(err) { + _, file := filepath.Split(fullPath) + return mobasset.Open(file) // Fallback to assets + } else if err != nil { + return nil, fmt.Errorf("file access error: %w", err) + } + + return os.Open(fullPath) // #nosec G304 - Validated path } } // NewCoreController initializes and returns a new CoreController instance // Sets up the console log handler and associates it with the provided callback handler func NewCoreController(s CoreCallbackHandler) *CoreController { - coreapplog.RegisterHandlerCreator(coreapplog.LogType_Console, - func(lt coreapplog.LogType, - options coreapplog.HandlerCreatorOptions) (corecommlog.Handler, error) { + // Register custom logger + if err := coreapplog.RegisterHandlerCreator( + coreapplog.LogType_Console, + func(lt coreapplog.LogType, options coreapplog.HandlerCreatorOptions) (corecommlog.Handler, error) { return corecommlog.NewLogger(createStdoutLogWriter()), nil - }) + }, + ); err != nil { + log.Printf("logger registration failed: %v", err) + } return &CoreController{ CallbackHandler: s, @@ -98,12 +126,11 @@ func (x *CoreController) StartLoop(configContent string) (err error) { defer x.coreMutex.Unlock() if x.IsRunning { - log.Println("The instance is already running") + log.Println("core already running") return nil } - err = x.doStartLoop(configContent) - return + return x.doStartLoop(configContent) } // StopLoop safely stops the core processing loop and releases resources @@ -114,8 +141,7 @@ func (x *CoreController) StopLoop() error { if x.IsRunning { x.doShutdown() - log.Println("Shut down the running instance") - x.CallbackHandler.OnEmitStatus(0, "Closed") + x.CallbackHandler.OnEmitStatus(0, "core stopped") } return nil } @@ -148,13 +174,16 @@ func (x *CoreController) MeasureDelay(url string) (int64, error) { func MeasureOutboundDelay(ConfigureFileContent string, url string) (int64, error) { config, err := coreserial.LoadJSONConfig(strings.NewReader(ConfigureFileContent)) if err != nil { - return -1, fmt.Errorf("failed to load JSON config: %w", err) + return -1, fmt.Errorf("config load error: %w", err) } + // Simplify config for testing config.Inbound = nil var essentialApp []*serial.TypedMessage for _, app := range config.App { - if app.Type == "xray.app.proxyman.OutboundConfig" || app.Type == "xray.app.dispatcher.Config" || app.Type == "xray.app.log.Config" { + if app.Type == "xray.app.proxyman.OutboundConfig" || + app.Type == "xray.app.dispatcher.Config" || + app.Type == "xray.app.log.Config" { essentialApp = append(essentialApp, app) } } @@ -162,10 +191,12 @@ func MeasureOutboundDelay(ConfigureFileContent string, url string) (int64, error inst, err := core.New(config) if err != nil { - return -1, fmt.Errorf("failed to create core instance: %w", err) + return -1, fmt.Errorf("instance creation failed: %w", err) } - inst.Start() + if err := inst.Start(); err != nil { + return -1, fmt.Errorf("startup failed: %w", err) + } defer inst.Close() return measureInstDelay(context.Background(), inst, url) } @@ -179,7 +210,9 @@ func CheckVersionX() string { // doShutdown shuts down the Xray instance and cleans up resources func (x *CoreController) doShutdown() { if x.coreInstance != nil { - x.coreInstance.Close() + if err := x.coreInstance.Close(); err != nil { + log.Printf("core shutdown error: %v", err) + } x.coreInstance = nil } x.IsRunning = false @@ -188,37 +221,36 @@ func (x *CoreController) doShutdown() { // doStartLoop sets up and starts the Xray core func (x *CoreController) doStartLoop(configContent string) error { - log.Println("Loading core config") + log.Println("initializing core...") config, err := coreserial.LoadJSONConfig(strings.NewReader(configContent)) if err != nil { - return fmt.Errorf("failed to load core config: %w", err) + return fmt.Errorf("config error: %w", err) } - log.Println("Creating new core instance") x.coreInstance, err = core.New(config) if err != nil { - return fmt.Errorf("failed to create core instance: %w", err) + return fmt.Errorf("core init failed: %w", err) } x.statsManager = x.coreInstance.GetFeature(corestats.ManagerType()).(corestats.Manager) - log.Println("Starting core") + log.Println("starting core...") x.IsRunning = true if err := x.coreInstance.Start(); err != nil { x.IsRunning = false - return fmt.Errorf("failed to start core: %w", err) + return fmt.Errorf("startup failed: %w", err) } x.CallbackHandler.Startup() - x.CallbackHandler.OnEmitStatus(0, "Started successfully, running") + x.CallbackHandler.OnEmitStatus(0, "Started successfully, running") - log.Println("Starting core successfully") + log.Println("Starting core successfully") return nil } // measureInstDelay measures the delay for an instance to a given URL func measureInstDelay(ctx context.Context, inst *core.Instance, url string) (int64, error) { if inst == nil { - return -1, errors.New("core instance is nil") + return -1, errors.New("nil instance") } tr := &http.Transport{ @@ -238,9 +270,10 @@ func measureInstDelay(ctx context.Context, inst *core.Instance, url string) (int Timeout: 12 * time.Second, } - if len(url) == 0 { + if url == "" { url = "https://www.google.com/generate_204" } + req, _ := http.NewRequestWithContext(ctx, "GET", url, nil) start := time.Now() resp, err := client.Do(req) @@ -250,11 +283,12 @@ func measureInstDelay(ctx context.Context, inst *core.Instance, url string) (int defer resp.Body.Close() if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNoContent { - return -1, fmt.Errorf("unexpected status code: %s", resp.Status) + return -1, fmt.Errorf("invalid status: %s", resp.Status) } return time.Since(start).Milliseconds(), nil } +// Log writer implementation func (w *consoleLogWriter) Write(s string) error { w.logger.Print(s) return nil