diff --git a/dlls/shell32/xdg.c b/dlls/shell32/xdg.c index 3b1a1bc1201..b541aa73d59 100644 --- a/dlls/shell32/xdg.c +++ b/dlls/shell32/xdg.c @@ -16,10 +16,38 @@ * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + * + * + * XDG_UserDirLookup() and helper functions are based on code from: + * http://www.freedesktop.org/wiki/Software/xdg-user-dirs + * + * Copyright (c) 2007 Red Hat, inc + * + * From the xdg-user-dirs license: + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ #include "config.h" +#include #include #include #include @@ -712,3 +740,230 @@ char *XDG_GetStringValue(XDG_PARSED_FILE *file, const char *group_name, const ch return NULL; } + +/* Get the name of the xdg configuration file. + * + * [in] home_dir - $HOME + * [out] config_file - the name of the configuration file + */ +static HRESULT get_xdg_config_file(char * home_dir, char ** config_file) +{ + char *config_home; + + config_home = getenv("XDG_CONFIG_HOME"); + if (!config_home || !config_home[0]) + { + *config_file = HeapAlloc(GetProcessHeap(), 0, strlen(home_dir) + strlen("/.config/user-dirs.dirs") + 1); + if (!*config_file) + return E_OUTOFMEMORY; + + strcpy(*config_file, home_dir); + strcat(*config_file, "/.config/user-dirs.dirs"); + } + else + { + *config_file = HeapAlloc(GetProcessHeap(), 0, strlen(config_home) + strlen("/user-dirs.dirs") + 1); + if (!*config_file) + return E_OUTOFMEMORY; + + strcpy(*config_file, config_home); + strcat(*config_file, "/user-dirs.dirs"); + } + return S_OK; +} + +/* Parse the key in a line in the xdg-user-dir config file. + * i.e. XDG_PICTURES_DIR="$HOME/Pictures" + * ^ ^ + * + * [in] xdg_dirs - array of xdg directories to look for + * [in] num_dirs - number of elements in xdg_dirs + * [in/out] p_ptr - pointer to where we are in the buffer + * Returns the index to xdg_dirs if we find the key, or -1 on error. + */ +static int parse_config1(const char ** xdg_dirs, const unsigned int num_dirs, char ** p_ptr) +{ + char *p; + int i; + + p = *p_ptr; + while (*p == ' ' || *p == '\t') + p++; + if (strncmp(p, "XDG_", 4)) + return -1; + + p += 4; + for (i = 0; i < num_dirs; i++) + { + if (!strncmp(p, xdg_dirs[i], strlen(xdg_dirs[i]))) + { + p += strlen(xdg_dirs[i]); + break; + } + } + if (i == num_dirs) + return -1; + if (strncmp(p, "_DIR", 4)) + return -1; + p += 4; + while (*p == ' ' || *p == '\t') + p++; + if (*p != '=') + return -1; + p++; + while (*p == ' ' || *p == '\t') + p++; + if (*p != '"') + return -1; + p++; + + *p_ptr = p; + return i; +} + +/* Parse the value in a line in the xdg-user-dir config file. + * i.e. XDG_PICTURES_DIR="$HOME/Pictures" + * ^ ^ + * + * [in] p - pointer to the buffer + * [in] home_dir - $HOME + * [out] out_ptr - the directory name + */ +static HRESULT parse_config2(char * p, const char * home_dir, char ** out_ptr) +{ + BOOL relative; + char *out, *d; + + relative = FALSE; + + if (!strncmp(p, "$HOME/", 6)) + { + p += 6; + relative = TRUE; + } + else if (*p != '/') + return E_FAIL; + + if (relative) + { + out = HeapAlloc(GetProcessHeap(), 0, strlen(home_dir) + strlen(p) + 2); + if (!out) + return E_OUTOFMEMORY; + + strcpy(out, home_dir); + strcat(out, "/"); + } + else + { + out = HeapAlloc(GetProcessHeap(), 0, strlen(p) + 1); + if (!out) + return E_OUTOFMEMORY; + *out = 0; + } + + d = out + strlen(out); + while (*p && *p != '"') + { + if ((*p == '\\') && (*(p + 1) != 0)) + p++; + *d++ = *p++; + } + *d = 0; + *out_ptr = out; + return S_OK; +} + +/* Parse part of a line in the xdg-user-dir config file. + * i.e. XDG_PICTURES_DIR="$HOME/Pictures" + * ^ ^ + * + * The calling function is responsible for freeing all elements of out_ptr as + * well as out_ptr itself. + * + * [in] xdg_dirs - array of xdg directories to look for + * [in] num_dirs - number of elements in xdg_dirs + * [out] out_ptr - an array of the xdg directories names + */ +HRESULT XDG_UserDirLookup(const char ** xdg_dirs, const unsigned int num_dirs, char *** out_ptr) +{ + FILE *file; + char **out; + char *home_dir, *config_file; + char buffer[512]; + int i, len; + HRESULT hr; + + *out_ptr = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, num_dirs * sizeof(char *)); + out = *out_ptr; + if (!out) + return E_OUTOFMEMORY; + + home_dir = getenv("HOME"); + if (!home_dir) + { + hr = E_FAIL; + goto xdg_user_dir_lookup_error; + } + + hr = get_xdg_config_file(home_dir, &config_file); + if (FAILED(hr)) + goto xdg_user_dir_lookup_error; + + file = fopen(config_file, "r"); + HeapFree(GetProcessHeap(), 0, config_file); + if (!file) + { + hr = E_HANDLE; + goto xdg_user_dir_lookup_error; + } + + while (fgets(buffer, sizeof(buffer), file)) + { + int idx; + char *p; + + /* Remove newline at end */ + len = strlen(buffer); + if (len > 0 && buffer[len-1] == '\n') + buffer[len-1] = 0; + + /* Parse the key */ + p = buffer; + idx = parse_config1(xdg_dirs, num_dirs, &p); + if (idx < 0) + continue; + if (out[idx]) + continue; + + /* Parse the value */ + hr = parse_config2(p, home_dir, &out[idx]); + if (FAILED(hr)) + { + if (hr == E_OUTOFMEMORY) + goto xdg_user_dir_lookup_error; + continue; + } + } + hr = S_OK; + + /* Remove entries for directories that do no exist */ + for (i = 0; i < num_dirs; i++) + { + struct stat statFolder; + + if (!out[i]) + continue; + if (!stat(out[i], &statFolder) && S_ISDIR(statFolder.st_mode)) + continue; + HeapFree(GetProcessHeap(), 0, out[i]); + out[i] = NULL; + } + +xdg_user_dir_lookup_error: + if (FAILED(hr)) + { + for (i = 0; i < num_dirs; i++) HeapFree(GetProcessHeap(), 0, out[i]); + HeapFree(GetProcessHeap(), 0, out_ptr); + } + return hr; +} diff --git a/dlls/shell32/xdg.h b/dlls/shell32/xdg.h index 6c083c66e32..9ae2872baab 100644 --- a/dlls/shell32/xdg.h +++ b/dlls/shell32/xdg.h @@ -47,5 +47,6 @@ HRESULT TRASH_UnpackItemID(LPCSHITEMID id, TRASH_ELEMENT *element, WIN32_FIND_DA HRESULT TRASH_EnumItems(LPITEMIDLIST **pidls, int *count); void TRASH_DisposeElement(TRASH_ELEMENT *element); +HRESULT XDG_UserDirLookup(const char ** xdg_dirs, const unsigned int num_dirs, char *** out_ptr); #endif /* ndef __XDG_H__ */