Based on Nekogram. Key additions: - Rebrand to FoxiGram (app name, APK name, applicationId com.foxigram.app) - Embedded Xray (VLESS+Reality) proxy client via JNI libxray.so - Bundled hidden one-tap proxies (LTE + WiFi), read-only in UI - Auto-restore proxy on restart, rebind to active network (LTE/WiFi) - Server credentials externalized to git-ignored XrayServers.java (+ template) - libxray Go source included; compiled .so, keystore, google-services.json ignored
225 lines
5.4 KiB
Go
225 lines
5.4 KiB
Go
// Copyright 2024 The BoringSSL Authors
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// https://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"cmp"
|
|
"crypto/sha256"
|
|
"fmt"
|
|
"os/exec"
|
|
"slices"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
type treeEntryMode int
|
|
|
|
const (
|
|
treeEntryRegular treeEntryMode = iota
|
|
treeEntryExecutable
|
|
treeEntrySymlink
|
|
)
|
|
|
|
func (m treeEntryMode) String() string {
|
|
switch m {
|
|
case treeEntryRegular:
|
|
return "regular file"
|
|
case treeEntryExecutable:
|
|
return "executable file"
|
|
case treeEntrySymlink:
|
|
return "symbolic link"
|
|
}
|
|
panic(fmt.Sprintf("unknown mode %d", m))
|
|
}
|
|
|
|
type treeEntry struct {
|
|
path string
|
|
mode treeEntryMode
|
|
sha256 []byte
|
|
}
|
|
|
|
func sortTree(tree []treeEntry) {
|
|
slices.SortFunc(tree, func(a, b treeEntry) int { return cmp.Compare(a.path, b.path) })
|
|
}
|
|
|
|
func compareTrees(got, want []treeEntry) error {
|
|
// Check for duplicate files.
|
|
for i := 0; i < len(got)-1; i++ {
|
|
if got[i].path == got[i+1].path {
|
|
return fmt.Errorf("duplicate file %q in archive", got[i].path)
|
|
}
|
|
}
|
|
|
|
// Check for differences between the two trees.
|
|
for i := 0; i < len(got) && i < len(want); i++ {
|
|
if got[i].path == want[i].path {
|
|
if got[i].mode != want[i].mode {
|
|
return fmt.Errorf("file %q was a %s but should have been a %s", got[i].path, got[i].mode, want[i].mode)
|
|
}
|
|
if !bytes.Equal(got[i].sha256, want[i].sha256) {
|
|
return fmt.Errorf("hash of %q was %x but should have been %x", got[i].path, got[i].sha256, want[i].sha256)
|
|
}
|
|
} else if got[i].path < want[i].path {
|
|
return fmt.Errorf("unexpected file %q", got[i].path)
|
|
} else {
|
|
return fmt.Errorf("missing file %q", want[i].path)
|
|
}
|
|
}
|
|
if len(want) < len(got) {
|
|
return fmt.Errorf("unexpected file %q", got[len(want)].path)
|
|
}
|
|
if len(got) < len(want) {
|
|
return fmt.Errorf("missing file %q", want[len(got)].path)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type gitTreeEntry struct {
|
|
path string
|
|
mode treeEntryMode
|
|
objectName string
|
|
}
|
|
|
|
func gitListTree(treeish string) ([]gitTreeEntry, error) {
|
|
var stdout, stderr bytes.Buffer
|
|
cmd := exec.Command("git", "ls-tree", "-r", "-z", treeish)
|
|
cmd.Stdout = &stdout
|
|
cmd.Stderr = &stderr
|
|
if err := cmd.Run(); err != nil {
|
|
return nil, fmt.Errorf("error listing git tree %q: %w\n%s\n", treeish, err, stderr.String())
|
|
}
|
|
lines := strings.Split(stdout.String(), "\x00")
|
|
ret := make([]gitTreeEntry, 0, len(lines))
|
|
for _, line := range lines {
|
|
if len(line) == 0 {
|
|
continue
|
|
}
|
|
|
|
idx := strings.IndexByte(line, '\t')
|
|
if idx < 0 {
|
|
return nil, fmt.Errorf("could not parse ls-tree output %q", line)
|
|
}
|
|
|
|
info, path := line[:idx], line[idx+1:]
|
|
infos := strings.Split(info, " ")
|
|
if len(infos) != 3 {
|
|
return nil, fmt.Errorf("could not parse ls-tree output %q", line)
|
|
}
|
|
|
|
perms, objectType, objectName := infos[0], infos[1], infos[2]
|
|
if objectType != "blob" {
|
|
return nil, fmt.Errorf("unexpected object type in ls-tree output %q", line)
|
|
}
|
|
|
|
var mode treeEntryMode
|
|
switch perms {
|
|
case "100644":
|
|
mode = treeEntryRegular
|
|
case "100755":
|
|
mode = treeEntryExecutable
|
|
case "120000":
|
|
mode = treeEntrySymlink
|
|
default:
|
|
return nil, fmt.Errorf("unexpected file mode in ls-tree output %q", line)
|
|
}
|
|
|
|
ret = append(ret, gitTreeEntry{path: path, mode: mode, objectName: objectName})
|
|
}
|
|
return ret, nil
|
|
}
|
|
|
|
func gitHashBlob(objectName string) ([]byte, error) {
|
|
h := sha256.New()
|
|
var stderr bytes.Buffer
|
|
cmd := exec.Command("git", "cat-file", "blob", objectName)
|
|
cmd.Stdout = h
|
|
cmd.Stderr = &stderr
|
|
if err := cmd.Run(); err != nil {
|
|
return nil, fmt.Errorf("error hashing git object %q: %w\n%s\n", objectName, err, stderr.String())
|
|
}
|
|
return h.Sum(nil), nil
|
|
}
|
|
|
|
func gitHashTree(s *stepPrinter, treeish string) ([]treeEntry, error) {
|
|
gitTree, err := gitListTree(treeish)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
s.setTotal(len(gitTree))
|
|
|
|
// Hashing objects one by one is slow, so parallelize. Ideally we could
|
|
// just use the object name, but git uses SHA-1, so checking a SHA-265
|
|
// hash seems prudent.
|
|
var workerErr error
|
|
var workerLock sync.Mutex
|
|
|
|
var wg sync.WaitGroup
|
|
jobs := make(chan gitTreeEntry, *numWorkers)
|
|
results := make(chan treeEntry, *numWorkers)
|
|
for i := 0; i < *numWorkers; i++ {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
for job := range jobs {
|
|
workerLock.Lock()
|
|
shouldStop := workerErr != nil
|
|
workerLock.Unlock()
|
|
if shouldStop {
|
|
break
|
|
}
|
|
|
|
sha256, err := gitHashBlob(job.objectName)
|
|
if err != nil {
|
|
workerLock.Lock()
|
|
if workerErr == nil {
|
|
workerErr = err
|
|
}
|
|
workerLock.Unlock()
|
|
break
|
|
}
|
|
|
|
results <- treeEntry{path: job.path, mode: job.mode, sha256: sha256}
|
|
}
|
|
}()
|
|
}
|
|
|
|
go func() {
|
|
for _, job := range gitTree {
|
|
jobs <- job
|
|
}
|
|
close(jobs)
|
|
wg.Wait()
|
|
close(results)
|
|
}()
|
|
|
|
tree := make([]treeEntry, 0, len(gitTree))
|
|
for result := range results {
|
|
s.addProgress(1)
|
|
tree = append(tree, result)
|
|
}
|
|
|
|
if workerErr != nil {
|
|
return nil, workerErr
|
|
}
|
|
|
|
if len(tree) != len(gitTree) {
|
|
panic("input and output sizes did not match")
|
|
}
|
|
|
|
sortTree(tree)
|
|
return tree, nil
|
|
}
|