Add missing properties for inbound/global/outbound rules.
This commit is contained in:
Родитель
1b8e03d2c0
Коммит
0972e3cb53
|
@ -6,6 +6,7 @@ namespace Microsoft.IIS.Administration.WebServer.UrlRewrite
|
||||||
{
|
{
|
||||||
sealed class InboundRule : RuleElement {
|
sealed class InboundRule : RuleElement {
|
||||||
|
|
||||||
|
public const string RedirectTypeAttribute = "redirectType";
|
||||||
public const string ResponseCacheDirectiveAttribute = "responseCacheDirective";
|
public const string ResponseCacheDirectiveAttribute = "responseCacheDirective";
|
||||||
|
|
||||||
private InboundActionElement _action;
|
private InboundActionElement _action;
|
||||||
|
|
|
@ -4,12 +4,40 @@
|
||||||
|
|
||||||
namespace Microsoft.IIS.Administration.WebServer.UrlRewrite
|
namespace Microsoft.IIS.Administration.WebServer.UrlRewrite
|
||||||
{
|
{
|
||||||
|
using Microsoft.IIS.Administration.Core;
|
||||||
|
using System;
|
||||||
|
|
||||||
// Keep public for resolution of enums from 'dynamic' types in helper classes i.e. DynamicHelper
|
// Keep public for resolution of enums from 'dynamic' types in helper classes i.e. DynamicHelper
|
||||||
public enum LogicalGrouping {
|
public enum LogicalGrouping {
|
||||||
|
|
||||||
MatchAll = 0,
|
MatchAll = 0,
|
||||||
|
|
||||||
MatchAny = 1,
|
MatchAny = 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static class LogicalGroupingHelper
|
||||||
|
{
|
||||||
|
public static string ToJsonModel(LogicalGrouping logicalGrouping)
|
||||||
|
{
|
||||||
|
switch (logicalGrouping) {
|
||||||
|
case LogicalGrouping.MatchAll:
|
||||||
|
return "match_all";
|
||||||
|
case LogicalGrouping.MatchAny:
|
||||||
|
return "match_any";
|
||||||
|
default:
|
||||||
|
throw new ArgumentException(nameof(logicalGrouping));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LogicalGrouping FromJsonModel(string model)
|
||||||
|
{
|
||||||
|
switch (model.ToLowerInvariant()) {
|
||||||
|
case "match_all":
|
||||||
|
return LogicalGrouping.MatchAll;
|
||||||
|
case "match_any":
|
||||||
|
return LogicalGrouping.MatchAny;
|
||||||
|
default:
|
||||||
|
throw new ApiArgumentException("condition_match_constraints");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -174,15 +174,23 @@ namespace Microsoft.IIS.Administration.WebServer.UrlRewrite
|
||||||
//
|
//
|
||||||
// action
|
// action
|
||||||
if (fields.Exists("action")) {
|
if (fields.Exists("action")) {
|
||||||
obj.action = new {
|
obj.action = new ExpandoObject();
|
||||||
type = ActionTypeHelper.ToJsonModel(rule.Action.Type),
|
dynamic action = obj.action;
|
||||||
url = rule.Action.Url,
|
|
||||||
append_query_string = rule.Action.AppendQueryString,
|
action.type = ActionTypeHelper.ToJsonModel(rule.Action.Type);
|
||||||
status_code = rule.Action.StatusCode,
|
action.url = rule.Action.Url;
|
||||||
sub_status_code = rule.Action.SubStatusCode,
|
action.append_query_string = rule.Action.AppendQueryString;
|
||||||
description = rule.Action.StatusDescription,
|
|
||||||
reason = rule.Action.StatusReason
|
if (rule.Action.Type == ActionType.Redirect) {
|
||||||
};
|
action.redirect_type = Enum.GetName(typeof(RedirectType), rule.Action.RedirectType).ToLowerInvariant();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rule.Action.Type == ActionType.CustomResponse) {
|
||||||
|
action.status_code = rule.Action.StatusCode;
|
||||||
|
action.sub_status_code = rule.Action.SubStatusCode;
|
||||||
|
action.description = rule.Action.StatusDescription;
|
||||||
|
action.reason = rule.Action.StatusReason;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -195,6 +203,18 @@ namespace Microsoft.IIS.Administration.WebServer.UrlRewrite
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// condition_match_constraints
|
||||||
|
if (fields.Exists("condition_match_constraints")) {
|
||||||
|
obj.condition_match_constraints = LogicalGroupingHelper.ToJsonModel(rule.Conditions.LogicalGrouping);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// track_all_captures
|
||||||
|
if (fields.Exists("track_all_captures")) {
|
||||||
|
obj.track_all_captures = rule.Conditions.TrackAllCaptures;
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// conditions
|
// conditions
|
||||||
if (fields.Exists("conditions")) {
|
if (fields.Exists("conditions")) {
|
||||||
|
@ -381,6 +401,7 @@ namespace Microsoft.IIS.Administration.WebServer.UrlRewrite
|
||||||
DynamicHelper.If<long>((object)action.sub_status_code, v => rule.Action.SubStatusCode = v);
|
DynamicHelper.If<long>((object)action.sub_status_code, v => rule.Action.SubStatusCode = v);
|
||||||
DynamicHelper.If((object)action.description, v => rule.Action.StatusDescription = v);
|
DynamicHelper.If((object)action.description, v => rule.Action.StatusDescription = v);
|
||||||
DynamicHelper.If((object)action.reason, v => rule.Action.StatusReason = v);
|
DynamicHelper.If((object)action.reason, v => rule.Action.StatusReason = v);
|
||||||
|
DynamicHelper.If<RedirectType>((object)action.redirect_type, v => rule.Action.RedirectType = v);
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -421,6 +442,9 @@ namespace Microsoft.IIS.Administration.WebServer.UrlRewrite
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DynamicHelper.If((object)model.condition_match_constraints, v => rule.Conditions.LogicalGrouping = LogicalGroupingHelper.FromJsonModel(v));
|
||||||
|
DynamicHelper.If<bool>((object)model.track_all_captures, v => rule.Conditions.TrackAllCaptures = v);
|
||||||
|
|
||||||
//
|
//
|
||||||
// Conditions
|
// Conditions
|
||||||
if (model.conditions != null) {
|
if (model.conditions != null) {
|
||||||
|
|
|
@ -158,15 +158,24 @@ namespace Microsoft.IIS.Administration.WebServer.UrlRewrite
|
||||||
//
|
//
|
||||||
// action
|
// action
|
||||||
if (fields.Exists("action")) {
|
if (fields.Exists("action")) {
|
||||||
obj.action = new {
|
obj.action = new ExpandoObject();
|
||||||
type = ActionTypeHelper.ToJsonModel(rule.Action.Type),
|
dynamic action = obj.action;
|
||||||
url = rule.Action.Url,
|
|
||||||
append_query_string = rule.Action.AppendQueryString,
|
action.type = ActionTypeHelper.ToJsonModel(rule.Action.Type);
|
||||||
status_code = rule.Action.StatusCode,
|
action.url = rule.Action.Url;
|
||||||
sub_status_code = rule.Action.SubStatusCode,
|
action.append_query_string = rule.Action.AppendQueryString;
|
||||||
description = rule.Action.StatusDescription,
|
action.log_rewritten_url = rule.Action.LogRewrittenUrl;
|
||||||
reason = rule.Action.StatusReason
|
|
||||||
};
|
if (rule.Action.Type == ActionType.Redirect) {
|
||||||
|
action.redirect_type = Enum.GetName(typeof(RedirectType), rule.Action.RedirectType).ToLowerInvariant();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rule.Action.Type == ActionType.CustomResponse) {
|
||||||
|
action.status_code = rule.Action.StatusCode;
|
||||||
|
action.sub_status_code = rule.Action.SubStatusCode;
|
||||||
|
action.description = rule.Action.StatusDescription;
|
||||||
|
action.reason = rule.Action.StatusReason;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -179,6 +188,18 @@ namespace Microsoft.IIS.Administration.WebServer.UrlRewrite
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// condition_match_constraints
|
||||||
|
if (fields.Exists("condition_match_constraints")) {
|
||||||
|
obj.condition_match_constraints = LogicalGroupingHelper.ToJsonModel(rule.Conditions.LogicalGrouping);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// track_all_captures
|
||||||
|
if (fields.Exists("track_all_captures")) {
|
||||||
|
obj.track_all_captures = rule.Conditions.TrackAllCaptures;
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// conditions
|
// conditions
|
||||||
if (fields.Exists("conditions")) {
|
if (fields.Exists("conditions")) {
|
||||||
|
@ -397,10 +418,12 @@ namespace Microsoft.IIS.Administration.WebServer.UrlRewrite
|
||||||
DynamicHelper.If((object)action.type, v => rule.Action.Type = ActionTypeHelper.FromJsonModel(v));
|
DynamicHelper.If((object)action.type, v => rule.Action.Type = ActionTypeHelper.FromJsonModel(v));
|
||||||
DynamicHelper.If((object)action.url, v => rule.Action.Url = v);
|
DynamicHelper.If((object)action.url, v => rule.Action.Url = v);
|
||||||
DynamicHelper.If<bool>((object)action.append_query_string, v => rule.Action.AppendQueryString = v);
|
DynamicHelper.If<bool>((object)action.append_query_string, v => rule.Action.AppendQueryString = v);
|
||||||
|
DynamicHelper.If<bool>((object)action.log_rewritten_url, v => rule.Action.LogRewrittenUrl = v);
|
||||||
DynamicHelper.If<long>((object)action.status_code, v => rule.Action.StatusCode = v);
|
DynamicHelper.If<long>((object)action.status_code, v => rule.Action.StatusCode = v);
|
||||||
DynamicHelper.If<long>((object)action.sub_status_code, v => rule.Action.SubStatusCode = v);
|
DynamicHelper.If<long>((object)action.sub_status_code, v => rule.Action.SubStatusCode = v);
|
||||||
DynamicHelper.If((object)action.description, v => rule.Action.StatusDescription = v);
|
DynamicHelper.If((object)action.description, v => rule.Action.StatusDescription = v);
|
||||||
DynamicHelper.If((object)action.reason, v => rule.Action.StatusReason = v);
|
DynamicHelper.If((object)action.reason, v => rule.Action.StatusReason = v);
|
||||||
|
DynamicHelper.If<RedirectType>((object)action.redirect_type, v => rule.Action.RedirectType = v);
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -443,6 +466,9 @@ namespace Microsoft.IIS.Administration.WebServer.UrlRewrite
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DynamicHelper.If((object)model.condition_match_constraints, v => rule.Conditions.LogicalGrouping = LogicalGroupingHelper.FromJsonModel(v));
|
||||||
|
DynamicHelper.If<bool>((object)model.track_all_captures, v => rule.Conditions.TrackAllCaptures = v);
|
||||||
|
|
||||||
//
|
//
|
||||||
// Conditions
|
// Conditions
|
||||||
if (model.conditions != null) {
|
if (model.conditions != null) {
|
||||||
|
|
|
@ -345,6 +345,18 @@ namespace Microsoft.IIS.Administration.WebServer.UrlRewrite
|
||||||
obj.replace_server_variable = rule.Action.ReplaceServerVariable;
|
obj.replace_server_variable = rule.Action.ReplaceServerVariable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// condition_match_constraints
|
||||||
|
if (fields.Exists("condition_match_constraints")) {
|
||||||
|
obj.condition_match_constraints = LogicalGroupingHelper.ToJsonModel(rule.Conditions.LogicalGrouping);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// track_all_captures
|
||||||
|
if (fields.Exists("track_all_captures")) {
|
||||||
|
obj.track_all_captures = rule.Conditions.TrackAllCaptures;
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// conditions
|
// conditions
|
||||||
if (fields.Exists("conditions")) {
|
if (fields.Exists("conditions")) {
|
||||||
|
@ -771,6 +783,9 @@ namespace Microsoft.IIS.Administration.WebServer.UrlRewrite
|
||||||
rule.PreCondition = pc.Name;
|
rule.PreCondition = pc.Name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DynamicHelper.If((object)model.condition_match_constraints, v => rule.Conditions.LogicalGrouping = LogicalGroupingHelper.FromJsonModel(v));
|
||||||
|
DynamicHelper.If<bool>((object)model.track_all_captures, v => rule.Conditions.TrackAllCaptures = v);
|
||||||
|
|
||||||
//
|
//
|
||||||
// Conditions
|
// Conditions
|
||||||
if (model.conditions != null) {
|
if (model.conditions != null) {
|
||||||
|
|
|
@ -63,8 +63,7 @@ namespace Microsoft.IIS.Administration.Tests
|
||||||
type = "rewrite",
|
type = "rewrite",
|
||||||
url = "def.aspx?a={R:1}&c={R:2}",
|
url = "def.aspx?a={R:1}&c={R:2}",
|
||||||
append_query_string = true,
|
append_query_string = true,
|
||||||
description = "A test rule",
|
log_rewritten_url = true
|
||||||
reason = "Replace url"
|
|
||||||
},
|
},
|
||||||
server_variables = new object[] {
|
server_variables = new object[] {
|
||||||
new {
|
new {
|
||||||
|
@ -73,6 +72,8 @@ namespace Microsoft.IIS.Administration.Tests
|
||||||
replace = true
|
replace = true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
condition_match_constraints = "match_any",
|
||||||
|
track_all_captures = false,
|
||||||
conditions = new object[] {
|
conditions = new object[] {
|
||||||
new {
|
new {
|
||||||
input = "{REQUEST_FILENAME}",
|
input = "{REQUEST_FILENAME}",
|
||||||
|
@ -102,8 +103,8 @@ namespace Microsoft.IIS.Administration.Tests
|
||||||
type = "redirect",
|
type = "redirect",
|
||||||
url = "def.aspx",
|
url = "def.aspx",
|
||||||
append_query_string = false,
|
append_query_string = false,
|
||||||
description = "A test rule2",
|
log_rewritten_url = false,
|
||||||
reason = "Replace url2"
|
redirect_type = "found"
|
||||||
},
|
},
|
||||||
server_variables = new object[] {
|
server_variables = new object[] {
|
||||||
new {
|
new {
|
||||||
|
@ -112,6 +113,8 @@ namespace Microsoft.IIS.Administration.Tests
|
||||||
replace = false
|
replace = false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
condition_match_constraints = "match_all",
|
||||||
|
track_all_captures = true,
|
||||||
conditions = new object[] {
|
conditions = new object[] {
|
||||||
new {
|
new {
|
||||||
input = "{REQUEST_FILENAME}2",
|
input = "{REQUEST_FILENAME}2",
|
||||||
|
@ -168,9 +171,8 @@ namespace Microsoft.IIS.Administration.Tests
|
||||||
action = new {
|
action = new {
|
||||||
type = "rewrite",
|
type = "rewrite",
|
||||||
url = "def.aspx?a={R:1}&c={R:2}",
|
url = "def.aspx?a={R:1}&c={R:2}",
|
||||||
append_query_string = true,
|
append_query_string = true
|
||||||
description = "A test rule",
|
// log_rewritten_url not present in schema for global rule
|
||||||
reason = "Replace url"
|
|
||||||
},
|
},
|
||||||
server_variables = new object[] {
|
server_variables = new object[] {
|
||||||
new {
|
new {
|
||||||
|
@ -179,6 +181,8 @@ namespace Microsoft.IIS.Administration.Tests
|
||||||
replace = true
|
replace = true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
condition_match_constraints = "match_any",
|
||||||
|
track_all_captures = true,
|
||||||
conditions = new object[] {
|
conditions = new object[] {
|
||||||
new {
|
new {
|
||||||
input = "{REQUEST_FILENAME}",
|
input = "{REQUEST_FILENAME}",
|
||||||
|
@ -208,8 +212,8 @@ namespace Microsoft.IIS.Administration.Tests
|
||||||
type = "redirect",
|
type = "redirect",
|
||||||
url = "def.aspx",
|
url = "def.aspx",
|
||||||
append_query_string = false,
|
append_query_string = false,
|
||||||
description = "A test rule2",
|
redirect_type = "found"
|
||||||
reason = "Replace url2"
|
// log_rewritten_url not present in schema for global rule
|
||||||
},
|
},
|
||||||
server_variables = new object[] {
|
server_variables = new object[] {
|
||||||
new {
|
new {
|
||||||
|
@ -218,6 +222,8 @@ namespace Microsoft.IIS.Administration.Tests
|
||||||
replace = false
|
replace = false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
condition_match_constraints = "match_all",
|
||||||
|
track_all_captures = false,
|
||||||
conditions = new object[] {
|
conditions = new object[] {
|
||||||
new {
|
new {
|
||||||
input = "{REQUEST_FILENAME}2",
|
input = "{REQUEST_FILENAME}2",
|
||||||
|
@ -544,6 +550,8 @@ namespace Microsoft.IIS.Administration.Tests
|
||||||
negate = true,
|
negate = true,
|
||||||
stop_processing = false,
|
stop_processing = false,
|
||||||
rewrite_value = "test rewrite value",
|
rewrite_value = "test rewrite value",
|
||||||
|
condition_match_constraints = "match_any",
|
||||||
|
track_all_captures = true,
|
||||||
conditions = new object[] {
|
conditions = new object[] {
|
||||||
new {
|
new {
|
||||||
input = "{URL}",
|
input = "{URL}",
|
||||||
|
@ -575,6 +583,8 @@ namespace Microsoft.IIS.Administration.Tests
|
||||||
negate = false,
|
negate = false,
|
||||||
stop_processing = true,
|
stop_processing = true,
|
||||||
rewrite_value = "test rewrite update",
|
rewrite_value = "test rewrite update",
|
||||||
|
condition_match_constraints = "match_all",
|
||||||
|
track_all_captures = false,
|
||||||
conditions = new object[] {
|
conditions = new object[] {
|
||||||
new {
|
new {
|
||||||
input = "{CONTENT_TYPE}",
|
input = "{CONTENT_TYPE}",
|
||||||
|
@ -728,6 +738,8 @@ namespace Microsoft.IIS.Administration.Tests
|
||||||
Assert.Equal(a.Value<bool>("ignore_case"), b.Value<bool>("ignore_case"));
|
Assert.Equal(a.Value<bool>("ignore_case"), b.Value<bool>("ignore_case"));
|
||||||
Assert.Equal(a.Value<bool>("negate"), b.Value<bool>("negate"));
|
Assert.Equal(a.Value<bool>("negate"), b.Value<bool>("negate"));
|
||||||
Assert.Equal(a.Value<bool>("stop_processing"), b.Value<bool>("stop_processing"));
|
Assert.Equal(a.Value<bool>("stop_processing"), b.Value<bool>("stop_processing"));
|
||||||
|
Assert.Equal(a.Value<string>("condition_match_constraints"), b.Value<string>("condition_match_constraints"));
|
||||||
|
Assert.Equal(a.Value<bool>("track_all_captures"), b.Value<bool>("track_all_captures"));
|
||||||
|
|
||||||
JObject action = a.Value<JObject>("action");
|
JObject action = a.Value<JObject>("action");
|
||||||
JObject resultAction = b.Value<JObject>("action");
|
JObject resultAction = b.Value<JObject>("action");
|
||||||
|
@ -735,8 +747,22 @@ namespace Microsoft.IIS.Administration.Tests
|
||||||
Assert.Equal(action.Value<string>("type"), resultAction.Value<string>("type"));
|
Assert.Equal(action.Value<string>("type"), resultAction.Value<string>("type"));
|
||||||
Assert.Equal(action.Value<bool>("append_query_string"), resultAction.Value<bool>("append_query_string"));
|
Assert.Equal(action.Value<bool>("append_query_string"), resultAction.Value<bool>("append_query_string"));
|
||||||
Assert.Equal(action.Value<string>("url"), resultAction.Value<string>("url"));
|
Assert.Equal(action.Value<string>("url"), resultAction.Value<string>("url"));
|
||||||
Assert.Equal(action.Value<string>("description"), resultAction.Value<string>("description"));
|
Assert.Equal(action.Value<bool>("log_rewritten_url"), resultAction.Value<bool>("log_rewritten_url"));
|
||||||
Assert.Equal(action.Value<string>("reason"), resultAction.Value<string>("reason"));
|
|
||||||
|
//
|
||||||
|
// redirect rule
|
||||||
|
if (action.Value<string>("type").Equals("redirect", StringComparison.OrdinalIgnoreCase)) {
|
||||||
|
Assert.Equal(action.Value<string>("redirect_type"), resultAction.Value<string>("redirect_type"));
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// custom response rule
|
||||||
|
if (action.Value<string>("type").Equals("custom_response", StringComparison.OrdinalIgnoreCase)) {
|
||||||
|
Assert.Equal(action.Value<string>("description"), resultAction.Value<string>("description"));
|
||||||
|
Assert.Equal(action.Value<string>("reason"), resultAction.Value<string>("reason"));
|
||||||
|
Assert.Equal(action.Value<long>("status_code"), resultAction.Value<long>("status_code"));
|
||||||
|
Assert.Equal(action.Value<long>("sub_status_code"), resultAction.Value<long>("sub_status_code"));
|
||||||
|
}
|
||||||
|
|
||||||
JObject[] aServerVariables = a["server_variables"].ToObject<JObject[]>();
|
JObject[] aServerVariables = a["server_variables"].ToObject<JObject[]>();
|
||||||
JObject[] bServerVariables = b["server_variables"].ToObject<JObject[]>();
|
JObject[] bServerVariables = b["server_variables"].ToObject<JObject[]>();
|
||||||
|
@ -856,6 +882,8 @@ namespace Microsoft.IIS.Administration.Tests
|
||||||
Assert.Equal(a.Value<bool>("negate"), b.Value<bool>("negate"));
|
Assert.Equal(a.Value<bool>("negate"), b.Value<bool>("negate"));
|
||||||
Assert.Equal(a.Value<bool>("stop_processing"), b.Value<bool>("stop_processing"));
|
Assert.Equal(a.Value<bool>("stop_processing"), b.Value<bool>("stop_processing"));
|
||||||
Assert.Equal(a.Value<string>("rewrite_value"), b.Value<string>("rewrite_value"));
|
Assert.Equal(a.Value<string>("rewrite_value"), b.Value<string>("rewrite_value"));
|
||||||
|
Assert.Equal(a.Value<string>("condition_match_constraints"), b.Value<string>("condition_match_constraints"));
|
||||||
|
Assert.Equal(a.Value<bool>("track_all_captures"), b.Value<bool>("track_all_captures"));
|
||||||
|
|
||||||
//
|
//
|
||||||
// Conditions
|
// Conditions
|
||||||
|
|
Загрузка…
Ссылка в новой задаче