Merge remote-tracking branch 'origin/master' into IDEA-CR-10038
authorVladimir Krivosheev <vladimir.krivosheev@jetbrains.com>
Mon, 25 Apr 2016 10:22:09 +0000 (12:22 +0200)
committerVladimir Krivosheev <vladimir.krivosheev@jetbrains.com>
Mon, 25 Apr 2016 10:22:09 +0000 (12:22 +0200)
47 files changed:
java/compiler/impl/src/com/intellij/compiler/server/BuildManager.java
java/execution/impl/src/com/intellij/execution/runners/ProcessProxyImpl.java
lib/annotations/netty/io/netty/resolver/annotations.xml [new file with mode: 0644]
platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebBrowserUrlProvider.java
platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt
platform/built-in-server/src/org/jetbrains/builtInWebServer/DefaultWebServerPathHandler.kt
platform/built-in-server/src/org/jetbrains/builtInWebServer/StaticFileHandler.kt
platform/built-in-server/src/org/jetbrains/builtInWebServer/WebServerFileHandler.kt
platform/built-in-server/src/org/jetbrains/ide/DiffHttpService.java
platform/built-in-server/src/org/jetbrains/ide/OpenFileHttpService.java [deleted file]
platform/built-in-server/src/org/jetbrains/ide/OpenFileHttpService.kt [new file with mode: 0644]
platform/built-in-server/src/org/jetbrains/ide/OpenFileXmlRpcHandler.java [deleted file]
platform/built-in-server/src/org/jetbrains/ide/ProjectSetRequestHandler.java
platform/built-in-server/src/org/jetbrains/ide/RestService.java
platform/built-in-server/src/org/jetbrains/ide/XmlRpcServerImpl.java
platform/built-in-server/testSrc/BuiltInWebServerTest.kt
platform/built-in-server/testSrc/IsLocalHostTest.kt [new file with mode: 0644]
platform/platform-api/src/com/intellij/ide/XmlRpcHandlerBean.java [deleted file]
platform/platform-api/src/com/intellij/openapi/ui/MessageDialogBuilder.java
platform/platform-api/src/com/intellij/util/net/NetUtils.java
platform/platform-impl/src/com/intellij/ide/impl/ProjectUtil.java
platform/platform-impl/src/com/intellij/idea/SocketLock.java
platform/platform-impl/src/com/intellij/util/Urls.java
platform/platform-impl/src/com/intellij/util/io/socketConnection/impl/SocketConnectionImpl.java
platform/platform-impl/src/org/jetbrains/ide/HttpRequestHandler.java [moved from platform/built-in-server-api/src/org/jetbrains/ide/HttpRequestHandler.java with 75% similarity]
platform/platform-impl/src/org/jetbrains/io/BuiltInServer.java
platform/platform-impl/src/org/jetbrains/io/DelegatingHttpRequestHandler.kt
platform/platform-impl/src/org/jetbrains/io/NettyUtil.java
platform/platform-impl/src/org/jetbrains/io/Responses.kt
platform/platform-impl/src/org/jetbrains/io/netty.kt
platform/platform-resources-en/src/messages/IdeBundle.properties
platform/platform-resources/src/META-INF/PlatformExtensionPoints.xml
platform/platform-resources/src/META-INF/built-in-server.xml
platform/platform-resources/src/META-INF/mime.types
platform/util/resources/misc/registry.properties
plugins/git4idea/rt/src/org/jetbrains/git4idea/http/GitAskPassApp.java
plugins/git4idea/rt/src/org/jetbrains/git4idea/http/GitAskPassXmlRpcClient.java
plugins/git4idea/rt/src/org/jetbrains/git4idea/http/GitAskPassXmlRpcHandler.java
plugins/git4idea/rt/src/org/jetbrains/git4idea/ssh/GitSSHHandler.java
plugins/git4idea/rt/src/org/jetbrains/git4idea/ssh/GitSSHXmlRpcClient.java
plugins/git4idea/rt/src/org/jetbrains/git4idea/ssh/SSHMain.java
plugins/git4idea/src/git4idea/commands/GitHandler.java
plugins/git4idea/src/git4idea/commands/GitHttpAuthService.java
plugins/git4idea/src/git4idea/rebase/GitRebaseEditorService.java
plugins/git4idea/src/org/jetbrains/git4idea/ssh/GitXmlRpcHandlerService.java
plugins/git4idea/src/org/jetbrains/git4idea/ssh/GitXmlRpcSshService.java
plugins/xpath/xpath-lang/src/org/intellij/lang/xpath/xslt/run/OutputTabAdapter.java

index 2d9742863629c584bcc2abb51d87bc3b622d012e..3983bf2b36ab814368c871a7fdb34c18a280c83b 100644 (file)
@@ -112,6 +112,7 @@ import java.awt.*;
 import java.io.File;
 import java.io.FileFilter;
 import java.io.IOException;
+import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.nio.charset.Charset;
 import java.text.SimpleDateFormat;
@@ -1205,7 +1206,7 @@ public class BuildManager implements Disposable {
     catch (Throwable e) {
       LOG.error(e);
     }
-    
+
     final OSProcessHandler processHandler = new OSProcessHandler(cmdLine) {
       @Override
       protected boolean shouldDestroyProcessRecursively() {
@@ -1336,7 +1337,7 @@ public class BuildManager implements Disposable {
                                    myMessageDispatcher);
       }
     });
-    Channel serverChannel = bootstrap.bind(NetUtils.getLoopbackAddress(), 0).syncUninterruptibly().channel();
+    Channel serverChannel = bootstrap.bind(InetAddress.getLoopbackAddress(), 0).syncUninterruptibly().channel();
     myChannelRegistrar.add(serverChannel);
     return ((InetSocketAddress)serverChannel.localAddress()).getPort();
   }
index 52596635976360b11dac4b4c2043512d9636942f..f09d7b5536397dd416424046c53fae5c4121d7c0 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2011 JetBrains s.r.o.
+ * Copyright 2000-2016 JetBrains s.r.o.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -17,13 +17,13 @@ package com.intellij.execution.runners;
 
 import com.intellij.execution.process.ProcessHandler;
 import com.intellij.openapi.util.Key;
-import com.intellij.util.net.NetUtils;
 import org.jetbrains.annotations.NonNls;
 
 import java.io.BufferedWriter;
 import java.io.IOException;
 import java.io.OutputStreamWriter;
 import java.io.PrintWriter;
+import java.net.InetAddress;
 import java.net.ServerSocket;
 import java.net.Socket;
 
