Beispiel #1
0
        public async Task <HttpResponseMessage> Download(string indexerID, string path, string jackett_apikey, string file)
        {
            try
            {
                var indexer = indexerService.GetWebIndexer(indexerID);

                if (!indexer.IsConfigured)
                {
                    logger.Warn(string.Format("Rejected a request to {0} which is unconfigured.", indexer.DisplayName));
                    return(Request.CreateResponse(HttpStatusCode.Forbidden, "This indexer is not configured."));
                }

                path = Encoding.UTF8.GetString(HttpServerUtility.UrlTokenDecode(path));
                path = protectionService.UnProtect(path);

                if (config.APIKey != jackett_apikey)
                {
                    return(new HttpResponseMessage(HttpStatusCode.Unauthorized));
                }

                var target        = new Uri(path, UriKind.RelativeOrAbsolute);
                var downloadBytes = await indexer.Download(target);

                // handle magnet URLs
                if (downloadBytes.Length >= 7 &&
                    downloadBytes[0] == 0x6d && // m
                    downloadBytes[1] == 0x61 && // a
                    downloadBytes[2] == 0x67 && // g
                    downloadBytes[3] == 0x6e && // n
                    downloadBytes[4] == 0x65 && // e
                    downloadBytes[5] == 0x74 && // t
                    downloadBytes[6] == 0x3a    // :
                    )
                {
                    var magneturi = Encoding.UTF8.GetString(downloadBytes);
                    var response  = Request.CreateResponse(HttpStatusCode.Moved);
                    response.Headers.Location = new Uri(magneturi);
                    return(response);
                }

                // This will fix torrents where the keys are not sorted, and thereby not supported by Sonarr.
                var    parser              = new BencodeParser();
                var    torrentDictionary   = parser.Parse(downloadBytes);
                byte[] sortedDownloadBytes = torrentDictionary.EncodeAsBytes();

                var result = new HttpResponseMessage(HttpStatusCode.OK);
                result.Content = new ByteArrayContent(sortedDownloadBytes);
                result.Content.Headers.ContentType        = new MediaTypeHeaderValue("application/x-bittorrent");
                result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
                {
                    FileName = StringUtil.MakeValidFileName(file, '_', false) + ".torrent" // call MakeValidFileName again to avoid any kind of injection attack
                };
                return(result);
            }
            catch (Exception e)
            {
                logger.Error(e, "Error downloading " + indexerID + " " + path);
                return(new HttpResponseMessage(HttpStatusCode.NotFound));
            }
        }
        public string ConvertToNormalLink(string link)
        {
            var encodedLink   = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(link));
            var decryptedLink = _protectionService.UnProtect(encodedLink);

            return(decryptedLink);
        }
Beispiel #3
0
        public void LoadValuesFromJson(JToken json, IProtectionService ps = null)
        {
            if (json == null)
            {
                return;
            }

            var arr = (JArray)json;

            foreach (var item in GetItems(forDisplay: false))
            {
                var arrItem = arr.FirstOrDefault(f => f.Value <string>("id") == item.ID);
                if (arrItem == null)
                {
                    continue;
                }

                switch (item.ItemType)
                {
                case ItemType.InputString:
                    var sItem    = (StringItem)item;
                    var newValue = arrItem.Value <string>("value");

                    if (string.Equals(item.Name, "password", StringComparison.InvariantCultureIgnoreCase))
                    {
                        if (newValue != PASSWORD_REPLACEMENT)
                        {
                            sItem.Value = newValue;
                            if (ps != null)
                            {
                                sItem.Value = ps.UnProtect(newValue);
                            }
                        }
                    }
                    else
                    {
                        sItem.Value = newValue;
                    }
                    break;

                case ItemType.HiddenData:
                    ((HiddenItem)item).Value = arrItem.Value <string>("value");
                    break;

                case ItemType.InputBool:
                    ((BoolItem)item).Value = arrItem.Value <bool>("value");
                    break;

                case ItemType.Recaptcha:
                    ((RecaptchaItem)item).Value     = arrItem.Value <string>("value");
                    ((RecaptchaItem)item).Cookie    = arrItem.Value <string>("cookie");
                    ((RecaptchaItem)item).Version   = arrItem.Value <string>("version");
                    ((RecaptchaItem)item).Challenge = arrItem.Value <string>("challenge");
                    break;
                }
            }
        }
