mirror of https://github.com/apache/cloudstack.git
CLOUDSTACK-7234: stop sending smb credentials to Hyper-v agent
This commit is contained in:
parent
97f5281cf4
commit
0ecc9f79af
|
|
@ -129,23 +129,23 @@ public class StoragePoolVO implements StoragePool {
|
|||
}
|
||||
|
||||
public StoragePoolVO() {
|
||||
this.status = StoragePoolStatus.Initial;
|
||||
status = StoragePoolStatus.Initial;
|
||||
}
|
||||
|
||||
public StoragePoolVO(long poolId, String name, String uuid, StoragePoolType type, long dataCenterId, Long podId, long availableBytes, long capacityBytes,
|
||||
String hostAddress, int port, String hostPath) {
|
||||
this.name = name;
|
||||
this.id = poolId;
|
||||
id = poolId;
|
||||
this.uuid = uuid;
|
||||
this.poolType = type;
|
||||
poolType = type;
|
||||
this.dataCenterId = dataCenterId;
|
||||
this.usedBytes = availableBytes;
|
||||
usedBytes = availableBytes;
|
||||
this.capacityBytes = capacityBytes;
|
||||
this.hostAddress = hostAddress;
|
||||
this.port = port;
|
||||
this.podId = podId;
|
||||
this.setStatus(StoragePoolStatus.Initial);
|
||||
this.setPath(hostPath);
|
||||
setStatus(StoragePoolStatus.Initial);
|
||||
setPath(hostPath);
|
||||
}
|
||||
|
||||
public StoragePoolVO(StoragePoolVO that) {
|
||||
|
|
@ -153,12 +153,12 @@ public class StoragePoolVO implements StoragePool {
|
|||
}
|
||||
|
||||
public StoragePoolVO(StoragePoolType type, String hostAddress, int port, String path) {
|
||||
this.poolType = type;
|
||||
poolType = type;
|
||||
this.hostAddress = hostAddress;
|
||||
this.port = port;
|
||||
this.setStatus(StoragePoolStatus.Initial);
|
||||
this.uuid = UUID.randomUUID().toString();
|
||||
this.setPath(path);
|
||||
setStatus(StoragePoolStatus.Initial);
|
||||
uuid = UUID.randomUUID().toString();
|
||||
setPath(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -177,7 +177,7 @@ public class StoragePoolVO implements StoragePool {
|
|||
}
|
||||
|
||||
public void setPoolType(StoragePoolType protocol) {
|
||||
this.poolType = protocol;
|
||||
poolType = protocol;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -258,14 +258,17 @@ public class StoragePoolVO implements StoragePool {
|
|||
}
|
||||
|
||||
public void setHostAddress(String host) {
|
||||
this.hostAddress = host;
|
||||
hostAddress = host;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPath() {
|
||||
String updatedPath = path;
|
||||
if (this.poolType == StoragePoolType.SMB) {
|
||||
if (poolType == StoragePoolType.SMB) {
|
||||
updatedPath = UriUtils.getUpdateUri(updatedPath, false);
|
||||
if (updatedPath.contains("password") && updatedPath.contains("?")) {
|
||||
updatedPath = updatedPath.substring(0, updatedPath.indexOf('?'));
|
||||
}
|
||||
}
|
||||
|
||||
return updatedPath;
|
||||
|
|
@ -285,7 +288,7 @@ public class StoragePoolVO implements StoragePool {
|
|||
}
|
||||
|
||||
public void setDataCenterId(long dcId) {
|
||||
this.dataCenterId = dcId;
|
||||
dataCenterId = dcId;
|
||||
}
|
||||
|
||||
public void setPodId(Long podId) {
|
||||
|
|
@ -327,9 +330,10 @@ public class StoragePoolVO implements StoragePool {
|
|||
}
|
||||
|
||||
public ScopeType getScope() {
|
||||
return this.scope;
|
||||
return scope;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HypervisorType getHypervisor() {
|
||||
return hypervisor;
|
||||
}
|
||||
|
|
@ -344,7 +348,7 @@ public class StoragePoolVO implements StoragePool {
|
|||
return false;
|
||||
}
|
||||
StoragePoolVO that = (StoragePoolVO)obj;
|
||||
return this.id == that.id;
|
||||
return id == that.id;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -359,7 +363,7 @@ public class StoragePoolVO implements StoragePool {
|
|||
|
||||
@Override
|
||||
public boolean isShared() {
|
||||
return this.scope == ScopeType.HOST ? false : true;
|
||||
return scope == ScopeType.HOST ? false : true;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
|
|
@ -69,52 +69,6 @@ namespace HypervResource
|
|||
}
|
||||
}
|
||||
|
||||
public string User
|
||||
{
|
||||
get
|
||||
{
|
||||
string user = null;
|
||||
if (uri != null)
|
||||
{
|
||||
var queryDictionary = System.Web.HttpUtility.ParseQueryString(uri.Query);
|
||||
user = System.Web.HttpUtility.UrlDecode(queryDictionary["user"]);
|
||||
}
|
||||
return user;
|
||||
}
|
||||
}
|
||||
|
||||
public string Password
|
||||
{
|
||||
get
|
||||
{
|
||||
string password = null;
|
||||
if (uri != null)
|
||||
{
|
||||
var queryDictionary = System.Web.HttpUtility.ParseQueryString(uri.Query);
|
||||
password = System.Web.HttpUtility.UrlDecode(queryDictionary["password"]);
|
||||
}
|
||||
return password;
|
||||
}
|
||||
}
|
||||
|
||||
public string Domain
|
||||
{
|
||||
get
|
||||
{
|
||||
string domain = null;
|
||||
if (uri != null)
|
||||
{
|
||||
var queryDictionary = System.Web.HttpUtility.ParseQueryString(uri.Query);
|
||||
if (queryDictionary["domain"] != null)
|
||||
{
|
||||
domain = System.Web.HttpUtility.UrlDecode(queryDictionary["domain"]);
|
||||
}
|
||||
else domain = uri.Host;
|
||||
}
|
||||
return domain;
|
||||
}
|
||||
}
|
||||
|
||||
public Boolean isLocal
|
||||
{
|
||||
get
|
||||
|
|
@ -457,36 +411,7 @@ namespace HypervResource
|
|||
return uncPath;
|
||||
}
|
||||
}
|
||||
public string User
|
||||
{
|
||||
get
|
||||
{
|
||||
var queryDictionary = System.Web.HttpUtility.ParseQueryString(uri.Query);
|
||||
return System.Web.HttpUtility.UrlDecode(queryDictionary["user"]);
|
||||
}
|
||||
}
|
||||
|
||||
public string Password
|
||||
{
|
||||
get
|
||||
{
|
||||
var queryDictionary = System.Web.HttpUtility.ParseQueryString(uri.Query);
|
||||
return System.Web.HttpUtility.UrlDecode(queryDictionary["password"]);
|
||||
}
|
||||
}
|
||||
|
||||
public string Domain
|
||||
{
|
||||
get
|
||||
{
|
||||
var queryDictionary = System.Web.HttpUtility.ParseQueryString(uri.Query);
|
||||
if (queryDictionary["domain"] != null)
|
||||
{
|
||||
return System.Web.HttpUtility.UrlDecode(queryDictionary["domain"]);
|
||||
}
|
||||
else return uri.Host;
|
||||
}
|
||||
}
|
||||
public static NFSTO ParseJson(dynamic json)
|
||||
{
|
||||
NFSTO result = null;
|
||||
|
|
|
|||
|
|
@ -84,48 +84,31 @@ namespace HypervResource
|
|||
/// <param name="destFile"></param>
|
||||
public static void DownloadCifsFileToLocalFile(string filePathRelativeToShare, NFSTO cifsShareDetails, string destFile)
|
||||
{
|
||||
try
|
||||
String dest = "";
|
||||
if (filePathRelativeToShare.EndsWith(".iso") || filePathRelativeToShare.EndsWith(".vhd") || filePathRelativeToShare.EndsWith(".vhdx"))
|
||||
{
|
||||
IntPtr token = IntPtr.Zero;
|
||||
|
||||
LogonUser(cifsShareDetails.User, cifsShareDetails.Domain, cifsShareDetails.Password, LOGON32_LOGON_NEW_CREDENTIALS, LOGON32_PROVIDER_DEFAULT, ref token);
|
||||
using (WindowsImpersonationContext remoteIdentity = new WindowsIdentity(token).Impersonate())
|
||||
dest = Path.Combine(cifsShareDetails.UncPath, filePathRelativeToShare);
|
||||
dest = Utils.NormalizePath(dest);
|
||||
}
|
||||
// if the filePathRelativeToShare string don't have filename and only a dir point then find the vhd files in that folder and use
|
||||
// In the clean setup, first copy command wont be having the filename it contains onlyu dir path.
|
||||
// we need to scan the folder point and then copy the file to destination.
|
||||
else if (!filePathRelativeToShare.EndsWith(".vhd") || !filePathRelativeToShare.EndsWith(".vhdx"))
|
||||
{
|
||||
// scan the folder and get the vhd filename.
|
||||
String uncPath = Path.Combine(cifsShareDetails.UncPath, Path.Combine(filePathRelativeToShare.Split('/')));
|
||||
//uncPath = uncPath.Replace("/", "\\");
|
||||
DirectoryInfo dir = new DirectoryInfo(uncPath);
|
||||
FileInfo[] vhdFiles = dir.GetFiles("*.vhd*");
|
||||
if (vhdFiles.Length > 0)
|
||||
{
|
||||
String dest = "";
|
||||
if (filePathRelativeToShare.EndsWith(".iso") || filePathRelativeToShare.EndsWith(".vhd") || filePathRelativeToShare.EndsWith(".vhdx"))
|
||||
{
|
||||
dest = Path.Combine(cifsShareDetails.UncPath, filePathRelativeToShare);
|
||||
dest = Utils.NormalizePath(dest);
|
||||
}
|
||||
// if the filePathRelativeToShare string don't have filename and only a dir point then find the vhd files in that folder and use
|
||||
// In the clean setup, first copy command wont be having the filename it contains onlyu dir path.
|
||||
// we need to scan the folder point and then copy the file to destination.
|
||||
else if (!filePathRelativeToShare.EndsWith(".vhd") || !filePathRelativeToShare.EndsWith(".vhdx"))
|
||||
{
|
||||
// scan the folder and get the vhd filename.
|
||||
String uncPath = Path.Combine(cifsShareDetails.UncPath, Path.Combine(filePathRelativeToShare.Split('/')));
|
||||
//uncPath = uncPath.Replace("/", "\\");
|
||||
DirectoryInfo dir = new DirectoryInfo(uncPath);
|
||||
FileInfo[] vhdFiles = dir.GetFiles("*.vhd*");
|
||||
if (vhdFiles.Length > 0)
|
||||
{
|
||||
FileInfo file = vhdFiles[0];
|
||||
dest = file.FullName;
|
||||
}
|
||||
}
|
||||
|
||||
s_logger.Info(CloudStackTypes.CopyCommand + ": copy " + Path.Combine(cifsShareDetails.UncPath, filePathRelativeToShare) + " to " + destFile);
|
||||
File.Copy(dest, destFile, true);
|
||||
remoteIdentity.Undo();
|
||||
FileInfo file = vhdFiles[0];
|
||||
dest = file.FullName;
|
||||
}
|
||||
}
|
||||
catch (UnauthorizedAccessException ex)
|
||||
{
|
||||
string errMsg = "Invalid user or password for the share " + cifsShareDetails.UncPath;
|
||||
s_logger.Error(errMsg);
|
||||
|
||||
throw new ArgumentException(errMsg, ex);
|
||||
}
|
||||
s_logger.Info(CloudStackTypes.CopyCommand + ": copy " + Path.Combine(cifsShareDetails.UncPath, filePathRelativeToShare) + " to " + destFile);
|
||||
File.Copy(dest, destFile, true);
|
||||
}
|
||||
|
||||
public static void GetShareDetails(string remoteUNC, out long capacity, out long available)
|
||||
|
|
@ -193,142 +176,11 @@ namespace HypervResource
|
|||
|
||||
// from http://stackoverflow.com/a/2541569/939250
|
||||
#region imports
|
||||
[DllImport("advapi32.dll", SetLastError = true)]
|
||||
private static extern bool LogonUser(string lpszUsername, string lpszDomain, string lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken);
|
||||
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
||||
private static extern bool CloseHandle(IntPtr handle);
|
||||
|
||||
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
||||
public extern static bool DuplicateToken(IntPtr existingTokenHandle, int SECURITY_IMPERSONATION_LEVEL, ref IntPtr duplicateTokenHandle);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
|
||||
private static extern bool GetDiskFreeSpaceEx(string lpDirectoryName,
|
||||
out ulong lpFreeBytesAvailable,
|
||||
out ulong lpTotalNumberOfBytes,
|
||||
out ulong lpTotalNumberOfFreeBytes);
|
||||
#endregion
|
||||
|
||||
#region consts
|
||||
// logon types
|
||||
const int LOGON32_LOGON_INTERACTIVE = 2;
|
||||
const int LOGON32_LOGON_NETWORK = 3;
|
||||
const int LOGON32_LOGON_NEW_CREDENTIALS = 9;
|
||||
|
||||
// logon providers
|
||||
const int LOGON32_PROVIDER_DEFAULT = 0;
|
||||
const int LOGON32_PROVIDER_WINNT50 = 3;
|
||||
const int LOGON32_PROVIDER_WINNT40 = 2;
|
||||
const int LOGON32_PROVIDER_WINNT35 = 1;
|
||||
|
||||
const int RESOURCE_CONNECTED = 0x00000001;
|
||||
const int RESOURCE_GLOBALNET = 0x00000002;
|
||||
const int RESOURCE_REMEMBERED = 0x00000003;
|
||||
|
||||
const int RESOURCETYPE_ANY = 0x00000000;
|
||||
const int RESOURCETYPE_DISK = 0x00000001;
|
||||
const int RESOURCETYPE_PRINT = 0x00000002;
|
||||
|
||||
const int RESOURCEDISPLAYTYPE_GENERIC = 0x00000000;
|
||||
const int RESOURCEDISPLAYTYPE_DOMAIN = 0x00000001;
|
||||
const int RESOURCEDISPLAYTYPE_SERVER = 0x00000002;
|
||||
const int RESOURCEDISPLAYTYPE_SHARE = 0x00000003;
|
||||
const int RESOURCEDISPLAYTYPE_FILE = 0x00000004;
|
||||
const int RESOURCEDISPLAYTYPE_GROUP = 0x00000005;
|
||||
|
||||
const int RESOURCEUSAGE_CONNECTABLE = 0x00000001;
|
||||
const int RESOURCEUSAGE_CONTAINER = 0x00000002;
|
||||
|
||||
|
||||
const int CONNECT_INTERACTIVE = 0x00000008;
|
||||
const int CONNECT_PROMPT = 0x00000010;
|
||||
const int CONNECT_REDIRECT = 0x00000080;
|
||||
const int CONNECT_UPDATE_PROFILE = 0x00000001;
|
||||
const int CONNECT_COMMANDLINE = 0x00000800;
|
||||
const int CONNECT_CMD_SAVECRED = 0x00001000;
|
||||
|
||||
const int CONNECT_LOCALDRIVE = 0x00000100;
|
||||
#endregion
|
||||
|
||||
#region Errors
|
||||
const int NO_ERROR = 0;
|
||||
|
||||
const int ERROR_ACCESS_DENIED = 5;
|
||||
const int ERROR_ALREADY_ASSIGNED = 85;
|
||||
const int ERROR_BAD_DEVICE = 1200;
|
||||
const int ERROR_BAD_NET_NAME = 67;
|
||||
const int ERROR_BAD_PROVIDER = 1204;
|
||||
const int ERROR_CANCELLED = 1223;
|
||||
const int ERROR_EXTENDED_ERROR = 1208;
|
||||
const int ERROR_INVALID_ADDRESS = 487;
|
||||
const int ERROR_INVALID_PARAMETER = 87;
|
||||
const int ERROR_INVALID_PASSWORD = 1216;
|
||||
const int ERROR_MORE_DATA = 234;
|
||||
const int ERROR_NO_MORE_ITEMS = 259;
|
||||
const int ERROR_NO_NET_OR_BAD_PATH = 1203;
|
||||
const int ERROR_NO_NETWORK = 1222;
|
||||
|
||||
const int ERROR_BAD_PROFILE = 1206;
|
||||
const int ERROR_CANNOT_OPEN_PROFILE = 1205;
|
||||
const int ERROR_DEVICE_IN_USE = 2404;
|
||||
const int ERROR_NOT_CONNECTED = 2250;
|
||||
const int ERROR_OPEN_FILES = 2401;
|
||||
|
||||
private struct ErrorClass
|
||||
{
|
||||
public int num;
|
||||
public string message;
|
||||
public ErrorClass(int num, string message)
|
||||
{
|
||||
this.num = num;
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
|
||||
private static ErrorClass[] ERROR_LIST = new ErrorClass[] {
|
||||
new ErrorClass(ERROR_ACCESS_DENIED, "Error: Access Denied"),
|
||||
new ErrorClass(ERROR_ALREADY_ASSIGNED, "Error: Already Assigned"),
|
||||
new ErrorClass(ERROR_BAD_DEVICE, "Error: Bad Device"),
|
||||
new ErrorClass(ERROR_BAD_NET_NAME, "Error: Bad Net Name"),
|
||||
new ErrorClass(ERROR_BAD_PROVIDER, "Error: Bad Provider"),
|
||||
new ErrorClass(ERROR_CANCELLED, "Error: Cancelled"),
|
||||
new ErrorClass(ERROR_EXTENDED_ERROR, "Error: Extended Error"),
|
||||
new ErrorClass(ERROR_INVALID_ADDRESS, "Error: Invalid Address"),
|
||||
new ErrorClass(ERROR_INVALID_PARAMETER, "Error: Invalid Parameter"),
|
||||
new ErrorClass(ERROR_INVALID_PASSWORD, "Error: Invalid Password"),
|
||||
new ErrorClass(ERROR_MORE_DATA, "Error: More Data"),
|
||||
new ErrorClass(ERROR_NO_MORE_ITEMS, "Error: No More Items"),
|
||||
new ErrorClass(ERROR_NO_NET_OR_BAD_PATH, "Error: No Net Or Bad Path"),
|
||||
new ErrorClass(ERROR_NO_NETWORK, "Error: No Network"),
|
||||
new ErrorClass(ERROR_BAD_PROFILE, "Error: Bad Profile"),
|
||||
new ErrorClass(ERROR_CANNOT_OPEN_PROFILE, "Error: Cannot Open Profile"),
|
||||
new ErrorClass(ERROR_DEVICE_IN_USE, "Error: Device In Use"),
|
||||
new ErrorClass(ERROR_EXTENDED_ERROR, "Error: Extended Error"),
|
||||
new ErrorClass(ERROR_NOT_CONNECTED, "Error: Not Connected"),
|
||||
new ErrorClass(ERROR_OPEN_FILES, "Error: Open Files"),
|
||||
};
|
||||
|
||||
private static string getErrorForNumber(int errNum)
|
||||
{
|
||||
foreach (ErrorClass er in ERROR_LIST)
|
||||
{
|
||||
if (er.num == errNum) return er.message;
|
||||
}
|
||||
return "Error: Unknown, " + errNum;
|
||||
}
|
||||
#endregion
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private class NETRESOURCE
|
||||
{
|
||||
public int dwScope = 0;
|
||||
public int dwType = 0;
|
||||
public int dwDisplayType = 0;
|
||||
public int dwUsage = 0;
|
||||
public string lpLocalName = "";
|
||||
public string lpRemoteName = "";
|
||||
public string lpComment = "";
|
||||
public string lpProvider = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,6 +65,8 @@ import org.apache.log4j.Logger;
|
|||
import com.google.gson.Gson;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import org.apache.cloudstack.storage.command.CopyCommand;
|
||||
|
||||
import com.cloud.agent.api.Answer;
|
||||
import com.cloud.agent.api.CheckRouterAnswer;
|
||||
import com.cloud.agent.api.CheckRouterCommand;
|
||||
|
|
@ -123,9 +125,11 @@ import com.cloud.agent.api.routing.SetStaticRouteCommand;
|
|||
import com.cloud.agent.api.routing.Site2SiteVpnCfgCommand;
|
||||
import com.cloud.agent.api.routing.VmDataCommand;
|
||||
import com.cloud.agent.api.routing.VpnUsersCfgCommand;
|
||||
import com.cloud.agent.api.to.DataStoreTO;
|
||||
import com.cloud.agent.api.to.DhcpTO;
|
||||
import com.cloud.agent.api.to.FirewallRuleTO;
|
||||
import com.cloud.agent.api.to.IpAddressTO;
|
||||
import com.cloud.agent.api.to.NfsTO;
|
||||
import com.cloud.agent.api.to.NicTO;
|
||||
import com.cloud.agent.api.to.PortForwardingRuleTO;
|
||||
import com.cloud.agent.api.to.StaticNatRuleTO;
|
||||
|
|
@ -486,6 +490,8 @@ public class HypervDirectConnectResource extends ServerResourceBase implements S
|
|||
answer = execute((PlugNicCommand)cmd);
|
||||
} else if (clazz == UnPlugNicCommand.class) {
|
||||
answer = execute((UnPlugNicCommand)cmd);
|
||||
} else if (clazz == CopyCommand.class) {
|
||||
answer = execute((CopyCommand)cmd);
|
||||
}
|
||||
else {
|
||||
if (clazz == StartCommand.class) {
|
||||
|
|
@ -519,6 +525,46 @@ public class HypervDirectConnectResource extends ServerResourceBase implements S
|
|||
return answer;
|
||||
}
|
||||
|
||||
private Answer execute(CopyCommand cmd) {
|
||||
URI agentUri = null;
|
||||
try {
|
||||
String cmdName = cmd.getClass().getName();
|
||||
agentUri =
|
||||
new URI("https", null, _agentIp, _port,
|
||||
"/api/HypervResource/" + cmdName, null, null);
|
||||
} catch (URISyntaxException e) {
|
||||
String errMsg = "Could not generate URI for Hyper-V agent";
|
||||
s_logger.error(errMsg, e);
|
||||
return null;
|
||||
}
|
||||
cleanPassword(cmd.getSrcTO().getDataStore());
|
||||
cleanPassword(cmd.getDestTO().getDataStore());
|
||||
|
||||
// Send the cmd to hyperv agent.
|
||||
String ansStr = postHttpRequest(s_gson.toJson(cmd), agentUri);
|
||||
if (ansStr == null) {
|
||||
return Answer.createUnsupportedCommandAnswer(cmd);
|
||||
}
|
||||
|
||||
Answer[] result = s_gson.fromJson(ansStr, Answer[].class);
|
||||
String logResult = cleanPassword(StringEscapeUtils.unescapeJava(result.toString()));
|
||||
s_logger.debug("executeRequest received response " + logResult);
|
||||
if (result.length > 0) {
|
||||
return result[0];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void cleanPassword(DataStoreTO dataStoreTO) {
|
||||
if (dataStoreTO instanceof NfsTO) {
|
||||
NfsTO nfsTO = (NfsTO)dataStoreTO;
|
||||
String url = nfsTO.getUrl();
|
||||
if (url.contains("cifs") && url.contains("password")) {
|
||||
nfsTO.setUrl(url.substring(0, url.indexOf('?')));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private PlugNicAnswer execute(PlugNicCommand cmd) {
|
||||
if (s_logger.isInfoEnabled()) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue