using System; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; using Jackett.Common.Services.Interfaces; using Jackett.Common.Utils; using Newtonsoft.Json.Linq; namespace Jackett.Common.Models.IndexerConfig { public class ConfigurationData { private const string PASSWORD_REPLACEMENT = "|||%%PREVJACKPASSWD%%|||"; protected Dictionary dynamics = new Dictionary(); // list for dynamic items public HiddenStringConfigurationItem CookieHeader { get; private set; } = new HiddenStringConfigurationItem(name: "CookieHeader"); public HiddenStringConfigurationItem LastError { get; private set; } = new HiddenStringConfigurationItem(name: "LastError"); public StringConfigurationItem SiteLink { get; private set; } = new StringConfigurationItem(name: "Site Link"); public TagsConfigurationItem Tags { get; private set; } = new TagsConfigurationItem(name: "Tags", charSet: "A-Za-z0-9\\-\\._~"); public ConfigurationData() { } /// /// Loads all JSON values into the matching properties. /// public void LoadConfigDataValuesFromJson(JToken json, IProtectionService ps = null) { if (json == null) return; var jsonArray = (JArray)json; foreach (var item in GetAllConfigurationItems().Where(x => x.CanBeSavedToFile)) { var jsonToken = jsonArray.FirstOrDefault(f => f.Value("id") == item.ID); if (jsonToken == null) continue; item.FromJson(jsonToken, ps); } } public JToken ToJson(IProtectionService ps, bool forDisplay = true) { var jArray = new JArray(); var configurationItems = GetConfigurationItems(forDisplay); foreach (var configurationItem in configurationItems) { var jObject = configurationItem.ToJson(ps, forDisplay); if (jObject != null) { jArray.Add(jObject); } } return jArray; } private IEnumerable GetAllConfigurationItems() { var properties = GetType() .GetProperties() .Where(p => p.CanRead) .Where(p => p.PropertyType.IsSubclassOf(typeof(ConfigurationItem))) .Where(p => p.GetValue(this) != null) .Select(p => (ConfigurationItem)p.GetValue(this)).ToList(); // remove/insert Site Link manualy to make sure it shows up first properties.Remove(SiteLink); properties.Insert(0, SiteLink); // remove/insert Tags manualy to make sure it shows up last properties.Remove(Tags); properties.AddRange(dynamics.Values); properties.Add(Tags); return properties; } private ConfigurationItem[] GetConfigurationItems(bool forDisplay) => GetAllConfigurationItems().Where(p => forDisplay ? p.CanBeShownToUser : p.CanBeSavedToFile).ToArray(); public void AddDynamic(string ID, ConfigurationItem item) => dynamics[ID] = item; public ConfigurationItem GetDynamic(string ID) => dynamics.TryGetValue(ID, out var value) ? value : null; public ConfigurationItem GetDynamicByName(string Name) => dynamics.Values.FirstOrDefault(i => string.Equals(i.Name, Name, StringComparison.InvariantCultureIgnoreCase)); public abstract class ConfigurationItem { public string ID => Name.Replace(" ", "").ToLower(); public string Name { get; } public string ItemType { get; } public bool CanBeShownToUser { get; } = true; public bool CanBeSavedToFile { get; } = true; protected ConfigurationItem(string name, string itemType, bool canBeShownToUser = true, bool canBeSavedToFile = true) { Name = name; // TODO Error when name is null/empty/not unique/... ItemType = itemType; CanBeShownToUser = canBeShownToUser; CanBeSavedToFile = canBeSavedToFile; } protected JObject CreateJObject() { return new JObject { ["id"] = ID, ["type"] = ItemType.ToLower(), ["name"] = Name }; } protected static T ReadValueAs(JToken jToken) => jToken.Value("value"); protected static bool HasPasswordValue(ConfigurationItem item) => string.Equals(item.Name, "password", StringComparison.InvariantCultureIgnoreCase); public virtual JObject ToJson(IProtectionService protectionService = null, bool forDisplay = true) => null; public virtual void FromJson(JToken jsonToken, IProtectionService protectionService = null) { } } /// /// Remove this class when all passwords are configured to use the correct class: PasswordConfigurationItem. /// public abstract class ConfigurationItemMaybePassword : ConfigurationItem { public string Value { get; set; } protected ConfigurationItemMaybePassword(string name, string itemType, bool canBeShownToUser = true, bool canBeSavedToFile = true) : base(name, itemType, canBeShownToUser, canBeSavedToFile) { } public override JObject ToJson(IProtectionService protectionService = null, bool forDisplay = true) { var jObject = CreateJObject(); if (string.Equals(Name, "password", StringComparison.InvariantCultureIgnoreCase)) { var password = Value; if (string.IsNullOrEmpty(password)) password = string.Empty; else if (protectionService != null) password = protectionService.Protect(password); jObject["value"] = password; } else { jObject["value"] = Value; } return jObject; } } public class StringConfigurationItem : ConfigurationItemMaybePassword { public StringConfigurationItem(string name) : base(name, itemType: "inputstring") { } public override void FromJson(JToken jsonToken, IProtectionService ps = null) { if (HasPasswordValue(this)) { var pw = ReadValueAs(jsonToken); if (pw != PASSWORD_REPLACEMENT) { Value = ps != null ? ps.UnProtect(pw) : pw; } } else { Value = ReadValueAs(jsonToken); } } } public class HiddenStringConfigurationItem : ConfigurationItemMaybePassword { public HiddenStringConfigurationItem(string name) : base(name, itemType: "hiddendata", canBeShownToUser: false) { } public override void FromJson(JToken jsonToken, IProtectionService ps = null) { Value = ReadValueAs(jsonToken); } } public class DisplayInfoConfigurationItem : ConfigurationItemMaybePassword { public DisplayInfoConfigurationItem(string name, string value) : base(name, itemType: "displayinfo", canBeSavedToFile: false) { Value = value; } } public class BoolConfigurationItem : ConfigurationItem { public bool Value { get; set; } public BoolConfigurationItem(string name) : base(name, itemType: "inputbool") { } public override JObject ToJson(IProtectionService ps = null, bool forDisplay = true) { var jObject = CreateJObject(); jObject["value"] = Value; return jObject; } public override void FromJson(JToken jsonToken, IProtectionService ps = null) { Value = ReadValueAs(jsonToken); } } public class DisplayImageConfigurationItem : ConfigurationItem { public byte[] Value { get; set; } public DisplayImageConfigurationItem(string name) : base(name, itemType: "displayimage", canBeSavedToFile: false) { } public override JObject ToJson(IProtectionService ps = null, bool forDisplay = true) { var jObject = CreateJObject(); var dataUri = DataUrlUtils.BytesToDataUrl(Value, "image/jpeg"); jObject["value"] = dataUri; return jObject; } } public class SingleSelectConfigurationItem : ConfigurationItem { public string Value { get; set; } public Dictionary Options { get; } public SingleSelectConfigurationItem(string name, Dictionary options) : base(name, itemType: "inputselect") => Options = options; public override JObject ToJson(IProtectionService ps = null, bool forDisplay = true) { var jObject = CreateJObject(); jObject["value"] = Value; jObject["options"] = new JObject(); foreach (var option in Options) { jObject["options"][option.Key] = option.Value; } return jObject; } public override void FromJson(JToken jsonToken, IProtectionService ps = null) { Value = ReadValueAs(jsonToken); } } public class MultiSelectConfigurationItem : ConfigurationItem { public string[] Values { get; set; } public Dictionary Options { get; } public MultiSelectConfigurationItem(string name, Dictionary options) : base(name, itemType: "inputcheckbox") => Options = options; public override JObject ToJson(IProtectionService ps, bool forDisplay) { var jObject = CreateJObject(); jObject["values"] = new JArray(Values); jObject["options"] = new JObject(); foreach (var option in Options) { jObject["options"][option.Key] = option.Value; } return jObject; } public override void FromJson(JToken jsonToken, IProtectionService ps = null) { var values = jsonToken.Value("values"); if (values != null) { Values = values.Values().ToArray(); } } } public class PasswordConfigurationItem : ConfigurationItem { public string Value { get; set; } public PasswordConfigurationItem(string name) : base(name, itemType: "password") { } public override JObject ToJson(IProtectionService protectionService = null, bool forDisplay = true) { var jObject = CreateJObject(); var password = Value; if (string.IsNullOrEmpty(password)) password = string.Empty; else if (forDisplay) password = PASSWORD_REPLACEMENT; else if (protectionService != null) password = protectionService.Protect(password); jObject["value"] = password; return jObject; } public override void FromJson(JToken jsonToken, IProtectionService ps = null) { var pw = ReadValueAs(jsonToken); if (pw != PASSWORD_REPLACEMENT) { Value = ps != null ? ps.UnProtect(pw) : pw; } } } public class TagsConfigurationItem : ConfigurationItem { public HashSet Values { get; } public string Pattern { get; set; } public char Separator { get; set; } public string Delimiters { get; set; } public HashSet Whitelist { get; } public HashSet Blacklist { get; } public TagsConfigurationItem(string name, string charSet = null, char separator = ',') : base(name, "inputtags") { Values = new HashSet(); Whitelist = new HashSet(); Blacklist = new HashSet(); if (!string.IsNullOrWhiteSpace(charSet)) { Pattern = $"^[{charSet}]+$"; Delimiters = $"[^{charSet}]+"; } Separator = separator; } public override JObject ToJson(IProtectionService ps = null, bool forDisplay = true) { var jObject = CreateJObject(); var separator = Separator.ToString(); jObject["value"] = string.Join(separator, Values); if (forDisplay) { jObject["separator"] = separator; if (!string.IsNullOrWhiteSpace(Delimiters)) jObject["delimiters"] = Delimiters; if (!string.IsNullOrWhiteSpace(Pattern)) jObject["pattern"] = Pattern; if (Whitelist.Count > 0) jObject["whitelist"] = string.Join(separator, Whitelist); if (Blacklist.Count > 0) jObject["blacklist"] = string.Join(separator, Blacklist); } return jObject; } public override void FromJson(JToken jsonToken, IProtectionService ps) { var value = ReadValueAs(jsonToken); if (value == null) return; Values.Clear(); var tags = Regex.Split(value, !string.IsNullOrWhiteSpace(Delimiters) ? Delimiters : $"{Separator}+").Select(t => t.Trim().ToLowerInvariant()); if (!string.IsNullOrWhiteSpace(Pattern)) tags = tags.Where(t => Whitelist.Contains(t) || Regex.IsMatch(t, Pattern)); if (Blacklist.Count > 0) tags = tags.Where(t => !Blacklist.Contains(t)); foreach (var tag in tags) Values.Add(tag); } } } }