Beispiel #4
0
        public async Task <IHttpActionResult> Blackhole(string indexerID, string path, string jackett_apikey, string file)
        {
            var jsonReply = new JObject();

            try
            {
                var indexer = indexerService.GetWebIndexer(indexerID);
                if (!indexer.IsConfigured)
                {
                    logger.Warn(string.Format("Rejected a request to {0} which is unconfigured.", indexer.DisplayName));
                    throw new Exception("This indexer is not configured.");
                }

                if (serverConfig.APIKey != jackett_apikey)
                {
                    throw new Exception("Incorrect API key");
                }

                path = Encoding.UTF8.GetString(HttpServerUtility.UrlTokenDecode(path));
                path = protectionService.UnProtect(path);
                var remoteFile    = new Uri(path, UriKind.RelativeOrAbsolute);
                var downloadBytes = await indexer.Download(remoteFile);

                if (string.IsNullOrWhiteSpace(serverConfig.BlackholeDir))
                {
                    throw new Exception("Blackhole directory not set!");
                }

                if (!Directory.Exists(serverConfig.BlackholeDir))
                {
                    throw new Exception("Blackhole directory does not exist: " + serverConfig.BlackholeDir);
                }

                var fileName = DateTime.Now.Ticks.ToString() + "-" + StringUtil.MakeValidFileName(indexer.DisplayName, '_', false);
                if (string.IsNullOrWhiteSpace(file))
                {
                    fileName += ".torrent";
                }
                else
                {
                    fileName += "-" + StringUtil.MakeValidFileName(file, '_', false); // call MakeValidFileName() again to avoid any possibility of path traversal attacks
                }
                File.WriteAllBytes(Path.Combine(serverConfig.BlackholeDir, fileName), downloadBytes);
                jsonReply["result"] = "success";
            }
            catch (Exception ex)
            {
                logger.Error(ex, "Error downloading to blackhole " + indexerID + " " + path);
                jsonReply["result"] = "error";
                jsonReply["error"]  = ex.Message;
            }

            return(Json(jsonReply));
        }
Beispiel #5
0
 public override void FromJson(JToken jsonToken, IProtectionService ps = null)
 {
     if (HasPasswordValue(this))
     {
         var pw = ReadValueAs <string>(jsonToken);
         if (pw != PASSWORD_REPLACEMENT)
         {
             Value = ps != null?ps.UnProtect(pw) : pw;
         }
     }
     else
     {
         Value = ReadValueAs <string>(jsonToken);
     }
 }
Beispiel #6
0
        public void LoadValuesFromJson(JToken json, IProtectionService ps= null)
        {
            if (json == null)
                return;

            var arr = (JArray)json;
            foreach (var item in GetItems(forDisplay: false))
            {
                var arrItem = arr.FirstOrDefault(f => f.Value<string>("id") == item.ID);
                if (arrItem == null)
                    continue;

                switch (item.ItemType)
                {
                    case ItemType.InputString:
                        var sItem = (StringItem)item;
                        var newValue = arrItem.Value<string>("value");

                        if (string.Equals(item.Name, "password", StringComparison.InvariantCultureIgnoreCase))
                        {
                            if (newValue != PASSWORD_REPLACEMENT)
                            {
                                sItem.Value = newValue;
                                if (ps != null)
                                    sItem.Value = ps.UnProtect(newValue);
                            }
                        } else
                        {
                            sItem.Value = newValue;
                        }
                        break;
                    case ItemType.HiddenData:
                        ((HiddenItem)item).Value = arrItem.Value<string>("value");
                        break;
                    case ItemType.InputBool:
                        ((BoolItem)item).Value = arrItem.Value<bool>("value");
                        break;
                    case ItemType.Recaptcha:
                        ((RecaptchaItem)item).Value = arrItem.Value<string>("value");
                        ((RecaptchaItem)item).Cookie = arrItem.Value<string>("cookie");
                        ((RecaptchaItem)item).Version = arrItem.Value<string>("version");
                        ((RecaptchaItem)item).Challenge = arrItem.Value<string>("challenge");
                        break;
                }
            }
        }
