Line data Source code
1 : // Snap Websites Server -- verify and manage version and names in filenames
2 : // Copyright (c) 2014-2019 Made to Order Software Corp. All Rights Reserved
3 : //
4 : // This program is free software; you can redistribute it and/or modify
5 : // it under the terms of the GNU General Public License as published by
6 : // the Free Software Foundation; either version 2 of the License, or
7 : // (at your option) any later version.
8 : //
9 : // This program is distributed in the hope that it will be useful,
10 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 : // GNU General Public License for more details.
13 : //
14 : // You should have received a copy of the GNU General Public License
15 : // along with this program; if not, write to the Free Software
16 : // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17 :
18 :
19 : // self
20 : //
21 : #include "snapwebsites/snap_version.h"
22 :
23 :
24 : // snapwebsites
25 : //
26 : #include "snapwebsites/minmax.h"
27 : #include "snapwebsites/snap_string_list.h"
28 :
29 :
30 : // snapdev
31 : //
32 : #include <snapdev/not_reached.h>
33 :
34 :
35 : // last include
36 : //
37 : #include <snapdev/poison.h>
38 :
39 :
40 :
41 : namespace snap
42 : {
43 : namespace snap_version
44 : {
45 :
46 :
47 : namespace
48 : {
49 : /** \brief List of operators.
50 : *
51 : * Operators are 1 or 2 characters. This string lists all the operators.
52 : * It is safe because the operators are used from a limited_auto_init
53 : * variable. Each string here is exactly 3 characters and thus we
54 : * can use the index x 3 to access the string operator.
55 : */
56 : char const * g_operators = "\0\0\0" // OPERATOR_UNORDERED
57 : "=\0\0" // OPERATOR_EQUAL
58 : "!=\0" // OPERATOR_EXCEPT
59 : "<\0\0" // OPERATOR_EARLIER
60 : ">\0\0" // OPERATOR_LATER
61 : "<=\0" // OPERATOR_EARLIER_OR_EQUAL
62 : ">=\0"; // OPERATOR_LATER_OR_EQUAL
63 : } // no name namespace
64 :
65 :
66 : /** \brief Find the extension used from a list of extension.
67 : *
68 : * This function checks the end of the \p filename for a match with one
69 : * of the specified extensions and returns the extension that matches.
70 : *
71 : * Note that the list of extensions MUST be sorted from the longest
72 : * extension first to the shortest last.
73 : *
74 : * The extensions are all expected
75 : *
76 : * \param[in] filename The name of the file to check.
77 : * \param[in] extensions A nullptr terminated array of extensions.
78 : *
79 : * \return The pointer of the extensions that matched or nullptr.
80 : */
81 0 : char const * find_extension(QString const& filename, char const **extensions)
82 : {
83 : #ifdef DEBUG
84 : // we expect at least one extensions in the list
85 0 : size_t length(strlen(extensions[0]));
86 : #endif
87 0 : do
88 : {
89 : #ifdef DEBUG
90 0 : size_t const l(strlen(*extensions));
91 0 : if(l > length)
92 : {
93 0 : throw snap_logic_exception(QString("Extension \"%1\" is longer than the previous extension \"%2\".")
94 0 : .arg(*extensions).arg(extensions[-1]));
95 : }
96 0 : length = l;
97 : #endif
98 0 : if(filename.endsWith(*extensions))
99 : {
100 0 : return *extensions;
101 : }
102 :
103 : // not that one, move on
104 0 : ++extensions;
105 : }
106 0 : while(*extensions != nullptr);
107 :
108 0 : return nullptr;
109 : }
110 :
111 :
112 : /** \brief Verify that the specified name is valid.
113 : *
114 : * A basic name must be composed of letters ([a-z]), digits ([0-9]), and
115 : * dashes (-). This function makes sure that the name only includes
116 : * those characters. The minimum size must also be two characters.
117 : *
118 : * Also, the name cannot have two or more dashes in a row, it also must
119 : * start with a letter and cannot end with a dash.
120 : *
121 : * Note that only lower case ASCII letters are accepted ([a-z]).
122 : *
123 : * The following is the regular expression representing a basic name:
124 : *
125 : * \code
126 : * [a-z][-a-z0-9]*[a-z0-9]
127 : * \endcode
128 : *
129 : * \param[in] name The name being checked.
130 : * \param[out] error A string where the error is saved if one is discovered.
131 : *
132 : * \return true if the name is considered valid, false if an error is
133 : * found and the error string is set to that error.
134 : */
135 0 : bool validate_basic_name(QString const & name, QString & error)
136 : {
137 0 : error.clear();
138 :
139 : // length constraint
140 0 : int const max_length(name.length());
141 0 : if(max_length < 2)
142 : {
143 : // name must be at least 2 characters
144 0 : error = QString("the name or browser in a versioned filename must be at least two characters. \"%1\" is not valid.").arg(name);
145 0 : return false;
146 : }
147 :
148 : // first character constraint
149 0 : ushort c(name[0].unicode());
150 0 : if(c < 'a' || c > 'z')
151 : {
152 : // name cannot start with dash (-) or a digit ([0-9])
153 0 : error = QString("the name or browser of a versioned filename must start with a letter [a-z]. \"%1\" is not valid.").arg(name);
154 0 : return false;
155 : }
156 :
157 : // inside constraints
158 0 : ushort p(c);
159 0 : for(int i(0); i < max_length; ++i, p = c)
160 : {
161 0 : c = name.at(i).unicode();
162 0 : if(c == '-')
163 : {
164 : // two '-' in a row constraint
165 0 : if(p == '-')
166 : {
167 : // found two '-' in a row
168 0 : error = QString("a name or browser versioned filename cannot include two dashes (--) one after another. \"%1\" is not valid.").arg(name);
169 0 : return false;
170 : }
171 : }
172 0 : else if((c < '0' || c > '9')
173 0 : && (c < 'a' || c > 'z'))
174 : {
175 : // name can only include [a-z0-9] and dashes (-)
176 0 : error = QString("a name or browser versioned filename can only include letters (a-z), digits (0-9), and dashes (-). \"%1\" is not valid.").arg(name);
177 0 : return false;
178 : }
179 : }
180 :
181 : // no ending '-' constraint
182 0 : if(c == '-') // c and p have the value, the last character
183 : {
184 0 : error = QString("a versioned name or browser cannot end with a dash (-) or a colon (:). \"%1\" is not valid.").arg(name);
185 0 : return false;
186 : }
187 :
188 0 : return true;
189 : }
190 :
191 :
192 : /** \brief Verify that the name or browser strings are valid.
193 : *
194 : * The \p name parameter is checked for validity. It may be composed
195 : * of a namespace and a name separated by the namespace scope operator (::).
196 : *
197 : * If no scope operator is found in the name, then no namespace is defined
198 : * and that parameter remains empty.
199 : *
200 : * \code
201 : * [<namespace>::]<name>
202 : * \endcode
203 : *
204 : * The namespace and name parts must both be valid basic names.
205 : *
206 : * The names must exclusively be composed of lowercase letters. This will
207 : * allow, one day, to run Snap! on computers that do not distinguish
208 : * between case (i.e. Mac OS/X.)
209 : *
210 : * \note
211 : * This function is used to verify the name and the browser strings.
212 : *
213 : * \param[in,out] name The name to be checked against the name pattern.
214 : * \param[out] error The error message in case an error occurs.
215 : * \param[out] namespace_string The namespace if one was defined.
216 : *
217 : * \return true if the name matches, false otherwise.
218 : *
219 : * \sa validate_basic_name()
220 : */
221 0 : bool validate_name(QString & name, QString & error, QString & namespace_string)
222 : {
223 0 : error.clear();
224 0 : namespace_string.clear();
225 :
226 0 : int const pos(name.indexOf("::"));
227 0 : if(pos != -1)
228 : {
229 : // name includes a namespace
230 0 : namespace_string = name.mid(0, pos);
231 0 : name = name.mid(pos + 2);
232 0 : if(!validate_basic_name(namespace_string, error))
233 : {
234 0 : return false;
235 : }
236 : }
237 :
238 0 : return validate_basic_name(name, error);
239 : }
240 :
241 :
242 : /** \brief Validate a version.
243 : *
244 : * This function validates a version string and returns the result.
245 : *
246 : * The validation includes three steps:
247 : *
248 : * \li Parse the input \p version_string parameter in separate numbers.
249 : * \li Save those numbers in the \p version vector.
250 : * \li Canonicalized the \p version vector by removing ending zeroes.
251 : *
252 : * The function only supports sets of numbers in the version. Something
253 : * similar to 1.2.3. The regex of \p version_string looks like this:
254 : *
255 : * \code
256 : * [0-9]+(\.[0-9]+)*
257 : * \endcode
258 : *
259 : * The versions are viewed as:
260 : *
261 : * \li Major Release Version (public)
262 : * \li Minor Release Version (public)
263 : * \li Patch Version (still public)
264 : * \li Development or Build Version (not public)
265 : *
266 : * While in development, each change should be reflected by incrementing
267 : * the development (or build) version number by 1. That way your browser
268 : * will automatically reload the new file.
269 : *
270 : * Once the development is over and a new version is to be released,
271 : * remove the development version or reset it to zero and increase the
272 : * Patch Version, or one of the Release Versions as appropriate.
273 : *
274 : * If you are trying to install a 3rd party JavaScript library which uses
275 : * a different scheme for their version, learn of their scheme and adapt
276 : * it to our versions. For example, a version defined as:
277 : *
278 : * \code
279 : * <major-version>.<minor-version>[<patch>]
280 : * \endcode
281 : *
282 : * where \<patch\> is a letter, can easily be converted to a 1.2.3 type of
283 : * version where the letters are numbered starting at 1 (if no patch letter,
284 : * use zero.)
285 : *
286 : * In the end the function returns an array of integers in the \p version
287 : * parameter. This parameter is used by subsequent compare() calls.
288 : *
289 : * \note
290 : * The version "0" is considered valid although maybe not useful (We
291 : * suggest that you do not use it, use at least 0.0.0.1)
292 : *
293 : * \note
294 : * Although we only mention 4 numbers in a version, this function does
295 : * not enforce a limit. So you could use 5 or more numbers in your
296 : * version definitions.
297 : *
298 : * \param[in,out] version_string The version to be parsed.
299 : * \param[out] version The array of numbers.
300 : * \param[out] error The error message if an error occurs.
301 : *
302 : * \return true if the version is considered valid.
303 : */
304 0 : bool validate_version(QString const & version_string, version_numbers_vector_t & version, QString & error)
305 : {
306 0 : version.clear();
307 :
308 0 : int const max_length(version_string.length());
309 0 : if(max_length < 1)
310 : {
311 0 : error = "The version in a versioned filename is required after the name. \"" + version_string + "\" is not valid.";
312 0 : return false;
313 : }
314 0 : if(version_string.at(max_length - 1).unicode() == '.')
315 : {
316 0 : error = "The version in a versioned filename cannot end with a period. \"" + version_string + "\" is not valid.";
317 0 : return false;
318 : }
319 :
320 0 : for(int i(0); i < max_length;)
321 : {
322 : // force the version to have a digit at the start
323 : // and after each period
324 0 : ushort c(version_string.at(i).unicode());
325 0 : if(c < '0' || c > '9')
326 : {
327 0 : error = "The version of a versioned filename is expected to have a number at the start and after each period. \"" + version_string + "\" is not valid.";
328 0 : return false;
329 : }
330 0 : int value(c - '0');
331 : // start with ++i because we just checked character 'i'
332 0 : for(++i; i < max_length;)
333 : {
334 0 : c = version_string.at(i).unicode();
335 0 : ++i;
336 0 : if(c < '0' || c > '9')
337 : {
338 0 : if(c != '.')
339 : {
340 0 : error = "The version of a versioned filename is expected to be composed of numbers and periods (.) only. \"" + version_string + "\" is not valid.";
341 0 : return false;
342 : }
343 0 : if(i == max_length)
344 : {
345 0 : throw snap_logic_exception("The version_string was already checked for an ending '.' and yet we reached that case later in the function.");
346 : }
347 0 : break;
348 : }
349 0 : value = value * 10 + c - '0';
350 : }
351 0 : version.push_back(value);
352 : }
353 :
354 : // canonicalize the array
355 0 : while(version.size() > 1 && 0 == version[version.size() - 1])
356 : {
357 0 : version.pop_back();
358 : }
359 :
360 0 : return true;
361 : }
362 :
363 :
364 : /** \brief Validate an operator string.
365 : *
366 : * This function validates an operator string and converts it to an operator
367 : * enumeration.
368 : *
369 : * The function clears the error string by default. If the operator cannot
370 : * be converted, then an error message is saved in that string.
371 : *
372 : * Supported operators are:
373 : *
374 : * \li '=' or '=='
375 : * \li '!=' or '<>'
376 : * \li '<'
377 : * \li '<='
378 : * \li '>'
379 : * \li '>='
380 : *
381 : * Note that '==' and '<>' are extension. These are accepted by the
382 : * canonicalized version are '=' and '!=' respectively.
383 : *
384 : * \note
385 : * Internally the strings get canonicalized in the version_operator
386 : * object. The get_operator_string() function always returns a canonicalized
387 : * version of the operator.
388 : *
389 : * \param[in] operator_string The operator to convert.
390 : * \param[out] op The new operator.
391 : * \param[out] error An error string if the operator is not valid.
392 : *
393 : * \return true if the operator is valid.
394 : */
395 0 : bool validate_operator(QString const & operator_string, operator_t & op, QString & error)
396 : {
397 0 : op = operator_t::OPERATOR_UNORDERED;
398 0 : error.clear();
399 :
400 0 : if(operator_string == "=" || operator_string == "==")
401 : {
402 0 : op = operator_t::OPERATOR_EQUAL;
403 : }
404 0 : else if(operator_string == "!=" || operator_string == "<>")
405 : {
406 0 : op = operator_t::OPERATOR_EXCEPT;
407 : }
408 0 : else if(operator_string == "<")
409 : {
410 : // support << as well, like in Debian?
411 0 : op = operator_t::OPERATOR_EARLIER;
412 : }
413 0 : else if(operator_string == ">")
414 : {
415 : // support >> as well, like in Debian?
416 0 : op = operator_t::OPERATOR_LATER;
417 : }
418 0 : else if(operator_string == "<=")
419 : {
420 0 : op = operator_t::OPERATOR_EARLIER_OR_EQUAL;
421 : }
422 0 : else if(operator_string == ">=")
423 : {
424 0 : op = operator_t::OPERATOR_LATER_OR_EQUAL;
425 : }
426 : else
427 : {
428 0 : error = "Operator " + operator_string + " is not recognized as a valid operator.";
429 0 : return false;
430 : }
431 :
432 0 : return true;
433 : }
434 :
435 :
436 : /** \fn name::clear()
437 : * \brief Clear the name.
438 : *
439 : * This is the only way to clear a name object. This function clears the
440 : * name (makes it an empty name) and clears the error message if there was
441 : * one.
442 : *
443 : * Note that set_name() cannot be used with an empty string because that is
444 : * not a valid entry. Names have to be at least two characters.
445 : *
446 : * By default, when a name object is constructed, the name is empty.
447 : */
448 :
449 :
450 : /** \brief Set the name of the string.
451 : *
452 : * Set the name of the item. This function verifies that the name is valid,
453 : * if so the function returns true and saves the new name in the name object.
454 : * Otherwise it doesn't change anything and returns false.
455 : *
456 : * This function clears the error by default so that way if no error occurs
457 : * the get_error() function returns an empty string.
458 : *
459 : * \param[in] name_string The name to save in this object.
460 : *
461 : * \return true if the name was valid.
462 : */
463 0 : bool name::set_name(QString const& name_string)
464 : {
465 0 : QString namespace_string;
466 0 : QString the_name(name_string);
467 0 : bool const r(validate_name(the_name, f_error, namespace_string));
468 0 : if(r)
469 : {
470 0 : f_name = the_name;
471 0 : f_namespace = namespace_string;
472 : }
473 0 : return r;
474 : }
475 :
476 :
477 : /** \fn name::get_name() const
478 : * \brief Retrieve the name.
479 : *
480 : * This function returns the last name that was set with the set_name()
481 : * function and was valid.
482 : *
483 : * This means only valid names or empty names are returned.
484 : *
485 : * \return The last name set with set_name().
486 : */
487 :
488 :
489 : /** \fn name::is_valid() const
490 : * \brief Check whether this name is considered valid.
491 : *
492 : * Although the set_name() function does not change the old value when it
493 : * fails, it is considered invalid if the new value was invalid (had a
494 : * character that is not considered valid in a name, was too short, etc.)
495 : *
496 : * This function returns true if the last set_name() generated no error.
497 : * Note that a new empty name (or after a call to the clear() function)
498 : * is considered valid even though in most cases a name is mandatory.
499 : *
500 : * \return true if the name is currently considered valid.
501 : */
502 :
503 :
504 : /** \fn name::get_error() const
505 : * \brief Retrieve the error.
506 : *
507 : * If the set_name() function returns false, then the error message will
508 : * be set to what happened (why the name was refused.) This error message
509 : * can be retrieved using this function.
510 : *
511 : * The clear() function empties the error message as well as the name.
512 : *
513 : * \return The last error message.
514 : */
515 :
516 :
517 : /** \brief Compare two names together.
518 : *
519 : * This function compares two names together and returns one of the
520 : * following:
521 : *
522 : * \li COMPARE_INVALID -- if the current name is considered invalid
523 : * \li COMPARE_SMALLER -- if this is smaller than \p rhs
524 : * \li COMPARE_EQUAL -- if this is equal \p rhs
525 : * \li COMPARE_LARGER -- if this is larger than \p rhs
526 : *
527 : * The special name "any" is viewed as a pattern that matches any
528 : * name. This comparing "any" against, say, "editor" returns
529 : * COMPARE_EQUAL. "any" can appear in this name or the rhs name.
530 : *
531 : * \param[in] rhs The right hand side name to compare with.
532 : *
533 : * \return one of the COMPARE_... values (-2, -1, 0, 1).
534 : */
535 0 : compare_t name::compare(name const& rhs) const
536 : {
537 0 : if(!is_valid() || !rhs.is_valid())
538 : {
539 0 : return compare_t::COMPARE_INVALID;
540 : }
541 :
542 0 : if(f_name == "any" || rhs.f_name == "any")
543 : {
544 0 : return compare_t::COMPARE_EQUAL;
545 : }
546 :
547 0 : if(f_name < rhs.f_name)
548 : {
549 0 : return compare_t::COMPARE_SMALLER;
550 : }
551 0 : if(f_name > rhs.f_name)
552 : {
553 0 : return compare_t::COMPARE_LARGER;
554 : }
555 0 : return compare_t::COMPARE_EQUAL;
556 : }
557 :
558 :
559 : /** \brief Set the specified version string as the new version.
560 : *
561 : * This function parses the supplied version string in an array of
562 : * version numbers saved internally.
563 : *
564 : * If an error occurs, the current version is not modified and
565 : * an error message is saved internally. The message can be retrieved
566 : * with the get_error() function.
567 : *
568 : * The error messages is cleared on entry so if no errore are discovered
569 : * in \p version_string then get_error() returns an empty string.
570 : *
571 : * \param[in] version_string The version string to parse in a series of
572 : * numbers.
573 : *
574 : * \return true if the version is considered valid.
575 : */
576 0 : bool version::set_version_string(QString const& version_string)
577 : {
578 0 : version_numbers_vector_t version_vector;
579 0 : bool const r(validate_version(version_string, version_vector, f_error));
580 0 : if(r)
581 : {
582 0 : set_version(version_vector);
583 : }
584 0 : return r;
585 : }
586 :
587 :
588 : /** \brief Set a new version from an array of numbers.
589 : *
590 : * This function can be used to set the version directly from a set of
591 : * numbers. The function canonicalize the version array by removing
592 : * any ending zeroes.
593 : *
594 : * \param[in] version_vector The value of the new version.
595 : */
596 : #pragma GCC diagnostic push
597 : #pragma GCC diagnostic ignored "-Wstrict-overflow"
598 0 : void version::set_version(version_numbers_vector_t const& version_vector)
599 : {
600 : #pragma GCC diagnostic pop
601 0 : f_error.clear(); // no error possible in this case
602 :
603 : // copy and then canonicalize the array
604 0 : f_version = version_vector;
605 0 : while(f_version.size() > 1 && 0 == f_version[f_version.size() - 1])
606 : {
607 0 : f_version.pop_back();
608 : }
609 :
610 : // make sure that if we have a new version we get a new version
611 : // string that corresponds one to one
612 0 : f_version_string.clear();
613 0 : }
614 :
615 :
616 : /** \brief Set the version operator.
617 : *
618 : * By default a version has operator Unordered. In general, a version is
619 : * 'unordered' when not part of an expression (i.e. in a filename, the
620 : * version is just that and no operator is defined.) In a list of versions
621 : * of a dependency, the version is always defined with an operator although
622 : * by default the \>= operator is not specified.
623 : *
624 : * \param[in] op The version operator.
625 : */
626 0 : void version::set_operator(version_operator const& op)
627 : {
628 0 : f_operator = op;
629 0 : }
630 :
631 :
632 : /** \fn version::get_version() const
633 : * \brief Retrieve the version as an array of numbers.
634 : *
635 : * This function returns the array of numbers representing this version.
636 : * The array will have been canonicalized, which means it will not end
637 : * with extra zeroes (it may be zero, if composed of one element.)
638 : *
639 : * By default, a version object is empty which means "no version".
640 : *
641 : * \return An array of version numbers.
642 : */
643 :
644 :
645 : /** \fn version::get_version_string() const
646 : * \brief Retrieve the version as a canonicalized string.
647 : *
648 : * This function returns the version as a canonicalized string. The version
649 : * is canonnicalized by removing all .0 from the end of a version. So version
650 : * 1.2 and 1.2.0 will both return string "1.2".
651 : *
652 : * \return The canonicalized version string.
653 : */
654 0 : QString const& version::get_version_string() const
655 : {
656 0 : if(f_version_string.isEmpty() && !f_version.isEmpty())
657 : {
658 : // create the version string
659 0 : f_version_string = QString("%1").arg(static_cast<int>(f_version[0]));
660 0 : int const max_size(f_version.size());
661 0 : for(int i(1); i < max_size; ++i)
662 : {
663 0 : f_version_string += QString(".%1").arg(static_cast<int>(f_version[i]));
664 : }
665 : }
666 :
667 0 : return f_version_string;
668 : }
669 :
670 :
671 : /** \brief Return the operator and version as a string.
672 : *
673 : * This function returns the operator followed by the version both
674 : * separated by a space. If the operator is OPERATOR_UNORDERED then
675 : * just the version string is returned, just as if you called the
676 : * get_version_string() function.
677 : *
678 : * \note
679 : * Dependencies are always expected to include an operator along their
680 : * version. When a version is specified without an operator, the
681 : * default OPERATOR_LATER_OR_EQUAL is used.
682 : *
683 : * \return The operator and version that this version object represent.
684 : */
685 0 : QString version::get_opversion_string() const
686 : {
687 0 : QString v(get_version_string());
688 :
689 0 : if(f_operator.get_operator() != operator_t::OPERATOR_UNORDERED)
690 : {
691 0 : v = f_operator.get_operator_string() + (" " + v);
692 : }
693 :
694 0 : return v;
695 : }
696 :
697 :
698 : /** \fn version::get_error() const
699 : * \brief Get errors
700 : *
701 : * The function retrieve the last error message that happened when you
702 : * called the set_version() function.
703 : *
704 : * The set_version() functions clear the error message out to represent
705 : * a "no error state."
706 : *
707 : * \return The error message or an empty string if none.
708 : */
709 :
710 :
711 : /** \brief Compare two versions against each others.
712 : *
713 : * This function compares this version against \p rhs and returns one of
714 : * the following:
715 : *
716 : * \li COMPARE_INVALID -- if the current name is considered invalid
717 : * \li COMPARE_SMALLER -- if this is smaller than \p rhs
718 : * \li COMPARE_EQUAL -- if this is equal \p rhs
719 : * \li COMPARE_LARGER -- if this is larger than \p rhs
720 : *
721 : * \param[in] rhs The right hand side name to compare with.
722 : *
723 : * \return One of the COMPARE_... values (-2, -1, 0, 1).
724 : */
725 0 : compare_t version::compare(version const& rhs) const
726 : {
727 0 : if(!is_valid() || !rhs.is_valid())
728 : {
729 0 : return compare_t::COMPARE_INVALID;
730 : }
731 :
732 0 : int const max_size(std::max SNAP_PREVENT_MACRO_SUBSTITUTION (f_version.size(), rhs.f_version.size()));
733 0 : for(int i(0); i < max_size; ++i)
734 : {
735 0 : int l(i >= f_version.size() ? 0 : static_cast<int>( f_version[i]));
736 0 : int r(i >= rhs.f_version.size() ? 0 : static_cast<int>(rhs.f_version[i]));
737 0 : if(l < r)
738 : {
739 0 : return compare_t::COMPARE_SMALLER;
740 : }
741 0 : if(l > r)
742 : {
743 0 : return compare_t::COMPARE_LARGER;
744 : }
745 : }
746 :
747 0 : return compare_t::COMPARE_EQUAL;
748 : }
749 :
750 :
751 :
752 :
753 :
754 : /** \brief Set the operator from a string.
755 : *
756 : * This function defines a version operator from a string as found in a
757 : * dependency string.
758 : *
759 : * The valid operators are:
760 : *
761 : * \li = or == -- OPERATOR_EQUAL, canonicalized as "="
762 : * \li != or <> -- OPERATOR_EXCEPT, canonicalized as "!="
763 : * \li < -- OPERATOR_EARLIER
764 : * \li > -- OPERATOR_LATER
765 : * \li <= -- OPERATOR_EARLIER_OR_EQUAL
766 : * \li >= -- OPERATOR_LATER_OR_EQUAL (TBD should this one be canonicalized
767 : * as an empty string?)
768 : *
769 : * By default a version operator object is set to OPERATOR_UNORDERED which
770 : * pretty much means it was not set yet.
771 : *
772 : * Note that Debian supported \<\< and \>\> as an equivalent to \<= and \>=
773 : * respectively. We do not support those operators since (1) Debian
774 : * deprecated them, and (2) they are definitively confusing.
775 : *
776 : * You can also use the set_operator() function which access an OPERATOR_...
777 : * enumeration.
778 : *
779 : * Note that it is possible to create a range with a shortcut in a dependency
780 : * declaration:
781 : *
782 : * \code
783 : * <smaller version> < <larger version>
784 : * <smaller version> <= <larger version>
785 : *
786 : * // for example:
787 : * 1.3.4 < 1.4.0
788 : * 1.3.4 <= 1.4.0
789 : * \endcode
790 : *
791 : * Once compiled in, this is represented using two version operators and
792 : * the operator is changed from \< to > and \<, and from \<= to >= and
793 : * \<= respectively, so the previous example becomes:
794 : *
795 : * \code
796 : * // This range:
797 : * my_lib (1.3.4 < 1.4.0)
798 : *
799 : * // is equivalent to those two entries
800 : * my_lib (> 1.3.4)
801 : * my_lib (< 1.4)
802 : *
803 : * // And that range:
804 : * my_lib (1.3.4 <= 1.4.0)
805 : *
806 : * // is equivalent to those two entries
807 : * my_lib (>= 1.3.4)
808 : * my_lib (<= 1.4)
809 : * \endcode
810 : *
811 : * \param[in] operator_string The string to be checked.
812 : *
813 : * \return true if the operator string represents a valid operator.
814 : */
815 0 : bool version_operator::set_operator_string(QString const& operator_string)
816 : {
817 : operator_t op;
818 0 : bool const r(validate_operator(operator_string, op, f_error));
819 0 : if(r)
820 : {
821 : // valid, save it
822 0 : set_operator(op);
823 : }
824 0 : return r;
825 : }
826 :
827 :
828 : /** \brief Set the operator using the enumeration.
829 : *
830 : * This function sets the operator using one of the enumeration values.
831 : *
832 : * \note
833 : * You may use this function to reset the version operator back to
834 : * OPERATOR_UNORDERED. In that case the operator string becomes
835 : * the empty string ("").
836 : *
837 : * \param[in] op The new operator for this version operator object.
838 : *
839 : * \return true if op is a valid operator.
840 : */
841 0 : bool version_operator::set_operator(operator_t op)
842 : {
843 0 : switch(op)
844 : {
845 0 : case operator_t::OPERATOR_UNORDERED:
846 : case operator_t::OPERATOR_EQUAL:
847 : case operator_t::OPERATOR_EXCEPT:
848 : case operator_t::OPERATOR_EARLIER:
849 : case operator_t::OPERATOR_LATER:
850 : case operator_t::OPERATOR_EARLIER_OR_EQUAL:
851 : case operator_t::OPERATOR_LATER_OR_EQUAL:
852 0 : f_operator = op;
853 0 : return true;
854 :
855 0 : default:
856 0 : return false;
857 :
858 : }
859 : }
860 :
861 :
862 : /** \brief Retrieve the canonicalized operator string.
863 : *
864 : * This function returns the string representing the operator. The
865 : * string is canonicalized, which means that it has one single
866 : * representation (i.e. we accept "==" which is represented as "="
867 : * when canonicalized.)
868 : *
869 : * \return The canonicalized operator string.
870 : */
871 0 : char const * version_operator::get_operator_string() const
872 : {
873 0 : return g_operators + static_cast<int>(static_cast<operator_t>(f_operator)) * 3;
874 : }
875 :
876 :
877 : /** \fn version_operator::get_operator() const
878 : * \brief Retrieve the operator.
879 : *
880 : * This function retrieves the operator as a number. The operator is used
881 : * to compare versions between each others while searching for dependencies.
882 : *
883 : * \return The operator, may be OPERATOR_UNORDERED if not initialized.
884 : */
885 :
886 :
887 :
888 :
889 :
890 : /** \brief Initialize a versioned filename object.
891 : *
892 : * The versioned filename class initializes the versioned filename object
893 : * with an extension which is mandatory and unique.
894 : *
895 : * \note
896 : * The period in the extension is optional. However, the extension cannot
897 : * be the empty string.
898 : *
899 : * \param[in] extension The expected extension (i.e. ".js" for JavaScript files).
900 : */
901 0 : versioned_filename::versioned_filename(QString const & extension)
902 : //: f_valid(false) -- auto-init
903 : //, f_error("") -- auto-init
904 0 : : f_extension(extension)
905 : //, f_name("") -- auto-init
906 : //, f_version_string("") -- auto-init
907 : //, f_version() -- auto-init
908 : {
909 0 : if(f_extension.isEmpty())
910 : {
911 0 : throw snap_version_exception_invalid_extension("the extension of a versioned filename cannot be the empty string");
912 : }
913 :
914 : // make sure the extension includes the period
915 0 : if(f_extension.at(0).unicode() != '.')
916 : {
917 0 : f_extension = "." + f_extension;
918 : }
919 0 : }
920 :
921 :
922 : /** \brief Set the name of a file through the parser.
923 : *
924 : * This function is used to setup a versioned filename from a full filename.
925 : * The input filename can include a path. It must end with the valid
926 : * extension (as defined when creating the versioned_filename object.)
927 : * Assuming the function returns true, the get_filename() function
928 : * returns the basename (i.e. the filename without the path nor the
929 : * extension, although you can get the extension if you ask for it.)
930 : *
931 : * The filename is then broken up in a name, a version, and browser, all of
932 : * which are checked for validity. If invalid, the function returns false.
933 : *
934 : * \code
935 : * .../some/path/name_version_browser.ext
936 : * \endcode
937 : *
938 : * Note that the browser part is optional. In general, if not indicated
939 : * it means the file is compatible with all browsers.
940 : *
941 : * \note
942 : * This function respects the contract: if the function returns false,
943 : * then the name, version, and browser information are not changed.
944 : *
945 : * However, on entry the value of f_error is set to the empty string and
946 : * the value of f_valid is set to false. So most of the functions will
947 : * continue to return the old value of the versioned filename, except
948 : * the compare() and relational operators.
949 : *
950 : * \param[in] filename The filename to be parsed for a version.
951 : *
952 : * \return true if the filename was a valid versioned filename.
953 : */
954 0 : bool versioned_filename::set_filename(QString const & filename)
955 : {
956 0 : f_error.clear();
957 :
958 : // the extension must be exactly "extension"
959 0 : if(!filename.endsWith(f_extension))
960 : {
961 0 : f_error = "this filename must end with \"" + f_extension + "\" in lowercase. \"" + filename + "\" is not valid.";
962 0 : return false;
963 : }
964 :
965 0 : int const max_length(filename.length() - f_extension.length());
966 :
967 0 : int start(filename.lastIndexOf('/'));
968 0 : if(start == -1)
969 : {
970 0 : start = 0;
971 : }
972 : else
973 : {
974 0 : ++start;
975 : }
976 :
977 : // now break the name in two or three parts: <name> and <version> [and <browser>]
978 0 : int p1(filename.indexOf('_', start));
979 0 : if(p1 == -1 || p1 > max_length)
980 : {
981 0 : f_error = "a versioned filename is expected to include an underscore (_) as the name and version separator. \"" + filename + "\" is not valid.";
982 0 : return false;
983 : }
984 : // and check whether the <browser> part is specified
985 0 : int p2(filename.indexOf('_', p1 + 1));
986 0 : if(p2 > max_length || p2 == -1)
987 : {
988 0 : p2 = max_length;
989 : }
990 : else
991 : {
992 : // filename ends with an underscore?
993 0 : if(p2 + 1 >= max_length)
994 : {
995 0 : f_error = QString("a browser name must be specified in a versioned filename if you include two underscores (_). \"%1\" is not valid.").arg(filename);
996 0 : return false;
997 : }
998 : }
999 :
1000 : // name
1001 0 : QString const name(filename.mid(start, p1 - start));
1002 0 : QString name_string(name);
1003 : // TBD: can we really allow a namespace in a filename?
1004 0 : QString namespace_string;
1005 0 : if(!validate_name(name_string, f_error, namespace_string))
1006 : {
1007 0 : return false;
1008 : }
1009 :
1010 : // version
1011 0 : ++p1;
1012 0 : QString const version_string(filename.mid(p1, p2 - p1));
1013 0 : version_numbers_vector_t version;
1014 0 : if(!validate_version(version_string, version, f_error))
1015 : {
1016 0 : return false;
1017 : }
1018 :
1019 : // browser
1020 0 : QString browser;
1021 0 : if(p2 < max_length)
1022 : {
1023 : // validate only if not empty (since it is optional, empty is okay)
1024 0 : browser = filename.mid(p2 + 1, max_length - p2 - 1);
1025 0 : if(!validate_basic_name(browser, f_error))
1026 : {
1027 0 : return false;
1028 : }
1029 : }
1030 :
1031 : // save the results
1032 0 : f_name.set_name(name);
1033 0 : f_version.set_version_string(version_string);
1034 0 : if(browser.isEmpty())
1035 : {
1036 : // browser info is optional and if not defined we need to clear
1037 : // the name (because a set_name("") generates an error)
1038 0 : f_browser.clear();
1039 : }
1040 : else
1041 : {
1042 0 : f_browser.set_name(browser);
1043 : }
1044 :
1045 0 : return true;
1046 : }
1047 :
1048 :
1049 : /** \brief Set the name of the versioned filename object.
1050 : *
1051 : * A versioned filename is composed of a name, a version, and an optional
1052 : * browser reference. This function is used to replace the name.
1053 : *
1054 : * The name is checked using the \p validate_name() function.
1055 : *
1056 : * \param[in] name The new file name.
1057 : *
1058 : * \return true if the name is valid.
1059 : */
1060 0 : bool versioned_filename::set_name(QString const& name)
1061 : {
1062 0 : QString namespace_string;
1063 0 : QString name_string(name);
1064 0 : bool const r(validate_name(name_string, f_error, namespace_string));
1065 0 : if(r)
1066 : {
1067 0 : f_name.set_name(name);
1068 : }
1069 :
1070 0 : return r;
1071 : }
1072 :
1073 :
1074 : /** \brief Set the version of the versioned filename.
1075 : *
1076 : * This function sets the version of the versioned filename. Usually, you
1077 : * will call the set_filename() function which sets the name, the version,
1078 : * and the optional browser all at once and especially let the parsing
1079 : * work to the versioned_filename class.
1080 : *
1081 : * \param[in] version_string The version in the form of a string.
1082 : *
1083 : * \return true if the version was considered valid.
1084 : */
1085 0 : bool versioned_filename::set_version(QString const& version_string)
1086 : {
1087 0 : version_numbers_vector_t version_vector;
1088 0 : bool r(validate_version(version_string, version_vector, f_error));
1089 0 : if(r)
1090 : {
1091 0 : f_version.set_version_string(version_string);
1092 : }
1093 :
1094 0 : return r;
1095 : }
1096 :
1097 :
1098 : /** \brief Return the canonicalized filename.
1099 : *
1100 : * This function returns the canonicalized filename. This means all version
1101 : * numbers have leading 0's removed, ending .0 are all removed, and the
1102 : * path is removed.
1103 : *
1104 : * The \p extension flag can be used to get the extension appended or not.
1105 : *
1106 : * \param[in] extension Set to true to get the extension.
1107 : *
1108 : * \return The canonicalized filename.
1109 : */
1110 0 : QString versioned_filename::get_filename(bool extension) const
1111 : {
1112 0 : if(!is_valid())
1113 : {
1114 0 : return "";
1115 : }
1116 : return f_name.get_name()
1117 0 : + "_" + f_version.get_version_string()
1118 0 : + (f_browser.get_name().isEmpty() ? "" : "_" + f_browser.get_name())
1119 0 : + (extension ? f_extension : "");
1120 : }
1121 :
1122 :
1123 : /** \brief Compare two versioned_filename's against each others.
1124 : *
1125 : * This function first makes sure that both filenames are considered
1126 : * valid, if not, the function returns COMPARE_INVALID (-2).
1127 : *
1128 : * Assuming the two filenames are valid, the function returns:
1129 : *
1130 : * \li COMPARE_SMALLER (-1) if this filename is considered to appear before rhs
1131 : * \li COMPARE_EQUAL (0) if both filenames are considered equal
1132 : * \li COMPARE_LARGER (1) if this filename is considered to appear after rhs
1133 : *
1134 : * The function first compares the name (get_name()) of each object.
1135 : * If not equal, return COMPARE_SMALLER or COMPARE_LARGER.
1136 : *
1137 : * When the name are equal, the function compares the browser (get_browser())
1138 : * of each object. If not equal, rethrn COMPARE_SMALLER or COMPARE_LARGER.
1139 : *
1140 : * When the name and the browser are equal, then the function compares the
1141 : * versions starting with the major release number. If a version array is
1142 : * longer than the other, the missing values in the smaller array are
1143 : * considered to be zero. That way "1.2.3" > "1.2" because "1.2" is the
1144 : * same as "1.2.0" and 3 > 0.
1145 : *
1146 : * \param[in] rhs The right hand side to compare against this versioned
1147 : * filename.
1148 : *
1149 : * \return -2, -1, 0, or 1 depending on the order (or unordered status)
1150 : */
1151 0 : compare_t versioned_filename::compare(versioned_filename const& rhs) const
1152 : {
1153 0 : if(!is_valid())
1154 : {
1155 0 : return compare_t::COMPARE_INVALID;
1156 : }
1157 :
1158 0 : compare_t c(f_name.compare(rhs.f_name));
1159 0 : if(c != compare_t::COMPARE_EQUAL)
1160 : {
1161 0 : return c;
1162 : }
1163 0 : c = f_browser.compare(rhs.f_browser);
1164 0 : if(c != compare_t::COMPARE_EQUAL)
1165 : {
1166 0 : return c;
1167 : }
1168 :
1169 0 : return f_version.compare(rhs.f_version);
1170 : }
1171 :
1172 :
1173 : /** \brief Compare two filenames for equality.
1174 : *
1175 : * This function returns true if both filenames are considered equal
1176 : * (i.e. if the compare() function returns 0.)
1177 : *
1178 : * Note that if one or both filenames are considered unordered, the
1179 : * function always returns false.
1180 : *
1181 : * \param[in] rhs The other filename to compare against.
1182 : *
1183 : * \return true if both filenames are considered equal.
1184 : */
1185 0 : bool versioned_filename::operator == (versioned_filename const & rhs) const
1186 : {
1187 0 : compare_t r(compare(rhs));
1188 0 : return r == compare_t::COMPARE_EQUAL;
1189 : }
1190 :
1191 :
1192 : /** \brief Compare two filenames for differences.
1193 : *
1194 : * This function returns true if both filenames are not considered equal
1195 : * (i.e. if the compare() function returns -1 or 1.)
1196 : *
1197 : * Note that if one or both filenames are considered unordered, the
1198 : * function always returns false.
1199 : *
1200 : * \param[in] rhs The other filename to compare against.
1201 : *
1202 : * \return true if both filenames are considered different.
1203 : */
1204 0 : bool versioned_filename::operator != (versioned_filename const& rhs) const
1205 : {
1206 0 : compare_t r(compare(rhs));
1207 0 : return r == compare_t::COMPARE_SMALLER || r == compare_t::COMPARE_LARGER;
1208 : }
1209 :
1210 :
1211 : /** \brief Compare two filenames for inequality.
1212 : *
1213 : * This function returns true if this filename is considered to appear before
1214 : * \p rhs filename (i.e. if the compare() function returns -1.)
1215 : *
1216 : * Note that if one or both filenames are considered unordered, the
1217 : * function always returns false.
1218 : *
1219 : * \param[in] rhs The other filename to compare against.
1220 : *
1221 : * \return true if both filenames are considered inequal.
1222 : */
1223 0 : bool versioned_filename::operator < (versioned_filename const& rhs) const
1224 : {
1225 0 : compare_t r(compare(rhs));
1226 0 : return r == compare_t::COMPARE_SMALLER;
1227 : }
1228 :
1229 :
1230 : /** \brief Compare two filenames for inequality.
1231 : *
1232 : * This function returns true if this filename is considered to appear before
1233 : * \p rhs filename or both are equal (i.e. if the compare() function
1234 : * returns -1 or 0.)
1235 : *
1236 : * Note that if one or both filenames are considered unordered, the
1237 : * function always returns false.
1238 : *
1239 : * \param[in] rhs The other filename to compare against.
1240 : *
1241 : * \return true if both filenames are considered inequal.
1242 : */
1243 0 : bool versioned_filename::operator <= (versioned_filename const& rhs) const
1244 : {
1245 0 : compare_t r(compare(rhs));
1246 0 : return r == compare_t::COMPARE_SMALLER || r == compare_t::COMPARE_EQUAL;
1247 : }
1248 :
1249 :
1250 : /** \brief Compare two filenames for inequality.
1251 : *
1252 : * This function returns true if this filename is considered to appear after
1253 : * \p rhs filename (i.e. if the compare() function returns 1.)
1254 : *
1255 : * Note that if one or both filenames are considered unordered, the
1256 : * function always returns false.
1257 : *
1258 : * \param[in] rhs The other filename to compare against.
1259 : *
1260 : * \return true if both filenames are considered inequal.
1261 : */
1262 0 : bool versioned_filename::operator > (versioned_filename const& rhs) const
1263 : {
1264 0 : compare_t r(compare(rhs));
1265 0 : return r > compare_t::COMPARE_EQUAL;
1266 : }
1267 :
1268 :
1269 : /** \brief Compare two filenames for inequality.
1270 : *
1271 : * This function returns true if this filename is considered to appear before
1272 : * \p rhs filename or both are equal (i.e. if the compare() function
1273 : * returns 0 or 1.)
1274 : *
1275 : * Note that if one or both filenames are considered unordered, the
1276 : * function always returns false.
1277 : *
1278 : * \param[in] rhs The other filename to compare against.
1279 : *
1280 : * \return true if both filenames are considered inequal.
1281 : */
1282 0 : bool versioned_filename::operator >= (versioned_filename const& rhs) const
1283 : {
1284 0 : compare_t r(compare(rhs));
1285 0 : return r >= compare_t::COMPARE_EQUAL;
1286 : }
1287 :
1288 :
1289 : /** \brief Define a dependency from a string.
1290 : *
1291 : * This function is generally called to transform a dependency string
1292 : * in a name, a list of versions and operators, and a list of browsers.
1293 : *
1294 : * The format of the string is as follow in simplified yacc:
1295 : *
1296 : * \code
1297 : * dependency: name
1298 : * | name versions
1299 : * | name versions browsers
1300 : * | name browsers
1301 : *
1302 : * name: NAME
1303 : *
1304 : * versions: '(' version_list ')'
1305 : *
1306 : * version_list: version_range
1307 : * | version_list ',' version_range
1308 : *
1309 : * version_range: version
1310 : * | version '<=' version
1311 : * | version '<' version
1312 : * | op version
1313 : *
1314 : * version: VERSION
1315 : *
1316 : * op: '='
1317 : * | '!='
1318 : * | '<'
1319 : * | '<='
1320 : * | '>'
1321 : * | '>='
1322 : *
1323 : * browsers: '[' browser_list ']'
1324 : *
1325 : * browser_list: name
1326 : * | browser_list ',' name
1327 : * \endcode
1328 : */
1329 0 : bool dependency::set_dependency(QString const& dependency_string)
1330 : {
1331 0 : f_error.clear();
1332 :
1333 : // trim spaces
1334 0 : QString const d(dependency_string.simplified());
1335 :
1336 : // get the end of the name
1337 0 : int space_pos(d.indexOf(' '));
1338 0 : if(space_pos < 0)
1339 : {
1340 0 : space_pos = d.length();
1341 : }
1342 0 : int paren_pos(d.indexOf('('));
1343 0 : if(paren_pos < 0)
1344 : {
1345 0 : paren_pos = d.length();
1346 : }
1347 0 : int bracket_pos(d.indexOf('['));
1348 0 : if(bracket_pos < 0)
1349 : {
1350 0 : bracket_pos = d.length();
1351 : }
1352 0 : if(paren_pos != d.length() && paren_pos > bracket_pos)
1353 : {
1354 : // cannot have versions after browsers
1355 0 : f_error = "version dependency syntax error, '[' found before '('";
1356 0 : return false;
1357 : }
1358 0 : int pos(std::min SNAP_PREVENT_MACRO_SUBSTITUTION (space_pos, std::min SNAP_PREVENT_MACRO_SUBSTITUTION (paren_pos, bracket_pos)));
1359 0 : QString const dep_name(d.left(pos));
1360 0 : QString namespace_string;
1361 0 : QString name_string(dep_name);
1362 0 : if(!validate_name(name_string, f_error, namespace_string))
1363 : {
1364 0 : return false;
1365 : }
1366 0 : f_name.set_name(dep_name);
1367 :
1368 : // skip the spaces (because of the simplied there should be 1 at most)
1369 0 : while(pos < d.length() && isspace(d.at(pos).unicode()))
1370 : {
1371 0 : ++pos;
1372 : }
1373 :
1374 : // read list of versions and operators
1375 0 : if(pos < d.length() && d.at(pos) == QChar('('))
1376 : {
1377 0 : ++pos;
1378 0 : int end(d.indexOf(')', pos));
1379 0 : if(end == -1)
1380 : {
1381 0 : f_error = "version dependency syntax error, ')' not found";
1382 0 : return false;
1383 : }
1384 0 : QString const version_list(d.mid(pos, end - pos));
1385 0 : snap_string_list version_strings(version_list.split(',', QString::SkipEmptyParts));
1386 0 : int const max_versions(version_strings.size());
1387 0 : for(int i(0); i < max_versions; ++i)
1388 : {
1389 0 : QString const vonly(version_strings[i].trimmed());
1390 0 : if(vonly.isEmpty())
1391 : {
1392 : // this happens because of the trimmed()
1393 0 : continue;
1394 : }
1395 0 : version_operator vo;
1396 0 : QByteArray utf8(vonly.toUtf8());
1397 0 : char const *s(utf8.data());
1398 0 : char const *start(s);
1399 0 : for(; (*s >= '0' && *s <= '9') || *s == '.'; ++s);
1400 0 : if(s == start)
1401 : {
1402 : // we assume an operator at the start
1403 0 : for(; (*s < '0' || *s > '9') && *s != '\0'; ++s);
1404 0 : QString const op(QString::fromUtf8(start, static_cast<int>(s - start)).trimmed());
1405 0 : if(!vo.set_operator_string(op))
1406 : {
1407 0 : f_error = vo.get_error();
1408 0 : return false;
1409 : }
1410 : // skip spaces after the operator
1411 0 : for(; isspace(*s); ++s);
1412 0 : start = s;
1413 0 : for(; (*s >= '0' && *s <= '9') || *s == '.'; ++s);
1414 : }
1415 :
1416 : // got a version, verify it
1417 0 : QString const version_string(QString::fromUtf8(start, static_cast<int>(s - start)));
1418 0 : version v;
1419 0 : if(!v.set_version_string(version_string))
1420 : {
1421 0 : f_error = v.get_error();
1422 0 : return false;
1423 : }
1424 0 : for(; isspace(*s); ++s);
1425 0 : if(*s != '\0')
1426 : {
1427 : // not end of the version, check for an operator
1428 0 : if(vo.get_operator() != operator_t::OPERATOR_UNORDERED)
1429 : {
1430 0 : f_error = "a version specification in a dependency can only include one operator, two found in \"" + vonly + "\" (missing ',' or ')' maybe?)";
1431 0 : return false;
1432 : }
1433 : // we assume an operator in between two versions
1434 : // (i.e. version <= version)
1435 0 : start = s;
1436 0 : for(; (*s < '0' || *s > '9') && *s != '\0'; ++s);
1437 0 : QString const op(QString::fromUtf8(start, static_cast<int>(s - start)).trimmed());
1438 0 : if(!vo.set_operator_string(op))
1439 : {
1440 0 : f_error = vo.get_error();
1441 0 : return false;
1442 : }
1443 0 : operator_t const range_op(vo.get_operator());
1444 0 : switch(range_op)
1445 : {
1446 0 : case operator_t::OPERATOR_UNORDERED:
1447 : case operator_t::OPERATOR_EQUAL:
1448 : case operator_t::OPERATOR_EXCEPT:
1449 0 : f_error = "unsupported operator \"" + QString(vo.get_operator_string()) + "\" for a range";
1450 0 : return false;
1451 :
1452 0 : default:
1453 : // otherwise we're good
1454 0 : break;
1455 :
1456 : }
1457 : // skip spaces after the operator
1458 0 : for(; isspace(*s); ++s);
1459 0 : start = s;
1460 0 : for(; (*s >= '0' && *s <= '9') || *s == '.'; ++s);
1461 0 : if(*s != '\0')
1462 : {
1463 0 : f_error = "a version range can have two versions separated by an operator, \"" + vonly + "\" is not valid";
1464 0 : return false;
1465 : }
1466 0 : QString const rhs_version(QString::fromUtf8(start, static_cast<int>(s - start)));
1467 0 : version rhs_v;
1468 0 : if(!rhs_v.set_version_string(rhs_version))
1469 : {
1470 0 : f_error = rhs_v.get_error();
1471 0 : return false;
1472 : }
1473 0 : switch(range_op)
1474 : {
1475 0 : case operator_t::OPERATOR_EARLIER:
1476 0 : vo.set_operator(operator_t::OPERATOR_LATER);
1477 0 : v.set_operator(vo);
1478 0 : f_versions.push_back(v);
1479 0 : vo.set_operator(operator_t::OPERATOR_EARLIER);
1480 0 : rhs_v.set_operator(vo);
1481 0 : f_versions.push_back(rhs_v);
1482 0 : if(v.compare(rhs_v) >= compare_t::COMPARE_EQUAL)
1483 : {
1484 0 : f_error = "versions are not in the correct order in range \"" + vonly + "\" since " + v.get_version_string() + " >= " + rhs_v.get_version_string();
1485 0 : return false;
1486 : }
1487 0 : break;
1488 :
1489 0 : case operator_t::OPERATOR_EARLIER_OR_EQUAL:
1490 0 : vo.set_operator(operator_t::OPERATOR_LATER_OR_EQUAL);
1491 0 : v.set_operator(vo);
1492 0 : f_versions.push_back(v);
1493 0 : vo.set_operator(operator_t::OPERATOR_EARLIER_OR_EQUAL);
1494 0 : rhs_v.set_operator(vo);
1495 0 : f_versions.push_back(rhs_v);
1496 0 : if(v.compare(rhs_v) >= compare_t::COMPARE_EQUAL)
1497 : {
1498 0 : f_error = "versions are not in the correct order in range \"" + vonly + "\" since " + v.get_version_string() + " >= " + rhs_v.get_version_string();
1499 0 : return false;
1500 : }
1501 0 : break;
1502 :
1503 0 : case operator_t::OPERATOR_LATER:
1504 0 : vo.set_operator(operator_t::OPERATOR_LATER);
1505 0 : rhs_v.set_operator(vo);
1506 0 : f_versions.push_back(rhs_v);
1507 0 : vo.set_operator(operator_t::OPERATOR_EARLIER);
1508 0 : v.set_operator(vo);
1509 0 : f_versions.push_back(v);
1510 0 : if(v.compare(rhs_v) <= compare_t::COMPARE_EQUAL)
1511 : {
1512 0 : f_error = "versions are not in the correct order in range \"" + vonly + "\" since " + v.get_version_string() + " <= " + rhs_v.get_version_string();
1513 0 : return false;
1514 : }
1515 0 : break;
1516 :
1517 0 : case operator_t::OPERATOR_LATER_OR_EQUAL:
1518 0 : vo.set_operator(operator_t::OPERATOR_LATER_OR_EQUAL);
1519 0 : rhs_v.set_operator(vo);
1520 0 : f_versions.push_back(rhs_v);
1521 0 : vo.set_operator(operator_t::OPERATOR_EARLIER_OR_EQUAL);
1522 0 : v.set_operator(vo);
1523 0 : f_versions.push_back(v);
1524 0 : if(v.compare(rhs_v) <= compare_t::COMPARE_EQUAL)
1525 : {
1526 0 : f_error = "versions are not in the correct order in range \"" + vonly + "\" since " + v.get_version_string() + " <= " + rhs_v.get_version_string();
1527 0 : return false;
1528 : }
1529 0 : break;
1530 :
1531 0 : default:
1532 0 : throw snap_logic_exception("unexpected operators in this second switch(range_op) statement");
1533 :
1534 : }
1535 : }
1536 : else
1537 : {
1538 0 : if(vo.get_operator() == operator_t::OPERATOR_UNORDERED)
1539 : {
1540 : // default operator is '>='
1541 0 : vo.set_operator(operator_t::OPERATOR_LATER_OR_EQUAL);
1542 : }
1543 0 : v.set_operator(vo);
1544 0 : f_versions.push_back(v);
1545 : }
1546 : }
1547 :
1548 : // skip the version including the ')'
1549 0 : pos = end + 1;
1550 :
1551 : // skip the spaces (because of the simplied there should be 1 at most)
1552 0 : while(pos < d.length() && isspace(d.at(pos).unicode()))
1553 : {
1554 0 : ++pos;
1555 : }
1556 : }
1557 :
1558 : // read list of browsers
1559 0 : if(pos < d.length() && d.at(pos) == '[')
1560 : {
1561 0 : ++pos;
1562 0 : int end(d.indexOf(']'));
1563 0 : if(end == -1)
1564 : {
1565 : // we could just emit some kind of a warning but then we may not
1566 : // be able to support additional features later...
1567 0 : f_error = "Invalid browser dependency list, the list of browsers must end with a ']'";
1568 0 : return false;
1569 : }
1570 0 : QString const browsers(d.mid(pos, end - pos));
1571 0 : snap_string_list const browser_list(browsers.split(',', QString::SkipEmptyParts));
1572 0 : int const max_size(browser_list.size());
1573 0 : for(int i(0); i < max_size; ++i)
1574 : {
1575 0 : QString const bn(browser_list[i].trimmed());
1576 0 : if(bn.isEmpty())
1577 : {
1578 : // this happens because of the trimmed()
1579 0 : continue;
1580 : }
1581 0 : name_string = bn;
1582 0 : if(!validate_name(name_string, f_error, namespace_string))
1583 : {
1584 0 : return false;
1585 : }
1586 0 : name browser;
1587 0 : browser.set_name(bn);
1588 0 : f_browsers.push_back(browser);
1589 : }
1590 :
1591 0 : pos = end + 1;
1592 :
1593 : // skip the spaces (because of the simplied there should be none here)
1594 0 : while(pos < d.length() && isspace(d.at(pos).unicode()))
1595 : {
1596 0 : ++pos;
1597 : }
1598 : }
1599 :
1600 0 : if(pos != d.length())
1601 : {
1602 0 : f_error = QString("left over data at the end of the dependency string \"%1\"").arg(dependency_string);
1603 0 : return false;
1604 : }
1605 :
1606 0 : return true;
1607 : }
1608 :
1609 :
1610 : /** \brief Get the canonicalized dependency string.
1611 : *
1612 : * When you set the dependency string with set_dependency() the string
1613 : * may miss some spaces or include additional spaces, some versions may
1614 : * end with ".0" or some numbers start with 0 (i.e. "5.03") and
1615 : * additional commas may be found in lists of versions and browsers.
1616 : *
1617 : * This function returned a fully cleaned up string with the dependency
1618 : * information as intended by the specification.
1619 : */
1620 0 : QString dependency::get_dependency_string() const
1621 : {
1622 0 : QString dep;
1623 :
1624 0 : if(!f_name.get_namespace().isEmpty())
1625 : {
1626 0 : dep = QString("%1::").arg(f_name.get_namespace());
1627 : }
1628 :
1629 0 : dep += f_name.get_name();
1630 :
1631 0 : int const max_versions(f_versions.count());
1632 0 : if(max_versions > 0)
1633 : {
1634 0 : dep += " (" + f_versions[0].get_opversion_string();
1635 0 : for(int i(1); i < max_versions; ++i)
1636 : {
1637 0 : dep += ", " + f_versions[i].get_opversion_string();
1638 : }
1639 0 : dep += ")";
1640 : }
1641 :
1642 0 : int const max_browsers(f_browsers.count());
1643 0 : if(max_browsers > 0)
1644 : {
1645 0 : dep += " [" + f_browsers[0].get_name();
1646 0 : for(int i(1); i < max_browsers; ++i)
1647 : {
1648 0 : dep += ", " + f_browsers[i].get_name();
1649 : }
1650 0 : dep += "]";
1651 : }
1652 :
1653 0 : return dep;
1654 : }
1655 :
1656 :
1657 : /** \brief Check the validity of a dependency declaration.
1658 : *
1659 : * This function retrieves the validity of the dependency.
1660 : *
1661 : * This includes the validity of the dependency object itself, the name,
1662 : * all the versions, and all the browser names.
1663 : *
1664 : * \return true if all the information is considered valid.
1665 : */
1666 0 : bool dependency::is_valid() const
1667 : {
1668 0 : if(!f_error.isEmpty() || !f_name.is_valid())
1669 : {
1670 0 : return false;
1671 : }
1672 :
1673 : // loop through all the versions
1674 0 : int const max_versions(f_versions.size());
1675 0 : for(int i(0); i < max_versions; ++i)
1676 : {
1677 0 : if(!f_versions[i].is_valid())
1678 : {
1679 0 : return false;
1680 : }
1681 : }
1682 :
1683 : // loop through all the browsers
1684 0 : int const max_browsers(f_browsers.size());
1685 0 : for(int i(0); i < max_browsers; ++i)
1686 : {
1687 0 : if(!f_browsers[i].is_valid())
1688 : {
1689 0 : return false;
1690 : }
1691 : }
1692 :
1693 0 : return true;
1694 : }
1695 :
1696 :
1697 : /** \brief Function to quickly find the Version and Browsers fields.
1698 : *
1699 : * This function initializes the Quick Find Version in Source object
1700 : * with a pointer to the input data and the size of the buffer.
1701 : *
1702 : * The find_version() function is expected to be called afterward to
1703 : * get the Version and Browsers fields. The validity of those fields
1704 : * is also getting checked when found. The Browsers field is optional,
1705 : * however the Version field is mandatory.
1706 : *
1707 : * The source is expected to be UTF-8.
1708 : */
1709 0 : quick_find_version_in_source::quick_find_version_in_source()
1710 : //: f_data(nullptr) -- auto-init
1711 : //, f_end(nullptr) -- auto-init
1712 : //, f_name() -- auto-init
1713 : //, f_layout() -- auto-init
1714 : //, f_version("") -- auto-init
1715 : //, f_browsers("") -- auto-init
1716 : //, f_error("") -- auto-init
1717 : //, f_description("") -- auto-init
1718 : //, f_depends() -- auto-init
1719 : {
1720 0 : }
1721 :
1722 :
1723 : /** \brief Search for the Version and other fields.
1724 : *
1725 : * This function reads the file. It must start with a C-like comment (a.
1726 : * slash (/) and an asterisk (*)).
1727 : *
1728 : * The C-like comment can include any number of fields. On a line you want
1729 : * to include a field name, a colon, followed by a value. For example, the
1730 : * version field is defined as:
1731 : *
1732 : * Version: 1.2.3
1733 : *
1734 : * And the browsers field is defined as a list of browser names:
1735 : *
1736 : * Browsers: ie, firefox, opera
1737 : *
1738 : * The list of browsers is used to select code using a C-like preprocessor
1739 : * in the .js and .css files. That allows us to not have to use tricks to
1740 : * support different browsers with very similar but different enough code
1741 : * for different browsers.
1742 : *
1743 : * \param[in] data The pointer to the string to be parsed.
1744 : * \param[in] size The size (number of bytes) of the string to be parsed.
1745 : *
1746 : * \return true if the function succeeded, false otherwise and an error is
1747 : * set which can be retrieved with get_error()
1748 : */
1749 0 : bool quick_find_version_in_source::find_version(char const *data, int const size)
1750 : {
1751 : class field_t
1752 : {
1753 : public:
1754 0 : field_t(char const *name)
1755 0 : : f_name(name)
1756 : {
1757 0 : }
1758 :
1759 0 : bool check(QString const & line, QString & value)
1760 : {
1761 : // find the field name if available
1762 :
1763 : // skip spaces at the beginning of the line
1764 0 : unsigned int const max_length(line.length());
1765 : unsigned int pos;
1766 0 : for(pos = 0; pos < max_length; ++pos)
1767 : {
1768 0 : if(!isspace(line.at(pos).unicode()))
1769 : {
1770 0 : break;
1771 : }
1772 : }
1773 :
1774 : // C-like comments most often have " * " at the start of the line
1775 0 : if(line.at(pos).unicode() == '*')
1776 : {
1777 : // skip spaces after the '*'
1778 0 : for(++pos; pos < max_length; ++pos)
1779 : {
1780 0 : if(!isspace(line.at(pos).unicode()))
1781 : {
1782 0 : break;
1783 : }
1784 : }
1785 : }
1786 :
1787 : // compare with the name of this field
1788 0 : char const *n(f_name);
1789 0 : for(; pos < max_length && *n != '\0'; ++pos, ++n)
1790 : {
1791 0 : int c(line.at(pos).unicode());
1792 0 : if(c >= 'a' && c <= 'z')
1793 : {
1794 : // uppercase
1795 0 : c &= 0x5F;
1796 : }
1797 0 : if(c != *n)
1798 : {
1799 : // no equal...
1800 0 : return false;
1801 : }
1802 : }
1803 :
1804 : // make sure there is a colon after the name
1805 0 : if(pos >= max_length
1806 0 : || line.at(pos).unicode() != ':')
1807 : {
1808 0 : return false;
1809 : }
1810 :
1811 : // got a field, save it in value
1812 0 : value = line.mid(pos + 1).simplified();
1813 :
1814 0 : return true;
1815 : }
1816 :
1817 : private:
1818 : char const * f_name;
1819 : };
1820 :
1821 0 : f_data = data;
1822 0 : f_end = data + size;
1823 :
1824 0 : QString l(get_line());
1825 0 : if(!l.startsWith("/*"))
1826 : {
1827 : // C comment must appear first
1828 0 : f_error = "file does not start with a C-like comment";
1829 0 : return false;
1830 : }
1831 :
1832 : // note: field names are case insensitive
1833 0 : field_t field_name("NAME");
1834 0 : field_t field_layout("LAYOUT");
1835 0 : field_t field_version("VERSION");
1836 0 : field_t field_browsers("BROWSERS");
1837 0 : field_t field_description("DESCRIPTION");
1838 0 : field_t field_depends("DEPENDS");
1839 : for(;;)
1840 : {
1841 0 : QString value;
1842 0 : if(field_name.check(l, value))
1843 : {
1844 0 : if(!f_name.get_name().isEmpty())
1845 : {
1846 0 : f_error = "name field cannot be defined more than once";
1847 0 : return false;
1848 : }
1849 0 : if(!f_name.set_name(value))
1850 : {
1851 0 : f_error = f_name.get_error();
1852 0 : return false;
1853 : }
1854 : }
1855 0 : if(field_layout.check(l, value))
1856 : {
1857 0 : if(!f_layout.get_name().isEmpty())
1858 : {
1859 0 : f_error = "layout field cannot be defined more than once";
1860 0 : return false;
1861 : }
1862 0 : if(!f_layout.set_name(value))
1863 : {
1864 0 : f_error = f_layout.get_error();
1865 0 : return false;
1866 : }
1867 : }
1868 0 : else if(field_version.check(l, value))
1869 : {
1870 0 : if(!f_version.get_version_string().isEmpty())
1871 : {
1872 : // more than one Version field
1873 0 : f_error = "version field cannot be defined more than once";
1874 0 : return false;
1875 : }
1876 0 : if(!f_version.set_version_string(value))
1877 : {
1878 0 : f_error = f_version.get_error();
1879 0 : return false;
1880 : }
1881 : }
1882 0 : else if(field_browsers.check(l, value))
1883 : {
1884 0 : if(!f_browsers.isEmpty())
1885 : {
1886 : // more than one Browsers field
1887 0 : f_error = "browser field cannot be defined more than once";
1888 0 : return false;
1889 : }
1890 0 : snap_string_list browser_list(value.split(','));
1891 0 : int const max_size(browser_list.size());
1892 0 : for(int i(0); i < max_size; ++i)
1893 : {
1894 0 : name browser;
1895 0 : if(!browser.set_name(browser_list[i].trimmed()))
1896 : {
1897 0 : f_error = browser.get_error();
1898 0 : return false;
1899 : }
1900 0 : f_browsers.push_back(browser);
1901 : }
1902 : }
1903 0 : else if(field_description.check(l, value))
1904 : {
1905 0 : if(!f_description.isEmpty())
1906 : {
1907 : // more than one Description field
1908 0 : f_error = "description field cannot be defined more than once";
1909 0 : return false;
1910 : }
1911 : // description can be anything
1912 0 : f_description = value;
1913 : }
1914 0 : else if(field_depends.check(l, value))
1915 : {
1916 0 : if(!f_depends.isEmpty())
1917 : {
1918 : // more than one Depends field
1919 0 : f_error = "depends field cannot be defined more than once";
1920 0 : return false;
1921 : }
1922 : // parse dependencies one by one
1923 0 : if(!value.isEmpty())
1924 : {
1925 0 : int paren(0);
1926 0 : int brack(0);
1927 0 : QChar const *start(value.data());
1928 0 : for(QChar const *s(start);; ++s)
1929 : {
1930 0 : switch(s->unicode())
1931 : {
1932 0 : case '(':
1933 0 : ++paren;
1934 0 : break;
1935 :
1936 0 : case ')':
1937 0 : --paren;
1938 0 : break;
1939 :
1940 0 : case '[':
1941 0 : ++brack;
1942 0 : break;
1943 :
1944 0 : case ']':
1945 0 : --brack;
1946 0 : break;
1947 :
1948 0 : case ',':
1949 : case '\0':
1950 0 : if(paren == 0 && brack == 0)
1951 : {
1952 : // got one!
1953 0 : size_t const len(s - start);
1954 0 : if(len > 0) // ignore empty entries
1955 : {
1956 0 : dependency d;
1957 0 : QString dep(start, static_cast<int>(s - start));
1958 0 : d.set_dependency(dep);
1959 0 : f_depends.push_back(d);
1960 : }
1961 0 : start = s;
1962 0 : if(s->unicode() == ',')
1963 : {
1964 : // skip the comma
1965 0 : ++start;
1966 0 : }
1967 : }
1968 0 : else if(s->unicode() == '\0')
1969 : {
1970 : // parenthesis or brackets mismatched
1971 0 : f_error = "depends field () or [] mismatch";
1972 0 : return false;
1973 : }
1974 0 : break;
1975 :
1976 : }
1977 : // by doing this test here we avoid having to duplicate
1978 : // the case when we save a dependency
1979 0 : if(s->unicode() == '\0')
1980 : {
1981 0 : break;
1982 : }
1983 0 : }
1984 : }
1985 : }
1986 0 : if(l.contains("*/"))
1987 : {
1988 : // stop with the end of the comment
1989 : // return true only if the version was specified
1990 0 : bool result(!f_version.get_version_string().isEmpty());
1991 0 : if(result && f_browsers.isEmpty())
1992 : {
1993 : // always have some browsers, "all" if nothing else
1994 0 : name browser;
1995 0 : browser.set_name("all");
1996 0 : f_browsers.push_back(browser);
1997 : }
1998 0 : return result;
1999 : }
2000 0 : l = get_line();
2001 0 : }
2002 : }
2003 :
2004 :
2005 : /** \brief Get one character from the input file.
2006 : *
2007 : * This function reads one byte from the file and returns it.
2008 : *
2009 : * \return The next character or EOF at the end of the file.
2010 : */
2011 0 : int quick_find_version_in_source::getc()
2012 : {
2013 0 : if(f_data >= f_end)
2014 : {
2015 0 : return EOF;
2016 : }
2017 : // note: UTF-8 can be ignored because what we are interested
2018 : // in is using ASCII only
2019 0 : return *f_data++;
2020 : }
2021 :
2022 :
2023 : /** \brief Get a line of text.
2024 : *
2025 : * This function reads the next line. Empty lines are skipped and not
2026 : * returned unless the end of the file is reached. In that case, the
2027 : * function returns anyway.
2028 : *
2029 : * \return The line just read or an empty string if the end of the line.
2030 : */
2031 0 : QString quick_find_version_in_source::get_line()
2032 : {
2033 0 : int c(0);
2034 : for(;;)
2035 : {
2036 0 : std::string raw;
2037 : for(;;)
2038 : {
2039 0 : c = getc();
2040 0 : if(c == '\n' || c == '\r' || c == EOF)
2041 : {
2042 : break;
2043 : }
2044 0 : raw += static_cast<char>(c);
2045 : }
2046 : // we need to support UTF-8 properly for descriptions
2047 0 : QString line(QString::fromUtf8(raw.c_str()).trimmed());
2048 0 : if(!line.isEmpty() || c == EOF)
2049 : {
2050 : // do not return empty line unless we reached the
2051 : // end of the file
2052 0 : return line;
2053 : }
2054 0 : }
2055 : NOTREACHED();
2056 : return "";
2057 : }
2058 :
2059 :
2060 : /** \brief Check whether the object is valid.
2061 : *
2062 : * This function returns true if all the data in this object is valid.
2063 : */
2064 0 : bool quick_find_version_in_source::is_valid() const
2065 : {
2066 : // first check internal values
2067 0 : if(!f_error.isEmpty()
2068 0 : || !f_name.is_valid()
2069 0 : || !f_version.is_valid())
2070 : {
2071 0 : return false;
2072 : }
2073 :
2074 : // check each browser name
2075 0 : int const max_size(f_browsers.size());
2076 0 : for(int i(0); i < max_size; ++i)
2077 : {
2078 0 : if(!f_browsers[i].is_valid())
2079 : {
2080 0 : return false;
2081 : }
2082 : }
2083 :
2084 0 : return true;
2085 : }
2086 :
2087 :
2088 : } // namespace snap_version
2089 6 : } // namespace snap
2090 : // vim: ts=4 sw=4 et
|