зеркало из https://github.com/mozilla/pjs.git
Removing at Nikolay's request
This commit is contained in:
Родитель
e11d701cfe
Коммит
d8205c2114
|
@ -1,265 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public
|
||||
* License Version 1.1 (the "License"); you may not use this file
|
||||
* except in compliance with the License. You may obtain a copy of
|
||||
* the License at http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS
|
||||
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
* implied. See the License for the specific language governing
|
||||
* rights and limitations under the License.
|
||||
*
|
||||
* The Original Code is The Waterfall Java Plugin Module
|
||||
*
|
||||
* The Initial Developer of the Original Code is Sun Microsystems Inc
|
||||
* Portions created by Sun Microsystems Inc are Copyright (C) 2001
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* $Id: MozillaAppletPeer.java,v 1.1 2001-05-10 18:12:32 edburns%acm.org Exp $
|
||||
*
|
||||
*
|
||||
* Contributor(s):
|
||||
*
|
||||
* Nikolay N. Igotti <inn@sparc.spb.su>
|
||||
*/
|
||||
|
||||
package sun.jvmp.mozilla;
|
||||
|
||||
import sun.jvmp.*;
|
||||
import sun.jvmp.security.*;
|
||||
import sun.jvmp.applet.*;
|
||||
import java.util.Hashtable;
|
||||
import java.net.URL;
|
||||
import java.net.MalformedURLException;
|
||||
import java.applet.*;
|
||||
import java.awt.Frame;
|
||||
|
||||
public class MozillaAppletPeer extends WFAppletContext
|
||||
implements HostObjectPeer, sun.jvmp.javascript.JSContext
|
||||
{
|
||||
private int m_id;
|
||||
private int m_winid;
|
||||
private long m_params = 0;
|
||||
MozillaPeerFactory m_factory;
|
||||
WFAppletViewer m_viewer = null;
|
||||
private boolean m_stopped = true;
|
||||
protected JSObject m_js = null;
|
||||
PluggableJVM jvm;
|
||||
|
||||
MozillaAppletPeer(MozillaHostObjectPeer fake, int id)
|
||||
{
|
||||
m_factory = fake.m_factory;;
|
||||
jvm = m_factory.m_jvm;
|
||||
jvm.trace("CREATED APPLET OBJECT: "+this,
|
||||
PluggableJVM.LOG_DEBUG);
|
||||
m_id = id;
|
||||
m_winid = 0;
|
||||
}
|
||||
|
||||
public int getID()
|
||||
{
|
||||
return m_id;
|
||||
}
|
||||
|
||||
public void setID(int id)
|
||||
{
|
||||
if (m_id != 0) return;
|
||||
m_id = id;
|
||||
}
|
||||
public int handleEvent(SecurityCaps caps, int eventID, long eventData)
|
||||
{
|
||||
int retval = 0;
|
||||
//jvm.trace("GOT EVENT "+eventID+"!!!");
|
||||
switch (eventID)
|
||||
{
|
||||
case PE_CREATE:
|
||||
retval = 1;
|
||||
break;
|
||||
case PE_SETTYPE:
|
||||
jvm.trace("SETTYPE called twice - IGNORED",
|
||||
PluggableJVM.LOG_WARNING);
|
||||
retval = 1;
|
||||
break;
|
||||
case PE_SETWINDOW:
|
||||
jvm.trace("PE_SETWINDOW", PluggableJVM.LOG_DEBUG);
|
||||
m_winid = (int)eventData;
|
||||
retval = handleSetWindow();
|
||||
break;
|
||||
case PE_NEWPARAMS:
|
||||
m_params = eventData;
|
||||
retval = handleNewParams();
|
||||
break;
|
||||
case PE_DESTROY:
|
||||
retval = destroy(caps);
|
||||
break;
|
||||
case PE_STOP:
|
||||
retval = 1;
|
||||
// do it before stop, as after we can't be sure
|
||||
// our browser peer is still alive
|
||||
m_stopped = true;
|
||||
if (m_viewer != null) m_viewer.stopApplet();
|
||||
break;
|
||||
case PE_START:
|
||||
retval = 1;
|
||||
m_stopped = false;
|
||||
m_viewer.startApplet();
|
||||
break;
|
||||
case PE_GETPEER:
|
||||
// this call is handled in both ways - direct and
|
||||
// queued as policy on place where to handle
|
||||
// JS calls can be changed on Mozilla side
|
||||
// At least for now it works OK with both cases
|
||||
retval = handleCall(caps, PE_GETPEER, eventData);
|
||||
break;
|
||||
default:
|
||||
retval = 0;
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
public int handleCall(SecurityCaps caps, int arg1, long arg2)
|
||||
{
|
||||
int retval = 0;
|
||||
switch (arg1)
|
||||
{
|
||||
case PE_GETPEER:
|
||||
if (m_viewer == null) break;
|
||||
Object o = null;
|
||||
switch (m_viewer.getLoadingStatus())
|
||||
{
|
||||
case sun.applet.AppletPanel.APPLET_START:
|
||||
o = m_viewer.getApplet();
|
||||
break;
|
||||
case sun.applet.AppletPanel.APPLET_ERROR:
|
||||
o = null;
|
||||
break;
|
||||
default:
|
||||
jvm.trace("XXX: Applet loading in progress....",
|
||||
PluggableJVM.LOG_WARNING);
|
||||
// here we just return NULL, as for the moment no valid
|
||||
// applet object exist - if someone really needs this
|
||||
// object - call later
|
||||
break;
|
||||
}
|
||||
nativeReturnJObject(o, arg2);
|
||||
retval = 1;
|
||||
break;
|
||||
default:
|
||||
retval = 0;
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
public int destroy(SecurityCaps caps)
|
||||
{
|
||||
jvm.trace("DESTROY", PluggableJVM.LOG_DEBUG);
|
||||
finalizeParams();
|
||||
m_params = 0;
|
||||
m_stopped = true;
|
||||
if (m_viewer != null) {
|
||||
m_viewer.destroyApplet(2500);
|
||||
m_viewer = null;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
public HostObjectPeerFactory getFactory()
|
||||
{
|
||||
return m_factory;
|
||||
}
|
||||
|
||||
private int handleNewParams()
|
||||
{
|
||||
String[][] params = getParams();
|
||||
String[] keys = params[0];
|
||||
String[] vals = params[1];
|
||||
URL docbase;
|
||||
Hashtable hparams = new Hashtable();
|
||||
for (int i=0; i < keys.length; i++)
|
||||
hparams.put(keys[i].toLowerCase(), vals[i]);
|
||||
//hparams.put("OWNER", this);
|
||||
//hparams.put("ID", new Integer(m_id));
|
||||
try
|
||||
{
|
||||
docbase = new URL((String)hparams.get("docbase"));
|
||||
}
|
||||
catch (MalformedURLException e)
|
||||
{
|
||||
jvm.trace("invalid docbase due "+e,
|
||||
PluggableJVM.LOG_ERROR);
|
||||
return 0;
|
||||
}
|
||||
m_viewer = m_factory.m_mgr.createAppletViewer(this,
|
||||
m_factory,
|
||||
hparams);
|
||||
m_viewer.setDocumentBase(docbase);
|
||||
return handleSetWindow();
|
||||
}
|
||||
|
||||
private int handleSetWindow()
|
||||
{
|
||||
if (m_stopped) return 0;
|
||||
if (m_winid != 0) {
|
||||
Frame f = jvm.getFrameWithId(m_winid);
|
||||
if (f != null) m_viewer.setWindow(f);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
/**
|
||||
* From AppletContext
|
||||
*/
|
||||
public void showStatus(String string)
|
||||
{
|
||||
//PluggableJVM.trace("Status: "+string);
|
||||
// calls back peer in Mozilla
|
||||
if (m_stopped) return;
|
||||
nativeShowStatus(string);
|
||||
}
|
||||
|
||||
public void showDocument(URL url, String target)
|
||||
{
|
||||
// never pass events after stop() - otherwise
|
||||
// we'll crash the browser
|
||||
if (m_stopped) return;
|
||||
// calls back peer in Mozilla
|
||||
nativeShowDocument(url.toString(), target);
|
||||
}
|
||||
|
||||
public void showDocument(URL url)
|
||||
{
|
||||
showDocument(url, "_top");
|
||||
}
|
||||
|
||||
/**
|
||||
* From JSContext
|
||||
*/
|
||||
public netscape.javascript.JSObject getJSObject()
|
||||
{
|
||||
// check if we got params from browser - fail otherwise
|
||||
if (m_params == 0) return null;
|
||||
if (m_js == null)
|
||||
m_js = new sun.jvmp.mozilla.JSObject(m_params);
|
||||
return m_js;
|
||||
}
|
||||
// duplicates of nsIJavaObjectInfo methods
|
||||
// funny way to return 2 arrays of strings
|
||||
native protected String[][] getParams();
|
||||
native protected void finalizeParams();
|
||||
native protected boolean nativeShowStatus(String status);
|
||||
native protected boolean nativeShowDocument(String url,
|
||||
String target);
|
||||
// method to write jobject ret to native pointer ptr
|
||||
native protected void nativeReturnJObject(Object ret, long ptr);
|
||||
// XXX: add others
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,107 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public
|
||||
* License Version 1.1 (the "License"); you may not use this file
|
||||
* except in compliance with the License. You may obtain a copy of
|
||||
* the License at http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS
|
||||
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
* implied. See the License for the specific language governing
|
||||
* rights and limitations under the License.
|
||||
*
|
||||
* The Original Code is The Waterfall Java Plugin Module
|
||||
*
|
||||
* The Initial Developer of the Original Code is Sun Microsystems Inc
|
||||
* Portions created by Sun Microsystems Inc are Copyright (C) 2001
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* $Id: MozillaHostObjectPeer.java,v 1.1 2001-05-10 18:12:32 edburns%acm.org Exp $
|
||||
*
|
||||
*
|
||||
* Contributor(s):
|
||||
*
|
||||
* Nikolay N. Igotti <inn@sparc.spb.su>
|
||||
*/
|
||||
|
||||
package sun.jvmp.mozilla;
|
||||
|
||||
import sun.jvmp.*;
|
||||
import sun.jvmp.security.*;
|
||||
|
||||
public class MozillaHostObjectPeer implements HostObjectPeer
|
||||
{
|
||||
private int m_id = 0;
|
||||
private long m_data = 0;
|
||||
private HostObjectPeer m_realPeer = null;
|
||||
MozillaPeerFactory m_factory = null;
|
||||
|
||||
public MozillaHostObjectPeer(MozillaPeerFactory factory, int version)
|
||||
{
|
||||
m_factory = factory;
|
||||
}
|
||||
|
||||
public int getID()
|
||||
{
|
||||
return m_id;
|
||||
}
|
||||
|
||||
public void setID(int id)
|
||||
{
|
||||
if (m_id != 0) return;
|
||||
m_id = id;
|
||||
}
|
||||
|
||||
public int handleEvent(SecurityCaps caps, int eventID, long eventData)
|
||||
{
|
||||
int retval = 0;
|
||||
// forward functionality to the real peer
|
||||
if (m_realPeer != null)
|
||||
return m_realPeer.handleEvent(caps, eventID, eventData);
|
||||
switch (eventID)
|
||||
{
|
||||
case PE_CREATE:
|
||||
retval = 1;
|
||||
break;
|
||||
case PE_SETTYPE:
|
||||
switch((int)eventData)
|
||||
{
|
||||
case PT_APPLET:
|
||||
case PT_OBJECT:
|
||||
m_realPeer = new MozillaAppletPeer(this, m_id);
|
||||
// maybe send CREATE event from here?
|
||||
retval = 1;
|
||||
break;
|
||||
default:
|
||||
m_factory.m_jvm.trace("Unknown tag type",
|
||||
PluggableJVM.LOG_WARNING);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
retval = 0;
|
||||
break;
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
public int destroy(SecurityCaps caps)
|
||||
{
|
||||
if (m_realPeer != null)
|
||||
return m_realPeer.destroy(caps);
|
||||
return 0;
|
||||
}
|
||||
|
||||
public int handleCall(SecurityCaps caps, int arg1, long arg2)
|
||||
{
|
||||
if (m_realPeer != null)
|
||||
return m_realPeer.handleCall(caps, arg1, arg2);
|
||||
return 0;
|
||||
}
|
||||
|
||||
public HostObjectPeerFactory getFactory()
|
||||
{
|
||||
return m_factory;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -1,237 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public
|
||||
* License Version 1.1 (the "License"); you may not use this file
|
||||
* except in compliance with the License. You may obtain a copy of
|
||||
* the License at http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS
|
||||
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
* implied. See the License for the specific language governing
|
||||
* rights and limitations under the License.
|
||||
*
|
||||
* The Original Code is The Waterfall Java Plugin Module
|
||||
*
|
||||
* The Initial Developer of the Original Code is Sun Microsystems Inc
|
||||
* Portions created by Sun Microsystems Inc are Copyright (C) 2001
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* $Id: MozillaPeerFactory.java,v 1.1 2001-05-10 18:12:32 edburns%acm.org Exp $
|
||||
*
|
||||
*
|
||||
* Contributor(s):
|
||||
*
|
||||
* Nikolay N. Igotti <inn@sparc.spb.su>
|
||||
*/
|
||||
|
||||
package sun.jvmp.mozilla;
|
||||
|
||||
import sun.jvmp.*;
|
||||
import sun.jvmp.security.*;
|
||||
import sun.jvmp.applet.*;
|
||||
import java.net.*;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.*;
|
||||
import java.security.*;
|
||||
|
||||
public class MozillaPeerFactory implements HostObjectPeerFactory,
|
||||
AccessControlDecider,
|
||||
BrowserSupport
|
||||
{
|
||||
protected final String cid = "@sun.com/wf/mozilla/appletpeer;1";
|
||||
protected final int version = 1;
|
||||
PluggableJVM m_jvm = null;
|
||||
protected long m_params = 0;
|
||||
protected int m_id;
|
||||
private static boolean initialized = false;
|
||||
protected URL[] m_codebase;
|
||||
AppletManager m_mgr;
|
||||
|
||||
protected MozillaPeerFactory(PluggableJVM jvm, long data)
|
||||
throws Exception
|
||||
{
|
||||
loadLibrary();
|
||||
m_jvm = jvm;
|
||||
m_params = data;
|
||||
/* as we could load only one factory, this will allow
|
||||
generic operations, like JS evaluation, to be performed without
|
||||
any applets at all */
|
||||
JSObject.m_evaluator = data;
|
||||
m_mgr = new AppletManager(jvm);
|
||||
try {
|
||||
m_codebase = null;
|
||||
URLClassLoader cl =
|
||||
(URLClassLoader)this.getClass().getClassLoader();
|
||||
if (cl != null) m_codebase = cl.getURLs();
|
||||
} catch (ClassCastException e) {
|
||||
// do nothing here
|
||||
}
|
||||
System.setSecurityManager(new MozillaSecurityManager());
|
||||
}
|
||||
|
||||
public static HostObjectPeerFactory start(PluggableJVM jvm, Long data)
|
||||
{
|
||||
MozillaPeerFactory factory = null;
|
||||
try {
|
||||
factory = new MozillaPeerFactory(jvm, data.longValue());
|
||||
} catch (Exception e) {
|
||||
jvm.trace("MOZILLA FACTORY NOT CREATED", PluggableJVM.LOG_ERROR);
|
||||
return null;
|
||||
}
|
||||
return factory;
|
||||
}
|
||||
|
||||
public String getCID()
|
||||
{
|
||||
return cid;
|
||||
}
|
||||
|
||||
public HostObjectPeer create(int version)
|
||||
{
|
||||
return new MozillaHostObjectPeer(this, version);
|
||||
}
|
||||
|
||||
public int handleEvent(SecurityCaps caps,
|
||||
int eventID,
|
||||
long eventData)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
private native int nativeHandleCall(int arg1, long arg2);
|
||||
|
||||
public int handleCall(SecurityCaps caps, int arg1, long arg2)
|
||||
{
|
||||
int rv = 0;
|
||||
if (arg1 > 0)
|
||||
rv = nativeHandleCall(arg1, arg2);
|
||||
else
|
||||
rv = 1;
|
||||
return rv;
|
||||
}
|
||||
|
||||
public int destroy(SecurityCaps caps)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
public PermissionCollection getPermissions(CodeSource codesource)
|
||||
{
|
||||
Permissions perms = new Permissions();
|
||||
URL location = codesource.getLocation();
|
||||
if (m_codebase != null && location != null) {
|
||||
for (int i=0; i<m_codebase.length; i++)
|
||||
{
|
||||
// XXX: maybe I'm totally wrong here
|
||||
if (location.equals(m_codebase[i]))
|
||||
{
|
||||
PluggableJVM.trace("extension granted all permissions to own codesource "+codesource,
|
||||
PluggableJVM.LOG_WARNING);
|
||||
perms.add(new java.security.AllPermission());
|
||||
return perms;
|
||||
}
|
||||
}
|
||||
}
|
||||
perms.add(new RuntimePermission("accessClassInPackage.sun.jvmp.mozilla"));
|
||||
return perms;
|
||||
}
|
||||
|
||||
private void loadLibrary() throws UnsatisfiedLinkError
|
||||
{
|
||||
String libname = "wf_moz6";
|
||||
try
|
||||
{
|
||||
System.loadLibrary(libname);
|
||||
}
|
||||
catch (UnsatisfiedLinkError ex)
|
||||
{
|
||||
PluggableJVM.trace("System could not load DLL: " + libname,
|
||||
PluggableJVM.LOG_ERROR);
|
||||
PluggableJVM.trace("Path is:" +
|
||||
System.getProperty("java.library.path"),
|
||||
PluggableJVM.LOG_WARNING);
|
||||
PluggableJVM.trace(ex.toString(),
|
||||
PluggableJVM.LOG_WARNING);
|
||||
throw ex;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* AccessControlDecider methods
|
||||
*/
|
||||
public int decide(CallingContext ctx, String principal, int cap_no)
|
||||
{
|
||||
return NA;
|
||||
}
|
||||
|
||||
public boolean belongs(int cap_no)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* BrowserSupport methods
|
||||
*/
|
||||
public ProxyInfo getProxyInfoForURL(URL url)
|
||||
{
|
||||
String s = nativeGetProxyInfoForURL(url.toString());
|
||||
//m_jvm.trace("Proxy string for:\""+
|
||||
//url.toString() +"\" is \""+s+"\"",
|
||||
//PluggableJVM.LOG_DEBUG);
|
||||
ProxyInfo info = extractAutoProxySetting(s);
|
||||
return info;
|
||||
}
|
||||
|
||||
public URLConnection getConnectionForURL(URL url)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
private native String nativeGetProxyInfoForURL(String s);
|
||||
private ProxyInfo extractAutoProxySetting(String s)
|
||||
{
|
||||
if (s != null)
|
||||
{
|
||||
StringTokenizer st = new StringTokenizer(s, ";", false);
|
||||
if (st.hasMoreTokens())
|
||||
{
|
||||
String pattern = st.nextToken();
|
||||
|
||||
int i = pattern.indexOf("PROXY");
|
||||
|
||||
if (i != -1) {
|
||||
// "PROXY" is specified
|
||||
return new ProxyInfo(pattern.substring(i + 6));
|
||||
}
|
||||
|
||||
i = pattern.indexOf("SOCKS");
|
||||
|
||||
if (i != -1)
|
||||
{
|
||||
// "SOCKS" is specified
|
||||
return new ProxyInfo(null, pattern.substring(i + 6));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// proxy string contains 'DIRECT' or unrecognized text
|
||||
return new ProxyInfo(null, -1);
|
||||
}
|
||||
|
||||
public int getID()
|
||||
{
|
||||
return m_id;
|
||||
}
|
||||
|
||||
public void setID(int id)
|
||||
{
|
||||
if (m_id != 0) return;
|
||||
m_id = id;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public
|
||||
* License Version 1.1 (the "License"); you may not use this file
|
||||
* except in compliance with the License. You may obtain a copy of
|
||||
* the License at http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS
|
||||
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
* implied. See the License for the specific language governing
|
||||
* rights and limitations under the License.
|
||||
*
|
||||
* The Original Code is The Waterfall Java Plugin Module
|
||||
*
|
||||
* The Initial Developer of the Original Code is Sun Microsystems Inc
|
||||
* Portions created by Sun Microsystems Inc are Copyright (C) 2001
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* $Id: MozillaSecurityManager.java,v 1.1 2001-05-10 18:12:32 edburns%acm.org Exp $
|
||||
*
|
||||
*
|
||||
* Contributor(s):
|
||||
*
|
||||
* Nikolay N. Igotti <inn@sparc.spb.su>
|
||||
*/
|
||||
|
||||
package sun.jvmp.mozilla;
|
||||
|
||||
import sun.jvmp.*;
|
||||
import sun.jvmp.security.*;
|
||||
import sun.jvmp.applet.*;
|
||||
import java.security.*;
|
||||
|
||||
public class MozillaSecurityManager extends WFAppletSecurityManager
|
||||
{
|
||||
public void checkPackageAccess(final String p)
|
||||
{
|
||||
//System.err.println("checkPackageAccesss: "+p);
|
||||
// this check used to prevent infinite recursion when calling implies()
|
||||
// in JavaScriptProtectionDomain,, as it has to load some classes
|
||||
if (p.equals("sun.jvmp.mozilla"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
super.checkPackageAccess(p);
|
||||
}
|
||||
|
||||
public void checkPermission(Permission p)
|
||||
{
|
||||
//System.err.println("checkPermission: "+p);
|
||||
super.checkPermission(p);
|
||||
}
|
||||
|
||||
}
|
Загрузка…
Ссылка в новой задаче