Beispiel #7
0
        public async Task <HttpResponseMessage> Download(string indexerID, string path, string jackett_apikey, string file)
        {
            try
            {
                var indexer = indexerService.GetWebIndexer(indexerID);

                if (!indexer.IsConfigured)
                {
                    logger.Warn(string.Format("Rejected a request to {0} which is unconfigured.", indexer.DisplayName));
                    return(Request.CreateResponse(HttpStatusCode.Forbidden, "This indexer is not configured."));
                }

                path = Encoding.UTF8.GetString(HttpServerUtility.UrlTokenDecode(path));
                path = protectionService.UnProtect(path);

                if (serverService.Config.APIKey != jackett_apikey)
                {
                    return(new HttpResponseMessage(HttpStatusCode.Unauthorized));
                }

                var target        = new Uri(path, UriKind.RelativeOrAbsolute);
                var downloadBytes = await indexer.Download(target);

                // This will fix torrents where the keys are not sorted, and thereby not supported by Sonarr.
                var torrentDictionary = BEncodedDictionary.DecodeTorrent(downloadBytes);
                downloadBytes = torrentDictionary.Encode();

                var result = new HttpResponseMessage(HttpStatusCode.OK);
                result.Content = new ByteArrayContent(downloadBytes);
                result.Content.Headers.ContentType        = new MediaTypeHeaderValue("application/x-bittorrent");
                result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
                {
                    FileName = StringUtil.MakeValidFileName(file, '_', false) // call MakeValidFileName again to avoid any kind of injection attack
                };
                return(result);
            }
            catch (Exception e)
            {
                logger.Error(e, "Error downloading " + indexerID + " " + path);
                return(new HttpResponseMessage(HttpStatusCode.NotFound));
            }
        }
Beispiel #8
0
        public async Task <IActionResult> DownloadImage(string indexerID, string path, string jackett_apikey, string file)
        {
            try
            {
                if (serverConfig.APIKey != jackett_apikey)
                {
                    return(Unauthorized());
                }

                var indexer = indexerService.GetWebIndexer(indexerID);
                if (!indexer.IsConfigured)
                {
                    logger.Warn($"Rejected a request to {indexer.DisplayName} which is unconfigured.");
                    return(Forbid("This indexer is not configured."));
                }

                path = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(path));
                path = protectionService.UnProtect(path);

                var target   = new Uri(path, UriKind.RelativeOrAbsolute);
                var response = await indexer.DownloadImage(target);

                if (response.Status != System.Net.HttpStatusCode.OK &&
                    response.Status != System.Net.HttpStatusCode.Continue &&
                    response.Status != System.Net.HttpStatusCode.PartialContent)
                {
                    return(new StatusCodeResult((int)response.Status));
                }

                var contentType = response.Headers.ContainsKey("content-type") ?
                                  response.Headers["content-type"].First() :
                                  "image/jpeg";
                return(File(response.ContentBytes, contentType));
            }
            catch (Exception e)
            {
                logger.Debug($"Error downloading image. indexer: {indexerID} path: {path}\n{e}");
                return(new StatusCodeResult((int)System.Net.HttpStatusCode.InternalServerError));
            }
        }
