GPAL - Generally Positive Automation Library v1.0
GPAL The Fluent Automation LIbrary
Loading...
Searching...
No Matches
Base64Helper.cs
1// =============================================================================
2// GPAL - Generally Positive Automation Library
3// Copyright © 2026 Software Decisions, Inc. All rights reserved.
4//
5// This file is part of GPAL.
6// Licensed under the Business Source License 1.1
7//
8// Primary development, architecture, and vision by Michael B. Vederman,
9// CEO of Software Decisions, Inc., Texas.
10//
11// Internal development maintained privately.
12// Public releases appear on GitHub: https://github.com/SoftwareDecisionsInc/GPAL.
13//
14// See LICENSE for full terms, including Additional Use Grant.
15// =============================================================================
16
17using System;
18using System.Collections.Generic;
19using System.IO;
20using System.Text;
21
22namespace GenerallyPositive
23{
29 public class Base64Helper
30 {
31 private byte[] _decodedBytes;
32 private string _suggestedExtension = ".bin"; // fallback
33
34 internal Base64Helper()
35 { }
43 public Base64Helper WithInput(string base64String)
44 {
45 string cleaned = base64String.Trim();
46
47 try
48 {
49 _decodedBytes = Convert.FromBase64String(cleaned);
50 }
51 catch (FormatException ex)
52 {
53 GPAL.PublishSimpleEvent(Enums.GPALEventType.EXCEPTION, "Invalid base64 string.", base64String, Enums.GPALObjectType.Other, ex);
54 }
55
56 if (_decodedBytes == null || _decodedBytes.Length == 0)
57 {
58 GPAL.PublishSimpleEvent(Enums.GPALEventType.WARNING, "Decoded base64 resulted in empty data.", base64String, Enums.GPALObjectType.Other);
59 }
60
61 // Peek at first few bytes to guess file type/extension
62 _suggestedExtension = FileTypeGuesser.GuessExtensionFromBytes(_decodedBytes);
63
64 return this;
65 }
66
74 public void SaveTo(GPALFile filename)
75 {
76 // If user gave no extension or a generic one, append/replace with suggested
77 string finalPath = ApplySuggestedExtension(filename.Filename, _suggestedExtension);
78
79 File.WriteAllBytes(finalPath, _decodedBytes);
80 }
81
86 public string SuggestedExtension => _suggestedExtension;
87
88 // --------------------------------------------------------------------
89 // Static helpers for simple string <-> base64 round trips (text only)
90 // --------------------------------------------------------------------
91
95 public static string EncodeToBase64(string text)
96 {
97 if (string.IsNullOrEmpty(text)) return string.Empty;
98 byte[] bytes = Encoding.UTF8.GetBytes(text);
99 return Convert.ToBase64String(bytes);
100 }
101
106 public static string DecodeFromBase64(string base64String)
107 {
108 byte[] bytes = null;
109
110 if (string.IsNullOrWhiteSpace(base64String))
111 GPAL.PublishSimpleEvent(Enums.GPALEventType.ERROR, "Base64 string cannot be null or empty.");
112 else
113 bytes = Convert.FromBase64String(base64String.Trim());
114
115 return Encoding.UTF8.GetString(bytes);
116 }
117
118 private static string ApplySuggestedExtension(string filename, string suggestedExt)
119 {
120 string ext = Path.GetExtension(filename).ToLowerInvariant();
121
122 if (string.IsNullOrEmpty(ext) || ext == ".txt" || ext == ".dat" || ext == ".bin")
123 {
124 string dir = Path.GetDirectoryName(filename) ?? "";
125 string nameWithoutExt = Path.GetFileNameWithoutExtension(filename);
126 return Path.Combine(dir, nameWithoutExt + suggestedExt);
127 }
128
129 return filename;
130 }
131
132 public static string GuessExtensionFromBytes(byte[] bytes)
133 {
134 return FileTypeGuesser.GuessExtensionFromBytes(bytes);
135 }
136
137}
138
139 internal static class FileTypeGuesser
140 {
141 // Signature definition
142 private class FileSignature
143 {
144 public string Extension;
145 public byte[] Pattern;
146 public int Offset;
147 }
148
149 // Signature table
150 private static readonly List<FileSignature> Signatures = new List<FileSignature>
151 {
152 // PDF
153 new FileSignature { Extension = ".pdf", Pattern = new byte[] { 0x25, 0x50, 0x44, 0x46 }, Offset = 0 },
154
155 // PNG
156 new FileSignature
157 {
158 Extension = ".png",
159 Pattern = new byte[] { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A },
160 Offset = 0
161 },
162
163 // JPEG
164 new FileSignature { Extension = ".jpg", Pattern = new byte[] { 0xFF, 0xD8 }, Offset = 0 },
165
166 // GIF87a / GIF89a
167 new FileSignature { Extension = ".gif", Pattern = Encoding.ASCII.GetBytes("GIF87a"), Offset = 0 },
168 new FileSignature { Extension = ".gif", Pattern = Encoding.ASCII.GetBytes("GIF89a"), Offset = 0 },
169
170 // BMP
171 new FileSignature { Extension = ".bmp", Pattern = Encoding.ASCII.GetBytes("BM"), Offset = 0 },
172
173 // ZIP container (docx, xlsx, jar, etc)
174 new FileSignature { Extension = ".zip", Pattern = new byte[] { 0x50, 0x4B, 0x03, 0x04 }, Offset = 0 },
175
176 // RAR
177 new FileSignature { Extension = ".rar", Pattern = Encoding.ASCII.GetBytes("Rar!"), Offset = 0 },
178
179 // 7z
180 new FileSignature
181 {
182 Extension = ".7z",
183 Pattern = new byte[] { 0x37, 0x7A, 0xBC, 0xAF, 0x27, 0x1C },
184 Offset = 0
185 },
186
187 // MP3 (ID3 tag)
188 new FileSignature { Extension = ".mp3", Pattern = Encoding.ASCII.GetBytes("ID3"), Offset = 0 },
189
190 // MP4 (ftyp at offset 4)
191 new FileSignature { Extension = ".mp4", Pattern = Encoding.ASCII.GetBytes("ftyp"), Offset = 4 },
192
193 // WAV / AVI (RIFF header)
194 new FileSignature { Extension = ".wav", Pattern = Encoding.ASCII.GetBytes("RIFF"), Offset = 0 }
195 };
196
197 // Main entry point
198 internal static string GuessExtensionFromBytes(byte[] bytes)
199 {
200 if (bytes == null || bytes.Length < 4)
201 return string.Empty;
202
203 foreach (var sig in Signatures)
204 {
205 if (IsMatch(bytes, sig))
206 {
207 // Special handling for ZIP based formats
208 if (sig.Extension == ".zip")
209 return DetectZipSubtype(bytes);
210
211 return sig.Extension;
212 }
213 }
214
215 return string.Empty;
216 }
217
218 // Pattern match routine
219 private static bool IsMatch(byte[] bytes, FileSignature sig)
220 {
221 if (bytes.Length < sig.Offset + sig.Pattern.Length)
222 return false;
223
224 for (int i = 0; i < sig.Pattern.Length; i++)
225 {
226 if (bytes[sig.Offset + i] != sig.Pattern[i])
227 return false;
228 }
229
230 return true;
231 }
232
233 // Detect Office formats inside ZIP without extracting
234 private static string DetectZipSubtype(byte[] bytes)
235 {
236 int scanLength = Math.Min(bytes.Length, 3000);
237 string headerText = Encoding.ASCII.GetString(bytes, 0, scanLength);
238
239 if (headerText.Contains("word/"))
240 return ".docx";
241
242 if (headerText.Contains("xl/"))
243 return ".xlsx";
244
245 if (headerText.Contains("ppt/"))
246 return ".pptx";
247
248 return ".zip";
249 }
250 }
251
252}
string SuggestedExtension
Returns the detected/suggested file extension for the decoded content. Useful for logging,...
void SaveTo(GPALFile filename)
Saves the decoded bytes to the specified file path. Uses the suggested extension if the filename lack...
static string DecodeFromBase64(string base64String)
Decodes a base64 string to UTF-8 text. Throws if the result is not valid UTF-8 or input is invalid ba...
Base64Helper WithInput(string base64String)
Sets the base64 input string to work with. Returns this instance for simple chaining: WithInput(....
static string EncodeToBase64(string text)
Encodes a plain string to base64 using UTF-8 encoding.
GPAL File object instantied with GPAL.File Used to load tokens into a GPALGrid [rows/columns].
Definition GPALFile.cs:36
string Filename
We have only one file, accessing it.
Definition GPALFile.cs:474
Everything starts here, all the GPAL controls and global settings using fluent syntax are here....
Definition GPAL.cs:49
static void PublishSimpleEvent(GPALEventType gPALEventType, string msg, dynamic gPALObject=null, Enums.GPALObjectType gPALObjectType=GPALObjectType.None, Exception ex=null)
Publish a message to either the information channel or exception channel (if exception passed in) Pub...
Definition GPAL.cs:2406