@@ -95,7 +95,7 @@ class ProcessProxyImpl implements ProcessProxy {
     if (myWriter == null) {
       try {
         if (mySocket == null) {
-          mySocket = new Socket(NetUtils.getLoopbackAddress(), myPortNumber);
+          mySocket = new Socket(InetAddress.getLoopbackAddress(), myPortNumber);
         }
         myWriter = new PrintWriter(new BufferedWriter(new OutputStreamWriter(mySocket.getOutputStream())));
       }
diff --git a/lib/annotations/netty/io/netty/resolver/annotations.xml b/lib/annotations/netty/io/netty/resolver/annotations.xml
new file mode 100644 (file)
index 0000000..1008967
--- /dev/null
@@ -0,0 +1,5 @@
+<root>
+  <item name='io.netty.resolver.HostsFileEntriesResolver java.net.InetAddress address(java.lang.String)'>
+    <annotation name='org.jetbrains.annotations.Nullable'/>
+  </item>
+</root>
\ No newline at end of file
index 1fa74d36be451589919320a42820f87e0e0e140a..1f5b2bffe4bf79acd30b9ad4c2024f7c75c0cb2e 100644 (file)
@@ -53,19 +53,20 @@ public class BuiltInWebBrowserUrlProvider extends WebBrowserUrlProvider implemen
     String path = info.getPath();
 
     String authority = currentAuthority == null ? "localhost:" + effectiveBuiltInServerPort : currentAuthority;
-    List<Url> urls = new SmartList<>(Urls.newHttpUrl(authority, '/' + project.getName() + '/' + path));
+    String query = "?" + BuiltInWebServerKt.TOKEN_PARAM_NAME + "=" + BuiltInWebServerKt.acquireToken();
+    List<Url> urls = new SmartList<>(Urls.newHttpUrl(authority, '/' + project.getName() + '/' + path, query));
 
     String path2 = info.getRootLessPathIfPossible();
     if (path2 != null) {
-      urls.add(Urls.newHttpUrl(authority, '/' + project.getName() + '/' + path2));
+      urls.add(Urls.newHttpUrl(authority, '/' + project.getName() + '/' + path2, query));
     }
 
     int defaultPort = BuiltInServerManager.getInstance().getPort();
     if (currentAuthority == null && defaultPort != effectiveBuiltInServerPort) {
       String defaultAuthority = "localhost:" + defaultPort;
-      urls.add(Urls.newHttpUrl(defaultAuthority, '/' + project.getName() + '/' + path));
+      urls.add(Urls.newHttpUrl(defaultAuthority, '/' + project.getName() + '/' + path, query));
       if (path2 != null) {
-        urls.add(Urls.newHttpUrl(defaultAuthority, '/' + project.getName() + '/' + path2));
+        urls.add(Urls.newHttpUrl(defaultAuthority, '/' + project.getName() + '/' + path2, query));
       }
     }
 
index 1dd2f1b53e3c89aa9660738f2683f5863eda1838..355a998cae51f6e62d059a9d85c2bd65f205bb5b 100644 (file)
  */
 package org.jetbrains.builtInWebServer
 
+import com.google.common.cache.CacheBuilder
 import com.google.common.net.InetAddresses
+import com.intellij.ide.impl.ProjectUtil
+import com.intellij.openapi.application.ApplicationNamesInfo
+import com.intellij.openapi.application.PathManager
 import com.intellij.openapi.diagnostic.Logger
 import com.intellij.openapi.diagnostic.catchAndLog
+import com.intellij.openapi.ide.CopyPasteManager
 import com.intellij.openapi.project.Project
 import com.intellij.openapi.project.ProjectManager
+import com.intellij.openapi.ui.MessageDialogBuilder
+import com.intellij.openapi.ui.Messages
 import com.intellij.openapi.util.SystemInfoRt
 import com.intellij.openapi.util.io.FileUtil
+import com.intellij.openapi.util.io.FileUtilRt
 import com.intellij.openapi.util.io.endsWithName
+import com.intellij.openapi.util.text.StringUtil
 import com.intellij.openapi.vfs.VirtualFile
-import com.intellij.util.UriUtil
 import com.intellij.util.directoryStreamIfExists
 import com.intellij.util.io.URLUtil
 import com.intellij.util.isDirectory
 import com.intellij.util.net.NetUtils
+import io.netty.channel.Channel
 import io.netty.channel.ChannelHandlerContext
-import io.netty.handler.codec.http.FullHttpRequest
-import io.netty.handler.codec.http.HttpMethod
-import io.netty.handler.codec.http.HttpResponseStatus
-import io.netty.handler.codec.http.QueryStringDecoder
+import io.netty.handler.codec.http.*
+import io.netty.handler.codec.http.cookie.DefaultCookie
+import io.netty.handler.codec.http.cookie.ServerCookieDecoder
+import io.netty.handler.codec.http.cookie.ServerCookieEncoder
 import org.jetbrains.ide.HttpRequestHandler
-import org.jetbrains.io.host
-import org.jetbrains.io.send
+import org.jetbrains.io.*
+import java.awt.datatransfer.StringSelection
+import java.io.File
+import java.io.IOException
 import java.net.InetAddress
-import java.net.UnknownHostException
 import java.nio.file.Path
+import java.util.*
+import java.util.concurrent.TimeUnit
+import javax.swing.SwingUtilities
 
 internal val LOG = Logger.getInstance(BuiltInWebServer::class.java)
 
 class BuiltInWebServer : HttpRequestHandler() {
+  override fun isAccessible(request: HttpRequest) = request.isLocalOrigin(onlyAnyOrLoopback = false, hostsOnly = true)
+
   override fun isSupported(request: FullHttpRequest) = super.isSupported(request) || request.method() == HttpMethod.POST
 
   override fun process(urlDecoder: QueryStringDecoder, request: FullHttpRequest, context: ChannelHandlerContext): Boolean {
@@ -72,12 +87,54 @@ class BuiltInWebServer : HttpRequestHandler() {
     else {
       projectName = host
     }
-    return doProcess(request, context, projectName)
+    return doProcess(urlDecoder, request, context, projectName)
+  }
+}
+
+const val TOKEN_PARAM_NAME = "__ij-st"
+
+private val STANDARD_COOKIE by lazy {
+  val productName = ApplicationNamesInfo.getInstance().lowercaseProductName
+  val configPath = PathManager.getConfigPath()
+  val cookieName = productName + "-" + Integer.toHexString(configPath.hashCode())
+  val file = File(configPath, cookieName)
+  var token: String? = null
+  if (file.exists()) {
+    try {
+      token = UUID.fromString(FileUtil.loadFile(file)).toString()
+    }
+    catch (e: Exception) {
+      LOG.warn(e)
+    }
+  }
+  if (token == null) {
+    token = UUID.randomUUID().toString()
+    FileUtil.writeToFile(file, token!!)
+  }
+
+  // explicit setting domain cookie on localhost doesn't work for chrome
+  // http://stackoverflow.com/questions/8134384/chrome-doesnt-create-cookie-for-domain-localhost-in-broken-https
+  val cookie = DefaultCookie(cookieName, token!!)
+  cookie.isHttpOnly = true
+  cookie.setMaxAge(TimeUnit.DAYS.toSeconds(365 * 10))
+  cookie.setPath("/")
+  cookie
+}
+
+// expire after access because we reuse tokens
+private val tokens = CacheBuilder.newBuilder().expireAfterAccess(1, TimeUnit.MINUTES).build<String, Boolean>()
+
+internal fun acquireToken(): String {
+  var token = tokens.asMap().keys.firstOrNull()
+  if (token == null) {
+    token = UUID.randomUUID().toString()
+    tokens.put(token, java.lang.Boolean.TRUE)
   }
+  return token
 }
 
-private fun doProcess(request: FullHttpRequest, context: ChannelHandlerContext, projectNameAsHost: String?): Boolean {
-  val decodedPath = URLUtil.unescapePercentSequences(UriUtil.trimParameters(request.uri()))
+private fun doProcess(urlDecoder: QueryStringDecoder, request: FullHttpRequest, context: ChannelHandlerContext, projectNameAsHost: String?): Boolean {
+  val decodedPath = URLUtil.unescapePercentSequences(urlDecoder.path())
   var offset: Int
   var isEmptyPath: Boolean
   val isCustomHost = projectNameAsHost != null
@@ -138,7 +195,11 @@ private fun doProcess(request: FullHttpRequest, context: ChannelHandlerContext,
 
   val path = toIdeaPath(decodedPath, offset)
   if (path == null) {
-    HttpResponseStatus.BAD_REQUEST.send(context.channel(), request)
+    HttpResponseStatus.BAD_REQUEST.orInSafeMode(HttpResponseStatus.NOT_FOUND).send(context.channel(), request)
+    return true
+  }
+
+  if (!validateToken(request, context.channel(), urlDecoder)) {
     return true
   }
 
@@ -152,6 +213,50 @@ private fun doProcess(request: FullHttpRequest, context: ChannelHandlerContext,
   return false
 }
 
+private fun validateToken(request: HttpRequest, channel: Channel, urlDecoder: QueryStringDecoder): Boolean {
+  val cookieString = request.headers().get(HttpHeaderNames.COOKIE)
+  if (cookieString != null) {
+    val cookies = ServerCookieDecoder.STRICT.decode(cookieString)
+    for (cookie in cookies) {
+      if (cookie.name() == STANDARD_COOKIE.name()) {
+        if (cookie.value() == STANDARD_COOKIE.value()) {
+          return true
+        }
+        break
+      }
+    }
+  }
+
+  // we must check referrer - if html cached, browser will send request without query
+  val token = urlDecoder.parameters().get(TOKEN_PARAM_NAME)?.firstOrNull() ?: request.referrer?.let { QueryStringDecoder(it).parameters().get(TOKEN_PARAM_NAME)?.firstOrNull() }
+  val url = "${channel.uriScheme}://${request.host!!}${urlDecoder.path()}"
+  if (token != null && tokens.getIfPresent(token) != null) {
+    tokens.invalidate(token)
+    // we redirect because it is not easy to change and maintain all places where we send response
+    val response = HttpResponseStatus.TEMPORARY_REDIRECT.response()
+    response.headers().add(HttpHeaderNames.LOCATION, url)
+    response.headers().set(HttpHeaderNames.SET_COOKIE, ServerCookieEncoder.STRICT.encode(STANDARD_COOKIE))
+    response.send(channel, request)
+    return true
+  }
+
+  SwingUtilities.invokeAndWait {
+    ProjectUtil.focusProjectWindow(null, true)
+
+    if (MessageDialogBuilder
+        .yesNo("", "Page '" + StringUtil.trimMiddle(url, 50) + "' requested without authorization, " +
+            "\nyou can copy URL and open it in browser to trust it.")
+        .icon(Messages.getWarningIcon())
+        .yesText("Copy authorization URL to clipboard")
+        .show() == Messages.YES) {
+      CopyPasteManager.getInstance().setContents(StringSelection(url + "?" + TOKEN_PARAM_NAME + "=" + acquireToken()))
+    }
+  }
+
+  HttpResponseStatus.UNAUTHORIZED.orInSafeMode(HttpResponseStatus.NOT_FOUND).send(channel, request)
+  return false
+}
+
 private fun toIdeaPath(decodedPath: String, offset: Int): String? {
   // must be absolute path (relative to DOCUMENT_ROOT, i.e. scheme://authority/) to properly canonicalize
   val path = decodedPath.substring(offset)
@@ -221,7 +326,9 @@ fun findIndexFile(basedir: Path): Path? {
   return null
 }
 
-fun isOwnHostName(host: String): Boolean {
+// is host loopback/any or network interface address (i.e. not custom domain)
+// must be not used to check is host on local machine
+internal fun isOwnHostName(host: String): Boolean {
   if (NetUtils.isLocalhost(host)) {
     return true
   }
@@ -237,7 +344,18 @@ fun isOwnHostName(host: String): Boolean {
     // develar.local is own host name: develar. equals to "develar.labs.intellij.net" (canonical host name)
     return localHostName.equals(host, ignoreCase = true) || (host.endsWith(".local") && localHostName.regionMatches(0, host, 0, host.length - ".local".length, true))
   }
-  catch (ignored: UnknownHostException) {
+  catch (ignored: IOException) {
     return false
   }
+}
+
+internal fun canBeAccessedDirectly(path: String): Boolean {
+  for (fileHandler in WebServerFileHandler.EP_NAME.extensions) {
+    for (ext in fileHandler.pageFileExtensions) {
+      if (FileUtilRt.extensionEquals(path, ext)) {
+        return true
+      }
+    }
+  }
+  return false
 }
\ No newline at end of file
index 128e3be572dc06dc12d132b9a0dfccb817dccfce..5f2fefc0950a5a41c851dcfdc34d703a1c6fdbc8 100644 (file)
@@ -20,13 +20,18 @@ import com.intellij.openapi.project.Project
 import com.intellij.openapi.util.io.endsWithName
 import com.intellij.openapi.util.io.endsWithSlash
 import com.intellij.openapi.util.io.getParentPath
+import com.intellij.openapi.vfs.VFileProperty
 import com.intellij.openapi.vfs.VirtualFile
 import com.intellij.util.PathUtilRt
+import com.intellij.util.isDirectory
+import io.netty.channel.Channel
 import io.netty.channel.ChannelHandlerContext
 import io.netty.handler.codec.http.FullHttpRequest
+import io.netty.handler.codec.http.HttpRequest
 import io.netty.handler.codec.http.HttpResponseStatus
-import org.jetbrains.io.send
+import org.jetbrains.io.*
 import java.nio.file.Path
+import java.nio.file.Paths
 
 private class DefaultWebServerPathHandler : WebServerPathHandler() {
   override fun process(path: String,
@@ -56,11 +61,6 @@ private class DefaultWebServerPathHandler : WebServerPathHandler() {
 
     var indexUsed = false
     if (pathInfo.isDirectory()) {
-      if (!endsWithSlash(decodedRawPath)) {
-        redirectToDirectory(request, channel, if (isCustomHost) path else "$projectName/$path")
-        return true
-      }
-
       var indexVirtualFile: VirtualFile? = null
       var indexFile: Path? = null
       if (pathInfo.file == null) {
@@ -71,7 +71,13 @@ private class DefaultWebServerPathHandler : WebServerPathHandler() {
       }
 
       if (indexFile == null && indexVirtualFile == null) {
-        HttpResponseStatus.NOT_FOUND.send(channel, request, "Index file doesn't exist.")
+        HttpResponseStatus.NOT_FOUND.send(channel, request)
+        return true
+      }
+
+      // we must redirect only after index file check to not expose directory status
+      if (!endsWithSlash(decodedRawPath)) {
+        redirectToDirectory(request, channel, if (isCustomHost) path else "$projectName/$path")
         return true
       }
 
@@ -80,6 +86,11 @@ private class DefaultWebServerPathHandler : WebServerPathHandler() {
       pathToFileManager.pathToInfoCache.put(path, pathInfo)
     }
 
+    if (request.origin == null && request.referrer == null && request.isRegularBrowser() && !canBeAccessedDirectly(pathInfo.name)) {
+      HttpResponseStatus.NOT_FOUND.send(context.channel(), request)
+      return true
+    }
+
     if (!indexUsed && !endsWithName(path, pathInfo.name)) {
       if (endsWithSlash(decodedRawPath)) {
         indexUsed = true
@@ -94,6 +105,10 @@ private class DefaultWebServerPathHandler : WebServerPathHandler() {
       }
     }
 
+    if (!checkAccess(pathInfo, channel, request)) {
+      return true
+    }
+
     val canonicalPath = if (indexUsed) "$path/${pathInfo.name}" else path
     for (fileHandler in WebServerFileHandler.EP_NAME.extensions) {
       LOG.catchAndLog {
@@ -104,4 +119,24 @@ private class DefaultWebServerPathHandler : WebServerPathHandler() {
     }
     return false
   }
+}
+
+private fun checkAccess(pathInfo: PathInfo, channel: Channel, request: HttpRequest): Boolean {
+  if (pathInfo.ioFile != null || pathInfo.file!!.isInLocalFileSystem) {
+    val file = pathInfo.ioFile ?: Paths.get(pathInfo.file!!.path)
+    if (file.isDirectory()) {
+      HttpResponseStatus.FORBIDDEN.orInSafeMode(HttpResponseStatus.NOT_FOUND).send(channel, request)
+      return false
+    }
+    else if (!checkAccess(file, Paths.get(pathInfo.root.path))) {
+      HttpResponseStatus.FORBIDDEN.orInSafeMode(HttpResponseStatus.NOT_FOUND).send(channel, request)
+      return false
+    }
+  }
+  else if (pathInfo.file!!.`is`(VFileProperty.HIDDEN)) {
+    HttpResponseStatus.FORBIDDEN.orInSafeMode(HttpResponseStatus.NOT_FOUND).send(channel, request)
+    return false
+  }
+
+  return true
 }
\ No newline at end of file
index ea215e5357094db7ff4a657c71490c0a0c9564b4..bd458b234473c2637f85e03998b4ca6211ef2cac 100644 (file)
@@ -1,38 +1,39 @@
 package org.jetbrains.builtInWebServer
 
 import com.intellij.openapi.project.Project
-import com.intellij.openapi.util.text.StringUtilRt
 import com.intellij.util.PathUtilRt
-import com.intellij.util.isDirectory
 import io.netty.buffer.ByteBufUtf8Writer
 import io.netty.channel.Channel
 import io.netty.channel.ChannelFutureListener
-import io.netty.handler.codec.http.*
+import io.netty.handler.codec.http.FullHttpRequest
+import io.netty.handler.codec.http.HttpMethod
+import io.netty.handler.codec.http.HttpUtil
+import io.netty.handler.codec.http.LastHttpContent
 import io.netty.handler.stream.ChunkedStream
 import org.jetbrains.builtInWebServer.ssi.SsiExternalResolver
 import org.jetbrains.builtInWebServer.ssi.SsiProcessor
 import org.jetbrains.io.FileResponses
 import org.jetbrains.io.addKeepAliveIfNeed
-import org.jetbrains.io.send
 import java.nio.file.Files
 import java.nio.file.Path
 import java.nio.file.Paths
 
 private class StaticFileHandler : WebServerFileHandler() {
+  override val pageFileExtensions = arrayOf("html", "htm", "shtml", "stm", "shtm")
+
   private var ssiProcessor: SsiProcessor? = null
 
   override fun process(pathInfo: PathInfo, canonicalPath: CharSequence, project: Project, request: FullHttpRequest, channel: Channel, projectNameIfNotCustomHost: String?): Boolean {
     if (pathInfo.ioFile != null || pathInfo.file!!.isInLocalFileSystem) {
       val ioFile = pathInfo.ioFile ?: Paths.get(pathInfo.file!!.path)
-
-      val nameSequence = pathInfo.name
+      val nameSequence = ioFile.fileName.toString()
       //noinspection SpellCheckingInspection
-      if (StringUtilRt.endsWithIgnoreCase(nameSequence, ".shtml") || StringUtilRt.endsWithIgnoreCase(nameSequence, ".stm") || StringUtilRt.endsWithIgnoreCase(nameSequence, ".shtm")) {
+      if (nameSequence.endsWith(".shtml", true) || nameSequence.endsWith(".stm", true) || nameSequence.endsWith(".shtm", true)) {
         processSsi(ioFile, PathUtilRt.getParentPath(canonicalPath.toString()), project, request, channel)
         return true
       }
 
-      sendIoFile(channel, ioFile, request)
+      FileResponses.sendFile(request, channel, ioFile)
     }
     else {
       val file = pathInfo.file!!
@@ -94,14 +95,17 @@ private class StaticFileHandler : WebServerFileHandler() {
   }
 }
 
-fun sendIoFile(channel: Channel, ioFile: Path, request: HttpRequest) {
-  if (hasAccess(ioFile)) {
-    FileResponses.sendFile(request, channel, ioFile)
-  }
-  else {
-    HttpResponseStatus.FORBIDDEN.send(channel, request)
+internal fun checkAccess(file: Path, root: Path = file.root): Boolean {
+  var parent = file
+  do {
+    if (!hasAccess(parent)) {
+      return false
+    }
+    parent = parent.parent ?: break
   }
+  while (parent != root)
+  return true
 }
 
-// deny access to .htaccess files
-private fun hasAccess(result: Path) = !result.isDirectory() && Files.isReadable(result) && !(Files.isHidden(result) || result.fileName.toString().startsWith(".ht"))
\ No newline at end of file
+// deny access to any dot prefixed file
+private fun hasAccess(result: Path) = Files.isReadable(result) && !(Files.isHidden(result) || result.fileName.toString().startsWith('.'))
\ No newline at end of file
index d2eed3717aca7bfb71b3c910fcbd1171082538e5..1e823c4861a475e8a9254d4790904247a6e1722e 100644 (file)
@@ -25,6 +25,9 @@ abstract class WebServerFileHandler {
     internal val EP_NAME = ExtensionPointName.create<WebServerFileHandler>("org.jetbrains.webServerFileHandler")
   }
 
+  open val pageFileExtensions: Array<String>
+    get() = emptyArray()
+
   /**
    * canonicalRequestPath contains index file name (if not specified in the request)
    */
@@ -34,7 +37,6 @@ abstract class WebServerFileHandler {
                        request: FullHttpRequest,
                        channel: Channel,
                        projectNameIfNotCustomHost: String?): Boolean
-
 }
 
 fun getRequestPath(canonicalPath: CharSequence, projectNameIfNotCustomHost: String?) = if (projectNameIfNotCustomHost == null) "/$canonicalPath" else "/$projectNameIfNotCustomHost/$canonicalPath"
\ No newline at end of file
index d3b76cf3b5c19099d9fc121be433be075fa90699..061c32227831383f01c3f52e4f1453f096c54b7f 100644 (file)
@@ -30,6 +30,7 @@ import com.intellij.openapi.util.text.StringUtil;
 import io.netty.channel.ChannelHandlerContext;
 import io.netty.handler.codec.http.FullHttpRequest;
 import io.netty.handler.codec.http.HttpMethod;
+import io.netty.handler.codec.http.HttpRequest;
 import io.netty.handler.codec.http.QueryStringDecoder;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
@@ -169,4 +170,9 @@ final class DiffHttpService extends RestService {
     reader.endArray();
     return null;
   }
+
+  @Override
+  public boolean isAccessible(@NotNull HttpRequest request) {
+    return true;
+  }
 }
diff --git a/platform/built-in-server/src/org/jetbrains/ide/OpenFileHttpService.java b/platform/built-in-server/src/org/jetbrains/ide/OpenFileHttpService.java
deleted file mode 100644 (file)
index 1a6ddfc..0000000
+++ /dev/null
@@ -1,350 +0,0 @@
-/*
- * Copyright 2000-2015 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.jetbrains.ide;
-
-import com.intellij.openapi.application.AccessToken;
-import com.intellij.openapi.application.ApplicationManager;
-import com.intellij.openapi.application.ModalityState;
-import com.intellij.openapi.application.WriteAction;
-import com.intellij.openapi.fileEditor.OpenFileDescriptor;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.project.ProjectManager;
-import com.intellij.openapi.project.ProjectUtil;
-import com.intellij.openapi.util.Ref;
-import com.intellij.openapi.util.io.FileUtil;
-import com.intellij.openapi.util.text.StringUtil;
-import com.intellij.openapi.util.text.StringUtilRt;
-import com.intellij.openapi.vcs.ProjectLevelVcsManager;
-import com.intellij.openapi.vcs.VcsRoot;
-import com.intellij.openapi.vfs.LocalFileSystem;
-import com.intellij.openapi.vfs.VirtualFile;
-import com.intellij.openapi.vfs.newvfs.ManagingFS;
-import com.intellij.openapi.vfs.newvfs.RefreshQueue;
-import com.intellij.openapi.vfs.newvfs.RefreshSession;
-import com.intellij.ui.AppUIUtil;
-import com.intellij.util.Consumer;
-import io.netty.channel.Channel;
-import io.netty.channel.ChannelHandlerContext;
-import io.netty.handler.codec.http.*;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-import org.jetbrains.builtInWebServer.WebServerPathToFileManager;
-import org.jetbrains.concurrency.AsyncPromise;
-import org.jetbrains.concurrency.Promise;
-
-import javax.swing.*;
-import java.io.File;
-import java.io.IOException;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * @api {get} /file Open file
- * @apiName file
- * @apiGroup Platform
- *
- * @apiParam {String} file The path of the file. Relative (to project base dir, VCS root, module source or content root) or absolute.
- * @apiParam {Integer} [line] The line number of the file (1-based).
- * @apiParam {Integer} [column] The column number of the file (1-based).
- * @apiParam {Boolean} [focused=true] Whether to focus project window.
- *
- * @apiExample {curl} Absolute path
- * curl http://localhost:63342/api/file//absolute/path/to/file.kt
- *
- * @apiExample {curl} Relative path
- * curl http://localhost:63342/api/file/relative/to/module/root/path/to/file.kt
- *
- * @apiExample {curl} With line and column
- * curl http://localhost:63342/api/file/relative/to/module/root/path/to/file.kt:100:34
- *
- * @apiExample {curl} Query parameters
- * curl http://localhost:63342/api/file?file=path/to/file.kt&line=100&column=34
- */
-class OpenFileHttpService extends RestService {
-  private static final RuntimeException NOT_FOUND = Promise.createError("not found");
-  private static final Pattern LINE_AND_COLUMN = Pattern.compile("^(.*?)(?::(\\d+))?(?::(\\d+))?$");
-  private long lastTimeRejected = -1;
-  private long waitUntilNextRequestTimeout = 0;
-
-  private volatile long refreshSessionId = 0;
-  private final ConcurrentLinkedQueue<OpenFileTask> requests = new ConcurrentLinkedQueue<OpenFileTask>();
-
-  @NotNull
-  @Override
-  protected String getServiceName() {
-    return "file";
-  }
-
-  @Override
-  protected boolean isMethodSupported(@NotNull HttpMethod method) {
-    return method == HttpMethod.GET || method == HttpMethod.POST;
-  }
-
-  @Nullable
-  @Override
-  public String execute(@NotNull QueryStringDecoder urlDecoder, @NotNull FullHttpRequest request, @NotNull ChannelHandlerContext context) throws IOException {
-    final boolean keepAlive = HttpUtil.isKeepAlive(request);
-    final Channel channel = context.channel();
-
-    OpenFileRequest apiRequest;
-    if (request.method() == HttpMethod.POST) {
-      apiRequest = gson.getValue().fromJson(createJsonReader(request), OpenFileRequest.class);
-    }
-    else {
-      apiRequest = new OpenFileRequest();
-      apiRequest.file = StringUtil.nullize(getStringParameter("file", urlDecoder), true);
-      apiRequest.line = getIntParameter("line", urlDecoder);
-      apiRequest.column = getIntParameter("column", urlDecoder);
-      apiRequest.focused = getBooleanParameter("focused", urlDecoder, true);
-    }
-
-    int prefixLength = 1 + PREFIX.length() + 1 + getServiceName().length() + 1;
-    String path = urlDecoder.path();
-    if (path.length() > prefixLength) {
-      Matcher matcher = LINE_AND_COLUMN.matcher(path).region(prefixLength, path.length());
-      LOG.assertTrue(matcher.matches());
-      if (apiRequest.file == null) {
-        apiRequest.file = matcher.group(1).trim();
-      }
-      if (apiRequest.line == -1) {
-        apiRequest.line = StringUtilRt.parseInt(matcher.group(2), 1);
-      }
-      if (apiRequest.column == -1) {
-        apiRequest.column = StringUtilRt.parseInt(matcher.group(3), 1);
-      }
-    }
-
-    if (apiRequest.file == null) {
-      sendStatus(HttpResponseStatus.BAD_REQUEST, keepAlive, channel);
-      return null;
-    }
-
-    openFile(apiRequest)
-      .done(new Consumer<Void>() {
-        @Override
-        public void consume(Void aVoid) {
-          sendStatus(HttpResponseStatus.OK, keepAlive, channel);
-        }
-      })
-      .rejected(new Consumer<Throwable>() {
-        @Override
-        public void consume(Throwable throwable) {
-          if (throwable == NOT_FOUND) {
-            sendStatus(HttpResponseStatus.NOT_FOUND, keepAlive, channel);
-          }
-          else {
-            // todo send error
-            sendStatus(HttpResponseStatus.INTERNAL_SERVER_ERROR, keepAlive, channel);
-            LOG.error(throwable);
-          }
-        }
-      });
-    return null;
-  }
-
-  private static void navigate(@Nullable Project project, @NotNull VirtualFile file, @NotNull OpenFileRequest request) {
-    if (project == null) {
-      project = getLastFocusedOrOpenedProject();
-      if (project == null) {
-        project = ProjectManager.getInstance().getDefaultProject();
-      }
-    }
-
-    // OpenFileDescriptor line and column number are 0-based.
-    new OpenFileDescriptor(project, file, Math.max(request.line - 1, 0), Math.max(request.column - 1, 0)).navigate(true);
-    if (request.focused) {
-      com.intellij.ide.impl.ProjectUtil.focusProjectWindow(project, true);
-    }
-  }
-
-  @NotNull
-  Promise<Void> openFile(@NotNull OpenFileRequest request) {
-    String systemIndependentName = FileUtil.toSystemIndependentName(FileUtil.expandUserHome(request.file));
-    final File file = new File(systemIndependentName);
-
-    if (file.isAbsolute()) {
-      if (com.intellij.ide.impl.ProjectUtil.isRemotePath(systemIndependentName)) {
-        final Ref<Promise<Void>> result = new Ref<>();
-        try {
-          SwingUtilities.invokeAndWait(new Runnable() {
-            @Override
-            public void run() {
-              if (System.currentTimeMillis() - lastTimeRejected < waitUntilNextRequestTimeout) {
-                return;
-              }
-              boolean value = com.intellij.ide.impl.ProjectUtil
-                .confirmLoadingFromRemotePath(systemIndependentName, "warning.load.file.from.share", "title.load.file.from.share");
-
-              if (value != Boolean.TRUE) {
-                lastTimeRejected = System.currentTimeMillis();
-                waitUntilNextRequestTimeout = Math.min(2 * Math.max(waitUntilNextRequestTimeout, 2000), 60 * 60 * 1000); //to avoid negative values
-                result.set(Promise.reject(NOT_FOUND));
-              } else {
-                waitUntilNextRequestTimeout = 0;
-              }
-            }
-          });
-        } catch (Throwable ignored) {}
-
-        if (result.get() != null) {
-          return result.get();
-        }
-      }
-
-      return openAbsolutePath(file, request);
-    }
-
-    // we don't want to call refresh for each attempt on findFileByRelativePath call, so, we do what ourSaveAndSyncHandlerImpl does on frame activation
-    RefreshQueue queue = RefreshQueue.getInstance();
-    queue.cancelSession(refreshSessionId);
-    OpenFileTask task = new OpenFileTask(FileUtil.toCanonicalPath(systemIndependentName, '/'), request);
-    requests.offer(task);
-    RefreshSession session = queue.createSession(true, true, new Runnable() {
-      @Override
-      public void run() {
-        OpenFileTask task;
-        while ((task = requests.poll()) != null) {
-          try {
-            if (openRelativePath(task.path, task.request)) {
-              task.promise.setResult(null);
-            }
-            else {
-              task.promise.setError(NOT_FOUND);
-            }
-          }
-          catch (Throwable e) {
-            task.promise.setError(e);
-          }
-        }
-      }
-    }, ModalityState.NON_MODAL);
-
-    session.addAllFiles(ManagingFS.getInstance().getLocalRoots());
-    refreshSessionId = session.getId();
-    session.launch();
-    return task.promise;
-  }
-
-  // path must be normalized
-  private static boolean openRelativePath(@NotNull final String path, @NotNull final OpenFileRequest request) {
-    VirtualFile virtualFile = null;
-    Project project = null;
-
-    Project[] projects = ProjectManager.getInstance().getOpenProjects();
-    for (Project openedProject : projects) {
-      VirtualFile openedProjectBaseDir = openedProject.getBaseDir();
-      if (openedProjectBaseDir != null) {
-        virtualFile = openedProjectBaseDir.findFileByRelativePath(path);
-      }
-
-      if (virtualFile == null) {
-        virtualFile = WebServerPathToFileManager.getInstance(openedProject).findVirtualFile(path);
-      }
-      if (virtualFile != null) {
-        project = openedProject;
-        break;
-      }
-    }
-
-    if (virtualFile == null) {
-      for (Project openedProject : projects) {
-        for (VcsRoot vcsRoot : ProjectLevelVcsManager.getInstance(openedProject).getAllVcsRoots()) {
-          VirtualFile root = vcsRoot.getPath();
-          if (root != null) {
-            virtualFile = root.findFileByRelativePath(path);
-            if (virtualFile != null) {
-              project = openedProject;
-              break;
-            }
-          }
-        }
-      }
-    }
-
-    if (virtualFile == null) {
-      return false;
-    }
-
-    final Project finalProject = project;
-    final VirtualFile finalVirtualFile = virtualFile;
-    AppUIUtil.invokeLaterIfProjectAlive(project, new Runnable() {
-      @Override
-      public void run() {
-        navigate(finalProject, finalVirtualFile, request);
-      }
-    });
-    return true;
-  }
-
-  @NotNull
-  private static Promise<Void> openAbsolutePath(@NotNull final File file, @NotNull final OpenFileRequest request) {
-    if (!file.exists()) {
-      return Promise.reject(NOT_FOUND);
-    }
-
-    final AsyncPromise<Void> promise = new AsyncPromise<Void>();
-    ApplicationManager.getApplication().invokeLater(new Runnable() {
-      @Override
-      public void run() {
-        try {
-          VirtualFile virtualFile;
-          AccessToken token = WriteAction.start();
-          try {
-            virtualFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(file);
-          }
-          finally {
-            token.finish();
-          }
-
-          if (virtualFile == null) {
-            promise.setError(NOT_FOUND);
-          }
-          else {
-            navigate(ProjectUtil.guessProjectForContentFile(virtualFile), virtualFile, request);
-            promise.setResult(null);
-          }
-        }
-        catch (Throwable e) {
-          promise.setError(e);
-        }
-      }
-    });
-    return promise;
-  }
-
-  private static final class OpenFileTask {
-    final String path;
-    final OpenFileRequest request;
-
-    final AsyncPromise<Void> promise = new AsyncPromise<Void>();
-
-    OpenFileTask(@NotNull String path, @NotNull OpenFileRequest request) {
-      this.path = path;
-      this.request = request;
-    }
-  }
-
-  static final class OpenFileRequest {
-    public String file;
-    // The line number of the file (1-based)
-    public int line;
-    // The column number of the file (1-based)
-    public int column;
-
-    public boolean focused = true;
-  }
-}
diff --git a/platform/built-in-server/src/org/jetbrains/ide/OpenFileHttpService.kt b/platform/built-in-server/src/org/jetbrains/ide/OpenFileHttpService.kt
new file mode 100644 (file)
index 0000000..d924544
--- /dev/null
@@ -0,0 +1,272 @@
+/*
+ * Copyright 2000-2015 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.ide
+
+import com.intellij.ide.impl.ProjectUtil.focusProjectWindow
+import com.intellij.openapi.application.ApplicationManager
+import com.intellij.openapi.application.ModalityState
+import com.intellij.openapi.application.runWriteAction
+import com.intellij.openapi.fileEditor.OpenFileDescriptor
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.project.ProjectManager
+import com.intellij.openapi.project.ProjectUtil
+import com.intellij.openapi.util.io.FileUtil
+import com.intellij.openapi.util.text.StringUtil
+import com.intellij.openapi.util.text.StringUtilRt
+import com.intellij.openapi.vcs.ProjectLevelVcsManager
+import com.intellij.openapi.vfs.LocalFileSystem
+import com.intellij.openapi.vfs.VirtualFile
+import com.intellij.openapi.vfs.newvfs.ManagingFS
+import com.intellij.openapi.vfs.newvfs.RefreshQueue
+import com.intellij.ui.AppUIUtil
+import com.intellij.util.exists
+import com.intellij.util.systemIndependentPath
+import io.netty.channel.ChannelHandlerContext
+import io.netty.handler.codec.http.*
+import org.jetbrains.builtInWebServer.WebServerPathToFileManager
+import org.jetbrains.builtInWebServer.checkAccess
+import org.jetbrains.concurrency.AsyncPromise
+import org.jetbrains.concurrency.Promise
+import org.jetbrains.concurrency.catchError
+import org.jetbrains.concurrency.rejectedPromise
+import org.jetbrains.io.orInSafeMode
+import org.jetbrains.io.send
+import java.nio.file.Path
+import java.nio.file.Paths
+import java.util.concurrent.ConcurrentLinkedQueue
+import java.util.regex.Pattern
+import javax.swing.SwingUtilities
+
+private val NOT_FOUND = Promise.createError("not found")
+private val LINE_AND_COLUMN = Pattern.compile("^(.*?)(?::(\\d+))?(?::(\\d+))?$")
+
+/**
+ * @api {get} /file Open file
+ * @apiName file
+ * @apiGroup Platform
+ *
+ * @apiParam {String} file The path of the file. Relative (to project base dir, VCS root, module source or content root) or absolute.
+ * @apiParam {Integer} [line] The line number of the file (1-based).
+ * @apiParam {Integer} [column] The column number of the file (1-based).
+ * @apiParam {Boolean} [focused=true] Whether to focus project window.
+ *
+ * @apiExample {curl} Absolute path
+ * curl http://localhost:63342/api/file//absolute/path/to/file.kt
+ *
+ * @apiExample {curl} Relative path
+ * curl http://localhost:63342/api/file/relative/to/module/root/path/to/file.kt
+ *
+ * @apiExample {curl} With line and column
+ * curl http://localhost:63342/api/file/relative/to/module/root/path/to/file.kt:100:34
+ *
+ * @apiExample {curl} Query parameters
+ * curl http://localhost:63342/api/file?file=path/to/file.kt&line=100&column=34
+ */
+internal class OpenFileHttpService : RestService() {
+  @Volatile private var refreshSessionId: Long = 0
+  private val requests = ConcurrentLinkedQueue<OpenFileTask>()
+
+  override fun getServiceName() = "file"
+
+  override fun isMethodSupported(method: HttpMethod) = method === HttpMethod.GET || method === HttpMethod.POST
+
+  override fun execute(urlDecoder: QueryStringDecoder, request: FullHttpRequest, context: ChannelHandlerContext): String? {
+    val keepAlive = HttpUtil.isKeepAlive(request)
+    val channel = context.channel()
+
+    val apiRequest: OpenFileRequest
+    if (request.method() === HttpMethod.POST) {
+      apiRequest = gson.value.fromJson(createJsonReader(request), OpenFileRequest::class.java)
+    }
+    else {
+      apiRequest = OpenFileRequest()
+      apiRequest.file = StringUtil.nullize(getStringParameter("file", urlDecoder), true)
+      apiRequest.line = getIntParameter("line", urlDecoder)
+      apiRequest.column = getIntParameter("column", urlDecoder)
+      apiRequest.focused = getBooleanParameter("focused", urlDecoder, true)
+    }
+
+    val prefixLength = 1 + PREFIX.length + 1 + serviceName.length + 1
+    val path = urlDecoder.path()
+    if (path.length > prefixLength) {
+      val matcher = LINE_AND_COLUMN.matcher(path).region(prefixLength, path.length)
+      LOG.assertTrue(matcher.matches())
+      if (apiRequest.file == null) {
+        apiRequest.file = matcher.group(1).trim { it <= ' ' }
+      }
+      if (apiRequest.line == -1) {
+        apiRequest.line = StringUtilRt.parseInt(matcher.group(2), 1)
+      }
+      if (apiRequest.column == -1) {
+        apiRequest.column = StringUtilRt.parseInt(matcher.group(3), 1)
+      }
+    }
+
+    if (apiRequest.file == null) {
+      sendStatus(HttpResponseStatus.BAD_REQUEST, keepAlive, channel)
+      return null
+    }
+
+    val promise = openFile(apiRequest, context, request) ?: return null
+    promise.done { sendStatus(HttpResponseStatus.OK, keepAlive, channel) }
+      .rejected {
+        if (it === NOT_FOUND) {
+          // don't expose file status
+          sendStatus(HttpResponseStatus.NOT_FOUND.orInSafeMode(HttpResponseStatus.OK), keepAlive, channel)
+          LOG.warn("File ${apiRequest.file} not found")
+        }
+        else {
+          // todo send error
+          sendStatus(HttpResponseStatus.INTERNAL_SERVER_ERROR, keepAlive, channel)
+          LOG.error(it)
+        }
+      }
+    return null
+  }
+
+  internal fun openFile(request: OpenFileRequest, context: ChannelHandlerContext, httpRequest: HttpRequest?): Promise<Void>? {
+    val systemIndependentPath = FileUtil.toSystemIndependentName(FileUtil.expandUserHome(request.file!!))
+    val file = Paths.get(FileUtil.toSystemDependentName(systemIndependentPath))
+    if (file.isAbsolute) {
+      if (!file.exists()) {
+        return rejectedPromise(NOT_FOUND)
+      }
+
+      var isAllowed = checkAccess(file)
+      if (isAllowed && com.intellij.ide.impl.ProjectUtil.isRemotePath(systemIndependentPath)) {
+        // invokeAndWait is added to avoid processing many requests in this place: e.g. to prevent abuse of opening many remote files
+        SwingUtilities.invokeAndWait {
+          isAllowed = com.intellij.ide.impl.ProjectUtil.confirmLoadingFromRemotePath(systemIndependentPath, "warning.load.file.from.share", "title.load.file.from.share")
+        }
+      }
+
+      if (isAllowed) {
+        return openAbsolutePath(file, request)
+      }
+      else {
+        HttpResponseStatus.FORBIDDEN.orInSafeMode(HttpResponseStatus.OK).send(context.channel(), httpRequest)
+        return null
+      }
+    }
+
+    // we don't want to call refresh for each attempt on findFileByRelativePath call, so, we do what ourSaveAndSyncHandlerImpl does on frame activation
+    val queue = RefreshQueue.getInstance()
+    queue.cancelSession(refreshSessionId)
+    val mainTask = OpenFileTask(FileUtil.toCanonicalPath(systemIndependentPath, '/'), request)
+    requests.offer(mainTask)
+    val session = queue.createSession(true, true, {
+      while (true) {
+        val task = requests.poll() ?: break
+        task.promise.catchError {
+          if (openRelativePath(task.path, task.request)) {
+            task.promise.setResult(null)
+          }
+          else {
+            task.promise.setError(NOT_FOUND)
+          }
+        }
+      }
+    }, ModalityState.NON_MODAL)
+
+    session.addAllFiles(*ManagingFS.getInstance().localRoots)
+    refreshSessionId = session.id
+    session.launch()
+    return mainTask.promise
+  }
+
+  override fun isAccessible(request: HttpRequest) = true
+}
+
+internal class OpenFileRequest {
+  var file: String? = null
+  // The line number of the file (1-based)
+  var line = 0
+  // The column number of the file (1-based)
+  var column = 0
+
+  var focused = true
+}
+
+private class OpenFileTask(internal val path: String, internal val request: OpenFileRequest) {
+  internal val promise = AsyncPromise<Void>()
+}
+
+private fun navigate(project: Project?, file: VirtualFile, request: OpenFileRequest) {
+  val effectiveProject = project ?: RestService.getLastFocusedOrOpenedProject() ?: ProjectManager.getInstance().defaultProject
+  // OpenFileDescriptor line and column number are 0-based.
+  OpenFileDescriptor(effectiveProject, file, Math.max(request.line - 1, 0), Math.max(request.column - 1, 0)).navigate(true)
+  if (request.focused) {
+    focusProjectWindow(project, true)
+  }
+}
+
+// path must be normalized
+private fun openRelativePath(path: String, request: OpenFileRequest): Boolean {
+  var virtualFile: VirtualFile? = null
+  var project: Project? = null
+
+  val projects = ProjectManager.getInstance().openProjects
+  for (openedProject in projects) {
+    openedProject.baseDir?.let {
+      virtualFile = it.findFileByRelativePath(path)
+    }
+
+    if (virtualFile == null) {
+      virtualFile = WebServerPathToFileManager.getInstance(openedProject).findVirtualFile(path)
+    }
+    if (virtualFile != null) {
+      project = openedProject
+      break
+    }
+  }
+
+  if (virtualFile == null) {
+    for (openedProject in projects) {
+      for (vcsRoot in ProjectLevelVcsManager.getInstance(openedProject).allVcsRoots) {
+        val root = vcsRoot.path
+        if (root != null) {
+          virtualFile = root.findFileByRelativePath(path)
+          if (virtualFile != null) {
+            project = openedProject
+            break
+          }
+        }
+      }
+    }
+  }
+
+  return virtualFile?.let {
+    AppUIUtil.invokeLaterIfProjectAlive(project!!, Runnable { navigate(project, it, request) })
+    true
+  } ?: false
+}
+
+private fun openAbsolutePath(file: Path, request: OpenFileRequest): Promise<Void> {
+  val promise = AsyncPromise<Void>()
+  ApplicationManager.getApplication().invokeLater {
+    promise.catchError {
+      val virtualFile = runWriteAction {  LocalFileSystem.getInstance().refreshAndFindFileByPath(file.systemIndependentPath) }
+      if (virtualFile == null) {
+        promise.setError(NOT_FOUND)
+      }
+      else {
+        navigate(ProjectUtil.guessProjectForContentFile(virtualFile), virtualFile, request)
+        promise.setResult(null)
+      }
+    }
+  }
+  return promise
+}
diff --git a/platform/built-in-server/src/org/jetbrains/ide/OpenFileXmlRpcHandler.java b/platform/built-in-server/src/org/jetbrains/ide/OpenFileXmlRpcHandler.java
deleted file mode 100644 (file)
index 9487bdc..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright 2000-2015 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.jetbrains.ide;
-
-import com.intellij.openapi.diagnostic.Logger;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.concurrency.Promise;
-
-class OpenFileXmlRpcHandler {
-  private static final Logger LOG = Logger.getInstance(OpenFileXmlRpcHandler.class);
-
-  // XML-RPC interface method - keep the signature intact
-  @SuppressWarnings("UnusedDeclaration")
-  public boolean open(String path) {
-    LOG.debug("open(" + path + ")");
-    return doOpen(path, -1, -1);
-  }
-
-  // XML-RPC interface method - keep the signature intact
-  @SuppressWarnings("UnusedDeclaration")
-  public boolean openAndNavigate(String path, int line, int column) {
-    LOG.debug("openAndNavigate(" + path + ", " + line + ", " + column + ")");
-    return doOpen(path, line, column);
-  }
-
-  private static boolean doOpen(@NotNull String path, int line, int column) {
-    OpenFileHttpService.OpenFileRequest request = new OpenFileHttpService.OpenFileRequest();
-    request.file = path;
-    request.line = line;
-    request.column = column;
-    request.focused = false;
-    return HttpRequestHandler.EP_NAME.findExtension(OpenFileHttpService.class).openFile(request).getState() != Promise.State.REJECTED;
-  }
-}
\ No newline at end of file
index 428635643535985174e3e90a59976c9ef469de37..da22bb803530f9885c6feb4e411b5f3879d76bf7 100644 (file)
@@ -23,6 +23,7 @@ import com.intellij.platform.ProjectSetReader;
 import io.netty.channel.ChannelHandlerContext;
 import io.netty.handler.codec.http.FullHttpRequest;
 import io.netty.handler.codec.http.HttpMethod;
+import io.netty.handler.codec.http.HttpRequest;
 import io.netty.handler.codec.http.QueryStringDecoder;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
@@ -74,4 +75,9 @@ public class ProjectSetRequestHandler extends RestService {
     sendOk(request, context);
     return null;
   }
+
+  @Override
+  public boolean isAccessible(@NotNull HttpRequest request) {
+    return true;
+  }
 }
index 2d8beb522d5af429b0af5882fcff68e294b53d0f..014364aa48e3847a68f2b754f2750a9a75c1a143 100644 (file)
  */
 package org.jetbrains.ide;
 
+import com.google.common.base.Supplier;
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
 import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
 import com.google.gson.stream.JsonReader;
 import com.google.gson.stream.JsonWriter;
 import com.google.gson.stream.MalformedJsonException;
+import com.intellij.ide.IdeBundle;
 import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.project.ProjectManager;
 import com.intellij.openapi.util.NotNullLazyValue;
+import com.intellij.openapi.util.Ref;
 import com.intellij.openapi.util.io.BufferExposingByteArrayOutputStream;
+import com.intellij.openapi.util.registry.Registry;
 import com.intellij.openapi.util.text.StringUtil;
 import com.intellij.openapi.util.text.StringUtilRt;
 import com.intellij.openapi.vfs.CharsetToolkit;
 import com.intellij.openapi.wm.IdeFocusManager;
 import com.intellij.openapi.wm.IdeFrame;
 import com.intellij.util.ExceptionUtil;
+import com.intellij.util.ObjectUtils;
 import com.intellij.util.containers.ContainerUtil;
+import com.intellij.util.net.NetUtils;
 import io.netty.buffer.ByteBufInputStream;
 import io.netty.buffer.Unpooled;
 import io.netty.channel.Channel;
@@ -39,14 +49,25 @@ import io.netty.channel.ChannelHandlerContext;
 import io.netty.handler.codec.http.*;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
+import org.jetbrains.io.NettyKt;
 import org.jetbrains.io.Responses;
 
+import javax.swing.*;
 import java.awt.*;
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.io.OutputStream;
 import java.io.OutputStreamWriter;
+import java.lang.reflect.InvocationTargetException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.URI;
+import java.net.URISyntaxException;
 import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static com.intellij.ide.impl.ProjectUtil.showYesNoDialog;
 
 /**
  * Document your service using <a href="http://apidocjs.com">apiDoc</a>. To extract big example from source code, consider to use *.coffee file near your source file.
@@ -68,6 +89,12 @@ public abstract class RestService extends HttpRequestHandler {
     }
   };
 
+  private final LoadingCache<InetAddress, AtomicInteger> abuseCounter =
+    CacheBuilder.newBuilder().expireAfterWrite(1, TimeUnit.MINUTES).build(CacheLoader.from((Supplier<AtomicInteger>)AtomicInteger::new));
+
+  private final Cache<String, Boolean> trustedOrigins =
+    CacheBuilder.newBuilder().maximumSize(1024).expireAfterWrite(1, TimeUnit.DAYS).build();
+
   @Override
   public final boolean isSupported(@NotNull FullHttpRequest request) {
     if (!isMethodSupported(request.method())) {
@@ -115,6 +142,17 @@ public abstract class RestService extends HttpRequestHandler {
   @Override
   public final boolean process(@NotNull QueryStringDecoder urlDecoder, @NotNull FullHttpRequest request, @NotNull ChannelHandlerContext context) throws IOException {
     try {
+      AtomicInteger counter = abuseCounter.get(((InetSocketAddress)context.channel().remoteAddress()).getAddress());
+      if (counter.incrementAndGet() > Registry.intValue("ide.rest.api.requests.per.minute", 30)) {
+        Responses.send(Responses.orInSafeMode(HttpResponseStatus.TOO_MANY_REQUESTS, HttpResponseStatus.OK), context.channel(), request);
+        return true;
+      }
+
+      if (!isHostTrusted(request)) {
+        Responses.send(Responses.orInSafeMode(HttpResponseStatus.FORBIDDEN, HttpResponseStatus.OK), context.channel(), request);
+        return true;
+      }
+
       String error = execute(urlDecoder, request, context);
       if (error != null) {
         Responses.send(HttpResponseStatus.BAD_REQUEST, context.channel(), request, error);
@@ -137,7 +175,44 @@ public abstract class RestService extends HttpRequestHandler {
     return true;
   }
 
-  protected final void activateLastFocusedFrame() {
+  private boolean isHostTrusted(@NotNull FullHttpRequest request) throws InterruptedException, InvocationTargetException {
+    String referrer = NettyKt.getOrigin(request);
+    if (referrer == null) {
+      referrer = NettyKt.getReferrer(request);
+    }
+
+    String host;
+    try {
+      host = StringUtil.nullize(referrer == null ? null : new URI(referrer).getHost());
+    }
+    catch (URISyntaxException ignored) {
+      return false;
+    }
+
+    Ref<Boolean> isTrusted = Ref.create();
+    if (host != null) {
+      if (NetUtils.isLocalhost(host)) {
+        isTrusted.set(true);
+      }
+      else {
+        isTrusted.set(trustedOrigins.getIfPresent(host));
+      }
+    }
+
+    if (isTrusted.isNull()) {
+      SwingUtilities.invokeAndWait(() -> {
+        isTrusted.set(showYesNoDialog(
+          IdeBundle.message("warning.use.rest.api", getServiceName(), ObjectUtils.chooseNotNull(host, "unknown host")),
+          "title.use.rest.api"));
+        if (host != null) {
+          trustedOrigins.put(host, isTrusted.get());
+        }
+      });
+    }
+    return isTrusted.get();
+  }
+
+  protected static void activateLastFocusedFrame() {
     IdeFrame frame = IdeFocusManager.getGlobalInstance().getLastFocusedFrame();
     if (frame instanceof Window) {
       ((Window)frame).toFront();
@@ -187,12 +262,14 @@ public abstract class RestService extends HttpRequestHandler {
     if (keepAlive) {
       HttpUtil.setKeepAlive(response, true);
     }
+    response.headers().set("X-Frame-Options", "Deny");
     Responses.send(response, channel, !keepAlive);
   }
 
-  protected static void send(@NotNull BufferExposingByteArrayOutputStream byteOut, @NotNull FullHttpRequest request, @NotNull ChannelHandlerContext context) {
+  protected static void send(@NotNull BufferExposingByteArrayOutputStream byteOut, @NotNull HttpRequest request, @NotNull ChannelHandlerContext context) {
     HttpResponse response = Responses.response("application/json", Unpooled.wrappedBuffer(byteOut.getInternalBuffer(), 0, byteOut.size()));
     Responses.addNoCache(response);
+    response.headers().set("X-Frame-Options", "Deny");
     Responses.send(response, context.channel(), request);
   }
 
index 3f26acc49bfd96d39f9f92ed49bceb42a04eb066..782136dd77f9a55b81d91f6c3450efb80150a050 100644 (file)
  */
 package org.jetbrains.ide;
 
-import com.intellij.ide.XmlRpcHandlerBean;
 import com.intellij.ide.XmlRpcServer;
-import com.intellij.openapi.application.ApplicationManager;
 import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.openapi.extensions.AbstractExtensionPointBean;
-import com.intellij.openapi.extensions.Extensions;
 import com.intellij.openapi.util.text.StringUtil;
 import com.intellij.openapi.vfs.CharsetToolkit;
 import gnu.trove.THashMap;
@@ -46,19 +42,9 @@ import java.util.Vector;
 public class XmlRpcServerImpl implements XmlRpcServer {
   private static final Logger LOG = Logger.getInstance(XmlRpcServerImpl.class);
 
-  private final Map<String, Object> handlerMapping;
+  private final Map<String, Object> handlerMapping = new THashMap<String, Object>();
 
   public XmlRpcServerImpl() {
-    handlerMapping = new THashMap<String, Object>();
-    for (XmlRpcHandlerBean handlerBean : Extensions.getExtensions(XmlRpcHandlerBean.EP_NAME)) {
-      try {
-        handlerMapping.put(handlerBean.name, AbstractExtensionPointBean.instantiate(handlerBean.findClass(handlerBean.implementation), ApplicationManager.getApplication().getPicoContainer(), true));
-      }
-      catch (ClassNotFoundException e) {
-        LOG.error(e);
-      }
-    }
-    LOG.debug("XmlRpcServerImpl instantiated, handlers " + handlerMapping);
   }
 
   static final class XmlRpcRequestHandler extends HttpRequestHandler {
index 519cc7b4f440cd99be16aa66a00e1bf98318a659..d457fac331be20e1cbd19504360ba89ada04cb1a 100644 (file)
@@ -6,6 +6,7 @@ import com.intellij.openapi.module.EmptyModuleType
 import com.intellij.openapi.module.ModuleManager
 import com.intellij.openapi.project.Project
 import com.intellij.openapi.roots.ModuleRootModificationUtil
+import com.intellij.openapi.util.SystemInfo
 import com.intellij.openapi.util.io.FileUtil
 import com.intellij.openapi.util.text.StringUtil
 import com.intellij.openapi.vfs.LocalFileSystem
@@ -16,6 +17,7 @@ import org.assertj.core.api.Assertions.assertThat
 import org.junit.ClassRule
 import org.junit.Rule
 import org.junit.Test
+import java.nio.file.Files
 
 internal class BuiltInWebServerTest : BuiltInServerTestCase() {
   override val urlPathPrefix: String
@@ -89,4 +91,25 @@ internal class HeavyBuiltInWebServerTest {
       testUrl("http://localhost:${BuiltInServerManager.getInstance().port}/$webPath", HttpResponseStatus.NOT_FOUND)
     }
   }
+
+  @Test
+  fun `hidden dir`() {
+    val projectDir = tempDirManager.newPath().resolve("foo/bar")
+    val projectDirPath = projectDir.systemIndependentPath
+    createHeavyProject("$projectDirPath/test.ipr").use { project ->
+      projectDir.createDirectories()
+      LocalFileSystem.getInstance().refreshAndFindFileByPath(projectDirPath)
+      createModule(projectDirPath, project)
+
+      val dir = projectDir.resolve(".doNotExposeMe")
+      if (SystemInfo.isWindows) {
+        Files.setAttribute(dir, "dos:hidden", true)
+      }
+
+      val path = dir.resolve("foo").write("doNotExposeMe").systemIndependentPath
+      val relativePath = FileUtil.getRelativePath(project.basePath!!, path, '/')
+      val webPath = StringUtil.replace(UrlEscapers.urlPathSegmentEscaper().escape("${project.name}/$relativePath"), "%2F", "/")
+      testUrl("http://localhost:${BuiltInServerManager.getInstance().port}/$webPath", HttpResponseStatus.FORBIDDEN)
+    }
+  }
 }
\ No newline at end of file
diff --git a/platform/built-in-server/testSrc/IsLocalHostTest.kt b/platform/built-in-server/testSrc/IsLocalHostTest.kt
new file mode 100644 (file)
index 0000000..7221389
--- /dev/null
@@ -0,0 +1,51 @@
+package org.jetbrains.io
+
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.Test
+
+class IsLocalHostTest {
+  @Test
+  fun `google ip`() {
+    assertThat(isLocalHost("37.29.1.113", false)).isFalse()
+  }
+
+  @Test
+  fun `google name`() {
+    assertThat(isLocalHost("google.com", false)).isFalse()
+  }
+
+  @Test
+  fun `jetbrains name`() {
+    assertThat(isLocalHost("jetbrains.com", false)).isFalse()
+  }
+
+  @Test
+  fun `unknown name`() {
+    assertThat(isLocalHost("foo.com", false)).isFalse()
+  }
+
+  @Test
+  fun `unknown unqualified name`() {
+    assertThat(isLocalHost("local", false)).isFalse()
+  }
+
+  @Test
+  fun `invalid ip`() {
+    assertThat(isLocalHost("0.0.0.0.0.0.0", false)).isFalse()
+  }
+
+  @Test
+  fun `any`() {
+    assertThat(isLocalHost("0.0.0.0", false)).isTrue()
+  }
+
+  @Test
+  fun `localhost`() {
+    assertThat(isLocalHost("localhost", false)).isTrue()
+  }
+
+  @Test
+  fun `localhost only loopback`() {
+    assertThat(isLocalHost("localhost", true)).isTrue()
+  }
+}
\ No newline at end of file
diff --git a/platform/platform-api/src/com/intellij/ide/XmlRpcHandlerBean.java b/platform/platform-api/src/com/intellij/ide/XmlRpcHandlerBean.java
deleted file mode 100644 (file)
index 77630f6..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright 2000-2015 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.intellij.ide;
-
-import com.intellij.openapi.extensions.AbstractExtensionPointBean;
-import com.intellij.openapi.extensions.ExtensionPointName;
-import com.intellij.util.xmlb.annotations.Attribute;
-
-public class XmlRpcHandlerBean extends AbstractExtensionPointBean {
-  public static final ExtensionPointName<XmlRpcHandlerBean> EP_NAME = ExtensionPointName.create("com.intellij.xmlRpcHandler");
-
-  @Attribute("name")
-  public String name;
-
-  @Attribute("implementation")
-  public String implementation;
-}
\ No newline at end of file
index e6a2e956947e786193410f5c8604473ed0e1bd83..445027fec0269733ba3ba398e1864e341dba4748 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2014 JetBrains s.r.o.
+ * Copyright 2000-2016 JetBrains s.r.o.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -102,10 +102,11 @@ public abstract class MessageDialogBuilder<T extends MessageDialogBuilder> {
         if (Messages.canShowMacSheetPanel() && !Messages.isApplicationInUnitTestOrHeadless()) {
           return MacMessages.getInstance().showYesNoDialog(myTitle, myMessage, yesText, noText, WindowManager.getInstance().suggestParentWindow(myProject), myDoNotAskOption);
         }
-      } catch (Exception ignored) {}
+      }
+      catch (Exception ignored) {
+      }
 
       return Messages.showDialog(myProject, myMessage, myTitle, new String[]{yesText, noText}, 0, myIcon, myDoNotAskOption) == 0 ? Messages.YES : Messages.NO;
-
     }
 
     public boolean is() {
index e00978ee0c6a7d1132814325acda5c7b2c5811f9..f62c0f750d91ec02e52fbf175900d481e819b55e 100644 (file)
@@ -33,12 +33,6 @@ public class NetUtils {
 
   private NetUtils() { }
 
-  /** @deprecated use {@link #canConnectToSocket(String, int)} (to be remove in IDEA 17) */
-  @SuppressWarnings("unused")
-  public static boolean canConnectToSocketOpenedByJavaProcess(String host, int port) {
-    return canConnectToSocket(host, port);
-  }
-
   public static boolean canConnectToSocket(String host, int port) {
     if (isLocalhost(host)) {
       return !canBindToLocalSocket(host, port);
@@ -48,14 +42,9 @@ public class NetUtils {
     }
   }
 
+  @Deprecated
   public static InetAddress getLoopbackAddress() {
-    try {
-      //  todo use JDK 7 InetAddress.getLoopbackAddress()
-      return InetAddress.getByName(null);
-    }
-    catch (UnknownHostException e) {
-      throw new RuntimeException(e);
-    }
+    return InetAddress.getLoopbackAddress();
   }
 
   public static boolean isLocalhost(@NotNull String host) {
@@ -224,6 +213,6 @@ public class NetUtils {
   }
 
   public static boolean isSniEnabled() {
-    return SystemInfo.isJavaVersionAtLeast("1.7") && SystemProperties.getBooleanProperty("jsse.enableSNIExtension", true);
+    return SystemProperties.getBooleanProperty("jsse.enableSNIExtension", true);
   }
 }
index e2461d2b2d5987d934162e51e1b4c761d65fbad7..4340318b9f69a33866fbd6302a8a92e61a4b30a9 100644 (file)
@@ -209,15 +209,18 @@ public class ProjectUtil {
   public static boolean confirmLoadingFromRemotePath(@NotNull String path,
                                                      @NotNull @PropertyKey(resourceBundle = IdeBundle.BUNDLE) String msgKey,
                                                      @NotNull @PropertyKey(resourceBundle = IdeBundle.BUNDLE) String titleKey) {
+    return showYesNoDialog(IdeBundle.message(msgKey, path), titleKey);
+  }
+
+  public static boolean showYesNoDialog(@NotNull String message, @NotNull @PropertyKey(resourceBundle = IdeBundle.BUNDLE) String titleKey) {
     final Window window = getActiveFrameOrWelcomeScreen();
-    final String msg = IdeBundle.message(msgKey, path);
-    final String title = IdeBundle.message(titleKey);
     final Icon icon = Messages.getWarningIcon();
-    final int answer = window == null ? Messages.showYesNoDialog(msg, title, icon) : Messages.showYesNoDialog(window, msg, title, icon);
+    String title = IdeBundle.message(titleKey);
+    final int answer = window == null ? Messages.showYesNoDialog(message, title, icon) : Messages.showYesNoDialog(window, message, title, icon);
     return answer == Messages.YES;
   }
 
-  private static Window getActiveFrameOrWelcomeScreen() {
+  public static Window getActiveFrameOrWelcomeScreen() {
     Window window = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusedWindow();
     if (window != null)  return window;
 
index 0e4585b9f5f3f1f66e99458d28ada15ddb437c8c..0fcf78556d4fb1f75ac70c9a66280ef227417617 100644 (file)
@@ -27,7 +27,6 @@ import com.intellij.util.Consumer;
 import com.intellij.util.NotNullProducer;
 import com.intellij.util.PlatformUtils;
 import com.intellij.util.containers.MultiMap;
-import com.intellij.util.net.NetUtils;
 import io.netty.buffer.ByteBuf;
 import io.netty.buffer.ByteBufOutputStream;
 import io.netty.channel.ChannelHandler;
@@ -40,6 +39,7 @@ import org.jetbrains.io.MessageDecoder;
 import java.io.*;
 import java.lang.management.ManagementFactory;
 import java.net.ConnectException;
+import java.net.InetAddress;
 import java.net.Socket;
 import java.util.Collection;
 import java.util.List;
@@ -189,7 +189,7 @@ public final class SocketLock {
     log("trying: port=%s", portNumber);
     args = checkForJetBrainsProtocolCommand(args);
     try {
-      Socket socket = new Socket(NetUtils.getLoopbackAddress(), portNumber);
+      Socket socket = new Socket(InetAddress.getLoopbackAddress(), portNumber);
       try {
         socket.setSoTimeout(1000);
 
@@ -249,7 +249,7 @@ public final class SocketLock {
 
   private static void printPID(int port) {
     try {
-      Socket socket = new Socket(NetUtils.getLoopbackAddress(), port);
+      Socket socket = new Socket(InetAddress.getLoopbackAddress(), port);
       socket.setSoTimeout(1000);
       @SuppressWarnings("IOResourceOpenedButNotSafelyClosed") DataOutputStream out = new DataOutputStream(socket.getOutputStream());
       out.writeUTF(PID_COMMAND);
@@ -325,6 +325,10 @@ public final class SocketLock {
             }
 
             contentLength = buffer.readUnsignedShort();
+            if (contentLength > 8192) {
+              context.close();
+              return;
+            }
             myState = State.CONTENT;
           }
           break;
index 5ae9a7c7117cebbf2739d2a3e96ac263deddfc4e..c487ab38c41e8c571e6476af136cc6e03cd853a1 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2015 JetBrains s.r.o.
+ * Copyright 2000-2016 JetBrains s.r.o.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -72,6 +72,11 @@ public final class Urls {
     return newUrl("http", authority, path);
   }
 
+  @NotNull
+  public static Url newHttpUrl(@NotNull String authority, @Nullable String path, @Nullable String parameters) {
+    return new UrlImpl("http", authority, path, parameters);
+  }
+
   @NotNull
   public static Url newUrl(@NotNull String scheme, @NotNull String authority, @Nullable String path) {
     return new UrlImpl(scheme, authority, path);
index cfe3db1224a6badb9f604a0558efb7d01de2b490..5fc8fa284a29c75fb2d84153b4cdb7d9c1924b59 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2010 JetBrains s.r.o.
+ * Copyright 2000-2016 JetBrains s.r.o.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -18,7 +18,6 @@ package com.intellij.util.io.socketConnection.impl;
 import com.intellij.openapi.application.ApplicationManager;
 import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.util.io.socketConnection.*;
-import com.intellij.util.net.NetUtils;
 import org.jetbrains.annotations.NotNull;
 
 import java.io.IOException;
@@ -72,7 +71,7 @@ public class SocketConnectionImpl<Request extends AbstractRequest, Response exte
         host = InetAddress.getLocalHost();
       }
       catch (UnknownHostException ignored) {
-        host = NetUtils.getLoopbackAddress();
+        host = InetAddress.getLoopbackAddress();
       }
     }
 
@@ -98,7 +97,7 @@ public class SocketConnectionImpl<Request extends AbstractRequest, Response exte
         Exception exception = null;
         InetAddress host = myHost;
         if (host == null) {
-          host = NetUtils.getLoopbackAddress();
+          host = InetAddress.getLoopbackAddress();
         }
 
         for (int attempt = 0; attempt < MAX_CONNECTION_ATTEMPTS; attempt++) {
similarity index 75%
rename from platform/built-in-server-api/src/org/jetbrains/ide/HttpRequestHandler.java
rename to platform/platform-impl/src/org/jetbrains/ide/HttpRequestHandler.java
index 7ac77ba4955405000eb3a1e83bad59118e533b05..815264ada9a8e6f3129c8083ec00f86c82044401 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2015 JetBrains s.r.o.
+ * Copyright 2000-2016 JetBrains s.r.o.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -19,8 +19,10 @@ import com.intellij.openapi.extensions.ExtensionPointName;
 import io.netty.channel.ChannelHandlerContext;
 import io.netty.handler.codec.http.FullHttpRequest;
 import io.netty.handler.codec.http.HttpMethod;
+import io.netty.handler.codec.http.HttpRequest;
 import io.netty.handler.codec.http.QueryStringDecoder;
 import org.jetbrains.annotations.NotNull;
+import org.jetbrains.io.NettyKt;
 
 import java.io.IOException;
 
@@ -41,6 +43,17 @@ public abstract class HttpRequestHandler {
     return false;
   }
 
+  @SuppressWarnings("SpellCheckingInspection")
+  /**
+   * Write request from browser without Origin will be always blocked regardles of your implementation.
+   */
+  public boolean isAccessible(@NotNull HttpRequest request) {
+    String host = NettyKt.getHost(request);
+    // If attacker.com DNS rebound to 127.0.0.1 and user open site directly — no Origin or Referer headers.
+    // So we should check Host header.
+    return host != null && NettyKt.isLocalOrigin(request) && NettyKt.parseAndCheckIsLocalHost("http://" + host);
+  }
+
   public boolean isSupported(@NotNull FullHttpRequest request) {
     return request.method() == HttpMethod.GET || request.method() == HttpMethod.HEAD;
   }
@@ -50,4 +63,4 @@ public abstract class HttpRequestHandler {
    */
   public abstract boolean process(@NotNull QueryStringDecoder urlDecoder, @NotNull FullHttpRequest request, @NotNull ChannelHandlerContext context)
     throws IOException;
-}
+}
\ No newline at end of file
index 35bc72e594ecbdef2994a8e5caed0597a15ff65c..7f5bf8f055bc8486abff44f2fee9191905648e98 100644 (file)
@@ -21,7 +21,6 @@ import com.intellij.util.ArrayUtil;
 import com.intellij.util.ExceptionUtil;
 import com.intellij.util.NotNullProducer;
 import com.intellij.util.SystemProperties;
-import com.intellij.util.net.NetUtils;
 import io.netty.bootstrap.ServerBootstrap;
 import io.netty.channel.*;
 import io.netty.channel.nio.NioEventLoopGroup;
@@ -140,7 +139,7 @@ public class BuiltInServer implements Disposable {
                           boolean tryAnyPort,
                           @NotNull ServerBootstrap bootstrap,
                           @NotNull ChannelRegistrar channelRegistrar) throws Exception {
-    InetAddress address = NetUtils.getLoopbackAddress();
+    InetAddress address = InetAddress.getLoopbackAddress();
 
     for (int i = 0; i < portsCount; i++) {
       int port = firstPort + i;
index bfad9825627f2adfbf7ba15005f00de8560a30c6..806d3e6a351b790f5a4a7c7aeab652b779fc539b 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2015 JetBrains s.r.o.
+ * Copyright 2000-2016 JetBrains s.r.o.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -28,7 +28,6 @@ import io.netty.util.AttributeKey
 import org.apache.sanselan.ImageFormat
 import org.apache.sanselan.Sanselan
 import org.jetbrains.ide.HttpRequestHandler
-
 import java.awt.image.BufferedImage
 
 private val PREV_HANDLER = AttributeKey.valueOf<HttpRequestHandler>("DelegatingHttpRequestHandler.handler")
@@ -38,10 +37,14 @@ internal class DelegatingHttpRequestHandler : DelegatingHttpRequestHandlerBase()
   override fun process(context: ChannelHandlerContext,
                        request: FullHttpRequest,
                        urlDecoder: QueryStringDecoder): Boolean {
+    fun HttpRequestHandler.checkAndProcess(): Boolean {
+      return isSupported(request) && !request.isWriteFromBrowserWithoutOrigin() && isAccessible(request) && process(urlDecoder, request, context)
+    }
+
     val prevHandlerAttribute = context.attr(PREV_HANDLER)
     val connectedHandler = prevHandlerAttribute.get()
     if (connectedHandler != null) {
-      if (connectedHandler.isSupported(request) && connectedHandler.process(urlDecoder, request, context)) {
+      if (connectedHandler.checkAndProcess()) {
         return true
       }
       // prev cached connectedHandler is not suitable for this request, so, let's find it again
@@ -50,7 +53,7 @@ internal class DelegatingHttpRequestHandler : DelegatingHttpRequestHandlerBase()
 
     for (handler in HttpRequestHandler.EP_NAME.extensions) {
       try {
-        if (handler.isSupported(request) && handler.process(urlDecoder, request, context)) {
+        if (handler.checkAndProcess()) {
           prevHandlerAttribute.set(handler)
           return true
         }
@@ -58,7 +61,6 @@ internal class DelegatingHttpRequestHandler : DelegatingHttpRequestHandlerBase()
       catch (e: Throwable) {
         Logger.getInstance(BuiltInServer::class.java).error(e)
       }
-
     }
 
     if (urlDecoder.path() == "/favicon.ico") {
index b3541f95b383c88601568be4e223ec1f734f8a37..33bd2693a253254d1787846f4681c853ae6c57a5 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2015 JetBrains s.r.o.
+ * Copyright 2000-2016 JetBrains s.r.o.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -19,10 +19,8 @@ import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.util.Condition;
 import io.netty.bootstrap.Bootstrap;
 import io.netty.bootstrap.BootstrapUtil;
-import io.netty.bootstrap.ServerBootstrap;
 import io.netty.channel.*;
 import io.netty.channel.nio.NioEventLoopGroup;
-import io.netty.channel.socket.nio.NioServerSocketChannel;
 import io.netty.channel.socket.nio.NioSocketChannel;
 import io.netty.channel.socket.oio.OioSocketChannel;
 import io.netty.handler.codec.http.HttpMethod;
@@ -30,6 +28,7 @@ import io.netty.handler.codec.http.HttpObjectAggregator;
 import io.netty.handler.codec.http.HttpRequestDecoder;
 import io.netty.handler.codec.http.HttpResponseEncoder;
 import io.netty.handler.codec.http.cors.CorsConfig;
+import io.netty.handler.codec.http.cors.CorsConfigBuilder;
 import io.netty.handler.codec.http.cors.CorsHandler;
 import io.netty.handler.stream.ChunkedWriteHandler;
 import io.netty.util.concurrent.GlobalEventExecutor;
@@ -199,22 +198,6 @@ public final class NettyUtil {
            (message.startsWith("Connection reset") || message.equals("Operation timed out") || message.equals("Connection timed out"));
   }
 
-  @SuppressWarnings("unused")
-  @Deprecated
-  @NotNull
-  public static ServerBootstrap nioServerBootstrap(@NotNull EventLoopGroup eventLoopGroup) {
-    ServerBootstrap bootstrap = new ServerBootstrap().group(eventLoopGroup).channel(NioServerSocketChannel.class);
-    bootstrap.childOption(ChannelOption.TCP_NODELAY, true).childOption(ChannelOption.SO_KEEPALIVE, true);
-    return bootstrap;
-  }
-
-  @SuppressWarnings("unused")
-  @Deprecated
-  @NotNull
-  public static Bootstrap oioClientBootstrap() {
-    return NettyKt.oioClientBootstrap();
-  }
-
   public static Bootstrap nioClientBootstrap() {
     return nioClientBootstrap(new NioEventLoopGroup(1, PooledThreadExecutor.INSTANCE));
   }
@@ -234,8 +217,9 @@ public final class NettyUtil {
     if (pipeline.get(ChunkedWriteHandler.class) == null) {
       pipeline.addLast("chunkedWriteHandler", new ChunkedWriteHandler());
     }
-    pipeline.addLast("corsHandler", new CorsHandlerDoNotUseOwnLogger(CorsConfig
-                                                                       .withAnyOrigin()
+    pipeline.addLast("corsHandler", new CorsHandlerDoNotUseOwnLogger(CorsConfigBuilder
+                                                                       .forAnyOrigin()
+                                                                       .shortCircuit()
                                                                        .allowCredentials()
                                                                        .allowNullOrigin()
                                                                        .allowedRequestMethods(HttpMethod.GET, HttpMethod.POST, HttpMethod.PUT, HttpMethod.DELETE, HttpMethod.HEAD, HttpMethod.PATCH)
index 3c293b37f0d4235f42701fd841cb323eecade772..533cdea6c2cc8a1977c9a43f05110e3b50215d4a 100644 (file)
@@ -18,6 +18,7 @@ package org.jetbrains.io
 
 import com.intellij.openapi.application.ApplicationManager
 import com.intellij.openapi.application.ex.ApplicationInfoEx
+import com.intellij.openapi.util.registry.Registry
 import io.netty.buffer.ByteBuf
 import io.netty.buffer.ByteBufAllocator
 import io.netty.buffer.ByteBufUtil
@@ -99,6 +100,11 @@ fun HttpResponse.addKeepAliveIfNeed(request: HttpRequest): Boolean {
 fun HttpResponse.addCommonHeaders() {
   addServer()
   setDate()
+  if (!headers().contains("X-Frame-Options")) {
+    headers().set("X-Frame-Options", "SameOrigin")
+  }
+  headers().set("X-Content-Type-Options", "nosniff")
+  headers().set("x-xss-protection", "1; mode=block")
 }
 
 fun HttpResponse.send(channel: Channel, close: Boolean) {
@@ -121,6 +127,15 @@ fun HttpResponseStatus.send(channel: Channel, request: HttpRequest? = null, desc
   createStatusResponse(this, request, description).send(channel, request)
 }
 
+fun HttpResponseStatus.orInSafeMode(safeStatus: HttpResponseStatus): HttpResponseStatus {
+  if (!Registry.`is`("ide.rest.api.paranoid.mode", true) || (ApplicationManager.getApplication()?.isUnitTestMode ?: false)) {
+    return this
+  }
+  else {
+    return safeStatus
+  }
+}
+
 private fun createStatusResponse(responseStatus: HttpResponseStatus, request: HttpRequest?, description: String?): HttpResponse {
   if (request != null && request.method() === HttpMethod.HEAD) {
     return responseStatus.response()
index ceb922cc1a3444395c5f94fee4b79af21f409ee2..0d0c87f8d0901bb1e46f75d1f21ee3e603eee6aa 100644 (file)
  */
 package org.jetbrains.io
 
+import com.google.common.net.InetAddresses
 import com.intellij.openapi.util.Condition
 import com.intellij.openapi.util.Conditions
+import com.intellij.util.net.NetUtils
 import io.netty.bootstrap.Bootstrap
 import io.netty.bootstrap.ServerBootstrap
 import io.netty.buffer.ByteBuf
@@ -27,12 +29,18 @@ import io.netty.channel.socket.nio.NioServerSocketChannel
 import io.netty.channel.socket.oio.OioServerSocketChannel
 import io.netty.channel.socket.oio.OioSocketChannel
 import io.netty.handler.codec.http.HttpHeaderNames
+import io.netty.handler.codec.http.HttpMethod
 import io.netty.handler.codec.http.HttpRequest
 import io.netty.handler.ssl.SslHandler
+import io.netty.resolver.HostsFileEntriesResolver
 import io.netty.util.concurrent.GenericFutureListener
 import org.jetbrains.concurrency.AsyncPromise
 import org.jetbrains.ide.PooledThreadExecutor
+import java.io.IOException
+import java.net.InetAddress
 import java.net.InetSocketAddress
+import java.net.NetworkInterface
+import java.net.URI
 import java.util.concurrent.TimeUnit
 
 inline fun Bootstrap.handler(crossinline task: (Channel) -> Unit): Bootstrap {
@@ -59,6 +67,7 @@ fun oioClientBootstrap(): Bootstrap {
 }
 
 inline fun ChannelFuture.addChannelListener(crossinline listener: (future: ChannelFuture) -> Unit) {
+  @Suppress("RedundantSamConstructor")
   addListener(GenericFutureListener<ChannelFuture> { listener(it) })
 }
 
@@ -98,6 +107,15 @@ val Channel.uriScheme: String
 val HttpRequest.host: String?
   get() = headers().getAsString(HttpHeaderNames.HOST)
 
+val HttpRequest.origin: String?
+  get() = headers().getAsString(HttpHeaderNames.ORIGIN)
+
+val HttpRequest.referrer: String?
+  get() = headers().getAsString(HttpHeaderNames.REFERER)
+
+val HttpRequest.userAgent: String?
+  get() = headers().getAsString(HttpHeaderNames.USER_AGENT)
+
 inline fun <T> ByteBuf.releaseIfError(task: () -> T): T {
   try {
     return task()
@@ -110,4 +128,65 @@ inline fun <T> ByteBuf.releaseIfError(task: () -> T): T {
       throw e
     }
   }
+}
+
+fun isLocalHost(host: String, onlyAnyOrLoopback: Boolean, hostsOnly: Boolean = false): Boolean {
+  if (NetUtils.isLocalhost(host)) {
+    return true
+  }
+
+  // if IP address, it is safe to use getByName (not affected by DNS rebinding)
+  if (onlyAnyOrLoopback && !InetAddresses.isInetAddress(host)) {
+    return false
+  }
+
+  fun InetAddress.isLocal() = isAnyLocalAddress || isLoopbackAddress || NetworkInterface.getByInetAddress(this) != null
+
+  try {
+    val address = InetAddress.getByName(host)
+    if (!address.isLocal()) {
+      return false
+    }
+    // be aware - on windows hosts file doesn't contain localhost
+    // hosts can contain remote addresses, so, we check it
+    if (hostsOnly && !InetAddresses.isInetAddress(host)) {
+      return HostsFileEntriesResolver.DEFAULT.address(host).let { it != null && it.isLocal() }
+    }
+    else {
+      return true
+    }
+  }
+  catch (ignored: IOException) {
+    return false
+  }
+}
+
+@JvmOverloads
+fun HttpRequest.isLocalOrigin(onlyAnyOrLoopback: Boolean = true, hostsOnly: Boolean = false) = parseAndCheckIsLocalHost(origin, onlyAnyOrLoopback, hostsOnly) && parseAndCheckIsLocalHost(referrer, onlyAnyOrLoopback, hostsOnly)
+
+private fun isTrustedChromeExtension(uri: URI): Boolean {
+  return uri.scheme == "chrome-extension" && (uri.host == "hmhgeddbohgjknpmjagkdomcpobmllji" || uri.host == "offnedcbhjldheanlbojaefbfbllddna")
+}
+
+@JvmOverloads
+fun parseAndCheckIsLocalHost(uri: String?, onlyAnyOrLoopback: Boolean = true, hostsOnly: Boolean = false): Boolean {
+  if (uri == null) {
+    return true
+  }
+
+  try {
+    val parsedUri = URI(uri)
+    return isTrustedChromeExtension(parsedUri) || isLocalHost(parsedUri.host, onlyAnyOrLoopback, hostsOnly)
+  }
+  catch (ignored: Exception) {
+  }
+  return false
+}
+
+fun HttpRequest.isRegularBrowser() = userAgent?.startsWith("Mozilla/5.0") ?: false
+
+// forbid POST requests from browser without Origin
+fun HttpRequest.isWriteFromBrowserWithoutOrigin(): Boolean {
+  val method = method()
+  return origin.isNullOrEmpty() && isRegularBrowser() && (method == HttpMethod.POST || method == HttpMethod.PATCH || method == HttpMethod.PUT || method == HttpMethod.DELETE)
 }
\ No newline at end of file
index 88607e0eaf70f96d6480c8f242245207a414da92..d3f826be420af7666cabd0ff814375ff71bd2504 100644 (file)
@@ -1195,4 +1195,7 @@ edit.custom.settings.confirm=File \n''{0}''\n does not exist. Create?
 warning.load.project.from.share=You are opening a project from a network share. Do you trust this location?\n{0}
 title.load.project.from.share=Loading Project From Network
 warning.load.file.from.share=You are opening a file from a network share. Do you want to continue?\n{0}
-title.load.file.from.share=Loading File From Network
\ No newline at end of file
+title.load.file.from.share=Loading File From Network
+
+warning.use.rest.api=''{0}'' API is requested. Do you trust ''{1}''?
+title.use.rest.api=Using REST API
\ No newline at end of file
index 58990d478097f519f019d700ecd4023baad4c010..05e5bcead2250affbafcb91d0c2c4c91856a0396 100644 (file)
 
     <extensionPoint name="statistics.usagesCollector" interface="com.intellij.internal.statistic.UsagesCollector"/>
 
-    <!--suppress ExtensionPointBeanClass -->
-    <extensionPoint name="xmlRpcHandler" beanClass="com.intellij.ide.XmlRpcHandlerBean"/>
-
     <extensionPoint name="editorHighlighterProvider" beanClass="com.intellij.openapi.fileTypes.FileTypeExtensionPoint">
       <with attribute="implementationClass" implements="com.intellij.openapi.fileTypes.EditorHighlighterProvider"/>
     </extensionPoint>
     <extensionPoint name="colorPickerListenerFactory" interface="com.intellij.ui.ColorPickerListenerFactory"/>
 
     <extensionPoint name="search.topHitProvider" interface="com.intellij.ide.SearchTopHitProvider"/>
-    
+
     <extensionPoint name="search.optionContributor" interface="com.intellij.ide.ui.search.SearchableOptionContributor"/>
 
     <extensionPoint name="ui.suitableFontProvider" interface="com.intellij.ui.SuitableFontProvider"/>
-    
+
     <extensionPoint name="schemeImporter" beanClass="com.intellij.openapi.options.SchemeImporterEP">
       <with attribute="schemeClass" implements="com.intellij.openapi.options.Scheme"/>
       <with attribute="implementationClass" implements="com.intellij.openapi.options.SchemeImporter"/>
index b38e318efdefa5e079bec4bb2760c28f29653a11..cc795eaf1080d8a69808ea36247b39e67ea05973 100644 (file)
@@ -22,8 +22,6 @@
 
     <applicationService serviceInterface="com.intellij.ide.XmlRpcServer" serviceImplementation="org.jetbrains.ide.XmlRpcServerImpl"/>
 
-    <xmlRpcHandler name="fileOpener" implementation="org.jetbrains.ide.OpenFileXmlRpcHandler"/>
-
     <httpRequestHandler implementation="org.jetbrains.ide.XmlRpcServerImpl$XmlRpcRequestHandler"/>
     <httpRequestHandler implementation="org.jetbrains.ide.ProjectSetRequestHandler"/>
     <httpRequestHandler implementation="org.jetbrains.ide.DiffHttpService"/>
index 3ae07a4849375c80f401754e44aa4ef2afd762bc..1dbfd66428f90b56e98bc5a0b9a86f15a42669c4 100644 (file)
   application/xslt+xml                  xsl
   application/zip                       zip
   text/css                              css
-  text/html                             html htm shtml
+  text/html                             html htm shtml stm shtm
   text/mathml                           mml;
   text/plain                            txt
   text/vcard                            vcard vcf
index a3d6810581ba2df2af5d2ab5d46b3527ecf7ecd9..d21da51552b6313f049ce332b2c1dd785d2751bd 100644 (file)
@@ -731,4 +731,7 @@ ide.screenreader.autodetect.accessibility=false
 ide.screenreader.autodetect.accessibility.description=Automatically detect whether accessible context is enabled
 
 editor.rainbow.identifiers=false
-editor.rainbow.identifiers.description=Rainbow identifiers in editor
\ No newline at end of file
+editor.rainbow.identifiers.description=Rainbow identifiers in editor
+
+ide.rest.api.paranoid.mode=true
+ide.rest.api.requests.per.minute=30
\ No newline at end of file
index 8c8e425570a2f600f2fd467799d26c1e227f997f..8ce927804e52b8146282ea81d95761bdedc49339 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2013 JetBrains s.r.o.
+ * Copyright 2000-2016 JetBrains s.r.o.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -56,16 +56,16 @@ public class GitAskPassApp implements GitExternalApp {
       boolean usernameNeeded = arguments.getFirst();
       String url = arguments.getSecond();
 
-      int handler = Integer.parseInt(getNotNull(GitAskPassXmlRpcHandler.GIT_ASK_PASS_HANDLER_ENV));
+      String token = getNotNull(GitAskPassXmlRpcHandler.GIT_ASK_PASS_HANDLER_ENV);
       int xmlRpcPort = Integer.parseInt(getNotNull(GitAskPassXmlRpcHandler.GIT_ASK_PASS_PORT_ENV));
       GitAskPassXmlRpcClient xmlRpcClient = new GitAskPassXmlRpcClient(xmlRpcPort);
 
       if (usernameNeeded) {
-        String username = xmlRpcClient.askUsername(handler, url);
+        String username = xmlRpcClient.askUsername(token, url);
         System.out.println(username);
       }
       else {
-        String pass = xmlRpcClient.askPassword(handler, url);
+        String pass = xmlRpcClient.askPassword(token, url);
         System.out.println(pass);
       }
     }
@@ -77,11 +77,11 @@ public class GitAskPassApp implements GitExternalApp {
 
   @NotNull
   private static String getNotNull(@NotNull String env) {
-    String handlerValue = System.getenv(env);
-    if (handlerValue == null) {
+    String value = System.getenv(env);
+    if (value == null) {
       throw new IllegalStateException(env + " environment variable is not defined!");
     }
-    return handlerValue;
+    return value;
   }
 
   @NotNull
index 5d3f8681f0990e60e75aa3b87c595d3c8a2bf755..85e49697416ed35dbf805a66a819bd49d844ca51 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2013 JetBrains s.r.o.
+ * Copyright 2000-2016 JetBrains s.r.o.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -38,9 +38,9 @@ class GitAskPassXmlRpcClient {
 
   // Obsolete collection usage because of the XmlRpcClientLite API
   @SuppressWarnings({"UseOfObsoleteCollectionType", "unchecked"})
-  String askUsername(int handler, @NotNull String url) {
+  String askUsername(String token, @NotNull String url) {
     Vector parameters = new Vector();
-    parameters.add(handler);
+    parameters.add(token);
     parameters.add(url);
 
     try {
@@ -56,9 +56,9 @@ class GitAskPassXmlRpcClient {
 
   // Obsolete collection usage because of the XmlRpcClientLite API
   @SuppressWarnings({"UseOfObsoleteCollectionType", "unchecked"})
-  String askPassword(int handler, @NotNull String url) {
+  String askPassword(String token, @NotNull String url) {
     Vector parameters = new Vector();
-    parameters.add(handler);
+    parameters.add(token);
     parameters.add(url);
 
     try {
index 36cc365c1d80040b6c66c22fd1caea5d3fe6e632..9c9900a1f3caceb518231713c0d6b5429d5325cb 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2013 JetBrains s.r.o.
+ * Copyright 2000-2016 JetBrains s.r.o.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -31,25 +31,25 @@ public interface GitAskPassXmlRpcHandler {
 
   /**
    * Get the username from the user to access the given URL.
-   * @param handler XML RPC handler number.
+   * @param token   Access token.
    * @param url     URL which Git tries to access.
    * @return The Username which should be used for the URL.
    */
   // UnusedDeclaration suppressed: the method is used via XML RPC
   @SuppressWarnings("UnusedDeclaration")
   @NotNull
-  String askUsername(int handler, @NotNull String url);
+  String askUsername(String token, @NotNull String url);
 
   /**
    * Get the password from the user to access the given URL.
    * It is assumed that the username either is specified in the URL (http://username@host.com), or has been asked earlier.
-   * @param handler XML RPC handler number.
+   * @param token   Access token.
    * @param url     URL which Git tries to access.
    * @return The password which should be used for the URL.
    */
   // UnusedDeclaration suppressed: the method is used via XML RPC
   @SuppressWarnings("UnusedDeclaration")
   @NotNull
-  String askPassword(int handler, @NotNull String url);
+  String askPassword(String token, @NotNull String url);
 
 }
index 2bc849d83fb0d94c42107a8b8d31983f1429cb26..4ce4ea31b6a25d87152f1ed7aa28e7ab1f028839 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2012 JetBrains s.r.o.
+ * Copyright 2000-2016 JetBrains s.r.o.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -29,16 +29,10 @@ public interface GitSSHHandler {
    */
   @NonNls String GIT_SSH_PREFIX = "git-ssh-";
   /**
-   * Name of environment variable for SSH handler number
+   * Name of environment variable for SSH handler access token
    */
   @NonNls String SSH_HANDLER_ENV = "GIT4IDEA_SSH_HANDLER";
-  /**
-   * Name of environment variable for SSH handler number
-   */
   @NonNls String SSH_IGNORE_KNOWN_HOSTS_ENV = "GIT4IDEA_SSH_IGNORE_KNOWN_HOSTS";
-  /**
-   * Name of environment variable for SSH handler
-   */
   @NonNls String SSH_PORT_ENV = "GIT4IDEA_SSH_PORT";
   /**
    * Name of environment variable for SSH executable
@@ -60,7 +54,7 @@ public interface GitSSHHandler {
   /**
    * Verify server host key
    *
-   * @param handler                  a handler identifier
+   * @param token                    Access token.
    * @param hostName                 a host name
    * @param port                     a port number
    * @param serverHostKeyAlgorithm   an algorithm
@@ -68,7 +62,7 @@ public interface GitSSHHandler {
    * @param isNew                    true if the key is a new, false if the key was changed
    * @return true the host is verified, false otherwise
    */
-  boolean verifyServerHostKey(int handler,
+  boolean verifyServerHostKey(String token,
                               String hostName,
                               int port,
                               String serverHostKeyAlgorithm,
@@ -78,7 +72,7 @@ public interface GitSSHHandler {
   /**
    * Ask passphrase for the key
    *
-   * @param handler       a handler identifier
+   * @param token       Access token.
    * @param userName      a name of user
    * @param keyPath       a path for the key
    * @param resetPassword a reset password if one was stored in password database
@@ -86,12 +80,12 @@ public interface GitSSHHandler {
    * @return the passphrase entered by the user
    */
   @Nullable
-  String askPassphrase(final int handler, final String userName, final String keyPath, boolean resetPassword, final String lastError);
+  String askPassphrase(String token, final String userName, final String keyPath, boolean resetPassword, final String lastError);
 
   /**
    * Reply to challenge for keyboard-interactive method. Also used for
    *
-   * @param handlerNo   a handler identifier
+   * @param token   Access token.
    * @param userName    a user name (includes host and port)
    * @param name        name of challenge
    * @param instruction instruction
@@ -103,7 +97,7 @@ public interface GitSSHHandler {
    */
   @SuppressWarnings({"UseOfObsoleteCollectionType"})
   @Nullable
-  Vector<String> replyToChallenge(final int handlerNo,
+  Vector<String> replyToChallenge(String token,
                                   final String userName,
                                   final String name,
                                   final String instruction,
@@ -115,19 +109,19 @@ public interface GitSSHHandler {
   /**
    * Ask password for the specified user name
    *
-   * @param handlerNo     a handler identifier
+   * @param token         Access token.
    * @param userName      a name of user to ask password for
    * @param resetPassword a reset password if one was stored in password database
    * @param lastError     a last error
    * @return the password or null if authentication failed.
    */
   @Nullable
-  String askPassword(final int handlerNo, final String userName, boolean resetPassword, final String lastError);
+  String askPassword(String token, final String userName, boolean resetPassword, final String lastError);
 
   /**
    * Notify invoker about last successful authentication attempt.
    *
-   * @param handlerNo the handler
+   * @param token the handler
    * @param userName  the user name
    * @param method    the authentication method, the empty string means that authentication failed
    * @param error     the error shown in the case when authentication process failed
@@ -135,14 +129,14 @@ public interface GitSSHHandler {
    * @return The method doesn't return any sensible value, but it is needed here, since the Apache XML-RPC implementation which we use
    *         doesn't allow void methods: "IllegalArgumentException: void return types for handler methods not supported".
    */
-  String setLastSuccessful(final int handlerNo, final String userName, final String method, final String error);
+  String setLastSuccessful(String token, final String userName, final String method, final String error);
 
   /**
    * Get last successful authentication method
    *
-   * @param handlerNo the handler no
+   * @param token Access token
    * @param userName  the user name
    * @return the authentication method, the empty string means that last authentication failed
    */
-  String getLastSuccessful(final int handlerNo, final String userName);
+  String getLastSuccessful(String token, final String userName);
 }
index c4ddd216e1cd08a67816981531dcffca2b5c6a3c..593362a3ee8eef3ccfa97d3f1d1d607130005a02 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2012 JetBrains s.r.o.
+ * Copyright 2000-2016 JetBrains s.r.o.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -49,7 +49,7 @@ public class GitSSHXmlRpcClient implements GitSSHHandler {
    * {@inheritDoc}
    */
   @SuppressWarnings("unchecked")
-  public boolean verifyServerHostKey(final int handler,
+  public boolean verifyServerHostKey(String token,
                                      final String hostname,
                                      final int port,
                                      final String serverHostKeyAlgorithm,
@@ -59,7 +59,7 @@ public class GitSSHXmlRpcClient implements GitSSHHandler {
       return false;
     }
     Vector parameters = new Vector();
-    parameters.add(handler);
+    parameters.add(token);
     parameters.add(hostname);
     parameters.add(port);
     parameters.add(serverHostKeyAlgorithm);
@@ -91,7 +91,7 @@ public class GitSSHXmlRpcClient implements GitSSHHandler {
    */
   @Nullable
   @SuppressWarnings("unchecked")
-  public String askPassphrase(final int handler,
+  public String askPassphrase(String token,
                               final String username,
                               final String keyPath,
                               final boolean resetPassword,
@@ -100,7 +100,7 @@ public class GitSSHXmlRpcClient implements GitSSHHandler {
       return null;
     }
     Vector parameters = new Vector();
-    parameters.add(handler);
+    parameters.add(token);
     parameters.add(username);
     parameters.add(keyPath);
     parameters.add(resetPassword);
@@ -121,7 +121,7 @@ public class GitSSHXmlRpcClient implements GitSSHHandler {
    */
   @Nullable
   @SuppressWarnings("unchecked")
-  public Vector<String> replyToChallenge(final int handlerNo,
+  public Vector<String> replyToChallenge(String token,
                                          final String username,
                                          final String name,
                                          final String instruction,
@@ -133,7 +133,7 @@ public class GitSSHXmlRpcClient implements GitSSHHandler {
       return null;
     }
     Vector parameters = new Vector();
-    parameters.add(handlerNo);
+    parameters.add(token);
     parameters.add(username);
     parameters.add(name);
     parameters.add(instruction);
@@ -157,12 +157,12 @@ public class GitSSHXmlRpcClient implements GitSSHHandler {
    */
   @Nullable
   @SuppressWarnings("unchecked")
-  public String askPassword(final int handlerNo, final String username, final boolean resetPassword, final String lastError) {
+  public String askPassword(String token, final String username, final boolean resetPassword, final String lastError) {
     if (myClient == null) {
       return null;
     }
     Vector parameters = new Vector();
-    parameters.add(handlerNo);
+    parameters.add(token);
     parameters.add(username);
     parameters.add(resetPassword);
     parameters.add(lastError);
@@ -179,12 +179,12 @@ public class GitSSHXmlRpcClient implements GitSSHHandler {
 
   @Override
   @SuppressWarnings("unchecked")
-  public String setLastSuccessful(int handlerNo, String userName, String method, String error) {
+  public String setLastSuccessful(String token, String userName, String method, String error) {
     if (myClient == null) {
       return "";
     }
     Vector parameters = new Vector();
-    parameters.add(handlerNo);
+    parameters.add(token);
     parameters.add(userName);
     parameters.add(method);
     parameters.add(error);
@@ -204,22 +204,22 @@ public class GitSSHXmlRpcClient implements GitSSHHandler {
    */
   @Override
   @SuppressWarnings("unchecked")
-  public String getLastSuccessful(int handlerNo, String userName) {
+  public String getLastSuccessful(String token, String userName) {
     if (myClient == null) {
       return "";
     }
     Vector parameters = new Vector();
-    parameters.add(handlerNo);
+    parameters.add(token);
     parameters.add(userName);
     try {
       return (String)myClient.execute(methodName("getLastSuccessful"), parameters);
     }
     catch (XmlRpcException e) {
-      log("getLastSuccessful failed. handlerNo: " + handlerNo + ", userName: " + userName + ", client: " + myClient.getURL());
+      log("getLastSuccessful failed. token: " + token + ", userName: " + userName + ", client: " + myClient.getURL());
       throw new RuntimeException("Invocation failed " + e.getMessage(), e);
     }
     catch (IOException e) {
-      log("getLastSuccessful failed. handlerNo: " + handlerNo + ", userName: " + userName + ", client: " + myClient.getURL());
+      log("getLastSuccessful failed. token: " + token + ", userName: " + userName + ", client: " + myClient.getURL());
       throw new RuntimeException("Invocation failed " + e.getMessage(), e);
     }
   }
index b5bc260b95205032ebb61d238c54e188e0b806a5..67a74b92b0ac680fe8ec51b27c4af5859d235b00 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2012 JetBrains s.r.o.
+ * Copyright 2000-2016 JetBrains s.r.o.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -47,7 +47,7 @@ public class SSHMain implements GitExternalApp {
   /**
    * Handler number
    */
-  private final int myHandlerNo;
+  private final String myHandlerNo;
   /**
    * the xml RPC port
    */
@@ -120,7 +120,7 @@ public class SSHMain implements GitExternalApp {
   private SSHMain(String host, String username, Integer port, String command) throws IOException {
     SSHConfig config = SSHConfig.load();
     myHost = config.lookup(username, host, port);
-    myHandlerNo = Integer.parseInt(System.getenv(GitSSHHandler.SSH_HANDLER_ENV));
+    myHandlerNo = System.getenv(GitSSHHandler.SSH_HANDLER_ENV);
     int xmlRpcPort = Integer.parseInt(System.getenv(GitSSHHandler.SSH_PORT_ENV));
     myXmlRpcClient = new GitSSHXmlRpcClient(xmlRpcPort, myHost.isBatchMode());
     myCommand = command;
index ef234be8808765ec7d585eee09dfb9dc2d15714c..52aec9f6388e50951f68a6a82a34c63e7143c579 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2014 JetBrains s.r.o.
+ * Copyright 2000-2016 JetBrains s.r.o.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -89,8 +89,8 @@ public abstract class GitHandler {
   private final File myWorkingDirectory;
 
   private boolean myEnvironmentCleanedUp = true; // the flag indicating that environment has been cleaned up, by default is true because there is nothing to clean
-  private int mySshHandler = -1;
-  private int myHttpHandler = -1;
+  private UUID mySshHandler;
+  private UUID myHttpHandler;
   private Processor<OutputStream> myInputProcessor; // The processor for stdin
 
   // if true process might be cancelled
@@ -463,7 +463,7 @@ public abstract class GitHandler {
     GitHttpAuthenticator httpAuthenticator = service.createAuthenticator(myProject, myCommand, ObjectUtils.assertNotNull(myUrls));
     myHttpHandler = service.registerHandler(httpAuthenticator, myProject);
     myEnvironmentCleanedUp = false;
-    myEnv.put(GitAskPassXmlRpcHandler.GIT_ASK_PASS_HANDLER_ENV, Integer.toString(myHttpHandler));
+    myEnv.put(GitAskPassXmlRpcHandler.GIT_ASK_PASS_HANDLER_ENV, myHttpHandler.toString());
     int port = service.getXmlRcpPort();
     myEnv.put(GitAskPassXmlRpcHandler.GIT_ASK_PASS_PORT_ENV, Integer.toString(port));
     LOG.debug(String.format("handler=%s, port=%s", myHttpHandler, port));
@@ -475,7 +475,7 @@ public abstract class GitHandler {
     myEnv.put(GitSSHHandler.GIT_SSH_ENV, ssh.getScriptPath().getPath());
     mySshHandler = ssh.registerHandler(new GitSSHGUIHandler(myProject), myProject);
     myEnvironmentCleanedUp = false;
-    myEnv.put(GitSSHHandler.SSH_HANDLER_ENV, Integer.toString(mySshHandler));
+    myEnv.put(GitSSHHandler.SSH_HANDLER_ENV, mySshHandler.toString());
     int port = ssh.getXmlRcpPort();
     myEnv.put(GitSSHHandler.SSH_PORT_ENV, Integer.toString(port));
     LOG.debug(String.format("handler=%s, port=%s", mySshHandler, port));
@@ -602,10 +602,10 @@ public abstract class GitHandler {
     if (myEnvironmentCleanedUp) {
       return;
     }
-    if (mySshHandler >= 0) {
+    if (mySshHandler != null) {
       ServiceManager.getService(GitXmlRpcSshService.class).unregisterHandler(mySshHandler);
     }
-    if (myHttpHandler >= 0) {
+    if (myHttpHandler != null) {
       ServiceManager.getService(GitHttpAuthService.class).unregisterHandler(myHttpHandler);
     }
     myEnvironmentCleanedUp = true;
index 6ddbcab22147b9f4ee7fc07e70cb842022fa8566..7cc1a8deb06cef22fef593ff5ca54195404198f6 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2013 JetBrains s.r.o.
+ * Copyright 2000-2016 JetBrains s.r.o.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@ import org.jetbrains.git4idea.ssh.GitXmlRpcHandlerService;
 import org.jetbrains.git4idea.util.ScriptGenerator;
 
 import java.util.Collection;
+import java.util.UUID;
 
 /**
  * Provides the authentication mechanism for Git HTTP connections.
@@ -57,14 +58,14 @@ public abstract class GitHttpAuthService extends GitXmlRpcHandlerService<GitHttp
   public class InternalRequestHandlerDelegate implements GitAskPassXmlRpcHandler {
     @NotNull
     @Override
-    public String askUsername(int handler, @NotNull String url) {
-      return getHandler(handler).askUsername(url);
+    public String askUsername(String token, @NotNull String url) {
+      return getHandler(UUID.fromString(token)).askUsername(url);
     }
 
     @NotNull
     @Override
-    public String askPassword(int handler, @NotNull String url) {
-      return getHandler(handler).askPassword(url);
+    public String askPassword(String token, @NotNull String url) {
+      return getHandler(UUID.fromString(token)).askPassword(url);
     }
   }
 
index ea9d7ab8c2c07c5a5d7e575215b0d3cc5598ef4c..78874b69f6a0448892626a62fed94970fb830e6f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * Copyright 2000-2016 JetBrains s.r.o.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -27,6 +27,7 @@ import org.jetbrains.annotations.NotNull;
 import org.jetbrains.git4idea.util.ScriptGenerator;
 import org.jetbrains.ide.BuiltInServerManager;
 
+import java.security.SecureRandom;
 import java.util.Map;
 import java.util.Random;
 
@@ -53,7 +54,7 @@ public class GitRebaseEditorService {
   /**
    * Random number generator
    */
-  private static final Random oursRandom = new Random();
+  private static final Random oursRandom = new SecureRandom();
   /**
    * The prefix for rebase editors
    */
index 12c99d9d26ad6ff5da929837a40558e62ae15d3e..a07b2d63d235257e1871a917b608e796a07411fd 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2013 JetBrains s.r.o.
+ * Copyright 2000-2016 JetBrains s.r.o.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -30,6 +30,7 @@ import org.jetbrains.ide.BuiltInServerManager;
 
 import java.io.File;
 import java.io.IOException;
+import java.util.UUID;
 
 /**
  * <p>The provider of external application scripts called by Git when a remote operation needs communication with the user.</p>
@@ -57,8 +58,7 @@ public abstract class GitXmlRpcHandlerService<T> {
   @Nullable private File myScriptPath;
   @NotNull private final Object SCRIPT_FILE_LOCK = new Object();
 
-  @NotNull private final THashMap<Integer, T> handlers = new THashMap<Integer, T>();
-  private int myNextHandlerKey;
+  @NotNull private final THashMap<UUID, T> handlers = new THashMap<UUID, T>();
   @NotNull private final Object HANDLERS_LOCK = new Object();
 
   /**
@@ -111,14 +111,14 @@ public abstract class GitXmlRpcHandlerService<T> {
    * @param parentDisposable a disposable to unregister the handler if it doesn't get unregistered manually
    * @return an identifier to pass to the environment variable
    */
-  public int registerHandler(@NotNull T handler, @NotNull Disposable parentDisposable) {
+  public UUID registerHandler(@NotNull T handler, @NotNull Disposable parentDisposable) {
     synchronized (HANDLERS_LOCK) {
       XmlRpcServer xmlRpcServer = XmlRpcServer.SERVICE.getInstance();
       if (!xmlRpcServer.hasHandler(myHandlerName)) {
         xmlRpcServer.addHandler(myHandlerName, createRpcRequestHandlerDelegate());
       }
 
-      final int key = myNextHandlerKey;
+      final UUID key = UUID.randomUUID();
       handlers.put(key, handler);
       Disposer.register(parentDisposable, new Disposable() {
         @Override
@@ -126,7 +126,6 @@ public abstract class GitXmlRpcHandlerService<T> {
           handlers.remove(key);
         }
       });
-      myNextHandlerKey++;
       return key;
     }
   }
@@ -146,7 +145,7 @@ public abstract class GitXmlRpcHandlerService<T> {
    * @return the registered handler
    */
   @NotNull
-  protected T getHandler(int key) {
+  protected T getHandler(UUID key) {
     synchronized (HANDLERS_LOCK) {
       T rc = handlers.get(key);
       if (rc == null) {
@@ -161,7 +160,7 @@ public abstract class GitXmlRpcHandlerService<T> {
    *
    * @param key the key to unregister
    */
-  public void unregisterHandler(int key) {
+  public void unregisterHandler(UUID key) {
     synchronized (HANDLERS_LOCK) {
       if (handlers.remove(key) == null) {
         throw new IllegalArgumentException("The handler " + key + " is not registered");
index 5d802d36e065965c91231b8734a40ef092d8528b..9b03e6db8c940cc0a100139ee54c13b88c310f95 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2013 JetBrains s.r.o.
+ * Copyright 2000-2016 JetBrains s.r.o.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@ import git4idea.commands.GitSSHGUIHandler;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.git4idea.util.ScriptGenerator;
 
+import java.util.UUID;
 import java.util.Vector;
 
 /**
@@ -49,37 +50,37 @@ public class GitXmlRpcSshService extends GitXmlRpcHandlerService<GitSSHGUIHandle
   public class InternalRequestHandler implements GitSSHHandler {
 
     @Override
-    public boolean verifyServerHostKey(int handler, String hostname, int port, String serverHostKeyAlgorithm, String serverHostKey,
+    public boolean verifyServerHostKey(String handler, String hostname, int port, String serverHostKeyAlgorithm, String serverHostKey,
                                        boolean isNew) {
-      return getHandler(handler).verifyServerHostKey(hostname, port, serverHostKeyAlgorithm, serverHostKey, isNew);
+      return getHandler(UUID.fromString(handler)).verifyServerHostKey(hostname, port, serverHostKeyAlgorithm, serverHostKey, isNew);
     }
 
     @Override
-    public String askPassphrase(int handler, String username, String keyPath, boolean resetPassword, String lastError) {
-      return adjustNull(getHandler(handler).askPassphrase(username, keyPath, resetPassword, lastError));
+    public String askPassphrase(String handler, String username, String keyPath, boolean resetPassword, String lastError) {
+      return adjustNull(getHandler(UUID.fromString(handler)).askPassphrase(username, keyPath, resetPassword, lastError));
     }
 
     @Override
     @SuppressWarnings({"UseOfObsoleteCollectionType"})
-    public Vector<String> replyToChallenge(int handlerNo, String username, String name, String instruction, int numPrompts,
+    public Vector<String> replyToChallenge(String token, String username, String name, String instruction, int numPrompts,
                                            Vector<String> prompt, Vector<Boolean> echo, String lastError) {
-      return adjustNull(getHandler(handlerNo).replyToChallenge(username, name, instruction, numPrompts, prompt, echo, lastError));
+      return adjustNull(getHandler(UUID.fromString(token)).replyToChallenge(username, name, instruction, numPrompts, prompt, echo, lastError));
     }
 
     @Override
-    public String askPassword(int handlerNo, String username, boolean resetPassword, String lastError) {
-      return adjustNull(getHandler(handlerNo).askPassword(username, resetPassword, lastError));
+    public String askPassword(String token, String username, boolean resetPassword, String lastError) {
+      return adjustNull(getHandler(UUID.fromString(token)).askPassword(username, resetPassword, lastError));
     }
 
     @Override
-    public String setLastSuccessful(int handlerNo, String userName, String method, String error) {
-      getHandler(handlerNo).setLastSuccessful(userName, method, error);
+    public String setLastSuccessful(String token, String userName, String method, String error) {
+      getHandler(UUID.fromString(token)).setLastSuccessful(userName, method, error);
       return "";
     }
 
     @Override
-    public String getLastSuccessful(int handlerNo, String userName) {
-      return getHandler(handlerNo).getLastSuccessful(userName);
+    public String getLastSuccessful(String token, String userName) {
+      return getHandler(UUID.fromString(token)).getLastSuccessful(userName);
     }
 
     /**
index e59ea477c6fd3ce3193d1fe19dd93df398608a5a..cb186d7025a1cc99e61ed7f81b71c4e7b221d601 100644 (file)
@@ -22,7 +22,6 @@ import com.intellij.execution.process.ProcessOutputTypes;
 import com.intellij.openapi.application.ApplicationManager;
 import com.intellij.openapi.util.Key;
 import com.intellij.openapi.vfs.CharsetToolkit;
-import com.intellij.util.net.NetUtils;
 import org.jetbrains.annotations.Nullable;
 
 import java.io.IOException;
@@ -30,6 +29,7 @@ import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.UnsupportedEncodingException;
 import java.net.ConnectException;
+import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.Socket;
 
@@ -95,7 +95,7 @@ class OutputTabAdapter extends ProcessAdapter {
     @Nullable
     private InputStream connect(int port) throws IOException {
         final long s = System.currentTimeMillis();
-        final InetSocketAddress endpoint = new InetSocketAddress(NetUtils.getLoopbackAddress(), port);
+        final InetSocketAddress endpoint = new InetSocketAddress(InetAddress.getLoopbackAddress(), port);
 
         myStartedProcess.notifyTextAvailable("Connecting to XSLT runner on " + endpoint + "\n", ProcessOutputTypes.SYSTEM);