Beispiel #9
0
        //TODO: Remove this section once users have moved off DPAPI
        private bool MigratedFromDPAPI(JToken jsonConfig)
        {
            bool isWindows = Environment.OSVersion.Platform == PlatformID.Win32NT;

            if (!isWindows && DotNetCoreUtil.IsRunningOnDotNetCore)
            {
                // User isn't running Windows, but is running on .NET Core framework, no access to the DPAPI, so don't bother trying to migrate
                return(false);
            }

            LoadValuesFromJson(jsonConfig, false);

            StringItem passwordPropertyValue = null;
            string     passwordValue         = "";

            try
            {
                // try dynamic items first (e.g. all cardigann indexers)
                passwordPropertyValue = (StringItem)configData.GetDynamicByName("password");

                if (passwordPropertyValue == null) // if there's no dynamic password try the static property
                {
                    passwordPropertyValue = (StringItem)configData.GetType().GetProperty("Password").GetValue(configData, null);

                    // protection is based on the item.Name value (property name might be different, example: Abnormal), so check the Name again
                    if (!string.Equals(passwordPropertyValue.Name, "password", StringComparison.InvariantCultureIgnoreCase))
                    {
                        logger.Debug($"Skipping non default password property (unencrpyted password) for [{ID}] while attempting migration");
                        return(false);
                    }
                }

                passwordValue = passwordPropertyValue.Value;
            }
            catch (Exception)
            {
                logger.Debug($"Unable to source password for [{ID}] while attempting migration, likely a tracker without a password setting");
                return(false);
            }

            if (!string.IsNullOrEmpty(passwordValue))
            {
                try
                {
                    protectionService.UnProtect(passwordValue);
                    //Password successfully unprotected using Microsoft.AspNetCore.DataProtection, no further action needed as we've already converted the password previously
                    return(false);
                }
                catch (Exception ex)
                {
                    if (ex.Message != "The provided payload cannot be decrypted because it was not protected with this protection provider.")
                    {
                        logger.Info($"Password could not be unprotected using Microsoft.AspNetCore.DataProtection - {ID} : " + ex);
                    }

                    logger.Info($"Attempting legacy Unprotect - {ID} : ");

                    try
                    {
                        string unprotectedPassword = protectionService.LegacyUnProtect(passwordValue);
                        //Password successfully unprotected using Windows/Mono DPAPI

                        passwordPropertyValue.Value = unprotectedPassword;
                        SaveConfig();
                        IsConfigured = true;

                        logger.Info($"Password successfully migrated for {ID}");

                        return(true);
                    }
                    catch (Exception exception)
                    {
                        logger.Info($"Password could not be unprotected using legacy DPAPI - {ID} : " + exception);
                    }
                }
            }

            return(false);
        }
Beispiel #10
0
        public async Task <IActionResult> Blackhole(string indexerID, string path, string jackett_apikey, string file)
        {
            var jsonReply = new JObject();

            try
            {
                if (serverConfig.APIKey != jackett_apikey)
                {
                    return(Unauthorized());
                }

                var indexer = indexerService.GetWebIndexer(indexerID);
                if (!indexer.IsConfigured)
                {
                    logger.Warn(string.Format("Rejected a request to {0} which is unconfigured.", indexer.DisplayName));
                    throw new Exception("This indexer is not configured.");
                }

                path = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(path));
                path = protectionService.UnProtect(path);
                var remoteFile    = new Uri(path, UriKind.RelativeOrAbsolute);
                var fileExtension = ".torrent";

                byte[] downloadBytes;
                if (remoteFile.OriginalString.StartsWith("magnet"))
                {
                    downloadBytes = Encoding.UTF8.GetBytes(remoteFile.OriginalString);
                }
                else
                {
                    downloadBytes = await indexer.Download(remoteFile);
                }

                // handle magnet URLs
                if (downloadBytes.Length >= 7 &&
                    downloadBytes[0] == 0x6d && // m
                    downloadBytes[1] == 0x61 && // a
                    downloadBytes[2] == 0x67 && // g
                    downloadBytes[3] == 0x6e && // n
                    downloadBytes[4] == 0x65 && // e
                    downloadBytes[5] == 0x74 && // t
                    downloadBytes[6] == 0x3a    // :
                    )
                {
                    fileExtension = ".magnet";
                }

                if (string.IsNullOrWhiteSpace(serverConfig.BlackholeDir))
                {
                    throw new Exception("Blackhole directory not set!");
                }

                if (!Directory.Exists(serverConfig.BlackholeDir))
                {
                    throw new Exception("Blackhole directory does not exist: " + serverConfig.BlackholeDir);
                }

                var fileName = DateTime.Now.Ticks.ToString() + "-" + StringUtil.MakeValidFileName(indexer.DisplayName, '_', false);
                if (string.IsNullOrWhiteSpace(file))
                {
                    fileName += fileExtension;
                }
                else
                {
                    fileName += "-" + StringUtil.MakeValidFileName(file + fileExtension, '_', false); // call MakeValidFileName() again to avoid any possibility of path traversal attacks
                }
                System.IO.File.WriteAllBytes(Path.Combine(serverConfig.BlackholeDir, fileName), downloadBytes);
                jsonReply["result"] = "success";
            }
            catch (Exception ex)
            {
                logger.Error(ex, "Error downloading to blackhole " + indexerID + " " + path);
                jsonReply["result"] = "error";
                jsonReply["error"]  = ex.Message;
            }

            return(Json(jsonReply));
        }
