1 // sass.hpp must go before all system headers to get the
2 // __EXTENSIONS__ fix on Solaris.
3 #include "sass.hpp"
4 
5 #include <iostream>
6 #include "output.hpp"
7 #include "plugins.hpp"
8 #include "util.hpp"
9 
10 #ifdef _WIN32
11 #include <windows.h>
12 #else
13 #include <sys/types.h>
14 #include <dirent.h>
15 #include <errno.h>
16 #include <dlfcn.h>
17 #endif
18 
19 namespace Sass {
20 
Plugins(void)21   Plugins::Plugins(void) { }
~Plugins(void)22   Plugins::~Plugins(void)
23   {
24     for (auto function : functions) {
25       sass_delete_function(function);
26     }
27     for (auto importer : importers) {
28       sass_delete_importer(importer);
29     }
30     for (auto header : headers) {
31       sass_delete_importer(header);
32     }
33   }
34 
35   // check if plugin is compatible with this version
36   // plugins may be linked static against libsass
37   // we try to be compatible between major versions
compatibility(const char * their_version)38   inline bool compatibility(const char* their_version)
39   {
40 // const char* their_version = "3.1.2";
41     // first check if anyone has an unknown version
42     const char* our_version = libsass_version();
43     if (!strcmp(their_version, "[na]")) return false;
44     if (!strcmp(our_version, "[na]")) return false;
45 
46     // find the position of the second dot
47     size_t pos = sass::string(our_version).find('.', 0);
48     if (pos != sass::string::npos) pos = sass::string(our_version).find('.', pos + 1);
49 
50     // if we do not have two dots we fallback to compare complete string
51     if (pos == sass::string::npos) { return strcmp(their_version, our_version) ? 0 : 1; }
52     // otherwise only compare up to the second dot (major versions)
53     else { return strncmp(their_version, our_version, pos) ? 0 : 1; }
54 
55   }
56 
57   // load one specific plugin
load_plugin(const sass::string & path)58   bool Plugins::load_plugin (const sass::string& path)
59   {
60 
61     typedef const char* (*__plugin_version__)(void);
62     typedef Sass_Function_List (*__plugin_load_fns__)(void);
63     typedef Sass_Importer_List (*__plugin_load_imps__)(void);
64 
65     if (LOAD_LIB(plugin, path))
66     {
67       // try to load initial function to query libsass version suppor
68       if (LOAD_LIB_FN(__plugin_version__, plugin_version, "libsass_get_version"))
69       {
70         // get the libsass version of the plugin
71         if (!compatibility(plugin_version())) return false;
72         // try to get import address for "libsass_load_functions"
73         if (LOAD_LIB_FN(__plugin_load_fns__, plugin_load_functions, "libsass_load_functions"))
74         {
75           Sass_Function_List fns = plugin_load_functions(), _p = fns;
76           while (fns && *fns) { functions.push_back(*fns); ++ fns; }
77           sass_free_memory(_p); // only delete the container, items not yet
78         }
79         // try to get import address for "libsass_load_importers"
80         if (LOAD_LIB_FN(__plugin_load_imps__, plugin_load_importers, "libsass_load_importers"))
81         {
82           Sass_Importer_List imps = plugin_load_importers(), _p = imps;
83           while (imps && *imps) { importers.push_back(*imps); ++ imps; }
84           sass_free_memory(_p); // only delete the container, items not yet
85         }
86         // try to get import address for "libsass_load_headers"
87         if (LOAD_LIB_FN(__plugin_load_imps__, plugin_load_headers, "libsass_load_headers"))
88         {
89           Sass_Importer_List imps = plugin_load_headers(), _p = imps;
90           while (imps && *imps) { headers.push_back(*imps); ++ imps; }
91           sass_free_memory(_p); // only delete the container, items not yet
92         }
93         // success
94         return true;
95       }
96       else
97       {
98         // print debug message to stderr (should not happen)
99         std::cerr << "failed loading 'libsass_support' in <" << path << ">" << std::endl;
100         if (const char* dlsym_error = dlerror()) std::cerr << dlsym_error << std::endl;
101         CLOSE_LIB(plugin);
102       }
103     }
104     else
105     {
106       // print debug message to stderr (should not happen)
107       std::cerr << "failed loading plugin <" << path << ">" << std::endl;
108       if (const char* dlopen_error = dlerror()) std::cerr << dlopen_error << std::endl;
109     }
110 
111     return false;
112 
113   }
114 
load_plugins(const sass::string & path)115   size_t Plugins::load_plugins(const sass::string& path)
116   {
117 
118     // count plugins
119     size_t loaded = 0;
120 
121     #ifdef _WIN32
122 
123       try
124       {
125 
126         // use wchar (utf16)
127         WIN32_FIND_DATAW data;
128         // trailing slash is guaranteed
129         sass::string globsrch(path + "*.dll");
130         // convert to wide chars (utf16) for system call
131         std::wstring wglobsrch(UTF_8::convert_to_utf16(globsrch));
132         HANDLE hFile = FindFirstFileW(wglobsrch.c_str(), &data);
133         // check if system called returned a result
134         // ToDo: maybe we should print a debug message
135         if (hFile == INVALID_HANDLE_VALUE) return -1;
136 
137         // read directory
138         while (true)
139         {
140           try
141           {
142             // the system will report the filenames with wide chars (utf16)
143             sass::string entry = UTF_8::convert_from_utf16(data.cFileName);
144             // check if file ending matches exactly
145             if (!ends_with(entry, ".dll")) continue;
146             // load the plugin and increase counter
147             if (load_plugin(path + entry)) ++ loaded;
148             // check if there should be more entries
149             if (GetLastError() == ERROR_NO_MORE_FILES) break;
150             // load next entry (check for return type)
151             if (!FindNextFileW(hFile, &data)) break;
152           }
153           catch (...)
154           {
155             // report the error to the console (should not happen)
156             // seems like we got strange data from the system call?
157             std::cerr << "filename in plugin path has invalid utf8?" << std::endl;
158           }
159         }
160       }
161       catch (utf8::invalid_utf8&)
162       {
163         // report the error to the console (should not happen)
164         // implementors should make sure to provide valid utf8
165         std::cerr << "plugin path contains invalid utf8" << std::endl;
166       }
167 
168     #else
169 
170       DIR *dp;
171       struct dirent *dirp;
172       if((dp  = opendir(path.c_str())) == NULL) return -1;
173       while ((dirp = readdir(dp)) != NULL) {
174         #if __APPLE__
175           if (!ends_with(dirp->d_name, ".dylib")) continue;
176         #else
177           if (!ends_with(dirp->d_name, ".so")) continue;
178         #endif
179         if (load_plugin(path + dirp->d_name)) ++ loaded;
180       }
181       closedir(dp);
182 
183     #endif
184     return loaded;
185 
186   }
187 
188 }
189