1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  *   Licensed to the Apache Software Foundation (ASF) under one or more
12  *   contributor license agreements. See the NOTICE file distributed
13  *   with this work for additional information regarding copyright
14  *   ownership. The ASF licenses this file to you under the Apache
15  *   License, Version 2.0 (the "License"); you may not use this file
16  *   except in compliance with the License. You may obtain a copy of
17  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 
21 #include <options.hxx>
22 
23 #include <osl/diagnose.h>
24 #include <rtl/string.hxx>
25 #include <rtl/strbuf.hxx>
26 
27 #include <rtl/ustring.hxx>
28 #include <osl/file.hxx>
29 #include <o3tl/char16_t2wchar_t.hxx>
30 
31 #ifdef _WIN32
32 #   if !defined WIN32_LEAN_AND_MEAN
33 #      define WIN32_LEAN_AND_MEAN
34 #   endif
35 #   include <windows.h>
36 #endif
37 
38 #include <stdio.h>
39 #include <string.h>
40 
41 
Options(char const * progname)42 Options::Options(char const * progname)
43   : m_program(progname), m_stdin(false), m_verbose(false), m_quiet(false)
44 {
45 }
46 
~Options()47 Options::~Options()
48 {
49 }
50 
51 // static
checkArgument(std::vector<std::string> & rArgs,char const * arg,size_t len)52 bool Options::checkArgument (std::vector< std::string > & rArgs, char const * arg, size_t len)
53 {
54   bool result = ((arg != nullptr) && (len > 0));
55   OSL_PRECOND(result, "idlc::Options::checkArgument(): invalid arguments");
56   if (result)
57   {
58     switch(arg[0])
59     {
60     case '@':
61       result = len > 1;
62       if (result)
63       {
64         // "@<cmdfile>"
65         result = Options::checkCommandFile (rArgs, &(arg[1]));
66       }
67       break;
68     case '-':
69       result = len > 1;
70       if (result)
71       {
72         // "-<option>"
73         switch (arg[1])
74         {
75         case 'O':
76         case 'M':
77         case 'I':
78         case 'D':
79           {
80             // "-<option>[<param>]
81             std::string option(&(arg[0]), 2);
82             rArgs.push_back(option);
83             if (len > 2)
84             {
85               // "-<option><param>"
86               std::string param(&(arg[2]), len - 2);
87               rArgs.push_back(param);
88             }
89             break;
90           }
91         default:
92           // "-<option>" ([long] option, w/o param)
93           rArgs.emplace_back(arg, len);
94           break;
95         }
96       }
97       break;
98     default:
99       // "<param>"
100       rArgs.emplace_back(arg, len);
101       break;
102     }
103   }
104   return result;
105 }
106 
107 // static
checkCommandFile(std::vector<std::string> & rArgs,char const * filename)108 bool Options::checkCommandFile (std::vector< std::string > & rArgs, char const * filename)
109 {
110     FILE * fp = fopen(filename, "r");
111     if (fp == nullptr)
112     {
113         fprintf(stderr, "ERROR: can't open command file \"%s\"\n", filename);
114         return false;
115     }
116 
117     std::string buffer;
118     buffer.reserve(256);
119 
120     bool quoted = false;
121     int c = EOF;
122     while ((c = fgetc(fp)) != EOF)
123     {
124         switch(c)
125         {
126         case '\"':
127           quoted = !quoted;
128           break;
129         case ' ':
130         case '\t':
131         case '\r':
132         case '\n':
133           if (!quoted)
134           {
135               if (!buffer.empty())
136               {
137                   // append current argument.
138                   if (!Options::checkArgument(rArgs, buffer.c_str(), buffer.size()))
139                   {
140                       (void) fclose(fp);
141                       return false;
142                   }
143                   buffer.clear();
144               }
145               break;
146           }
147           [[fallthrough]];
148         default:
149           buffer.push_back(sal::static_int_cast<char>(c));
150           break;
151         }
152     }
153     if (!buffer.empty())
154     {
155         // append unterminated argument.
156         if (!Options::checkArgument(rArgs, buffer.c_str(), buffer.size()))
157         {
158             (void) fclose(fp);
159             return false;
160         }
161         buffer.clear();
162     }
163     return (fclose(fp) == 0);
164 }
165 
badOption(char const * reason,std::string const & rArg)166 bool Options::badOption(char const * reason, std::string const & rArg)
167 {
168   OStringBuffer message;
169   if (reason != nullptr)
170   {
171     message.append(reason); message.append(" option '"); message.append(rArg.c_str()); message.append("'");
172     throw IllegalArgument(message.makeStringAndClear());
173   }
174   return false;
175 }
176 
setOption(char const * option,std::string const & rArg)177 bool Options::setOption(char const * option, std::string const & rArg)
178 {
179   bool result = (0 == strcmp(option, rArg.c_str()));
180   if (result)
181     m_options[rArg.c_str()] = OString(rArg.c_str(), rArg.size());
182   return result;
183 }
184 
185 #ifdef _WIN32
186 /* Helper function to convert windows paths including spaces, brackets etc. into
187    a windows short Url. The ucpp preprocessor has problems with such paths and returns
188    with error.
189 */
convertIncPathtoShortWindowsPath(const OString & incPath)190 static OString convertIncPathtoShortWindowsPath(const OString& incPath) {
191     OUString path = OStringToOUString(incPath, RTL_TEXTENCODING_UTF8);
192 
193     std::vector<sal_Unicode> vec(path.getLength() + 1);
194     //GetShortPathNameW only works if the file can be found!
195     const DWORD len = GetShortPathNameW(
196         o3tl::toW(path.getStr()), o3tl::toW(vec.data()), path.getLength() + 1);
197 
198     if (len > 0)
199     {
200         OUString ret(vec.data(), len);
201         return OUStringToOString(ret, RTL_TEXTENCODING_UTF8);
202     }
203 
204     return incPath;
205 }
206 #endif
207 
initOptions(std::vector<std::string> & rArgs)208 bool Options::initOptions(std::vector< std::string > & rArgs)
209 {
210   std::vector< std::string >::const_iterator first = rArgs.begin(), last = rArgs.end();
211   for (; first != last; ++first)
212   {
213     if ((*first)[0] != '-')
214     {
215       OString filename((*first).c_str(), (*first).size());
216       OString tmp(filename.toAsciiLowerCase());
217       if (tmp.lastIndexOf(".idl") != (tmp.getLength() - 4))
218       {
219         throw IllegalArgument("'" + filename + "' is not a valid input file, only '*.idl' files will be accepted");
220       }
221       m_inputFiles.push_back(filename);
222       continue;
223     }
224 
225     std::string const option(*first);
226     switch((*first)[1])
227     {
228     case 'O':
229       {
230         if ((++first == last) || ((*first)[0] == '-'))
231         {
232           return badOption("invalid", option);
233         }
234         OString param((*first).c_str(), (*first).size());
235         m_options["-O"] = param;
236         break;
237       }
238     case 'M':
239       {
240         if ((++first == last) || ((*first)[0] == '-'))
241         {
242           return badOption("invalid", option);
243         }
244         OString param((*first).c_str(), (*first).size());
245         m_options["-M"] = param;
246         break;
247       }
248     case 'I':
249       {
250         if ((++first == last) || ((*first)[0] == '-'))
251         {
252           return badOption("invalid", option);
253         }
254         OString param((*first).c_str(), (*first).size());
255         {
256           // quote param token(s).
257           OStringBuffer buffer;
258           sal_Int32 k = 0;
259           do
260           {
261             if (!buffer.isEmpty())
262               buffer.append(' ');
263 //          buffer.append("-I\"");
264 #ifdef _WIN32
265             OString incpath = convertIncPathtoShortWindowsPath(param.getToken(0, ';', k));
266 #else
267             OString incpath = param.getToken(0, ';', k);
268 #endif
269             buffer.append(incpath);
270 //          buffer.append("\"");
271           } while (k != -1);
272           param = buffer.makeStringAndClear();
273         }
274         if (m_options.count("-I") > 0)
275         {
276           // append param.
277           param = m_options["-I"] + " " + param;
278         }
279         m_options["-I"] = param;
280         break;
281       }
282     case 'D':
283       {
284         if ((++first == last) || ((*first)[0] == '-'))
285         {
286           return badOption("invalid", option);
287         }
288         OString param = "-D" + rtl::OStringView((*first).c_str(), (*first).size());
289         if (m_options.count("-D") > 0)
290         {
291           param = m_options["-D"] + " " + param;
292         }
293         m_options["-D"] = param;
294         break;
295       }
296     case 'C':
297       {
298         if (!setOption("-C", option))
299         {
300           return badOption("invalid", option);
301         }
302         break;
303       }
304     case 'c':
305       {
306         if (!setOption("-cid", option))
307         {
308           return badOption("invalid", option);
309         }
310         break;
311       }
312     case 'q':
313       {
314         if (!setOption("-quiet", option))
315         {
316           return badOption("invalid", option);
317         }
318         m_quiet = true;
319         break;
320       }
321     case 'v':
322       {
323         if (!setOption("-verbose", option))
324         {
325           return badOption("invalid", option);
326         }
327         m_verbose = true;
328         break;
329       }
330     case 'w':
331       {
332         if (!(setOption("-w", option) || setOption("-we", option)))
333         {
334           return badOption("invalid", option);
335         }
336         break;
337       }
338     case 'h':
339     case '?':
340       {
341         if (!(setOption("-h", option) || setOption("-?", option)))
342         {
343           return badOption("invalid", option);
344         }
345         {
346           (void) fprintf(stdout, "%s", prepareHelp().getStr());
347           return false;
348         }
349         // break; // Unreachable
350       }
351     case 's':
352       {
353         if (!setOption("-stdin", option))
354         {
355           return badOption("invalid", option);
356         }
357         m_stdin = true;
358         break;
359       }
360     default:
361       return badOption("unknown", option);
362     }
363   }
364   return true;
365 }
366 
prepareHelp() const367 OString Options::prepareHelp() const
368 {
369     OString help = "\nusing: " +
370         m_program + " [-options] <file_1> ... <file_n> | @<filename> | -stdin\n"
371          "    <file_n>    = file_n specifies one or more idl files.\n"
372          "                  Only files with the extension '.idl' are valid.\n"
373          "    @<filename> = filename specifies the name of a command file.\n"
374          "    -stdin      = read idl file from standard input.\n"
375          "  Options:\n"
376          "    -O<path>    = path specifies the output directory.\n"
377          "                  The generated output is a registry file with\n"
378          "                  the same name as the idl input file (or 'stdin'\n"
379          "                  for -stdin).\n"
380          "    -M<path>    = path specifies the output directory for deps.\n"
381          "                  Generate GNU make dependency files with the\n"
382          "                  same name as the idl input file.\n"
383          "    -I<path>    = path specifies a directory where include\n"
384          "                  files will be searched by the preprocessor.\n"
385          "                  Multiple directories can be combined with ';'.\n"
386          "    -D<name>    = name defines a macro for the preprocessor.\n"
387          "    -C          = generate complete type information, including\n"
388          "                  documentation.\n"
389          "    -cid        = check if identifiers fulfill the UNO naming\n"
390          "                  requirements.\n"
391          "    -quiet      = no output.\n"
392          "    -verbose    = verbose output.\n"
393          "    -w          = display warning messages.\n"
394          "    -we         = treat warnings as errors.\n"
395          "    -h|-?       = print this help message and exit.\n\n" +
396         prepareVersion();
397 
398     return help;
399 }
400 
prepareVersion() const401 OString Options::prepareVersion() const
402 {
403     return m_program + " Version 1.1\n\n";
404 }
405 
406 
isValid(const OString & option) const407 bool Options::isValid(const OString& option) const
408 {
409     return (m_options.count(option) > 0);
410 }
411 
getOption(const OString & option)412 const OString& Options::getOption(const OString& option)
413 {
414     if (!isValid(option))
415     {
416         throw IllegalArgument("Option is not valid or currently not set.");
417     }
418     return m_options[option];
419 }
420 
421 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
422