Beispiel #11
0
        //TODO: Remove this section once users have moved off DPAPI
        private bool MigratedFromDPAPI(JToken jsonConfig)
        {
            if (EnvironmentUtil.IsRunningLegacyOwin)
            {
                //Still running legacy Owin and using the DPAPI, we don't want to migrate
                logger.Debug(ID + " - Running Owin, no need to migrate from DPAPI");
                return(false);
            }

            Version dotNetVersion = Microsoft.Extensions.PlatformAbstractions.PlatformServices.Default.Application.RuntimeFramework.Version;
            bool    isWindows     = Environment.OSVersion.Platform == PlatformID.Win32NT;

            if (!isWindows && dotNetVersion.Major < 4)
            {
                // User isn't running Windows, but is running on .NET Core framework, no access to the DPAPI, so don't bother trying to migrate
                return(false);
            }

            LoadValuesFromJson(jsonConfig, false);

            object passwordPropertyValue = null;
            string passwordValue         = "";

            try
            {
                passwordPropertyValue = configData.GetType().GetProperty("Password").GetValue(configData, null);
                passwordValue         = passwordPropertyValue.GetType().GetProperty("Value").GetValue(passwordPropertyValue, null).ToString();
            }
            catch (Exception)
            {
                logger.Debug($"Unable to source password for [{ID}] while attempting migration, likely a public tracker");
                return(false);
            }

            if (!string.IsNullOrEmpty(passwordValue))
            {
                try
                {
                    protectionService.UnProtect(passwordValue);
                    //Password successfully unprotected using Microsoft.AspNetCore.DataProtection, no further action needed as we've already converted the password previously
                    return(false);
                }
                catch (Exception ex)
                {
                    if (ex.Message != "The provided payload cannot be decrypted because it was not protected with this protection provider.")
                    {
                        logger.Info($"Password could not be unprotected using Microsoft.AspNetCore.DataProtection - {ID} : " + ex);
                    }

                    logger.Info($"Attempting legacy Unprotect - {ID} : ");

                    try
                    {
                        string unprotectedPassword = protectionService.LegacyUnProtect(passwordValue);
                        //Password successfully unprotected using Windows/Mono DPAPI

                        passwordPropertyValue.GetType().GetProperty("Value").SetValue(passwordPropertyValue, unprotectedPassword);
                        SaveConfig();
                        IsConfigured = true;

                        logger.Info($"Password successfully migrated for {ID}");

                        return(true);
                    }
                    catch (Exception exception)
                    {
                        logger.Info($"Password could not be unprotected using legacy DPAPI - {ID} : " + exception);
                    }
                }
            }

            return(false);
        }
