1// This is an open source non-commercial project. Dear PVS-Studio, please check
2// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
3
4#include <stdbool.h>
5
6#include "nvim/os/stdpaths_defs.h"
7#include "nvim/os/os.h"
8#include "nvim/path.h"
9#include "nvim/memory.h"
10#include "nvim/ascii.h"
11
12/// Names of the environment variables, mapped to XDGVarType values
13static const char *xdg_env_vars[] = {
14 [kXDGConfigHome] = "XDG_CONFIG_HOME",
15 [kXDGDataHome] = "XDG_DATA_HOME",
16 [kXDGCacheHome] = "XDG_CACHE_HOME",
17 [kXDGRuntimeDir] = "XDG_RUNTIME_DIR",
18 [kXDGConfigDirs] = "XDG_CONFIG_DIRS",
19 [kXDGDataDirs] = "XDG_DATA_DIRS",
20};
21
22#ifdef WIN32
23static const char *const xdg_defaults_env_vars[] = {
24 [kXDGConfigHome] = "LOCALAPPDATA",
25 [kXDGDataHome] = "LOCALAPPDATA",
26 [kXDGCacheHome] = "TEMP",
27 [kXDGRuntimeDir] = NULL,
28 [kXDGConfigDirs] = NULL,
29 [kXDGDataDirs] = NULL,
30};
31#endif
32
33/// Defaults for XDGVarType values
34///
35/// Used in case environment variables contain nothing. Need to be expanded.
36static const char *const xdg_defaults[] = {
37#ifdef WIN32
38 [kXDGConfigHome] = "~\\AppData\\Local",
39 [kXDGDataHome] = "~\\AppData\\Local",
40 [kXDGCacheHome] = "~\\AppData\\Local\\Temp",
41 [kXDGRuntimeDir] = NULL,
42 [kXDGConfigDirs] = NULL,
43 [kXDGDataDirs] = NULL,
44#else
45 [kXDGConfigHome] = "~/.config",
46 [kXDGDataHome] = "~/.local/share",
47 [kXDGCacheHome] = "~/.cache",
48 [kXDGRuntimeDir] = NULL,
49 [kXDGConfigDirs] = "/etc/xdg/",
50 [kXDGDataDirs] = "/usr/local/share/:/usr/share/",
51#endif
52};
53
54/// Return XDG variable value
55///
56/// @param[in] idx XDG variable to use.
57///
58/// @return [allocated] variable value.
59char *stdpaths_get_xdg_var(const XDGVarType idx)
60 FUNC_ATTR_WARN_UNUSED_RESULT
61{
62 const char *const env = xdg_env_vars[idx];
63 const char *const fallback = xdg_defaults[idx];
64
65 const char *env_val = os_getenv(env);
66
67#ifdef WIN32
68 if (env_val == NULL && xdg_defaults_env_vars[idx] != NULL) {
69 env_val = os_getenv(xdg_defaults_env_vars[idx]);
70 }
71#else
72 if (env_val == NULL && os_env_exists(env)) {
73 env_val = "";
74 }
75#endif
76
77 char *ret = NULL;
78 if (env_val != NULL) {
79 ret = xstrdup(env_val);
80 } else if (fallback) {
81 ret = (char *)expand_env_save((char_u *)fallback);
82 }
83
84 return ret;
85}
86
87/// Return Nvim-specific XDG directory subpath.
88///
89/// Windows: Uses "…/nvim-data" for kXDGDataHome to avoid storing
90/// configuration and data files in the same path. #4403
91///
92/// @param[in] idx XDG directory to use.
93///
94/// @return [allocated] "{xdg_directory}/nvim"
95char *get_xdg_home(const XDGVarType idx)
96 FUNC_ATTR_WARN_UNUSED_RESULT
97{
98 char *dir = stdpaths_get_xdg_var(idx);
99 if (dir) {
100#if defined(WIN32)
101 dir = concat_fnames_realloc(dir,
102 (idx == kXDGDataHome ? "nvim-data" : "nvim"),
103 true);
104#else
105 dir = concat_fnames_realloc(dir, "nvim", true);
106#endif
107 }
108 return dir;
109}
110
111/// Return subpath of $XDG_CONFIG_HOME
112///
113/// @param[in] fname New component of the path.
114///
115/// @return [allocated] `$XDG_CONFIG_HOME/nvim/{fname}`
116char *stdpaths_user_conf_subpath(const char *fname)
117 FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
118{
119 return concat_fnames_realloc(get_xdg_home(kXDGConfigHome), fname, true);
120}
121
122/// Return subpath of $XDG_DATA_HOME
123///
124/// @param[in] fname New component of the path.
125/// @param[in] trailing_pathseps Amount of trailing path separators to add.
126/// @param[in] escape_commas If true, all commas will be escaped.
127///
128/// @return [allocated] `$XDG_DATA_HOME/nvim/{fname}`.
129char *stdpaths_user_data_subpath(const char *fname,
130 const size_t trailing_pathseps,
131 const bool escape_commas)
132 FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
133{
134 char *ret = concat_fnames_realloc(get_xdg_home(kXDGDataHome), fname, true);
135 const size_t len = strlen(ret);
136 const size_t numcommas = (escape_commas ? memcnt(ret, ',', len) : 0);
137 if (numcommas || trailing_pathseps) {
138 ret = xrealloc(ret, len + trailing_pathseps + numcommas + 1);
139 for (size_t i = 0 ; i < len + numcommas ; i++) {
140 if (ret[i] == ',') {
141 memmove(ret + i + 1, ret + i, len - i + numcommas);
142 ret[i] = '\\';
143 i++;
144 }
145 }
146 if (trailing_pathseps) {
147 memset(ret + len + numcommas, PATHSEP, trailing_pathseps);
148 }
149 ret[len + trailing_pathseps + numcommas] = NUL;
150 }
151 return ret;
152}
153