From 91f717008f4024bb98cf8471af5ee3ec90c7ee68 Mon Sep 17 00:00:00 2001 From: czoczo Date: Sun, 1 Jun 2025 20:08:56 +0200 Subject: [PATCH] backend working snap just before avatar toggle --- webpage/backend/.gitignore | 1 + webpage/backend/main.go | 142 ++++++++++++++++--------------------- 2 files changed, 63 insertions(+), 80 deletions(-) create mode 100644 webpage/backend/.gitignore diff --git a/webpage/backend/.gitignore b/webpage/backend/.gitignore new file mode 100644 index 0000000..4b8e66b --- /dev/null +++ b/webpage/backend/.gitignore @@ -0,0 +1 @@ +BetterBashRepo diff --git a/webpage/backend/main.go b/webpage/backend/main.go index 0ce44d3..23482ff 100644 --- a/webpage/backend/main.go +++ b/webpage/backend/main.go @@ -1,6 +1,7 @@ package main import ( +// "bufio" // Keep for other potential uses, though not strictly needed for the new bb.sh logic if using SplitN "bytes" "encoding/base64" "fmt" @@ -9,7 +10,7 @@ import ( "os" "os/exec" "path/filepath" - "regexp" +// "regexp" // Keep for other potential uses, though not for bb.sh color insertion "strings" "sync" "sync/atomic" @@ -22,27 +23,15 @@ const ( ) var ( - // requestCounter stores the number of HTTP requests (excluding /stats and /reload). requestCounter uint64 - // repoMutex protects git operations like clone/pull - repoMutex sync.Mutex + repoMutex sync.Mutex ) -// colorComponentKeys defines the names for the color components in their encoding order. var colorComponentKeys = []string{ - "PRIMARY_COLOR", - "SECONDARY_COLOR", - "ROOT_COLOR", - "TIME_COLOR", - "ERR_COLOR", - "SEPARATOR_COLOR", - "BORDCOL", - "PATH_COLOR", - // The 9th component (C9, potentially "Reset") is decoded but not listed for output. + "PRIMARY_COLOR", "SECONDARY_COLOR", "ROOT_COLOR", "TIME_COLOR", + "ERR_COLOR", "SEPARATOR_COLOR", "BORDCOL", "PATH_COLOR", } -// decodeColorLogic processes the encoded data string and returns color information. -// It returns a map of color keys to bash color values, a formatted string list of these, and an error. func decodeColorLogic(encodedData string) (map[string]string, string, error) { standardBase64 := strings.ReplaceAll(encodedData, "-", "+") standardBase64 = strings.ReplaceAll(standardBase64, "_", "/") @@ -66,12 +55,12 @@ func decodeColorLogic(encodedData string) (map[string]string, string, error) { fiveBitValues[5] = (b[3] & 0x7C) >> 2 fiveBitValues[6] = ((b[3] & 0x03) << 3) | (b[4] >> 5) fiveBitValues[7] = b[4] & 0x1F - fiveBitValues[8] = b[5] >> 3 // C9, not used in direct output string but decoded + fiveBitValues[8] = b[5] >> 3 colorsMap := make(map[string]string) var resultList strings.Builder - for i := 0; i < 8; i++ { // Only first 8 components for output + for i := 0; i < 8; i++ { val5bit := fiveBitValues[i] baseColor07 := (val5bit >> 2) & 0x07 lightBit := (val5bit >> 1) & 0x01 @@ -89,13 +78,13 @@ func decodeColorLogic(encodedData string) (map[string]string, string, error) { bashColor := fmt.Sprintf(`\[\033[%d;%dm\]`, styleAttr, actualAnsiCode) colorsMap[colorComponentKeys[i]] = bashColor + // Ensure each definition is on a new line, directly usable in shell script resultList.WriteString(fmt.Sprintf("%s='%s'\n", colorComponentKeys[i], bashColor)) } - - return colorsMap, strings.TrimSpace(resultList.String()), nil + // Remove the last newline character from the block of definitions if present for cleaner insertion + return colorsMap, strings.TrimSuffix(resultList.String(), "\n"), nil } -// serveDecodedColorsOnlyHandler serves only the decoded color definitions. func serveDecodedColorsOnlyHandler(w http.ResponseWriter, r *http.Request, encodedData string) { _, formattedOutput, err := decodeColorLogic(encodedData) if err != nil { @@ -103,18 +92,21 @@ func serveDecodedColorsOnlyHandler(w http.ResponseWriter, r *http.Request, encod return } w.Header().Set("Content-Type", "text/plain; charset=utf-8") - fmt.Fprint(w, formattedOutput) + // The formattedOutput from decodeColorLogic is already KEY='VAL'\nKEY2='VAL2' + // So it prints multiple lines as intended. + fmt.Fprintln(w, formattedOutput) } // serveFileWithColorsHandler serves files from the repo, potentially modifying bb.sh. func serveFileWithColorsHandler(w http.ResponseWriter, r *http.Request, encodedData string, requestedFilePath string) { - colorsMap, _, err := decodeColorLogic(encodedData) + // Note: The 'colorsMap' is not strictly needed for the new bb.sh logic, + // as 'formattedColorDefinitions' is used directly. But decodeColorLogic provides it. + _, formattedColorDefinitions, err := decodeColorLogic(encodedData) if err != nil { http.Error(w, fmt.Sprintf("Failed to decode colors: %v", err), http.StatusBadRequest) return } - // Sanitize file path cleanFilePath := filepath.Clean(requestedFilePath) if strings.HasPrefix(cleanFilePath, "..") || strings.HasPrefix(cleanFilePath, "/") { http.Error(w, "Invalid file path.", http.StatusBadRequest) @@ -122,8 +114,6 @@ func serveFileWithColorsHandler(w http.ResponseWriter, r *http.Request, encodedD } fullPath := filepath.Join(localRepoPath, cleanFilePath) - - // Security check: ensure the path is still within the localRepoPath absRepoPath, _ := filepath.Abs(localRepoPath) absFilePath, _ := filepath.Abs(fullPath) if !strings.HasPrefix(absFilePath, absRepoPath) { @@ -145,51 +135,71 @@ func serveFileWithColorsHandler(w http.ResponseWriter, r *http.Request, encodedD return } - if cleanFilePath == bbShellPath { - // Special handling for prompt/bb.sh - content, err := os.ReadFile(fullPath) + originalContentBytes, err := os.ReadFile(fullPath) if err != nil { http.Error(w, fmt.Sprintf("Error reading %s: %v", bbShellPath, err), http.StatusInternalServerError) return } + originalContent := string(originalContentBytes) + + var finalScript strings.Builder + + // Split the original content to find the shebang and the rest of the script + lines := strings.SplitN(originalContent, "\n", 2) + firstLineOriginal := "" + restOfScript := "" - modifiedScript := string(content) - for key, colorValue := range colorsMap { - // Regex to find lines like `KEY='...'`, `export KEY="..."`, or `KEY=value` - // It captures `export ` (if present) and the key name. - // It replaces the whole line. - // We need to be careful with shell syntax. For simplicity, we assume KEY='...' - // For robust parsing, a proper shell parser would be needed. - // This regex looks for `KEY=`, potentially with `export` prefix, and replaces value. - // It assumes the value is single-quoted or simply the rest of the line. - // Example: PRIMARY_COLOR='...' or export PRIMARY_COLOR='...' - regex := regexp.MustCompile(fmt.Sprintf(`^(export\s+)?%s\s*=\s*('.*?'|".*?"|[^#\s]*)`, regexp.QuoteMeta(key))) - replacement := fmt.Sprintf("${1}%s='%s'", key, colorValue) - modifiedScript = regex.ReplaceAllString(modifiedScript, replacement) + if len(lines) > 0 { + firstLineOriginal = lines[0] + } + + // Correctly assign restOfScript + if len(lines) > 1 { + restOfScript = lines[1] + } else if len(lines) == 1 { // Script might be a single line or empty + if originalContent == firstLineOriginal { // single line script + restOfScript = "" + } else { // empty script, lines[0] would be "" + restOfScript = "" + } + } + + + if strings.HasPrefix(firstLineOriginal, "#!") { + finalScript.WriteString(firstLineOriginal) // Write shebang + finalScript.WriteString("\n") // Newline after shebang + finalScript.WriteString(formattedColorDefinitions) // This is KEY='VAL'\nKEY2='VAL2' + finalScript.WriteString("\n") // Ensure a newline after the injected block + if restOfScript != "" { + finalScript.WriteString(restOfScript) + } + } else { + // No shebang, or script didn't start with it. + // Per revised requirement, we should still try to inject. + // Prepend colors to the original content. + finalScript.WriteString(formattedColorDefinitions) + finalScript.WriteString("\n") + finalScript.WriteString(originalContent) } w.Header().Set("Content-Type", "text/plain; charset=utf-8") - fmt.Fprint(w, modifiedScript) + fmt.Fprint(w, finalScript.String()) } else { - // Serve other files as is http.ServeFile(w, r, fullPath) } } -// statsReportHandler serves the HTTP request counter. func statsReportHandler(w http.ResponseWriter, r *http.Request) { count := atomic.LoadUint64(&requestCounter) w.Header().Set("Content-Type", "text/plain; charset=utf-8") fmt.Fprintf(w, "%d", count) } -// rootPathHandler handles requests to the bare "/" path. func rootPathHandler(w http.ResponseWriter, r *http.Request) { http.Error(w, "Please provide an encoded string in the URL path (e.g., /YourEncodedString) or a file path (e.g. /YourEncodedString/path/to/file.sh)", http.StatusBadRequest) } -// runGitCommand executes a git command and logs its output. func runGitCommand(dir string, args ...string) error { cmd := exec.Command("git", args...) if dir != "" { @@ -214,7 +224,6 @@ func runGitCommand(dir string, args ...string) error { return nil } -// setupRepo clones the repository if it doesn't exist locally. func setupRepo() error { repoMutex.Lock() defer repoMutex.Unlock() @@ -227,23 +236,15 @@ func setupRepo() error { log.Printf("Repository cloned successfully into %s.", localRepoPath) } else { log.Printf("Local repository found at %s. Skipping clone.", localRepoPath) - // Optionally, pull latest on startup too - // log.Printf("Pulling latest changes for %s...", localRepoPath) - // if err := runGitCommand(localRepoPath, "pull", "origin", "master"); err != nil { - // return fmt.Errorf("failed to pull repository on startup: %w", err) - // } - // log.Printf("Repository updated successfully.") } return nil } -// reloadRepoHandler handles the /reload endpoint to pull the latest from git. func reloadRepoHandler(w http.ResponseWriter, r *http.Request) { repoMutex.Lock() defer repoMutex.Unlock() log.Printf("Attempting to pull latest changes for repository at %s", localRepoPath) - // Ensure the repo exists before trying to pull if _, err := os.Stat(localRepoPath); os.IsNotExist(err) { log.Printf("Local repository at %s does not exist. Cloning first.", localRepoPath) if err := runGitCommand("", "clone", gitRepoURL, localRepoPath); err != nil { @@ -256,15 +257,10 @@ func reloadRepoHandler(w http.ResponseWriter, r *http.Request) { return } - // Attempt to reset any local changes and then pull - // This is a forceful way to ensure it matches origin/master - // Be cautious with `git reset --hard` if local changes could be important, - // but for a cache/mirror this is often desired. err := runGitCommand(localRepoPath, "fetch", "origin") if err == nil { err = runGitCommand(localRepoPath, "reset", "--hard", "origin/master") } - // Fallback to simple pull if reset fails or as primary strategy if err != nil { log.Printf("Hard reset failed (or fetch failed): %v. Attempting simple pull.", err) err = runGitCommand(localRepoPath, "pull", "origin", "master") @@ -282,62 +278,49 @@ func reloadRepoHandler(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "Repository reloaded successfully.") } -// mainRouter is the primary HTTP handler. func mainRouter(w http.ResponseWriter, r *http.Request) { if r.URL.Path == "/stats" { statsReportHandler(w, r) return } - - // Increment request counter for non-stats paths. - // Reload is also a functional path so we count it. atomic.AddUint64(&requestCounter, 1) if r.URL.Path == "/reload" { reloadRepoHandler(w, r) return } - if r.URL.Path == "/" { rootPathHandler(w, r) return } - // Path structure: /[/] trimmedPath := strings.TrimPrefix(r.URL.Path, "/") parts := strings.SplitN(trimmedPath, "/", 2) - encodedData := parts[0] - if encodedData == "" { // Path was just "/" or "//..." - rootPathHandler(w, r) // Or a more specific error + + if encodedData == "" { + rootPathHandler(w, r) return } if len(parts) == 1 { - // Only / serveDecodedColorsOnlyHandler(w, r, encodedData) } else if len(parts) == 2 { - // // filePath := parts[1] - if filePath == "" { // e.g. /encodedData/ + if filePath == "" { http.Error(w, "File path cannot be empty if a second slash is provided.", http.StatusBadRequest) return } serveFileWithColorsHandler(w, r, encodedData, filePath) } - // SplitN with 2 parts will always result in len 1 or 2 if input is not empty. - // If trimmedPath was empty, parts would be {""}, len 1. This is caught by encodedData=="" } func main() { - // Clone the repository at startup if it doesn't exist if err := setupRepo(); err != nil { log.Fatalf("❌ Failed to setup repository: %s\n", err) - // Depending on strictness, you might want to os.Exit(1) or try to run without the repo functionality } http.HandleFunc("/", mainRouter) - port := "8080" if p := os.Getenv("PORT"); p != "" { port = p @@ -346,7 +329,7 @@ func main() { log.Printf("🎨 Color Theme Backend & File Server starting on port %s (e.g., http://localhost:%s)\n", port, port) log.Printf("Git Repo URL: %s", gitRepoURL) log.Printf("Local Repo Path: ./%s", localRepoPath) - log.Printf("Special file for color replacement: %s", bbShellPath) + log.Printf("Special file for color injection: %s", bbShellPath) log.Printf("Endpoints:") log.Printf(" GET / - Show color definitions") log.Printf(" GET // - Serve file from repo (e.g., /VcrS_H8A/removebb.sh)") @@ -355,7 +338,6 @@ func main() { log.Printf(" GET /stats - Show request count") log.Printf(" GET / - Show usage instructions") - if err := http.ListenAndServe(":"+port, nil); err != nil { log.Fatalf("❌ Failed to start server: %s\n", err) }