file-utils.vala 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. /*
  2. * file-utils.vala
  3. *
  4. * File system operations for valaq.
  5. * This class provides utility functions for file operations.
  6. */
  7. /**
  8. * Utility class for file system operations.
  9. *
  10. * Provides comprehensive file system functionality including VAPI file discovery,
  11. * path resolution, validation, and security checks. This class handles
  12. * the complex task of locating VAPI files across multiple standard directories
  13. * and provides safe file access with proper validation.
  14. */
  15. public class FileUtils : Object {
  16. /**
  17. * Array of VAPI search paths in order of preference.
  18. *
  19. * These paths are searched sequentially when resolving VAPI file names.
  20. * The order is important for precedence - earlier paths have priority.
  21. */
  22. private string[] vapi_search_paths;
  23. /**
  24. * Creates a new FileUtils instance.
  25. *
  26. * Initializes the VAPI search paths with standard system directories
  27. and any additional fallback paths that might exist on the system.
  28. */
  29. public FileUtils () {
  30. // Initialize VAPI search paths
  31. initialize_vapi_search_paths ();
  32. }
  33. /**
  34. * Initializes the VAPI search paths in order of preference.
  35. */
  36. private void initialize_vapi_search_paths () {
  37. vapi_search_paths = new string[0];
  38. // Primary search path
  39. vapi_search_paths += "/usr/share/vala-0.56/vapi";
  40. // Secondary search path
  41. vapi_search_paths += "/usr/share/vala/vapi";
  42. // Additional fallback paths (for backward compatibility)
  43. string[] fallback_paths = {
  44. "/usr/share/vala-0.54/vapi",
  45. "/usr/share/vala-0.52/vapi",
  46. "/usr/local/share/vala/vapi"
  47. };
  48. foreach (string path in fallback_paths) {
  49. if (file_exists (path)) {
  50. vapi_search_paths += path;
  51. }
  52. }
  53. }
  54. /**
  55. * Gets the VAPI search paths.
  56. *
  57. * @return Array of VAPI search paths in order of preference
  58. */
  59. public string[] get_vapi_search_paths () {
  60. return vapi_search_paths;
  61. }
  62. /**
  63. * Checks if a file exists.
  64. *
  65. * @param path Path to the file
  66. * @return True if file exists, false otherwise
  67. */
  68. public bool file_exists (string path) {
  69. return File.new_for_path (path).query_exists ();
  70. }
  71. /**
  72. * Checks if a path is a VAPI file.
  73. *
  74. * Determines if the given path points to a VAPI file by checking
  75. * the file extension. This is used for filtering and validation.
  76. *
  77. * @param path Path to check
  78. * @return True if path is a VAPI file, false otherwise
  79. */
  80. public bool is_vapi_file (string path) {
  81. return path.has_suffix (".vapi");
  82. }
  83. /**
  84. * Finds VAPI files in a directory.
  85. *
  86. * Scans the specified directory for files with .vapi extension.
  87. * Returns full paths to all found VAPI files. Handles directory
  88. * access errors gracefully and continues operation.
  89. *
  90. * @param directory Directory path to search for VAPI files
  91. * @return Array of VAPI file paths found in directory
  92. */
  93. public Gee.ArrayList<string> find_vapi_files (string directory) {
  94. var vapi_files = new Gee.ArrayList<string> ();
  95. try {
  96. var dir = File.new_for_path (directory);
  97. if (!dir.query_exists ()) {
  98. stderr.printf ("Warning: VAPI directory does not exist: %s\n", directory);
  99. return vapi_files;
  100. }
  101. var enumerator = dir.enumerate_children (
  102. FileAttribute.STANDARD_NAME + "," + FileAttribute.STANDARD_TYPE,
  103. FileQueryInfoFlags.NONE
  104. );
  105. FileInfo file_info;
  106. while ((file_info = enumerator.next_file ()) != null) {
  107. string filename = file_info.get_name ();
  108. if (is_vapi_file (filename)) {
  109. vapi_files.add (Path.build_filename (directory, filename));
  110. }
  111. }
  112. } catch (Error e) {
  113. stderr.printf ("Error scanning VAPI directory: %s\n", e.message);
  114. }
  115. return vapi_files;
  116. }
  117. /**
  118. * Finds VAPI files across all search paths.
  119. *
  120. * Scans all configured VAPI directories and returns a consolidated
  121. * list of available VAPI files. Removes duplicates based on basename
  122. * to avoid showing the same file from multiple directories.
  123. *
  124. * @return Array of VAPI file paths with duplicates removed
  125. */
  126. public Gee.ArrayList<string> find_all_vapi_files () {
  127. var all_vapi_files = new Gee.ArrayList<string> ();
  128. var seen_files = new Gee.HashSet<string> ();
  129. foreach (string search_path in vapi_search_paths) {
  130. var vapi_files = find_vapi_files (search_path);
  131. foreach (string file_path in vapi_files) {
  132. var file = File.new_for_path (file_path);
  133. string basename = file.get_basename ();
  134. // Avoid duplicates by checking basename
  135. if (!seen_files.contains (basename)) {
  136. seen_files.add (basename);
  137. all_vapi_files.add (file_path);
  138. }
  139. }
  140. }
  141. return all_vapi_files;
  142. }
  143. /**
  144. * Resolves a VAPI file name across all search paths.
  145. * Supports both basenames (e.g., "zlib.vapi") and names without extension (e.g., "zlib").
  146. * The first found file is used, with primary search path taking precedence.
  147. *
  148. * @param vapi_name Name of the VAPI file (with or without .vapi extension)
  149. * @return Full path to the VAPI file, or null if not found
  150. */
  151. public string? resolve_vapi_file (string vapi_name) {
  152. // Validate input
  153. if (vapi_name == "" || vapi_name.strip () == "") {
  154. return null;
  155. }
  156. // Check if this is a full path (contains path separators)
  157. if (vapi_name.contains ("/") || vapi_name.contains ("\\")) {
  158. // For full paths, we don't resolve through search paths
  159. // Just check if the file exists as-is
  160. return file_exists (vapi_name) ? vapi_name : null;
  161. }
  162. // This is a basename - ensure it has .vapi extension for searching
  163. string filename = vapi_name;
  164. if (!filename.has_suffix (".vapi")) {
  165. filename += ".vapi";
  166. }
  167. // First, check the current working directory
  168. string current_dir = Environment.get_current_dir ();
  169. string current_dir_path = Path.build_filename (current_dir, filename);
  170. if (file_exists (current_dir_path)) {
  171. return current_dir_path;
  172. }
  173. // Then search in all VAPI paths in order of preference
  174. foreach (string search_path in vapi_search_paths) {
  175. string full_path = Path.build_filename (search_path, filename);
  176. if (file_exists (full_path)) {
  177. return full_path;
  178. }
  179. }
  180. return null;
  181. }
  182. /**
  183. * Gets information about which search path contains a VAPI file.
  184. *
  185. * @param vapi_name Name of the VAPI file (with or without .vapi extension)
  186. * @return Array containing [search_path, full_path], or null if not found
  187. */
  188. public string[]? find_vapi_file_location (string vapi_name) {
  189. // Ensure the name has .vapi extension
  190. string filename = vapi_name;
  191. if (!filename.has_suffix (".vapi")) {
  192. filename += ".vapi";
  193. }
  194. // Search in all paths in order of preference
  195. foreach (string search_path in vapi_search_paths) {
  196. string full_path = Path.build_filename (search_path, filename);
  197. if (file_exists (full_path)) {
  198. return { search_path, full_path };
  199. }
  200. }
  201. return null;
  202. }
  203. /**
  204. * Gets the default VAPI directory.
  205. *
  206. * @return Path to the default VAPI directory (first in search paths)
  207. */
  208. public string get_default_vapi_directory () {
  209. // Return the first search path that exists
  210. foreach (string path in vapi_search_paths) {
  211. if (file_exists (path)) {
  212. return path;
  213. }
  214. }
  215. // Default fallback to the primary search path
  216. return vapi_search_paths.length > 0 ? vapi_search_paths[0] : "/usr/share/vala/vapi";
  217. }
  218. /**
  219. * Validates a file path for security and accessibility.
  220. *
  221. * Performs security checks to prevent directory traversal attacks
  222. * and ensures the path is within allowed directories. This is
  223. * crucial for preventing unauthorized file access.
  224. *
  225. * Security measures:
  226. * - Prevents "../" directory traversal
  227. * - Restricts access to VAPI directories and current working directory
  228. * - Allows absolute paths to existing VAPI files
  229. *
  230. * @param path Path to validate for security and accessibility
  231. * @return True if path is safe and allowed, false otherwise
  232. */
  233. public bool validate_path (string path) {
  234. // Check for directory traversal attempts
  235. if (path.contains ("../") || path.contains ("..\\")) {
  236. return false;
  237. }
  238. // Convert to absolute path and check if it's within allowed directories
  239. var file = File.new_for_path (path);
  240. string absolute_path;
  241. try {
  242. absolute_path = file.get_path ();
  243. } catch (Error e) {
  244. return false;
  245. }
  246. // Allow paths in any VAPI search directory or current working directory
  247. string[] allowed_prefixes = {};
  248. // Add all VAPI search paths
  249. foreach (string vapi_path in vapi_search_paths) {
  250. allowed_prefixes += vapi_path;
  251. }
  252. // Add current working directory
  253. allowed_prefixes += Environment.get_current_dir ();
  254. foreach (string prefix in allowed_prefixes) {
  255. if (absolute_path.has_prefix (prefix)) {
  256. return true;
  257. }
  258. }
  259. // Also allow absolute paths to VAPI files directly
  260. if (absolute_path.has_suffix (".vapi") && file_exists (absolute_path)) {
  261. return true;
  262. }
  263. return false;
  264. }
  265. }