reapack

Package manager for REAPER
Log | Files | Refs | Submodules | README | LICENSE

remote.cpp (4819B)


      1 /* ReaPack: Package manager for REAPER
      2  * Copyright (C) 2015-2025  Christian Fillion
      3  *
      4  * This program is free software: you can redistribute it and/or modify
      5  * it under the terms of the GNU Lesser General Public License as published by
      6  * the Free Software Foundation, either version 3 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 Lesser General Public License for more details.
     13  *
     14  * You should have received a copy of the GNU Lesser General Public License
     15  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
     16  */
     17 
     18 #include "remote.hpp"
     19 
     20 #include "errors.hpp"
     21 
     22 #include <boost/lexical_cast.hpp>
     23 #include <boost/logic/tribool_io.hpp>
     24 #include <regex>
     25 #include <sstream>
     26 
     27 static char DATA_DELIMITER = '|';
     28 
     29 static bool validateName(const std::string &name)
     30 {
     31   using namespace std::regex_constants;
     32 
     33   // see https://en.wikipedia.org/wiki/Filename#Reserved%5Fcharacters%5Fand%5Fwords
     34   static const std::regex validPattern("[^*\\\\:<>?/|\"[:cntrl:]]+");
     35   static const std::regex invalidPattern(
     36     "[\\.\x20].*|.+[\\.\x20]|CLOCK\\$|COM\\d|LPT\\d", icase);
     37 
     38   std::smatch valid, invalid;
     39   std::regex_match(name, valid, validPattern);
     40   std::regex_match(name, invalid, invalidPattern);
     41 
     42   return !valid.empty() && invalid.empty();
     43 }
     44 
     45 static bool validateUrl(const std::string &url)
     46 {
     47   // see http://tools.ietf.org/html/rfc3986#section-2
     48   static const std::regex pattern(
     49     "(?:[a-zA-Z0-9._~:/?#[\\]@!$&'()*+,;=-]|%[a-f0-9]{2})+");
     50 
     51   std::smatch match;
     52   std::regex_match(url, match, pattern);
     53 
     54   return !match.empty();
     55 }
     56 
     57 Remote Remote::fromString(const std::string &data)
     58 {
     59   std::istringstream stream(data);
     60 
     61   std::string name;
     62   std::getline(stream, name, DATA_DELIMITER);
     63 
     64   std::string url;
     65   std::getline(stream, url, DATA_DELIMITER);
     66 
     67   std::string enabled;
     68   std::getline(stream, enabled, DATA_DELIMITER);
     69 
     70   std::string autoInstall;
     71   std::getline(stream, autoInstall, DATA_DELIMITER);
     72 
     73   if(!validateName(name) || !validateUrl(url))
     74     return {};
     75 
     76   Remote remote(name, url);
     77 
     78   try {
     79     remote.setEnabled(boost::lexical_cast<bool>(enabled));
     80   }
     81   catch(const boost::bad_lexical_cast &) {}
     82 
     83   try {
     84     remote.setAutoInstall(boost::lexical_cast<tribool>(autoInstall));
     85   }
     86   catch(const boost::bad_lexical_cast &) {}
     87 
     88   return remote;
     89 }
     90 
     91 Remote::Remote()
     92   : m_enabled(true), m_protected(false), m_autoInstall(boost::logic::indeterminate)
     93 {
     94 }
     95 
     96 Remote::Remote(const std::string &name, const std::string &url,
     97     const bool enabled, const tribool &autoInstall)
     98   : m_enabled(enabled), m_protected(false), m_autoInstall(autoInstall)
     99 {
    100   setName(name);
    101   setUrl(url);
    102 }
    103 
    104 void Remote::setName(const std::string &name)
    105 {
    106   if(name.empty())
    107     throw reapack_error("empty repository name");
    108   if(!validateName(name)) // also checks for emptyness
    109     throw reapack_error(String::format("invalid repository name '%s'", name.c_str()));
    110 
    111   m_name = name;
    112 }
    113 
    114 void Remote::setUrl(const std::string &url)
    115 {
    116   if(!validateUrl(url))
    117     throw reapack_error("invalid url");
    118   else if(m_protected && url != m_url)
    119     throw reapack_error("cannot change the URL of a protected repository");
    120 
    121   m_url = url;
    122 }
    123 
    124 bool Remote::autoInstall(bool fallback) const
    125 {
    126   if(boost::logic::indeterminate(m_autoInstall))
    127     return fallback;
    128   else
    129     return bool{m_autoInstall};
    130 }
    131 
    132 std::string Remote::toString() const
    133 {
    134   std::ostringstream out;
    135   out << m_name << DATA_DELIMITER;
    136   out << m_url << DATA_DELIMITER;
    137   out << m_enabled << DATA_DELIMITER;
    138   out << m_autoInstall;
    139 
    140   return out.str();
    141 }
    142 
    143 void RemoteList::add(const Remote &remote)
    144 {
    145   if(!remote)
    146     return;
    147 
    148   size_t index = 0;
    149   const auto &it = m_map.find(remote.name());
    150 
    151   if(it == m_map.end()) {
    152     // insert remote
    153     index = m_remotes.size();
    154     m_remotes.push_back(remote);
    155   }
    156   else {
    157     // replace remote
    158     index = it->second;
    159     m_remotes[index] = remote;
    160   }
    161 
    162   m_map[remote.name()] = index;
    163 }
    164 
    165 void RemoteList::remove(const std::string &name)
    166 {
    167   const auto &it = m_map.find(name);
    168 
    169   if(it == m_map.end())
    170     return;
    171 
    172   m_remotes.erase(m_remotes.begin() + it->second);
    173 
    174   for(auto walk = m_map.begin(); walk != m_map.end(); walk++) {
    175     if(walk->second > it->second)
    176       walk->second--;
    177   }
    178 
    179   m_map.erase(it);
    180 }
    181 
    182 Remote RemoteList::get(const std::string &name) const
    183 {
    184   const auto &it = m_map.find(name);
    185 
    186   if(it == m_map.end())
    187     return {};
    188   else
    189     return m_remotes[it->second];
    190 }
    191 
    192 std::vector<Remote> RemoteList::getEnabled() const
    193 {
    194   std::vector<Remote> list;
    195 
    196   for(const Remote &remote : m_remotes) {
    197     if(remote.isEnabled())
    198       list.push_back(remote);
    199   }
    200 
    201   return list;
    202 }