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 }