Beispiel #12
0
        public void LoadValuesFromJson(JToken json, IProtectionService ps = null)
        {
            if (json == null)
            {
                return;
            }

            var arr = (JArray)json;

            // transistion from alternatelink to sitelink
            var alternatelinkItem = arr.FirstOrDefault(f => f.Value <string>("id") == "alternatelink");

            if (alternatelinkItem != null && !string.IsNullOrEmpty(alternatelinkItem.Value <string>("value")))
            {
                //SiteLink.Value = alternatelinkItem.Value<string>("value");
            }

            foreach (var item in GetItems(forDisplay: false))
            {
                var arrItem = arr.FirstOrDefault(f => f.Value <string>("id") == item.ID);
                if (arrItem == null)
                {
                    continue;
                }

                switch (item.ItemType)
                {
                case ItemType.InputString:
                    var sItem    = (StringItem)item;
                    var newValue = arrItem.Value <string>("value");

                    if (string.Equals(item.Name, "password", StringComparison.InvariantCultureIgnoreCase))
                    {
                        if (newValue != PASSWORD_REPLACEMENT)
                        {
                            sItem.Value = newValue;
                            if (ps != null)
                            {
                                sItem.Value = ps.UnProtect(newValue);
                            }
                        }
                    }
                    else
                    {
                        sItem.Value = newValue;
                    }
                    break;

                case ItemType.HiddenData:
                    ((HiddenItem)item).Value = arrItem.Value <string>("value");
                    break;

                case ItemType.InputBool:
                    ((BoolItem)item).Value = arrItem.Value <bool>("value");
                    break;

                case ItemType.InputCheckbox:
                    var values = arrItem.Value <JArray>("values");
                    if (values != null)
                    {
                        ((CheckboxItem)item).Values = values.Values <string>().ToArray();
                    }
                    break;

                case ItemType.InputSelect:
                    ((SelectItem)item).Value = arrItem.Value <string>("value");
                    break;

                case ItemType.Recaptcha:
                    ((RecaptchaItem)item).Value     = arrItem.Value <string>("value");
                    ((RecaptchaItem)item).Cookie    = arrItem.Value <string>("cookie");
                    ((RecaptchaItem)item).Version   = arrItem.Value <string>("version");
                    ((RecaptchaItem)item).Challenge = arrItem.Value <string>("challenge");
                    break;
                }
            }
        }
