1 /* 2 * Copyright 2003-2021 The Music Player Daemon Project 3 * http://www.musicpd.org 4 * 5 * This program is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation; either version 2 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License along 16 * with this program; if not, write to the Free Software Foundation, Inc., 17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 */ 19 20 #ifndef MPD_CLIENT_H 21 #define MPD_CLIENT_H 22 23 #include "Message.hxx" 24 #include "command/CommandResult.hxx" 25 #include "command/CommandListBuilder.hxx" 26 #include "input/LastInputStream.hxx" 27 #include "tag/Mask.hxx" 28 #include "event/FullyBufferedSocket.hxx" 29 #include "event/CoarseTimerEvent.hxx" 30 31 #include <boost/intrusive/link_mode.hpp> 32 #include <boost/intrusive/list_hook.hpp> 33 34 #include <cstddef> 35 #include <list> 36 #include <memory> 37 #include <set> 38 #include <string> 39 40 class SocketAddress; 41 class UniqueSocketDescriptor; 42 class EventLoop; 43 class Path; 44 struct Instance; 45 struct Partition; 46 class PlayerControl; 47 struct playlist; 48 class Database; 49 class Storage; 50 class BackgroundCommand; 51 52 class Client final 53 : FullyBufferedSocket, 54 public boost::intrusive::list_base_hook<boost::intrusive::tag<Partition>, 55 boost::intrusive::link_mode<boost::intrusive::normal_link>>, 56 public boost::intrusive::list_base_hook<boost::intrusive::link_mode<boost::intrusive::normal_link>> { 57 CoarseTimerEvent timeout_event; 58 59 Partition *partition; 60 61 unsigned permission; 62 63 /** the uid of the client process, or -1 if unknown */ 64 const int uid; 65 66 CommandListBuilder cmd_list; 67 68 const unsigned int num; /* client number */ 69 70 /** is this client waiting for an "idle" response? */ 71 bool idle_waiting = false; 72 73 /** idle flags pending on this client, to be sent as soon as 74 the client enters "idle" */ 75 unsigned idle_flags = 0; 76 77 /** idle flags that the client wants to receive */ 78 unsigned idle_subscriptions; 79 80 public: 81 // TODO: make this attribute "private" 82 /** 83 * The tags this client is interested in. 84 */ 85 TagMask tag_mask = TagMask::All(); 86 87 /** 88 * The maximum number of bytes transmitted in a binary 89 * response. Can be changed with the "binarylimit" command. 90 */ 91 size_t binary_limit = 8192; 92 93 /** 94 * This caches the last "albumart" InputStream instance, to 95 * avoid repeating the search for each chunk requested by this 96 * client. 97 */ 98 LastInputStream last_album_art; 99 100 private: 101 static constexpr size_t MAX_SUBSCRIPTIONS = 16; 102 103 /** 104 * A list of channel names this client is subscribed to. 105 */ 106 std::set<std::string> subscriptions; 107 108 /** 109 * The number of subscriptions in #subscriptions. Used to 110 * limit the number of subscriptions. 111 */ 112 unsigned num_subscriptions = 0; 113 114 static constexpr size_t MAX_MESSAGES = 64; 115 116 /** 117 * A list of messages this client has received. 118 */ 119 std::list<ClientMessage> messages; 120 121 /** 122 * The command currently running in background. If this is 123 * set, then the client is occupied and will not process any 124 * new input. If the connection gets closed, the 125 * #BackgroundCommand will be cancelled. 126 */ 127 std::unique_ptr<BackgroundCommand> background_command; 128 129 public: 130 Client(EventLoop &loop, Partition &partition, 131 UniqueSocketDescriptor fd, int uid, 132 unsigned _permission, 133 int num) noexcept; 134 135 ~Client() noexcept; 136 137 using FullyBufferedSocket::GetEventLoop; 138 using FullyBufferedSocket::GetOutputMaxSize; 139 140 [[gnu::pure]] IsExpired() const141 bool IsExpired() const noexcept { 142 return !FullyBufferedSocket::IsDefined(); 143 } 144 145 void Close() noexcept; 146 void SetExpired() noexcept; 147 148 bool Write(const void *data, size_t length) noexcept; 149 150 /** 151 * Write a null-terminated string. 152 */ Write(std::string_view s)153 bool Write(std::string_view s) noexcept { 154 return Write(s.data(), s.size()); 155 } 156 WriteOK()157 bool WriteOK() noexcept { 158 return Write("OK\n"); 159 } 160 161 /** 162 * returns the uid of the client process, or a negative value 163 * if the uid is unknown 164 */ GetUID() const165 int GetUID() const noexcept { 166 return uid; 167 } 168 169 /** 170 * Is this client running on the same machine, connected with 171 * a local (UNIX domain) socket? 172 */ IsLocal() const173 bool IsLocal() const noexcept { 174 return uid >= 0; 175 } 176 GetPermission() const177 unsigned GetPermission() const noexcept { 178 return permission; 179 } 180 SetPermission(unsigned _permission)181 void SetPermission(unsigned _permission) noexcept { 182 permission = _permission; 183 } 184 185 /** 186 * Send "idle" response to this client. 187 */ 188 void IdleNotify() noexcept; 189 void IdleAdd(unsigned flags) noexcept; 190 bool IdleWait(unsigned flags) noexcept; 191 192 /** 193 * Called by a command handler to defer execution to a 194 * #BackgroundCommand. 195 */ 196 void SetBackgroundCommand(std::unique_ptr<BackgroundCommand> _bc) noexcept; 197 198 /** 199 * Called by the current #BackgroundCommand when it has 200 * finished, after sending the response. This method then 201 * deletes the #BackgroundCommand. 202 */ 203 void OnBackgroundCommandFinished() noexcept; 204 205 enum class SubscribeResult { 206 /** success */ 207 OK, 208 209 /** invalid channel name */ 210 INVALID, 211 212 /** already subscribed to this channel */ 213 ALREADY, 214 215 /** too many subscriptions */ 216 FULL, 217 }; 218 219 [[gnu::pure]] IsSubscribed(const char * channel_name) const220 bool IsSubscribed(const char *channel_name) const noexcept { 221 return subscriptions.find(channel_name) != subscriptions.end(); 222 } 223 GetSubscriptions() const224 const auto &GetSubscriptions() const noexcept { 225 return subscriptions; 226 } 227 228 SubscribeResult Subscribe(const char *channel) noexcept; 229 bool Unsubscribe(const char *channel) noexcept; 230 void UnsubscribeAll() noexcept; 231 bool PushMessage(const ClientMessage &msg) noexcept; 232 233 template<typename F> ConsumeMessages(F && f)234 void ConsumeMessages(F &&f) { 235 while (!messages.empty()) { 236 f(messages.front()); 237 messages.pop_front(); 238 } 239 } 240 241 /** 242 * Is this client allowed to use the specified local file? 243 * 244 * Note that this function is vulnerable to timing/symlink attacks. 245 * We cannot fix this as long as there are plugins that open a file by 246 * its name, and not by file descriptor / callbacks. 247 * 248 * Throws #std::runtime_error on error. 249 * 250 * @param path_fs the absolute path name in filesystem encoding 251 */ 252 void AllowFile(Path path_fs) const; 253 GetPartition() const254 Partition &GetPartition() const noexcept { 255 return *partition; 256 } 257 258 void SetPartition(Partition &new_partition) noexcept; 259 260 [[gnu::pure]] 261 Instance &GetInstance() const noexcept; 262 263 [[gnu::pure]] 264 playlist &GetPlaylist() const noexcept; 265 266 [[gnu::pure]] 267 PlayerControl &GetPlayerControl() const noexcept; 268 269 /** 270 * Wrapper for Instance::GetDatabase(). 271 */ 272 [[gnu::pure]] 273 const Database *GetDatabase() const noexcept; 274 275 /** 276 * Wrapper for Instance::GetDatabaseOrThrow(). 277 */ 278 const Database &GetDatabaseOrThrow() const; 279 280 [[gnu::pure]] 281 const Storage *GetStorage() const noexcept; 282 283 private: 284 CommandResult ProcessCommandList(bool list_ok, 285 std::list<std::string> &&list) noexcept; 286 287 CommandResult ProcessLine(char *line) noexcept; 288 289 /* virtual methods from class BufferedSocket */ 290 InputResult OnSocketInput(void *data, size_t length) noexcept override; 291 void OnSocketError(std::exception_ptr ep) noexcept override; 292 void OnSocketClosed() noexcept override; 293 294 /* callback for TimerEvent */ 295 void OnTimeout() noexcept; 296 }; 297 298 void 299 client_new(EventLoop &loop, Partition &partition, 300 UniqueSocketDescriptor fd, SocketAddress address, int uid, 301 unsigned permission) noexcept; 302 303 #endif 304