Добавлена работа с файлами. Добавлен показ изображений и загрузка файлов. Изменен дизайн

This commit is contained in:
Калинин Сергей Валерьевич
2025-07-21 13:10:19 +03:00
parent 10c9635894
commit c4ded33507
4 changed files with 528 additions and 69 deletions

223
vault.go
View File

@@ -11,6 +11,7 @@ import (
// "context"
"log"
// "time"
"io"
"os"
"fmt"
"flag"
@@ -20,6 +21,7 @@ import (
"strings"
"time"
"encoding/json"
"encoding/base64"
"crypto/tls"
"net/http"
"html/template"
@@ -85,15 +87,45 @@ type WrapInfo struct {
CreationPath string `json:"creation_path"`
}
type FileViewerData struct {
Title string
Base64Data string
MimeType string
FileSize string
FileName string
IsImage bool
}
// const MaxTextLength = 100
func vaultDataWrap(vaultAddr string, text string) string {
secret := make(map[string]string)
secret["wrapped_data"] = text
func vaultDataWrap(vaultAddr string, dataType string, content string) string {
secret := make(map[string]string)
// switch dataType {
// case "text":
// secret["wrapped_data"] = text
// case "file":
// secret["file"] = text
// default:
// secret["wrapped_data"] = text
// }
// Определяем ключ для данных
if Debug {
log.Println("Тип данныж:", dataType)
}
if dataType == "text" {
// Для текста используем ключ "text"
secret["text"] = content
} else {
// Для файла используем имя файла как ключ
// Если dataType не "text" и не пустое, считаем его именем файла
if dataType == "" {
dataType = "file" // fallback, если имя не указано
}
secret[dataType] = content
}
postBody, err := json.Marshal(secret)
if err != nil {
log.Println("Errored marshaling the text:", text)
log.Println("Errored marshaling the text:", content)
}
customTransport := &(*http.DefaultTransport.(*http.Transport))
@@ -201,50 +233,100 @@ func getStaticPage(w http.ResponseWriter, r *http.Request) {
}
}
func showFileViewer(w http.ResponseWriter, r *http.Request, fileData []byte, fileName string) {
// Определяем MIME-тип файла
mimeType := http.DetectContentType(fileData)
isImage := strings.HasPrefix(mimeType, "image/")
// Кодируем в base64
base64Data := base64.StdEncoding.EncodeToString(fileData)
// Подготавливаем данные для шаблона
data := FileViewerData{
Title: "Просмотр файла",
Base64Data: base64Data,
MimeType: mimeType,
FileSize: fmt.Sprintf("%.2f", float64(len(fileData))/1024),
FileName: fileName,
IsImage: isImage,
}
// Парсим шаблон
tmpl, err := template.ParseFiles(filepath.Join(TemplateDir, "file-viewer.html"))
if err != nil {
http.Error(w, "Error loading template", http.StatusInternalServerError)
return
}
// Отображаем шаблон
w.Header().Set("Content-Type", "text/html; charset=utf-8")
err = tmpl.Execute(w, data)
if err != nil {
http.Error(w, "Error rendering template", http.StatusInternalServerError)
}
}
// hvs.CAES - 95
// s.Dj7kZS - 26
func unwrapDataFromHtmlForm(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
token := r.FormValue("input_token")
vaultPath := VaultAddress + "/v1/sys/wrapping/unwrap"
Data = ""
// fmt.Fprintln(w, r.URL.RawQuery)
// log.Println(w, r.URL.RawQuery)
if Debug {
log.Printf("Текст для шифровки: %s ", token)
log.Printf("Адрес сервера Hashicorp Vault: %s ", vaultPath)
}
// Проверка текста на соответствие шаблону
// Нормализация токена
re := regexp.MustCompile(`^(hvs|s)\.[\w\-\_]+`)
if Debug {
fmt.Println(re.Match([]byte(token)))
}
token = strings.TrimSpace(strings.ReplaceAll(token, "\r\n", ""))
token = strings.ReplaceAll(token, "\r", "")
token = strings.ReplaceAll(token, "\n", "")
token = strings.ReplaceAll(token, " ", "")
token = strings.TrimSpace(token)
if token != "" && re.MatchString(token) {
unwrappedData := vaultDataUnWrap(vaultPath, token)
// 1. Сначала проверяем наличие текстовых данных
if textData, exists := unwrappedData["text"]; exists {
Data = textData
} else {
// 2. Если текста нет, ищем файловые данные (любой ключ, кроме "text")
var fileData []byte
var fileName string
for key, value := range unwrappedData {
if key != "text" { // Пропускаем текстовые данные, если они есть
// Декодируем base64
decoded, err := base64.StdEncoding.DecodeString(value)
if err != nil {
log.Printf("Ошибка декодирования файла %s: %v", key, err)
continue
}
fileData = decoded
fileName = key
break
}
}
if token != "" && re.Match([]byte(token)) {
b := new(bytes.Buffer)
for key, value := range vaultDataUnWrap(vaultPath, token) {
fmt.Fprintf(b, "%s: %s\n", key, value)
if fileData != nil {
// Если нашли файл - показываем просмотрщик
showFileViewer(w, r, fileData, fileName)
return
} else {
// Если не нашли ни текста, ни файла - выводим все данные как текст
b := new(bytes.Buffer)
for key, value := range unwrappedData {
fmt.Fprintf(b, "%s: %s\n", key, value)
}
Data = b.String()
}
}
Data = b.String()
if Data == "" {
Data = "Ошибка! Токен не найден."
}
if Debug {
log.Println(Data)
Data = "Ошибка! Токен не содержит данных."
}
} else if token != "" {
Data = "Введенные данные не соответствуют формату. Введите корректный токен."
}
getStaticPage(w, r)
// http.Redirect(w, r, "http://"+r.Host, http.StatusMovedPermanently)
}
func genPassword(w http.ResponseWriter, r *http.Request) {
@@ -307,7 +389,7 @@ func wrapDataFromHtmlForm(w http.ResponseWriter, r *http.Request) {
fmt.Println("Введен текст:", secret)
}
if secret != "" {
Data = vaultDataWrap(vaultPath, secret)
Data = vaultDataWrap(vaultPath, "text", secret)
if Data == "" {
Data = "Ошибка! Токен не найден."
}
@@ -325,6 +407,80 @@ func wrapDataFromHtmlForm(w http.ResponseWriter, r *http.Request) {
getStaticPage(w, r)
}
func wrapDataFromFile(w http.ResponseWriter, r *http.Request) {
MaxFileLength := 100000 // Максимальный размер файла (100KB)
// 1. Проверяем метод (должен быть POST)
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
// 2. Проверяем Content-Type (должен быть multipart/form-data)
contentType := r.Header.Get("Content-Type")
if !strings.HasPrefix(contentType, "multipart/form-data") {
http.Error(w, "Expected multipart/form-data", http.StatusBadRequest)
return
}
// 3. Парсим форму с ограничением размера (10 МБ)
maxMemory := 10 << 20 // 10 MB
if err := r.ParseMultipartForm(int64(maxMemory)); err != nil {
log.Printf("Failed to parse multipart form: %v", err)
http.Error(w, "Unable to parse form (file too big?)", http.StatusBadRequest)
return
}
// 4. Получаем файл из формы (включая метаданные)
file, fileHeader, err := r.FormFile("file")
if err != nil {
log.Printf("Failed to get file from form: %v", err)
http.Error(w, "Error retrieving the file", http.StatusBadRequest)
return
}
defer file.Close()
// Получаем оригинальное имя файла
fileName := fileHeader.Filename
if fileName == "" {
fileName = "unnamed_file" // Запасной вариант, если имя не указано
}
if Debug {
log.Printf("Processing file: %s (size: %d bytes)", fileName, fileHeader.Size)
}
// 5. Читаем содержимое файла
fileBytes, err := io.ReadAll(file)
if err != nil {
log.Printf("Failed to read file: %v", err)
http.Error(w, "Error reading the file", http.StatusInternalServerError)
return
}
// 6. Проверяем размер файла
if len(fileBytes) > MaxFileLength {
errMsg := fmt.Sprintf("File too large (%d > %d)", len(fileBytes), MaxFileLength)
http.Error(w, errMsg, http.StatusBadRequest)
return
}
// 7. Кодируем в base64
encoded := base64.StdEncoding.EncodeToString(fileBytes)
// 8. Обёртываем данные в Vault с именем файла
vaultPath := VaultAddress + "/v1/sys/wrapping/wrap"
token := vaultDataWrap(vaultPath, fileName, encoded)
if token == "" {
http.Error(w, "Failed to wrap data in Vault", http.StatusInternalServerError)
return
}
ttl, _ := strconv.Atoi(TokenTTL)
ttl = ttl / 3600
Data = fmt.Sprintf("%s\n\n---\nВремя жизни токена %s ч.", token, strconv.Itoa(ttl))
getStaticPage(w, r)
}
func main() {
var (
logFile string
@@ -371,8 +527,8 @@ func main() {
} else if os.Getenv("VAULT_TOKEN") != "" && VaultToken == "" {
VaultToken = os.Getenv("VAULT_TOKEN")
}
if os.Getenv("MAX_TEXT_LENGTH") != "" && MaxTextLength == 100 {
if os.Getenv("MAX_TEXT_LENGTH") != "" && MaxTextLength == 100 {
MaxTextLength, err = strconv.Atoi(os.Getenv("MAX_TEXT_LENGTH"))
if err != nil {
log.Printf("Ошибка преобразования значения ", os.Getenv("MAX_TEXT_LENGTH"))
@@ -386,6 +542,7 @@ func main() {
rtr := mux.NewRouter()
rtr.HandleFunc("/unwrap", unwrapDataFromHtmlForm)
rtr.HandleFunc("/wrap", wrapDataFromHtmlForm)
rtr.HandleFunc("/wrapfile", wrapDataFromFile).Methods("POST")
rtr.HandleFunc("/genpassword/{passLength:[0-9]+}", genPassword)
rtr.HandleFunc("/genpassword", genPassword)