Beispiel #13
0
        //TODO: Remove this section once users have moved off DPAPI
        private bool MigratedFromDPAPI(JToken jsonConfig)
        {
            var  currentAssembly   = Assembly.GetExecutingAssembly();
            bool runningLegacyOwin = new StackTrace().GetFrames()
                                     .Select(x => x.GetMethod().ReflectedType.Assembly).Distinct()
                                     .Where(x => x.GetReferencedAssemblies().Any(y => y.FullName == currentAssembly.FullName))
                                     .Where(x => x.ManifestModule.Name == "Jackett.dll" || x.ManifestModule.Name == "JackettConsole.exe")
                                     .Count() == 2;

            if (runningLegacyOwin)
            {
                //Still running legacy Owin and using the DPAPI, we don't want to migrate
                logger.Debug(ID + " - Running Owin, no need to migrate from DPAPI");
                return(false);
            }

            Version dotNetVersion = Microsoft.Extensions.PlatformAbstractions.PlatformServices.Default.Application.RuntimeFramework.Version;
            bool    isWindows     = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);

            if (!isWindows && dotNetVersion.Major < 4)
            {
                // User isn't running Windows, but is running on .NET Core framewrok, no access to the DPAPI, so don't bother trying to migrate
                return(false);
            }

            LoadValuesFromJson(jsonConfig, false);

            object passwordPropertyValue = null;
            string passwordValue         = "";

            try
            {
                passwordPropertyValue = configData.GetType().GetProperty("Password").GetValue(configData, null);
                passwordValue         = passwordPropertyValue.GetType().GetProperty("Value").GetValue(passwordPropertyValue, null).ToString();
            }
            catch (Exception ex)
            {
                logger.Warn($"Attempt to source password from json failed - {ID} : " + ex.ToString());
                return(false);
            }

            if (!string.IsNullOrEmpty(passwordValue))
            {
                try
                {
                    protectionService.UnProtect(passwordValue);
                    //Password successfully unprotected using Microsoft.AspNetCore.DataProtection, no further action needed as we've already converted the password previously
                    return(false);
                }
                catch (Exception ex)
                {
                    if (ex.Message != "The provided payload cannot be decrypted because it was not protected with this protection provider.")
                    {
                        logger.Info($"Password could not be unprotected using Microsoft.AspNetCore.DataProtection - {ID} : " + ex.ToString());
                    }

                    logger.Info($"Attempting legacy Unprotect - {ID} : ");

                    try
                    {
                        string unprotectedPassword = protectionService.LegacyUnProtect(passwordValue);
                        //Password successfully unprotected using Windows/Mono DPAPI

                        passwordPropertyValue.GetType().GetProperty("Value").SetValue(passwordPropertyValue, unprotectedPassword);
                        SaveConfig();
                        IsConfigured = true;

                        logger.Info($"Password successfully migrated for {ID}");

                        return(true);
                    }
                    catch (Exception exception)
                    {
                        logger.Info($"Password could not be unprotected using legacy DPAPI - {ID} : " + exception.ToString());
                    }
                }
            }

            return(false);
        }
        public async Task <IActionResult> Download(string indexerID, string path, string jackett_apikey, string file)
        {
            try
            {
                if (serverConfig.APIKey != jackett_apikey)
                {
                    return(Unauthorized());
                }

                var indexer = indexerService.GetWebIndexer(indexerID);

                if (!indexer.IsConfigured)
                {
                    logger.Warn(string.Format("Rejected a request to {0} which is unconfigured.", indexer.DisplayName));
                    return(Forbid("This indexer is not configured."));
                }

                path = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(path));
                path = protectionService.UnProtect(path);

                var target        = new Uri(path, UriKind.RelativeOrAbsolute);
                var downloadBytes = await indexer.Download(target);

                // handle magnet URLs
                if (downloadBytes.Length >= 7 &&
                    downloadBytes[0] == 0x6d && // m
                    downloadBytes[1] == 0x61 && // a
                    downloadBytes[2] == 0x67 && // g
                    downloadBytes[3] == 0x6e && // n
                    downloadBytes[4] == 0x65 && // e
                    downloadBytes[5] == 0x74 && // t
                    downloadBytes[6] == 0x3a    // :
                    )
                {
                    // some sites provide magnet links with non-ascii characters, the only way to be sure the url
                    // is well encoded is to unscape and escape again
                    // https://github.com/Jackett/Jackett/issues/5372
                    // https://github.com/Jackett/Jackett/issues/4761
                    var magneturi = Uri.EscapeUriString(Uri.UnescapeDataString(Encoding.UTF8.GetString(downloadBytes)));
                    return(Redirect(magneturi));
                }

                // This will fix torrents where the keys are not sorted, and thereby not supported by Sonarr.
                byte[] sortedDownloadBytes = null;
                try
                {
                    var parser            = new BencodeParser();
                    var torrentDictionary = parser.Parse(downloadBytes);
                    sortedDownloadBytes = torrentDictionary.EncodeAsBytes();
                }
                catch (Exception e)
                {
                    var content = indexer.Encoding.GetString(downloadBytes);
                    logger.Error(content);
                    throw new Exception("BencodeParser failed", e);
                }

                var fileName = StringUtil.MakeValidFileName(file, '_', false) + ".torrent"; // call MakeValidFileName again to avoid any kind of injection attack

                return(File(sortedDownloadBytes, "application/x-bittorrent", fileName));
            }
            catch (Exception e)
            {
                logger.Error(e, "Error downloading " + indexerID + " " + path);
                return(NotFound());
            }
        }
        public async Task <IActionResult> Download(string indexerID, string path, string jackett_apikey, string file)
        {
            try
            {
                if (serverConfig.APIKey != jackett_apikey)
                {
                    return(Unauthorized());
                }

                var indexer = indexerService.GetWebIndexer(indexerID);

                if (!indexer.IsConfigured)
                {
                    logger.Warn($"Rejected a request to {indexer.DisplayName} which is unconfigured.");
                    return(Forbid("This indexer is not configured."));
                }

                path = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(path));
                path = protectionService.UnProtect(path);

                var target        = new Uri(path, UriKind.RelativeOrAbsolute);
                var downloadBytes = await indexer.Download(target);

                // handle magnet URLs
                if (downloadBytes.Length >= 7 &&
                    downloadBytes[0] == 0x6d && // m
                    downloadBytes[1] == 0x61 && // a
                    downloadBytes[2] == 0x67 && // g
                    downloadBytes[3] == 0x6e && // n
                    downloadBytes[4] == 0x65 && // e
                    downloadBytes[5] == 0x74 && // t
                    downloadBytes[6] == 0x3a    // :
                    )
                {
                    var magnetUrl = Encoding.UTF8.GetString(downloadBytes);
                    return(Redirect(magnetUrl));
                }

                // This will fix torrents where the keys are not sorted, and thereby not supported by Sonarr.
                byte[] sortedDownloadBytes = null;
                try
                {
                    var parser            = new BencodeParser();
                    var torrentDictionary = parser.Parse(downloadBytes);
                    sortedDownloadBytes = torrentDictionary.EncodeAsBytes();
                }
                catch (Exception e)
                {
                    var content = indexer.Encoding.GetString(downloadBytes);
                    logger.Error(content);
                    throw new Exception("BencodeParser failed", e);
                }

                var fileName = StringUtil.MakeValidFileName(file, '_', false) + ".torrent"; // call MakeValidFileName again to avoid any kind of injection attack

                return(File(sortedDownloadBytes, "application/x-bittorrent", fileName));
            }
            catch (Exception e)
            {
                logger.Error($"Error downloading. indexer: {indexerID} path: {path}\n{e}");
                return(NotFound());
            }
        }
