public void Process(UriWrapper uri, IHttpProcessor processor, User user) { // Read out settings if (uri.Action == "read" || uri.Action == null) { // If no parameter provided, return settings processor.WriteJson(new SettingsResponse(null, Injection.Kernel.Get<IServerSettings>().SettingsModel)); return; } // Check for required JSON parameter if (!uri.Parameters.ContainsKey("json")) { processor.WriteJson(new SettingsResponse("Missing required parameter 'json'", null)); return; } // Update settings if (uri.Action == "update") { // Take in settings in the JSON format (same as it is stored on disk), // pass it on to the Settings class for processing string json = HttpUtility.UrlDecode(uri.Parameters["json"]); // Attempt to write settings bool success = false; try { success = Injection.Kernel.Get<IServerSettings>().WriteSettings(json); Injection.Kernel.Get<IServerSettings>().Reload(); } catch (JsonException) { // Failure if invalid JSON provided processor.WriteJson(new SettingsResponse("Invalid JSON", null)); return; } // If settings fail to write, report error if (!success) { processor.WriteJson(new SettingsResponse("Settings could not be changed", null)); return; } // If settings wrote successfully, return success processor.WriteJson(new SettingsResponse(null, Injection.Kernel.Get<IServerSettings>().SettingsModel)); return; } // Invalid action processor.WriteJson(new SettingsResponse("Invalid action", null)); return; }
public void Process(UriWrapper uri, IHttpProcessor processor, User user) { // Read out settings if (uri.Action == "read" || uri.Action == null) { // If no parameter provided, return settings processor.WriteJson(new SettingsResponse(null, Injection.Kernel.Get <IServerSettings>().SettingsModel)); return; } // Check for required JSON parameter if (!uri.Parameters.ContainsKey("json")) { processor.WriteJson(new SettingsResponse("Missing required parameter 'json'", null)); return; } // Update settings if (uri.Action == "update") { // Take in settings in the JSON format (same as it is stored on disk), // pass it on to the Settings class for processing string json = HttpUtility.UrlDecode(uri.Parameters["json"]); // Attempt to write settings bool success = false; try { success = Injection.Kernel.Get <IServerSettings>().WriteSettings(json); Injection.Kernel.Get <IServerSettings>().Reload(); } catch (JsonException) { // Failure if invalid JSON provided processor.WriteJson(new SettingsResponse("Invalid JSON", null)); return; } // If settings fail to write, report error if (!success) { processor.WriteJson(new SettingsResponse("Settings could not be changed", null)); return; } // If settings wrote successfully, return success processor.WriteJson(new SettingsResponse(null, Injection.Kernel.Get <IServerSettings>().SettingsModel)); return; } // Invalid action processor.WriteJson(new SettingsResponse("Invalid action", null)); return; }
/// <summary> /// Attempt to authenticate a user using URI parameters /// </summary> public User AuthenticateUri(UriWrapper uri) { // Attempt to parse various parameters from the URI string sessionId = null; string username = null; string password = null; string clientName = null; try { uri.Parameters.TryGetValue("s", out sessionId); uri.Parameters.TryGetValue("u", out username); uri.Parameters.TryGetValue("p", out password); uri.Parameters.TryGetValue("c", out clientName); } catch { } // If logging in, we are creating a new session if (uri.ApiAction == "login") { // Must use username and password, and create a session User user = Injection.Kernel.Get<IUserRepository>().UserForName(username); // Validate User ID, and ensure successful session creation if (user.UserId != null && user.CreateSession(password, clientName)) { return user; } } // Otherwise, check for a session key parameter if (sessionId != null) { // This will return the user on success, or null on failure return this.AuthenticateSession(sessionId); } // On failure or no session, return null return null; }
/// <summary> /// Attempt to authenticate a user using URI parameters /// </summary> public User AuthenticateUri(UriWrapper uri) { // Attempt to parse various parameters from the URI string sessionId = null; string username = null; string password = null; string clientName = null; try { uri.Parameters.TryGetValue("s", out sessionId); uri.Parameters.TryGetValue("u", out username); uri.Parameters.TryGetValue("p", out password); uri.Parameters.TryGetValue("c", out clientName); } catch { } // If logging in, we are creating a new session if (uri.ApiAction == "login") { // Must use username and password, and create a session User user = Injection.Kernel.Get <IUserRepository>().UserForName(username); // Validate User ID, and ensure successful session creation if (user.UserId != null && user.CreateSession(password, clientName)) { return(user); } } // Otherwise, check for a session key parameter if (sessionId != null) { // This will return the user on success, or null on failure return(this.AuthenticateSession(sessionId)); } // On failure or no session, return null return(null); }
/// <summary> /// Process records play stats for artists, albums, songs /// </summary> public void Process(UriWrapper uri, IHttpProcessor processor, User user) { // Ensure an event is present if (!uri.Parameters.ContainsKey("event")) { processor.WriteJson(new StatsResponse("Please specify an event parameter with comma separated list of events")); return; } // Split events into id, stat type, UNIX timestamp triples string[] events = uri.Parameters["event"].Split(','); // Ensure data sent if (events.Length < 1) { // Report empty data list processor.WriteJson(new StatsResponse("Event data list is empty")); return; } // Ensure events are in triples if (events.Length % 3 != 0) { processor.WriteJson(new StatsResponse("Event data list must be in comma-separated triples of form itemId,statType,timestamp")); return; } // Iterate all events for (int i = 0; i <= events.Length - 3; i += 3) { // Store item ID, stat type, and UNIX timestamp string itemId = events[i]; string statType = events[i+1]; string timeStamp = events[i+2]; // Initialize to null defaults int itemIdInt = -1; StatType statTypeEnum = StatType.Unknown; long timeStampLong = -1; // Perform three checks for valid item ID, stat type, UNIX timestamp bool success = Int32.TryParse(itemId, out itemIdInt); if (success) { success = Enum.TryParse<StatType>(statType, true, out statTypeEnum); } if (success) { success = Int64.TryParse(timeStamp, out timeStampLong); } if (success) { // If all three are successful, generate an item type from the ID ItemType itemType = Injection.Kernel.Get<IItemRepository>().ItemTypeForItemId(itemIdInt); // Case: type is song, stat is playcount if ((itemType == ItemType.Song) && (statTypeEnum == StatType.PLAYED)) { // Also record a play for the artist, album, and folder Song song = Injection.Kernel.Get<ISongRepository>().SongForId(itemIdInt); // Trigger now playing service if available NowPlayingService nowPlaying = (NowPlayingService)ServiceManager.GetInstance("nowplaying"); if (nowPlaying != null) { nowPlaying.Register(user, song, timeStampLong); } if ((object)song.AlbumId != null) { Injection.Kernel.Get<IStatRepository>().RecordStat((int)song.AlbumId, statTypeEnum, timeStampLong); } if ((object)song.ArtistId != null) { Injection.Kernel.Get<IStatRepository>().RecordStat((int)song.ArtistId, statTypeEnum, timeStampLong); } if ((object)song.FolderId != null) { Injection.Kernel.Get<IStatRepository>().RecordStat((int)song.FolderId, statTypeEnum, timeStampLong); } } // Record stats for the generic item Injection.Kernel.Get<IStatRepository>().RecordStat(itemIdInt, statTypeEnum, timeStampLong); } } // After all stat iterations, return a successful response processor.WriteJson(new StatsResponse(null)); }
/// <summary> /// Process records play stats for artists, albums, songs /// </summary> public void Process(UriWrapper uri, IHttpProcessor processor, User user) { // Ensure an event is present if (!uri.Parameters.ContainsKey("event")) { processor.WriteJson(new StatsResponse("Please specify an event parameter with comma separated list of events")); return; } // Split events into id, stat type, UNIX timestamp triples string[] events = uri.Parameters["event"].Split(','); // Ensure data sent if (events.Length < 1) { // Report empty data list processor.WriteJson(new StatsResponse("Event data list is empty")); return; } // Ensure events are in triples if (events.Length % 3 != 0) { processor.WriteJson(new StatsResponse("Event data list must be in comma-separated triples of form itemId,statType,timestamp")); return; } // Iterate all events for (int i = 0; i <= events.Length - 3; i += 3) { // Store item ID, stat type, and UNIX timestamp string itemId = events[i]; string statType = events[i + 1]; string timeStamp = events[i + 2]; // Initialize to null defaults int itemIdInt = -1; StatType statTypeEnum = StatType.Unknown; long timeStampLong = -1; // Perform three checks for valid item ID, stat type, UNIX timestamp bool success = Int32.TryParse(itemId, out itemIdInt); if (success) { success = Enum.TryParse <StatType>(statType, true, out statTypeEnum); } if (success) { success = Int64.TryParse(timeStamp, out timeStampLong); } if (success) { // If all three are successful, generate an item type from the ID ItemType itemType = Injection.Kernel.Get <IItemRepository>().ItemTypeForItemId(itemIdInt); // Case: type is song, stat is playcount if ((itemType == ItemType.Song) && (statTypeEnum == StatType.PLAYED)) { // Also record a play for the artist, album, and folder Song song = Injection.Kernel.Get <ISongRepository>().SongForId(itemIdInt); // Trigger now playing service if available NowPlayingService nowPlaying = (NowPlayingService)ServiceManager.GetInstance("nowplaying"); if (nowPlaying != null) { nowPlaying.Register(user, song, timeStampLong); } if ((object)song.AlbumId != null) { Injection.Kernel.Get <IStatRepository>().RecordStat((int)song.AlbumId, statTypeEnum, timeStampLong); } if ((object)song.ArtistId != null) { Injection.Kernel.Get <IStatRepository>().RecordStat((int)song.ArtistId, statTypeEnum, timeStampLong); } if ((object)song.FolderId != null) { Injection.Kernel.Get <IStatRepository>().RecordStat((int)song.FolderId, statTypeEnum, timeStampLong); } } // Record stats for the generic item Injection.Kernel.Get <IStatRepository>().RecordStat(itemIdInt, statTypeEnum, timeStampLong); } } // After all stat iterations, return a successful response processor.WriteJson(new StatsResponse(null)); }
// Process API calls based on the class's HTTP URL private void ApiProcess() { // The API request wrapper UriWrapper uri = new UriWrapper(this.HttpUrl, this.HttpMethod); // The user who is accessing the API User apiUser = null; // The handler being accessed IApiHandler api = null; // No API request found? Serve web UI if (!uri.IsApiCall) { api = Injection.Kernel.Get<IApiHandlerFactory>().CreateApiHandler("web"); api.Process(uri, this, apiUser); return; } // Get client IP address string ip = ((IPEndPoint)this.Socket.Client.RemoteEndPoint).Address.ToString(); // Check for valid API action ("web" and "error" are technically valid, but can't be used in this way) if (uri.ApiAction == null || uri.ApiAction == "web" || uri.ApiAction == "error") { ErrorApiHandler errorApi = (ErrorApiHandler)Injection.Kernel.Get<IApiHandlerFactory>().CreateApiHandler("error"); errorApi.Process(uri, this, apiUser, "Invalid API call"); logger.IfInfo(String.Format("[{0}] API: {1}", ip, this.HttpUrl)); return; } // Check for session cookie authentication, unless this is a login request string sessionId = null; if (uri.ApiAction != "login") { sessionId = this.GetSessionCookie(); apiUser = Injection.Kernel.Get<IApiAuthenticate>().AuthenticateSession(sessionId); } // If no cookie, try parameter authentication if (apiUser == null) { apiUser = Injection.Kernel.Get<IApiAuthenticate>().AuthenticateUri(uri); // If user still null, failed authentication, so serve error if (apiUser == null) { ErrorApiHandler errorApi = (ErrorApiHandler)Injection.Kernel.Get<IApiHandlerFactory>().CreateApiHandler("error"); errorApi.Process(uri, this, apiUser, "Authentication failed"); logger.IfInfo(String.Format("[{0}] API: {1}", ip, this.HttpUrl)); return; } } // apiUser.SessionId will be generated on new login, so that takes precedence for new session cookie apiUser.SessionId = apiUser.SessionId ?? sessionId; this.SetSessionCookie(apiUser.SessionId); // Store user's current session object apiUser.CurrentSession = Injection.Kernel.Get<ISessionRepository>().SessionForSessionId(apiUser.SessionId); // Retrieve the requested API handler by its action IApiHandler apiHandler = Injection.Kernel.Get<IApiHandlerFactory>().CreateApiHandler(uri.ApiAction); // Check for valid API action if (apiHandler == null) { ErrorApiHandler errorApi = (ErrorApiHandler)Injection.Kernel.Get<IApiHandlerFactory>().CreateApiHandler("error"); errorApi.Process(uri, this, apiUser, "Invalid API call"); logger.IfInfo(String.Format("[{0}] API: {1}", ip, this.HttpUrl)); return; } // Log API call logger.IfInfo(String.Format("[{0}/{1}@{2}] API: {3} {4}", apiUser.UserName, apiUser.CurrentSession.ClientName, ip, this.HttpMethod, this.HttpUrl)); // Check if user has appropriate permissions for this action on this API handler if (!apiHandler.CheckPermission(apiUser, uri.Action)) { ErrorApiHandler errorApi = (ErrorApiHandler)Injection.Kernel.Get<IApiHandlerFactory>().CreateApiHandler("error"); errorApi.Process(uri, this, apiUser, "Permission denied"); return; } // Finally, process and return results apiHandler.Process(uri, this, apiUser); }
/// <summary> /// Process a Last.fm API request /// </summary> public void Process(UriWrapper uri, IHttpProcessor processor, User user) { // Create Last.fm object for this user Lastfm lfm = new Lastfm(user); // Pull URL parameters for Last.fm integration string eve = null; uri.Parameters.TryGetValue("event", out eve); if (uri.Action == null || uri.Action == "auth") { // If not authenticated, pass back authorization URL if (!lfm.SessionAuthenticated) { processor.WriteJson(new ScrobbleResponse(null, lfm.AuthUrl)); } else { // Else, already authenticated processor.WriteJson(new ScrobbleResponse("LFMAlreadyAuthenticated")); } return; } // If Last.fm is not authenticated, provide an authorization URL if (!lfm.SessionAuthenticated) { logger.IfInfo("You must authenticate before you can scrobble."); processor.WriteJson(new ScrobbleResponse("LFMNotAuthenticated", lfm.AuthUrl)); return; } // Create list of scrobble data IList<LfmScrobbleData> scrobbles = new List<LfmScrobbleData>(); // Get Last.fm API enumerations LfmScrobbleType scrobbleType = Lastfm.ScrobbleTypeForString(uri.Action); // On invalid scrobble type, return error JSON if (scrobbleType == LfmScrobbleType.INVALID) { processor.WriteJson(new ScrobbleResponse("LFMInvalidScrobbleType")); return; } // On now playing scrobble type if (scrobbleType == LfmScrobbleType.NOWPLAYING) { // Ensure ID specified for scrobble if (uri.Id == null) { processor.WriteJson(new ScrobbleResponse("LFMNoIdSpecifiedForNowPlaying")); return; } // Add successful scrobble to list, submit scrobbles.Add(new LfmScrobbleData((int)uri.Id, null)); lfm.Scrobble(scrobbles, scrobbleType); } // Else, unknown scrobble event else { // On null event, return error JSON if (eve == null) { processor.WriteJson(new ScrobbleResponse("LFMNoEventSpecifiedForScrobble")); return; } // Ensure input is a comma-separated pair string[] input = eve.Split(','); if ((input.Length % 2) != 0) { processor.WriteJson(new ScrobbleResponse("LFMInvalidInput")); return; } // Add scrobbles from input data pairs int i = 0; while (i < input.Length) { scrobbles.Add(new LfmScrobbleData(int.Parse(input[i]), long.Parse(input[i + 1]))); i = i + 2; } } // Scrobble all plays string result = lfm.Scrobble(scrobbles, scrobbleType); dynamic resp = null; // No response, service must be offline if (result == null) { processor.WriteJson(new ScrobbleResponse("LFMServiceOffline")); return; } // If result is not null, store deserialize and store it try { resp = JsonConvert.DeserializeObject(result); } catch (Exception e) { logger.Error(e); } // Check for nowplaying or scrobbles fields if ((resp.nowplaying != null) || (resp.scrobbles != null)) { // Write blank scrobble response processor.WriteJson(new ScrobbleResponse()); return; } // Write error JSON if it exists else if (resp.error != null) { processor.WriteJson(new ScrobbleResponse(string.Format("LFM{0}: {1}", resp.error, resp.message))); return; } }
/// <summary> /// Process returns a serialized list of albums and songs in JSON format /// </summary> public void Process(UriWrapper uri, IHttpProcessor processor, User user) { // List of songs and albums to be returned via handler IList<Song> songs = new List<Song>(); IList<Album> albums = new List<Album>(); PairList<string, int> sectionPositions = new PairList<string, int>(); // Check if an ID was passed if (uri.Id != null) { // Add album by ID to the list Album a = Injection.Kernel.Get<IAlbumRepository>().AlbumForId((int)uri.Id); albums.Add(a); // Add album's songs to response songs = a.ListOfSongs(); } // Check for a request for range of songs else if (uri.Parameters.ContainsKey("range")) { string[] range = uri.Parameters["range"].Split(','); // Ensure valid range was parsed if (range.Length != 2) { processor.WriteJson(new AlbumsResponse("Parameter 'range' requires a valid, comma-separated character tuple", null, null, null)); return; } // Validate as characters char start, end; if (!Char.TryParse(range[0], out start) || !Char.TryParse(range[1], out end)) { processor.WriteJson(new AlbumsResponse("Parameter 'range' requires characters which are single alphanumeric values", null, null, null)); return; } // Grab range of albums albums = Injection.Kernel.Get<IAlbumRepository>().RangeAlbums(start, end); } // Check for a request to limit/paginate songs, like SQL // Note: can be combined with range or all albums if (uri.Parameters.ContainsKey("limit") && uri.Id == null) { string[] limit = uri.Parameters["limit"].Split(','); // Ensure valid limit was parsed if (limit.Length < 1 || limit.Length > 2 ) { processor.WriteJson(new AlbumsResponse("Parameter 'limit' requires a single integer, or a valid, comma-separated integer tuple", null, null, null)); return; } // Validate as integers int index = 0; int duration = Int32.MinValue; if (!Int32.TryParse(limit[0], out index)) { processor.WriteJson(new AlbumsResponse("Parameter 'limit' requires a valid integer start index", null, null, null)); return; } // Ensure positive index if (index < 0) { processor.WriteJson(new AlbumsResponse("Parameter 'limit' requires a non-negative integer start index", null, null, null)); return; } // Check for duration if (limit.Length == 2) { if (!Int32.TryParse(limit[1], out duration)) { processor.WriteJson(new AlbumsResponse("Parameter 'limit' requires a valid integer duration", null, null, null)); return; } // Ensure positive duration if (duration < 0) { processor.WriteJson(new AlbumsResponse("Parameter 'limit' requires a non-negative integer duration", null, null, null)); return; } } // Check if results list already populated by range if (albums.Count > 0) { // No duration? Return just specified number of albums if (duration == Int32.MinValue) { albums = albums.Skip(0).Take(index).ToList(); } else { // Else, return albums starting at index, up to count duration albums = albums.Skip(index).Take(duration).ToList(); } } else { // If no albums in list, grab directly using model method albums = Injection.Kernel.Get<IAlbumRepository>().LimitAlbums(index, duration); } } // Finally, if no albums already in list, send the whole list if (albums.Count == 0) { albums = Injection.Kernel.Get<IAlbumRepository>().AllAlbums(); sectionPositions = Utility.SectionPositionsFromSortedList(new List<IGroupingItem>(albums.Select(c => (IGroupingItem)c))); } // Send it! processor.WriteJson(new AlbumsResponse(null, albums, songs, sectionPositions)); }