Beispiel #16
0
        public void LoadValuesFromJson(JToken json, IProtectionService ps = null)
        {
            if (json == null)
            {
                return;
            }
            var arr = (JArray)json;

            foreach (var item in GetItems(false))
            {
                var arrItem = arr.FirstOrDefault(f => f.Value <string>("id") == item.ID);
                if (arrItem == null)
                {
                    continue;
                }
                switch (item)
                {
                case HiddenItem hiddenItem:
                    hiddenItem.Value = arrItem.Value <string>("value");
                    break;

                case BoolItem boolItem:
                    boolItem.Value = arrItem.Value <bool>("value");
                    break;

                case CheckboxItem checkboxItem:
                    var values = arrItem.Value <JArray>("values");
                    if (values != null)
                    {
                        checkboxItem.Values = values.Values <string>().ToArray();
                    }
                    break;

                case SelectItem selectItem:
                    selectItem.Value = arrItem.Value <string>("value");
                    break;

                case RecaptchaItem recaptcha:
                    recaptcha.Value     = arrItem.Value <string>("value");
                    recaptcha.Cookie    = arrItem.Value <string>("cookie");
                    recaptcha.Version   = arrItem.Value <string>("version");
                    recaptcha.Challenge = arrItem.Value <string>("challenge");
                    break;

                case StringItem sItem:
                    var newValue = arrItem.Value <string>("value");
                    if (string.Equals(item.Name, "password", StringComparison.InvariantCultureIgnoreCase))
                    {
                        if (newValue != _PasswordReplacement)
                        {
                            sItem.Value = newValue;
                            if (ps != null)
                            {
                                sItem.Value = ps.UnProtect(newValue);
                            }
                        }
                    }
                    else
                    {
                        sItem.Value = newValue;
                    }

                    break;
                }
            }
        }
Beispiel #17
0
        /// <summary>
        /// Loads all JSON values into the matching properties.
        /// </summary>
        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 <string>("id") == item.ID);
                if (jsonToken == null)
                {
                    continue;
                }

                switch (item)
                {
                case StringConfigurationItem stringItem:
                {
                    if (HasPasswordValue(item))
                    {
                        var pw = ReadValueAs <string>(jsonToken);
                        if (pw != PASSWORD_REPLACEMENT)
                        {
                            stringItem.Value = ps != null?ps.UnProtect(pw) : pw;
                        }
                    }
                    else
                    {
                        stringItem.Value = ReadValueAs <string>(jsonToken);
                    }
                    break;
                }

                case HiddenStringConfigurationItem hiddenStringItem:
                {
                    hiddenStringItem.Value = ReadValueAs <string>(jsonToken);
                    break;
                }

                case BoolConfigurationItem boolItem:
                {
                    boolItem.Value = ReadValueAs <bool>(jsonToken);
                    break;
                }

                case SingleSelectConfigurationItem singleSelectItem:
                {
                    singleSelectItem.Value = ReadValueAs <string>(jsonToken);
                    break;
                }

                case MultiSelectConfigurationItem multiSelectItem:
                {
                    var values = jsonToken.Value <JArray>("values");
                    if (values != null)
                    {
                        multiSelectItem.Values = values.Values <string>().ToArray();
                    }
                    break;
                }

                case PasswordConfigurationItem passwordItem:
                {
                    var pw = ReadValueAs <string>(jsonToken);
                    if (pw != PASSWORD_REPLACEMENT)
                    {
                        passwordItem.Value = ps != null?ps.UnProtect(pw) : pw;
                    }
                    break;
                }
                }
            }
        }