From b247ddfa9998c5f5dc61fb300169a73eee75f061 Mon Sep 17 00:00:00 2001
From: Vladimir Krivosheev
Date: Mon, 11 Apr 2016 13:39:04 +0200
Subject: [PATCH 01/67] add isAllowRequestOnlyFromLocalOrigin and enabled it by
---
.../org/jetbrains/ide/HttpRequestHandler.java | 4 +++
.../BuiltInWebBrowserUrlProvider.java | 3 +-
.../builtInWebServer/BuiltInWebServer.kt | 25 +----------------
.../org/jetbrains/ide/DiffHttpService.java | 5 ++++
.../jetbrains/ide/OpenFileHttpService.java | 5 ++++
.../ide/ProjectSetRequestHandler.java | 5 ++++
.../src/com/intellij/util/net/NetUtils.java | 8 +-----
.../io/DelegatingHttpRequestHandler.kt | 25 ++++++++++++++---
.../src/org/jetbrains/io/netty.kt | 28 +++++++++++++++++++
9 files changed, 72 insertions(+), 36 deletions(-)
diff --git a/platform/built-in-server-api/src/org/jetbrains/ide/HttpRequestHandler.java b/platform/built-in-server-api/src/org/jetbrains/ide/HttpRequestHandler.java
index 7ac77ba49554..18f473c77e8a 100644
--- a/platform/built-in-server-api/src/org/jetbrains/ide/HttpRequestHandler.java
+++ b/platform/built-in-server-api/src/org/jetbrains/ide/HttpRequestHandler.java
@@ -50,4 +50,8 @@ public abstract class HttpRequestHandler {
*/
public abstract boolean process(@NotNull QueryStringDecoder urlDecoder, @NotNull FullHttpRequest request, @NotNull ChannelHandlerContext context)
throws IOException;
+
+ public boolean isAllowRequestOnlyFromLocalOrigin() {
+ return true;
+ }
}
diff --git a/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebBrowserUrlProvider.java b/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebBrowserUrlProvider.java
index 1fa74d36be45..2789e1a00995 100644
--- a/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebBrowserUrlProvider.java
+++ b/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebBrowserUrlProvider.java
@@ -33,6 +33,7 @@ import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.ide.BuiltInServerManager;
+import org.jetbrains.io.NettyKt;
import java.util.Collections;
import java.util.List;
@@ -83,7 +84,7 @@ public class BuiltInWebBrowserUrlProvider extends WebBrowserUrlProvider implemen
}
String host = currentAuthority.substring(0, portIndex);
- if (!BuiltInWebServerKt.isOwnHostName(host)) {
+ if (!NettyKt.isOwnHostName(host)) {
return false;
}
diff --git a/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt b/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt
index 1dd2f1b53e3c..8942e1d80f89 100644
--- a/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt
+++ b/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt
@@ -28,7 +28,6 @@ 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.ChannelHandlerContext
import io.netty.handler.codec.http.FullHttpRequest
import io.netty.handler.codec.http.HttpMethod
@@ -36,9 +35,8 @@ import io.netty.handler.codec.http.HttpResponseStatus
import io.netty.handler.codec.http.QueryStringDecoder
import org.jetbrains.ide.HttpRequestHandler
import org.jetbrains.io.host
+import org.jetbrains.io.isOwnHostName
import org.jetbrains.io.send
-import java.net.InetAddress
-import java.net.UnknownHostException
import java.nio.file.Path
internal val LOG = Logger.getInstance(BuiltInWebServer::class.java)
@@ -219,25 +217,4 @@ fun findIndexFile(basedir: Path): Path? {
}
}
return null
-}
-
-fun isOwnHostName(host: String): Boolean {
- if (NetUtils.isLocalhost(host)) {
- return true
- }
-
- try {
- val address = InetAddress.getByName(host)
- if (host == address.hostAddress || host.equals(address.canonicalHostName, ignoreCase = true)) {
- return true
- }
-
- val localHostName = InetAddress.getLocalHost().hostName
- // WEB-8889
- // 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) {
- return false
- }
}
\ No newline at end of file
diff --git a/platform/built-in-server/src/org/jetbrains/ide/DiffHttpService.java b/platform/built-in-server/src/org/jetbrains/ide/DiffHttpService.java
index d3b76cf3b5c1..0652efa37ff5 100644
--- a/platform/built-in-server/src/org/jetbrains/ide/DiffHttpService.java
+++ b/platform/built-in-server/src/org/jetbrains/ide/DiffHttpService.java
@@ -169,4 +169,9 @@ final class DiffHttpService extends RestService {
reader.endArray();
return null;
}
+
+ @Override
+ public boolean isAllowRequestOnlyFromLocalOrigin() {
+ return false;
+ }
}
diff --git a/platform/built-in-server/src/org/jetbrains/ide/OpenFileHttpService.java b/platform/built-in-server/src/org/jetbrains/ide/OpenFileHttpService.java
index 1a6ddfc1f86b..d2a54ca323a2 100644
--- a/platform/built-in-server/src/org/jetbrains/ide/OpenFileHttpService.java
+++ b/platform/built-in-server/src/org/jetbrains/ide/OpenFileHttpService.java
@@ -347,4 +347,9 @@ class OpenFileHttpService extends RestService {
public boolean focused = true;
}
+
+ @Override
+ public boolean isAllowRequestOnlyFromLocalOrigin() {
+ return false;
+ }
}
diff --git a/platform/built-in-server/src/org/jetbrains/ide/ProjectSetRequestHandler.java b/platform/built-in-server/src/org/jetbrains/ide/ProjectSetRequestHandler.java
index 428635643535..70151e1962bc 100644
--- a/platform/built-in-server/src/org/jetbrains/ide/ProjectSetRequestHandler.java
+++ b/platform/built-in-server/src/org/jetbrains/ide/ProjectSetRequestHandler.java
@@ -74,4 +74,9 @@ public class ProjectSetRequestHandler extends RestService {
sendOk(request, context);
return null;
}
+
+ @Override
+ public boolean isAllowRequestOnlyFromLocalOrigin() {
+ return false;
+ }
}
diff --git a/platform/platform-api/src/com/intellij/util/net/NetUtils.java b/platform/platform-api/src/com/intellij/util/net/NetUtils.java
index e00978ee0c6a..c261cea5d7ef 100644
--- a/platform/platform-api/src/com/intellij/util/net/NetUtils.java
+++ b/platform/platform-api/src/com/intellij/util/net/NetUtils.java
@@ -49,13 +49,7 @@ public class NetUtils {
}
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) {
diff --git a/platform/platform-impl/src/org/jetbrains/io/DelegatingHttpRequestHandler.kt b/platform/platform-impl/src/org/jetbrains/io/DelegatingHttpRequestHandler.kt
index bfad9825627f..c77a9b6e832e 100644
--- a/platform/platform-impl/src/org/jetbrains/io/DelegatingHttpRequestHandler.kt
+++ b/platform/platform-impl/src/org/jetbrains/io/DelegatingHttpRequestHandler.kt
@@ -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.
@@ -30,6 +30,7 @@ import org.apache.sanselan.Sanselan
import org.jetbrains.ide.HttpRequestHandler
import java.awt.image.BufferedImage
+import java.net.URI
private val PREV_HANDLER = AttributeKey.valueOf("DelegatingHttpRequestHandler.handler")
@@ -38,10 +39,27 @@ internal class DelegatingHttpRequestHandler : DelegatingHttpRequestHandlerBase()
override fun process(context: ChannelHandlerContext,
request: FullHttpRequest,
urlDecoder: QueryStringDecoder): Boolean {
+ fun HttpRequestHandler.checkAndProcess(): Boolean {
+ if (isAllowRequestOnlyFromLocalOrigin) {
+ request.origin?.let {
+ try {
+ if (!isOwnHostName(URI(it).host)) {
+ return false
+ }
+ }
+ catch (e: Exception) {
+ return false
+ }
+ }
+ }
+
+ return 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.isSupported(request) && connectedHandler.checkAndProcess()) {
return true
}
// prev cached connectedHandler is not suitable for this request, so, let's find it again
@@ -50,7 +68,7 @@ internal class DelegatingHttpRequestHandler : DelegatingHttpRequestHandlerBase()
for (handler in HttpRequestHandler.EP_NAME.extensions) {
try {
- if (handler.isSupported(request) && handler.process(urlDecoder, request, context)) {
+ if (handler.isSupported(request) && handler.checkAndProcess()) {
prevHandlerAttribute.set(handler)
return true
}
@@ -58,7 +76,6 @@ internal class DelegatingHttpRequestHandler : DelegatingHttpRequestHandlerBase()
catch (e: Throwable) {
Logger.getInstance(BuiltInServer::class.java).error(e)
}
-
}
if (urlDecoder.path() == "/favicon.ico") {
diff --git a/platform/platform-impl/src/org/jetbrains/io/netty.kt b/platform/platform-impl/src/org/jetbrains/io/netty.kt
index ceb922cc1a34..6ddf9d49b31e 100644
--- a/platform/platform-impl/src/org/jetbrains/io/netty.kt
+++ b/platform/platform-impl/src/org/jetbrains/io/netty.kt
@@ -17,6 +17,7 @@ package org.jetbrains.io
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
@@ -32,7 +33,9 @@ import io.netty.handler.ssl.SslHandler
import io.netty.util.concurrent.GenericFutureListener
import org.jetbrains.concurrency.AsyncPromise
import org.jetbrains.ide.PooledThreadExecutor
+import java.net.InetAddress
import java.net.InetSocketAddress
+import java.net.UnknownHostException
import java.util.concurrent.TimeUnit
inline fun Bootstrap.handler(crossinline task: (Channel) -> Unit): Bootstrap {
@@ -59,6 +62,7 @@ fun oioClientBootstrap(): Bootstrap {
}
inline fun ChannelFuture.addChannelListener(crossinline listener: (future: ChannelFuture) -> Unit) {
+ @Suppress("RedundantSamConstructor")
addListener(GenericFutureListener { listener(it) })
}
@@ -98,6 +102,9 @@ val Channel.uriScheme: String
val HttpRequest.host: String?
get() = headers().getAsString(HttpHeaderNames.HOST)
+val HttpRequest.origin: String?
+ get() = headers().getAsString(HttpHeaderNames.ORIGIN)
+
inline fun ByteBuf.releaseIfError(task: () -> T): T {
try {
return task()
@@ -110,4 +117,25 @@ inline fun ByteBuf.releaseIfError(task: () -> T): T {
throw e
}
}
+}
+
+fun isOwnHostName(host: String): Boolean {
+ if (NetUtils.isLocalhost(host)) {
+ return true
+ }
+
+ try {
+ val address = InetAddress.getByName(host)
+ if (host == address.hostAddress || host.equals(address.canonicalHostName, ignoreCase = true)) {
+ return true
+ }
+
+ val localHostName = InetAddress.getLocalHost().hostName
+ // WEB-8889
+ // 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) {
+ return false
+ }
}
\ No newline at end of file
From b6762b192f71f65b1da33e04bfcf7f63c2ae20b5 Mon Sep 17 00:00:00 2001
From: Vladimir Krivosheev
Date: Mon, 11 Apr 2016 14:16:00 +0200
Subject: [PATCH 02/67] IDEA-CR-10038 check Referer as well
---
.../io/DelegatingHttpRequestHandler.kt | 24 ++++++++++---------
.../src/org/jetbrains/io/netty.kt | 3 +++
2 files changed, 16 insertions(+), 11 deletions(-)
diff --git a/platform/platform-impl/src/org/jetbrains/io/DelegatingHttpRequestHandler.kt b/platform/platform-impl/src/org/jetbrains/io/DelegatingHttpRequestHandler.kt
index c77a9b6e832e..e6d1d39eb6b0 100644
--- a/platform/platform-impl/src/org/jetbrains/io/DelegatingHttpRequestHandler.kt
+++ b/platform/platform-impl/src/org/jetbrains/io/DelegatingHttpRequestHandler.kt
@@ -40,17 +40,8 @@ internal class DelegatingHttpRequestHandler : DelegatingHttpRequestHandlerBase()
request: FullHttpRequest,
urlDecoder: QueryStringDecoder): Boolean {
fun HttpRequestHandler.checkAndProcess(): Boolean {
- if (isAllowRequestOnlyFromLocalOrigin) {
- request.origin?.let {
- try {
- if (!isOwnHostName(URI(it).host)) {
- return false
- }
- }
- catch (e: Exception) {
- return false
- }
- }
+ if (isAllowRequestOnlyFromLocalOrigin && (!parseAndCheckIsOwnHostName(request.origin) || !parseAndCheckIsOwnHostName(request.referrer))) {
+ return false
}
return process(urlDecoder, request, context)
@@ -102,4 +93,15 @@ internal class DelegatingHttpRequestHandler : DelegatingHttpRequestHandlerBase()
super.exceptionCaught(context, cause)
}
}
+}
+
+private fun parseAndCheckIsOwnHostName(uri: String?): Boolean {
+ try {
+ if (uri == null || isOwnHostName(URI(uri).host)) {
+ return true
+ }
+ }
+ catch (ignored: Exception) {
+ }
+ return false
}
\ No newline at end of file
diff --git a/platform/platform-impl/src/org/jetbrains/io/netty.kt b/platform/platform-impl/src/org/jetbrains/io/netty.kt
index 6ddf9d49b31e..367a53806429 100644
--- a/platform/platform-impl/src/org/jetbrains/io/netty.kt
+++ b/platform/platform-impl/src/org/jetbrains/io/netty.kt
@@ -105,6 +105,9 @@ val HttpRequest.host: String?
val HttpRequest.origin: String?
get() = headers().getAsString(HttpHeaderNames.ORIGIN)
+val HttpRequest.referrer: String?
+ get() = headers().getAsString(HttpHeaderNames.REFERER)
+
inline fun ByteBuf.releaseIfError(task: () -> T): T {
try {
return task()
From 9480e5f00fe16b64f6f7254532063397d3eb0d49 Mon Sep 17 00:00:00 2001
From: Vladimir Krivosheev
Date: Mon, 11 Apr 2016 15:22:33 +0200
Subject: [PATCH 03/67] cleanup
---
.../src/org/jetbrains/builtInWebServer/StaticFileHandler.kt | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/platform/built-in-server/src/org/jetbrains/builtInWebServer/StaticFileHandler.kt b/platform/built-in-server/src/org/jetbrains/builtInWebServer/StaticFileHandler.kt
index ea215e535709..6830ed6c9b16 100644
--- a/platform/built-in-server/src/org/jetbrains/builtInWebServer/StaticFileHandler.kt
+++ b/platform/built-in-server/src/org/jetbrains/builtInWebServer/StaticFileHandler.kt
@@ -1,7 +1,6 @@
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
@@ -24,10 +23,9 @@ private class StaticFileHandler : WebServerFileHandler() {
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
}
From 161378798234d70fd16e9d8304ffefd2abc5c1a7 Mon Sep 17 00:00:00 2001
From: Vladimir Krivosheev
Date: Mon, 11 Apr 2016 16:16:24 +0200
Subject: [PATCH 04/67] don't expose file status if not local origin
---
.../src/org/jetbrains/ide/OpenFileHttpService.java | 4 +++-
.../src/org/jetbrains/io/DelegatingHttpRequestHandler.kt | 5 ++++-
2 files changed, 7 insertions(+), 2 deletions(-)
diff --git a/platform/built-in-server/src/org/jetbrains/ide/OpenFileHttpService.java b/platform/built-in-server/src/org/jetbrains/ide/OpenFileHttpService.java
index d2a54ca323a2..dfc841949790 100644
--- a/platform/built-in-server/src/org/jetbrains/ide/OpenFileHttpService.java
+++ b/platform/built-in-server/src/org/jetbrains/ide/OpenFileHttpService.java
@@ -44,6 +44,7 @@ import org.jetbrains.annotations.Nullable;
import org.jetbrains.builtInWebServer.WebServerPathToFileManager;
import org.jetbrains.concurrency.AsyncPromise;
import org.jetbrains.concurrency.Promise;
+import org.jetbrains.io.DelegatingHttpRequestHandlerKt;
import javax.swing.*;
import java.io.File;
@@ -144,7 +145,8 @@ class OpenFileHttpService extends RestService {
@Override
public void consume(Throwable throwable) {
if (throwable == NOT_FOUND) {
- sendStatus(HttpResponseStatus.NOT_FOUND, keepAlive, channel);
+ // don't expose file status if not local origin
+ sendStatus(DelegatingHttpRequestHandlerKt.isLocalOrigin(request) ? HttpResponseStatus.NOT_FOUND : HttpResponseStatus.OK, keepAlive, channel);
}
else {
// todo send error
diff --git a/platform/platform-impl/src/org/jetbrains/io/DelegatingHttpRequestHandler.kt b/platform/platform-impl/src/org/jetbrains/io/DelegatingHttpRequestHandler.kt
index e6d1d39eb6b0..1e2e75721c1e 100644
--- a/platform/platform-impl/src/org/jetbrains/io/DelegatingHttpRequestHandler.kt
+++ b/platform/platform-impl/src/org/jetbrains/io/DelegatingHttpRequestHandler.kt
@@ -23,6 +23,7 @@ import io.netty.buffer.Unpooled
import io.netty.channel.ChannelHandler
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.QueryStringDecoder
import io.netty.util.AttributeKey
import org.apache.sanselan.ImageFormat
@@ -40,7 +41,7 @@ internal class DelegatingHttpRequestHandler : DelegatingHttpRequestHandlerBase()
request: FullHttpRequest,
urlDecoder: QueryStringDecoder): Boolean {
fun HttpRequestHandler.checkAndProcess(): Boolean {
- if (isAllowRequestOnlyFromLocalOrigin && (!parseAndCheckIsOwnHostName(request.origin) || !parseAndCheckIsOwnHostName(request.referrer))) {
+ if (isAllowRequestOnlyFromLocalOrigin && !request.isLocalOrigin()) {
return false
}
@@ -95,6 +96,8 @@ internal class DelegatingHttpRequestHandler : DelegatingHttpRequestHandlerBase()
}
}
+fun HttpRequest.isLocalOrigin() = parseAndCheckIsOwnHostName(origin) && parseAndCheckIsOwnHostName(referrer)
+
private fun parseAndCheckIsOwnHostName(uri: String?): Boolean {
try {
if (uri == null || isOwnHostName(URI(uri).host)) {
From cfd55771e4e2a00ad45372c5b524f4f8a3d34161 Mon Sep 17 00:00:00 2001
From: Vladimir Krivosheev
Date: Mon, 11 Apr 2016 16:34:34 +0200
Subject: [PATCH 05/67] check intermediate directories
---
.../builtInWebServer/StaticFileHandler.kt | 33 +++++++++++++++----
.../testSrc/BuiltInWebServerTest.kt | 23 +++++++++++++
2 files changed, 49 insertions(+), 7 deletions(-)
diff --git a/platform/built-in-server/src/org/jetbrains/builtInWebServer/StaticFileHandler.kt b/platform/built-in-server/src/org/jetbrains/builtInWebServer/StaticFileHandler.kt
index 6830ed6c9b16..6a0355247728 100644
--- a/platform/built-in-server/src/org/jetbrains/builtInWebServer/StaticFileHandler.kt
+++ b/platform/built-in-server/src/org/jetbrains/builtInWebServer/StaticFileHandler.kt
@@ -1,6 +1,7 @@
package org.jetbrains.builtInWebServer
import com.intellij.openapi.project.Project
+import com.intellij.openapi.vfs.VFileProperty
import com.intellij.util.PathUtilRt
import com.intellij.util.isDirectory
import io.netty.buffer.ByteBufUtf8Writer
@@ -30,10 +31,15 @@ private class StaticFileHandler : WebServerFileHandler() {
return true
}
- sendIoFile(channel, ioFile, request)
+ sendIoFile(channel, ioFile, Paths.get(pathInfo.root.path), request)
}
else {
val file = pathInfo.file!!
+ if (file.`is`(VFileProperty.HIDDEN)) {
+ HttpResponseStatus.FORBIDDEN.send(channel, request)
+ return true
+ }
+
val response = FileResponses.prepareSend(request, channel, file.timeStamp, file.name) ?: return true
val keepAlive = response.addKeepAliveIfNeed(request)
@@ -92,14 +98,27 @@ private class StaticFileHandler : WebServerFileHandler() {
}
}
-fun sendIoFile(channel: Channel, ioFile: Path, request: HttpRequest) {
- if (hasAccess(ioFile)) {
- FileResponses.sendFile(request, channel, ioFile)
- }
- else {
+private fun sendIoFile(channel: Channel, file: Path, root: Path, request: HttpRequest) {
+ if (file.isDirectory()) {
HttpResponseStatus.FORBIDDEN.send(channel, request)
}
+ else if (checkAccess(channel, file, request, root)) {
+ FileResponses.sendFile(request, channel, file)
+ }
+}
+
+fun checkAccess(channel: Channel, file: Path, request: HttpRequest, root: Path): Boolean {
+ var parent = file
+ do {
+ if (!hasAccess(parent)) {
+ HttpResponseStatus.FORBIDDEN.send(channel, request)
+ 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
+private fun hasAccess(result: Path) = Files.isReadable(result) && !(Files.isHidden(result) || result.fileName.toString().startsWith(".ht"))
\ No newline at end of file
diff --git a/platform/built-in-server/testSrc/BuiltInWebServerTest.kt b/platform/built-in-server/testSrc/BuiltInWebServerTest.kt
index 519cc7b4f440..d457fac331be 100644
--- a/platform/built-in-server/testSrc/BuiltInWebServerTest.kt
+++ b/platform/built-in-server/testSrc/BuiltInWebServerTest.kt
@@ -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
From 139ad5aa9269ca0d2a2e667e90409270d3a5dc37 Mon Sep 17 00:00:00 2001
From: Vladimir Krivosheev
Date: Mon, 11 Apr 2016 17:41:58 +0200
Subject: [PATCH 06/67] convert OpenFileHttpService to kotlin
---
.../jetbrains/ide/OpenFileHttpService.java | 446 +++++++-----------
1 file changed, 171 insertions(+), 275 deletions(-)
diff --git a/platform/built-in-server/src/org/jetbrains/ide/OpenFileHttpService.java b/platform/built-in-server/src/org/jetbrains/ide/OpenFileHttpService.java
index dfc841949790..a027da1575ce 100644
--- a/platform/built-in-server/src/org/jetbrains/ide/OpenFileHttpService.java
+++ b/platform/built-in-server/src/org/jetbrains/ide/OpenFileHttpService.java
@@ -13,45 +13,41 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.jetbrains.ide;
+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 org.jetbrains.io.DelegatingHttpRequestHandlerKt;
+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.concurrency.AsyncPromise
+import org.jetbrains.concurrency.Promise
+import org.jetbrains.concurrency.catchError
+import org.jetbrains.concurrency.rejectedPromise
+import org.jetbrains.io.isLocalOrigin
+import java.nio.file.Path
+import java.nio.file.Paths
+import java.util.concurrent.ConcurrentLinkedQueue
+import java.util.regex.Pattern
-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;
+private val NOT_FOUND = Promise.createError("not found")
+private val LINE_AND_COLUMN = Pattern.compile("^(.*?)(?::(\\d+))?(?::(\\d+))?$")
/**
* @api {get} /file Open file
@@ -75,283 +71,183 @@ import java.util.regex.Pattern;
* @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;
+internal class OpenFileHttpService : RestService() {
+ @Volatile private var refreshSessionId: Long = 0
+ private val requests = ConcurrentLinkedQueue()
- private volatile long refreshSessionId = 0;
- private final ConcurrentLinkedQueue requests = new ConcurrentLinkedQueue();
+ override fun getServiceName() = "file"
- @NotNull
- @Override
- protected String getServiceName() {
- return "file";
- }
+ override fun isMethodSupported(method: HttpMethod) = method === HttpMethod.GET || method === HttpMethod.POST
- @Override
- protected boolean isMethodSupported(@NotNull HttpMethod method) {
- return 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()
- @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);
+ val apiRequest: OpenFileRequest
+ if (request.method() === HttpMethod.POST) {
+ apiRequest = gson.value.fromJson(RestService.createJsonReader(request), OpenFileRequest::class.java)
}
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);
+ apiRequest = OpenFileRequest()
+ apiRequest.file = StringUtil.nullize(RestService.getStringParameter("file", urlDecoder), true)
+ apiRequest.line = RestService.getIntParameter("line", urlDecoder)
+ apiRequest.column = RestService.getIntParameter("column", urlDecoder)
+ apiRequest.focused = RestService.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());
+ val prefixLength = 1 + RestService.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)
+ RestService.LOG.assertTrue(matcher.matches())
if (apiRequest.file == null) {
- apiRequest.file = matcher.group(1).trim();
+ apiRequest.file = matcher.group(1).trim { it <= ' ' }
}
if (apiRequest.line == -1) {
- apiRequest.line = StringUtilRt.parseInt(matcher.group(2), 1);
+ apiRequest.line = StringUtilRt.parseInt(matcher.group(2), 1)
}
if (apiRequest.column == -1) {
- apiRequest.column = StringUtilRt.parseInt(matcher.group(3), 1);
+ apiRequest.column = StringUtilRt.parseInt(matcher.group(3), 1)
}
}
if (apiRequest.file == null) {
- sendStatus(HttpResponseStatus.BAD_REQUEST, keepAlive, channel);
- return null;
+ RestService.sendStatus(HttpResponseStatus.BAD_REQUEST, keepAlive, channel)
+ return null
}
- openFile(apiRequest)
- .done(new Consumer() {
- @Override
- public void consume(Void aVoid) {
- sendStatus(HttpResponseStatus.OK, keepAlive, channel);
+ openFile(apiRequest).done { RestService.sendStatus(HttpResponseStatus.OK, keepAlive, channel) }
+ .rejected { throwable ->
+ if (throwable === NOT_FOUND) {
+ // don't expose file status if not local origin
+ RestService.sendStatus(if (request.isLocalOrigin()) HttpResponseStatus.NOT_FOUND else HttpResponseStatus.OK, keepAlive, channel)
}
- })
- .rejected(new Consumer() {
- @Override
- public void consume(Throwable throwable) {
- if (throwable == NOT_FOUND) {
- // don't expose file status if not local origin
- sendStatus(DelegatingHttpRequestHandlerKt.isLocalOrigin(request) ? HttpResponseStatus.NOT_FOUND : HttpResponseStatus.OK, 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 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> 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();
+ else {
+ // todo send error
+ RestService.sendStatus(HttpResponseStatus.INTERNAL_SERVER_ERROR, keepAlive, channel)
+ RestService.LOG.error(throwable)
}
}
+ return null
+ }
- return openAbsolutePath(file, request);
+ fun openFile(request: OpenFileRequest): Promise {
+ val path = FileUtil.expandUserHome(request.file!!)
+ val file = Paths.get(FileUtil.toSystemDependentName(path))
+ if (file.isAbsolute) {
+ 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 openAbsolutePath(@NotNull final File file, @NotNull final OpenFileRequest request) {
- if (!file.exists()) {
- return Promise.reject(NOT_FOUND);
- }
-
- final AsyncPromise promise = new AsyncPromise();
- 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);
+ val queue = RefreshQueue.getInstance()
+ queue.cancelSession(refreshSessionId)
+ val mainTask = OpenFileTask(FileUtil.toCanonicalPath(FileUtil.toSystemIndependentName(path), '/'), 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 {
- navigate(ProjectUtil.guessProjectForContentFile(virtualFile), virtualFile, request);
- promise.setResult(null);
+ task.promise.setError(NOT_FOUND)
}
}
- catch (Throwable e) {
- promise.setError(e);
- }
}
- });
- return promise;
+ }, ModalityState.NON_MODAL)
+
+ session.addAllFiles(*ManagingFS.getInstance().localRoots)
+ refreshSessionId = session.id
+ session.launch()
+ return mainTask.promise
}
- private static final class OpenFileTask {
- final String path;
- final OpenFileRequest request;
+ override fun isAllowRequestOnlyFromLocalOrigin() = false
+}
- final AsyncPromise promise = new AsyncPromise();
+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
- OpenFileTask(@NotNull String path, @NotNull OpenFileRequest request) {
- this.path = path;
- this.request = request;
+ var focused = true
+}
+
+private class OpenFileTask(internal val path: String, internal val request: OpenFileRequest) {
+ internal val promise = AsyncPromise()
+}
+
+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) {
+ com.intellij.ide.impl.ProjectUtil.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
}
}
- 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;
+ 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
+ }
+ }
+ }
+ }
}
- @Override
- public boolean isAllowRequestOnlyFromLocalOrigin() {
- return false;
+ virtualFile?.let {
+ AppUIUtil.invokeLaterIfProjectAlive(project!!, Runnable { navigate(project, it, request) })
+ return true
}
+ return false
+}
+
+private fun openAbsolutePath(file: Path, request: OpenFileRequest): Promise {
+ if (!file.exists()) {
+ return rejectedPromise(NOT_FOUND)
+ }
+
+ val promise = AsyncPromise()
+ 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
}
From 6b1b10bd3306612d480c17e4c808b68faa3ca9ed Mon Sep 17 00:00:00 2001
From: Vladimir Krivosheev
Date: Mon, 11 Apr 2016 17:42:19 +0200
Subject: [PATCH 07/67] cleanup
---
...OpenFileHttpService.java => OpenFileHttpService.kt} | 0
.../src/org/jetbrains/ide/OpenFileXmlRpcHandler.java | 10 +++++-----
2 files changed, 5 insertions(+), 5 deletions(-)
rename platform/built-in-server/src/org/jetbrains/ide/{OpenFileHttpService.java => OpenFileHttpService.kt} (100%)
diff --git a/platform/built-in-server/src/org/jetbrains/ide/OpenFileHttpService.java b/platform/built-in-server/src/org/jetbrains/ide/OpenFileHttpService.kt
similarity index 100%
rename from platform/built-in-server/src/org/jetbrains/ide/OpenFileHttpService.java
rename to platform/built-in-server/src/org/jetbrains/ide/OpenFileHttpService.kt
diff --git a/platform/built-in-server/src/org/jetbrains/ide/OpenFileXmlRpcHandler.java b/platform/built-in-server/src/org/jetbrains/ide/OpenFileXmlRpcHandler.java
index 9487bdc3ea9a..83875b3a613a 100644
--- a/platform/built-in-server/src/org/jetbrains/ide/OpenFileXmlRpcHandler.java
+++ b/platform/built-in-server/src/org/jetbrains/ide/OpenFileXmlRpcHandler.java
@@ -37,11 +37,11 @@ class OpenFileXmlRpcHandler {
}
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;
+ OpenFileRequest request = new OpenFileRequest();
+ request.setFile(path);
+ request.setLine(line);
+ request.setColumn(column);
+ request.setFocused(false);
return HttpRequestHandler.EP_NAME.findExtension(OpenFileHttpService.class).openFile(request).getState() != Promise.State.REJECTED;
}
}
\ No newline at end of file
From 9be60ac88207d1ee4d5bb36984902aa02047394a Mon Sep 17 00:00:00 2001
From: Vladimir Krivosheev
Date: Mon, 11 Apr 2016 19:08:47 +0200
Subject: [PATCH 08/67] fix isOwnHostName and add tests
---
.../BuiltInWebBrowserUrlProvider.java | 3 +-
.../builtInWebServer/BuiltInWebServer.kt | 27 ++++++++++-
.../testSrc/IsLocalHostTest.kt | 46 +++++++++++++++++++
.../io/DelegatingHttpRequestHandler.kt | 6 +--
.../src/org/jetbrains/io/netty.kt | 21 ++-------
5 files changed, 81 insertions(+), 22 deletions(-)
create mode 100644 platform/built-in-server/testSrc/IsLocalHostTest.kt
diff --git a/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebBrowserUrlProvider.java b/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebBrowserUrlProvider.java
index 2789e1a00995..1fa74d36be45 100644
--- a/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebBrowserUrlProvider.java
+++ b/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebBrowserUrlProvider.java
@@ -33,7 +33,6 @@ import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.ide.BuiltInServerManager;
-import org.jetbrains.io.NettyKt;
import java.util.Collections;
import java.util.List;
@@ -84,7 +83,7 @@ public class BuiltInWebBrowserUrlProvider extends WebBrowserUrlProvider implemen
}
String host = currentAuthority.substring(0, portIndex);
- if (!NettyKt.isOwnHostName(host)) {
+ if (!BuiltInWebServerKt.isOwnHostName(host)) {
return false;
}
diff --git a/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt b/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt
index 8942e1d80f89..9cf61b372f9a 100644
--- a/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt
+++ b/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt
@@ -28,6 +28,7 @@ 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.ChannelHandlerContext
import io.netty.handler.codec.http.FullHttpRequest
import io.netty.handler.codec.http.HttpMethod
@@ -35,8 +36,9 @@ import io.netty.handler.codec.http.HttpResponseStatus
import io.netty.handler.codec.http.QueryStringDecoder
import org.jetbrains.ide.HttpRequestHandler
import org.jetbrains.io.host
-import org.jetbrains.io.isOwnHostName
import org.jetbrains.io.send
+import java.io.IOException
+import java.net.InetAddress
import java.nio.file.Path
internal val LOG = Logger.getInstance(BuiltInWebServer::class.java)
@@ -217,4 +219,27 @@ fun findIndexFile(basedir: Path): Path? {
}
}
return null
+}
+
+// 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
+ }
+
+ try {
+ val address = InetAddress.getByName(host)
+ if (host == address.hostAddress || host.equals(address.canonicalHostName, ignoreCase = true)) {
+ return true
+ }
+
+ val localHostName = InetAddress.getLocalHost().hostName
+ // WEB-8889
+ // 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: IOException) {
+ return false
+ }
}
\ 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
index 000000000000..ee42797e5f53
--- /dev/null
+++ b/platform/built-in-server/testSrc/IsLocalHostTest.kt
@@ -0,0 +1,46 @@
+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")).isFalse()
+ }
+
+ @Test
+ fun `google name`() {
+ assertThat(isLocalHost("google.com")).isFalse()
+ }
+
+ @Test
+ fun `jetbrains name`() {
+ assertThat(isLocalHost("jetbrains.com")).isFalse()
+ }
+
+ @Test
+ fun `unknown name`() {
+ assertThat(isLocalHost("foo.com")).isFalse()
+ }
+
+ @Test
+ fun `unknown unqualified name`() {
+ assertThat(isLocalHost("local")).isFalse()
+ }
+
+ @Test
+ fun `invalid ip`() {
+ assertThat(isLocalHost("0.0.0.0.0.0.0")).isFalse()
+ }
+
+ @Test
+ fun `any`() {
+ assertThat(isLocalHost("0.0.0.0")).isTrue()
+ }
+
+ @Test
+ fun `localhost`() {
+ assertThat(isLocalHost("localhost")).isTrue()
+ }
+}
\ No newline at end of file
diff --git a/platform/platform-impl/src/org/jetbrains/io/DelegatingHttpRequestHandler.kt b/platform/platform-impl/src/org/jetbrains/io/DelegatingHttpRequestHandler.kt
index 1e2e75721c1e..c26fab75d7db 100644
--- a/platform/platform-impl/src/org/jetbrains/io/DelegatingHttpRequestHandler.kt
+++ b/platform/platform-impl/src/org/jetbrains/io/DelegatingHttpRequestHandler.kt
@@ -96,11 +96,11 @@ internal class DelegatingHttpRequestHandler : DelegatingHttpRequestHandlerBase()
}
}
-fun HttpRequest.isLocalOrigin() = parseAndCheckIsOwnHostName(origin) && parseAndCheckIsOwnHostName(referrer)
+fun HttpRequest.isLocalOrigin() = parseAndCheckIsLocalHost(origin) && parseAndCheckIsLocalHost(referrer)
-private fun parseAndCheckIsOwnHostName(uri: String?): Boolean {
+private fun parseAndCheckIsLocalHost(uri: String?): Boolean {
try {
- if (uri == null || isOwnHostName(URI(uri).host)) {
+ if (uri == null || isLocalHost(URI(uri).host)) {
return true
}
}
diff --git a/platform/platform-impl/src/org/jetbrains/io/netty.kt b/platform/platform-impl/src/org/jetbrains/io/netty.kt
index 367a53806429..93b58a7af5de 100644
--- a/platform/platform-impl/src/org/jetbrains/io/netty.kt
+++ b/platform/platform-impl/src/org/jetbrains/io/netty.kt
@@ -17,7 +17,6 @@ package org.jetbrains.io
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
@@ -33,9 +32,10 @@ import io.netty.handler.ssl.SslHandler
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.UnknownHostException
+import java.net.NetworkInterface
import java.util.concurrent.TimeUnit
inline fun Bootstrap.handler(crossinline task: (Channel) -> Unit): Bootstrap {
@@ -122,23 +122,12 @@ inline fun ByteBuf.releaseIfError(task: () -> T): T {
}
}
-fun isOwnHostName(host: String): Boolean {
- if (NetUtils.isLocalhost(host)) {
- return true
- }
-
+fun isLocalHost(host: String): Boolean {
try {
val address = InetAddress.getByName(host)
- if (host == address.hostAddress || host.equals(address.canonicalHostName, ignoreCase = true)) {
- return true
- }
-
- val localHostName = InetAddress.getLocalHost().hostName
- // WEB-8889
- // 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))
+ return address.isAnyLocalAddress || address.isLoopbackAddress || NetworkInterface.getByInetAddress(address) != null
}
- catch (ignored: UnknownHostException) {
+ catch (ignored: IOException) {
return false
}
}
\ No newline at end of file
From fa00dfef3aa656fc28169d09a8986659908f0806 Mon Sep 17 00:00:00 2001
From: Vladimir Krivosheev
Date: Tue, 12 Apr 2016 12:28:33 +0200
Subject: [PATCH 09/67] =?UTF-8?q?OpenFileHttpService=20=E2=80=94=20do=20no?=
=?UTF-8?q?t=20expose=20file=20status=20if=20not=20local=20origin=20and=20?=
=?UTF-8?q?do=20not=20open=20hidden=20files?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../builtInWebServer/StaticFileHandler.kt | 6 ++-
.../org/jetbrains/ide/OpenFileHttpService.kt | 51 ++++++++++---------
.../jetbrains/ide/OpenFileXmlRpcHandler.java | 3 +-
3 files changed, 32 insertions(+), 28 deletions(-)
diff --git a/platform/built-in-server/src/org/jetbrains/builtInWebServer/StaticFileHandler.kt b/platform/built-in-server/src/org/jetbrains/builtInWebServer/StaticFileHandler.kt
index 6a0355247728..ef779182b485 100644
--- a/platform/built-in-server/src/org/jetbrains/builtInWebServer/StaticFileHandler.kt
+++ b/platform/built-in-server/src/org/jetbrains/builtInWebServer/StaticFileHandler.kt
@@ -13,6 +13,7 @@ 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.isLocalOrigin
import org.jetbrains.io.send
import java.nio.file.Files
import java.nio.file.Path
@@ -107,11 +108,12 @@ private fun sendIoFile(channel: Channel, file: Path, root: Path, request: HttpRe
}
}
-fun checkAccess(channel: Channel, file: Path, request: HttpRequest, root: Path): Boolean {
+fun checkAccess(channel: Channel, file: Path, request: HttpRequest, root: Path = file.root, doNotExposeStatusIfNotLocalOrigin: Boolean = false): Boolean {
var parent = file
do {
if (!hasAccess(parent)) {
- HttpResponseStatus.FORBIDDEN.send(channel, request)
+ (if (doNotExposeStatusIfNotLocalOrigin && !request.isLocalOrigin()) HttpResponseStatus.OK else HttpResponseStatus.FORBIDDEN)
+ .send(channel, request)
return false
}
parent = parent.parent ?: break
diff --git a/platform/built-in-server/src/org/jetbrains/ide/OpenFileHttpService.kt b/platform/built-in-server/src/org/jetbrains/ide/OpenFileHttpService.kt
index a027da1575ce..3bf738f3a241 100644
--- a/platform/built-in-server/src/org/jetbrains/ide/OpenFileHttpService.kt
+++ b/platform/built-in-server/src/org/jetbrains/ide/OpenFileHttpService.kt
@@ -15,6 +15,7 @@
*/
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
@@ -36,6 +37,7 @@ 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
@@ -85,21 +87,21 @@ internal class OpenFileHttpService : RestService() {
val apiRequest: OpenFileRequest
if (request.method() === HttpMethod.POST) {
- apiRequest = gson.value.fromJson(RestService.createJsonReader(request), OpenFileRequest::class.java)
+ apiRequest = gson.value.fromJson(createJsonReader(request), OpenFileRequest::class.java)
}
else {
apiRequest = OpenFileRequest()
- apiRequest.file = StringUtil.nullize(RestService.getStringParameter("file", urlDecoder), true)
- apiRequest.line = RestService.getIntParameter("line", urlDecoder)
- apiRequest.column = RestService.getIntParameter("column", urlDecoder)
- apiRequest.focused = RestService.getBooleanParameter("focused", urlDecoder, true)
+ 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 + RestService.PREFIX.length + 1 + serviceName.length + 1
+ 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)
- RestService.LOG.assertTrue(matcher.matches())
+ LOG.assertTrue(matcher.matches())
if (apiRequest.file == null) {
apiRequest.file = matcher.group(1).trim { it <= ' ' }
}
@@ -112,30 +114,34 @@ internal class OpenFileHttpService : RestService() {
}
if (apiRequest.file == null) {
- RestService.sendStatus(HttpResponseStatus.BAD_REQUEST, keepAlive, channel)
+ sendStatus(HttpResponseStatus.BAD_REQUEST, keepAlive, channel)
return null
}
- openFile(apiRequest).done { RestService.sendStatus(HttpResponseStatus.OK, keepAlive, channel) }
- .rejected { throwable ->
- if (throwable === NOT_FOUND) {
+ 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 if not local origin
- RestService.sendStatus(if (request.isLocalOrigin()) HttpResponseStatus.NOT_FOUND else HttpResponseStatus.OK, keepAlive, channel)
+ sendStatus(if (request.isLocalOrigin()) HttpResponseStatus.NOT_FOUND else HttpResponseStatus.OK, keepAlive, channel)
}
else {
// todo send error
- RestService.sendStatus(HttpResponseStatus.INTERNAL_SERVER_ERROR, keepAlive, channel)
- RestService.LOG.error(throwable)
+ sendStatus(HttpResponseStatus.INTERNAL_SERVER_ERROR, keepAlive, channel)
+ LOG.error(it)
}
}
return null
}
- fun openFile(request: OpenFileRequest): Promise {
+ fun openFile(request: OpenFileRequest, context: ChannelHandlerContext?, httpRequest: HttpRequest?): Promise? {
val path = FileUtil.expandUserHome(request.file!!)
val file = Paths.get(FileUtil.toSystemDependentName(path))
if (file.isAbsolute) {
- return openAbsolutePath(file, request)
+ if (!file.exists()) {
+ return rejectedPromise(NOT_FOUND)
+ }
+ return if (context == null || checkAccess(context.channel(), file, httpRequest!!, doNotExposeStatusIfNotLocalOrigin = true)) openAbsolutePath(file, request) else null
}
// we don't want to call refresh for each attempt on findFileByRelativePath call, so, we do what ourSaveAndSyncHandlerImpl does on frame activation
@@ -185,7 +191,7 @@ private fun navigate(project: Project?, file: VirtualFile, request: OpenFileRequ
// 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) {
- com.intellij.ide.impl.ProjectUtil.focusProjectWindow(project, true)
+ focusProjectWindow(project, true)
}
}
@@ -224,18 +230,13 @@ private fun openRelativePath(path: String, request: OpenFileRequest): Boolean {
}
}
- virtualFile?.let {
+ return virtualFile?.let {
AppUIUtil.invokeLaterIfProjectAlive(project!!, Runnable { navigate(project, it, request) })
- return true
- }
- return false
+ true
+ } ?: false
}
private fun openAbsolutePath(file: Path, request: OpenFileRequest): Promise {
- if (!file.exists()) {
- return rejectedPromise(NOT_FOUND)
- }
-
val promise = AsyncPromise()
ApplicationManager.getApplication().invokeLater {
promise.catchError {
diff --git a/platform/built-in-server/src/org/jetbrains/ide/OpenFileXmlRpcHandler.java b/platform/built-in-server/src/org/jetbrains/ide/OpenFileXmlRpcHandler.java
index 83875b3a613a..410bf62eb9d8 100644
--- a/platform/built-in-server/src/org/jetbrains/ide/OpenFileXmlRpcHandler.java
+++ b/platform/built-in-server/src/org/jetbrains/ide/OpenFileXmlRpcHandler.java
@@ -42,6 +42,7 @@ class OpenFileXmlRpcHandler {
request.setLine(line);
request.setColumn(column);
request.setFocused(false);
- return HttpRequestHandler.EP_NAME.findExtension(OpenFileHttpService.class).openFile(request).getState() != Promise.State.REJECTED;
+ Promise promise = HttpRequestHandler.EP_NAME.findExtension(OpenFileHttpService.class).openFile(request, null, null);
+ return promise != null && promise.getState() != Promise.State.REJECTED;
}
}
\ No newline at end of file
From c7f01496654526751505ef45cca2090e05800540 Mon Sep 17 00:00:00 2001
From: Vladimir Krivosheev
Date: Tue, 12 Apr 2016 16:55:59 +0200
Subject: [PATCH 10/67] trust our chrome-extensions
---
.../jetbrains/io/DelegatingHttpRequestHandler.kt | 13 ++++++++++---
1 file changed, 10 insertions(+), 3 deletions(-)
diff --git a/platform/platform-impl/src/org/jetbrains/io/DelegatingHttpRequestHandler.kt b/platform/platform-impl/src/org/jetbrains/io/DelegatingHttpRequestHandler.kt
index c26fab75d7db..6de23f94ac5a 100644
--- a/platform/platform-impl/src/org/jetbrains/io/DelegatingHttpRequestHandler.kt
+++ b/platform/platform-impl/src/org/jetbrains/io/DelegatingHttpRequestHandler.kt
@@ -98,11 +98,18 @@ internal class DelegatingHttpRequestHandler : DelegatingHttpRequestHandlerBase()
fun HttpRequest.isLocalOrigin() = parseAndCheckIsLocalHost(origin) && parseAndCheckIsLocalHost(referrer)
+private fun isTrustedChromeExtension(uri: URI): Boolean {
+ return uri.scheme == "chrome-extension" && (uri.host == "hmhgeddbohgjknpmjagkdomcpobmllji" || uri.host == "offnedcbhjldheanlbojaefbfbllddna")
+}
+
private fun parseAndCheckIsLocalHost(uri: String?): Boolean {
+ if (uri == null) {
+ return true
+ }
+
try {
- if (uri == null || isLocalHost(URI(uri).host)) {
- return true
- }
+ val parsedUri = URI(uri)
+ return isTrustedChromeExtension(parsedUri) || isLocalHost(parsedUri.host)
}
catch (ignored: Exception) {
}
From b74204b174866d76d92f2129b57eee181ca34507 Mon Sep 17 00:00:00 2001
From: Vladimir Krivosheev
Date: Wed, 13 Apr 2016 07:47:37 +0200
Subject: [PATCH 11/67] move isLocalOrigin to netty.kt (as in 145 branch)
---
.../io/DelegatingHttpRequestHandler.kt | 23 -------------------
.../src/org/jetbrains/io/netty.kt | 21 +++++++++++++++++
2 files changed, 21 insertions(+), 23 deletions(-)
diff --git a/platform/platform-impl/src/org/jetbrains/io/DelegatingHttpRequestHandler.kt b/platform/platform-impl/src/org/jetbrains/io/DelegatingHttpRequestHandler.kt
index 6de23f94ac5a..99178d48b310 100644
--- a/platform/platform-impl/src/org/jetbrains/io/DelegatingHttpRequestHandler.kt
+++ b/platform/platform-impl/src/org/jetbrains/io/DelegatingHttpRequestHandler.kt
@@ -23,15 +23,12 @@ import io.netty.buffer.Unpooled
import io.netty.channel.ChannelHandler
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.QueryStringDecoder
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
-import java.net.URI
private val PREV_HANDLER = AttributeKey.valueOf("DelegatingHttpRequestHandler.handler")
@@ -94,24 +91,4 @@ internal class DelegatingHttpRequestHandler : DelegatingHttpRequestHandlerBase()
super.exceptionCaught(context, cause)
}
}
-}
-
-fun HttpRequest.isLocalOrigin() = parseAndCheckIsLocalHost(origin) && parseAndCheckIsLocalHost(referrer)
-
-private fun isTrustedChromeExtension(uri: URI): Boolean {
- return uri.scheme == "chrome-extension" && (uri.host == "hmhgeddbohgjknpmjagkdomcpobmllji" || uri.host == "offnedcbhjldheanlbojaefbfbllddna")
-}
-
-private fun parseAndCheckIsLocalHost(uri: String?): Boolean {
- if (uri == null) {
- return true
- }
-
- try {
- val parsedUri = URI(uri)
- return isTrustedChromeExtension(parsedUri) || isLocalHost(parsedUri.host)
- }
- catch (ignored: Exception) {
- }
- return false
}
\ No newline at end of file
diff --git a/platform/platform-impl/src/org/jetbrains/io/netty.kt b/platform/platform-impl/src/org/jetbrains/io/netty.kt
index 93b58a7af5de..d9f355df42ec 100644
--- a/platform/platform-impl/src/org/jetbrains/io/netty.kt
+++ b/platform/platform-impl/src/org/jetbrains/io/netty.kt
@@ -36,6 +36,7 @@ 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 {
@@ -130,4 +131,24 @@ fun isLocalHost(host: String): Boolean {
catch (ignored: IOException) {
return false
}
+}
+
+fun HttpRequest.isLocalOrigin() = parseAndCheckIsLocalHost(origin) && parseAndCheckIsLocalHost(referrer)
+
+private fun isTrustedChromeExtension(uri: URI): Boolean {
+ return uri.scheme == "chrome-extension" && (uri.host == "hmhgeddbohgjknpmjagkdomcpobmllji" || uri.host == "offnedcbhjldheanlbojaefbfbllddna")
+}
+
+private fun parseAndCheckIsLocalHost(uri: String?): Boolean {
+ if (uri == null) {
+ return true
+ }
+
+ try {
+ val parsedUri = URI(uri)
+ return isTrustedChromeExtension(parsedUri) || isLocalHost(parsedUri.host)
+ }
+ catch (ignored: Exception) {
+ }
+ return false
}
\ No newline at end of file
From 2c49382630c5d003cb7c4b6db4e2fa76514c466e Mon Sep 17 00:00:00 2001
From: Vladimir Krivosheev
Date: Wed, 13 Apr 2016 09:37:21 +0200
Subject: [PATCH 12/67] =?UTF-8?q?HttpRequestHandler=20by=20default=20is=20?=
=?UTF-8?q?accessible=20only=20from=20any=20or=20loopback=20IP=20address?=
=?UTF-8?q?=20=E2=80=94=20domain=20name=20is=20not=20resolved=20and,=20so,?=
=?UTF-8?q?=20forbidden?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../builtInWebServer/BuiltInWebServer.kt | 8 +++----
.../org/jetbrains/ide/DiffHttpService.java | 5 +++--
.../org/jetbrains/ide/OpenFileHttpService.kt | 2 +-
.../ide/ProjectSetRequestHandler.java | 5 +++--
.../testSrc/IsLocalHostTest.kt | 21 +++++++++++-------
.../org/jetbrains/ide/HttpRequestHandler.java | 14 +++++++-----
.../io/DelegatingHttpRequestHandler.kt | 10 +++------
.../src/org/jetbrains/io/netty.kt | 22 +++++++++++++++----
8 files changed, 53 insertions(+), 34 deletions(-)
rename platform/{built-in-server-api => platform-impl}/src/org/jetbrains/ide/HttpRequestHandler.java (89%)
diff --git a/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt b/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt
index 9cf61b372f9a..604f2a6dce63 100644
--- a/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt
+++ b/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt
@@ -30,12 +30,10 @@ import com.intellij.util.io.URLUtil
import com.intellij.util.isDirectory
import com.intellij.util.net.NetUtils
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 org.jetbrains.ide.HttpRequestHandler
import org.jetbrains.io.host
+import org.jetbrains.io.isLocalOrigin
import org.jetbrains.io.send
import java.io.IOException
import java.net.InetAddress
@@ -44,6 +42,8 @@ import java.nio.file.Path
internal val LOG = Logger.getInstance(BuiltInWebServer::class.java)
class BuiltInWebServer : HttpRequestHandler() {
+ override fun isAccessible(request: HttpRequest) = request.isLocalOrigin(false)
+
override fun isSupported(request: FullHttpRequest) = super.isSupported(request) || request.method() == HttpMethod.POST
override fun process(urlDecoder: QueryStringDecoder, request: FullHttpRequest, context: ChannelHandlerContext): Boolean {
diff --git a/platform/built-in-server/src/org/jetbrains/ide/DiffHttpService.java b/platform/built-in-server/src/org/jetbrains/ide/DiffHttpService.java
index 0652efa37ff5..061c32227831 100644
--- a/platform/built-in-server/src/org/jetbrains/ide/DiffHttpService.java
+++ b/platform/built-in-server/src/org/jetbrains/ide/DiffHttpService.java
@@ -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;
@@ -171,7 +172,7 @@ final class DiffHttpService extends RestService {
}
@Override
- public boolean isAllowRequestOnlyFromLocalOrigin() {
- return false;
+ public boolean isAccessible(@NotNull HttpRequest request) {
+ return 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
index 3bf738f3a241..caff2214a43c 100644
--- a/platform/built-in-server/src/org/jetbrains/ide/OpenFileHttpService.kt
+++ b/platform/built-in-server/src/org/jetbrains/ide/OpenFileHttpService.kt
@@ -169,7 +169,7 @@ internal class OpenFileHttpService : RestService() {
return mainTask.promise
}
- override fun isAllowRequestOnlyFromLocalOrigin() = false
+ override fun isAccessible(request: HttpRequest) = true
}
internal class OpenFileRequest {
diff --git a/platform/built-in-server/src/org/jetbrains/ide/ProjectSetRequestHandler.java b/platform/built-in-server/src/org/jetbrains/ide/ProjectSetRequestHandler.java
index 70151e1962bc..da22bb803530 100644
--- a/platform/built-in-server/src/org/jetbrains/ide/ProjectSetRequestHandler.java
+++ b/platform/built-in-server/src/org/jetbrains/ide/ProjectSetRequestHandler.java
@@ -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;
@@ -76,7 +77,7 @@ public class ProjectSetRequestHandler extends RestService {
}
@Override
- public boolean isAllowRequestOnlyFromLocalOrigin() {
- return false;
+ public boolean isAccessible(@NotNull HttpRequest request) {
+ return true;
}
}
diff --git a/platform/built-in-server/testSrc/IsLocalHostTest.kt b/platform/built-in-server/testSrc/IsLocalHostTest.kt
index ee42797e5f53..722138936003 100644
--- a/platform/built-in-server/testSrc/IsLocalHostTest.kt
+++ b/platform/built-in-server/testSrc/IsLocalHostTest.kt
@@ -6,41 +6,46 @@ import org.junit.Test
class IsLocalHostTest {
@Test
fun `google ip`() {
- assertThat(isLocalHost("37.29.1.113")).isFalse()
+ assertThat(isLocalHost("37.29.1.113", false)).isFalse()
}
@Test
fun `google name`() {
- assertThat(isLocalHost("google.com")).isFalse()
+ assertThat(isLocalHost("google.com", false)).isFalse()
}
@Test
fun `jetbrains name`() {
- assertThat(isLocalHost("jetbrains.com")).isFalse()
+ assertThat(isLocalHost("jetbrains.com", false)).isFalse()
}
@Test
fun `unknown name`() {
- assertThat(isLocalHost("foo.com")).isFalse()
+ assertThat(isLocalHost("foo.com", false)).isFalse()
}
@Test
fun `unknown unqualified name`() {
- assertThat(isLocalHost("local")).isFalse()
+ assertThat(isLocalHost("local", false)).isFalse()
}
@Test
fun `invalid ip`() {
- assertThat(isLocalHost("0.0.0.0.0.0.0")).isFalse()
+ assertThat(isLocalHost("0.0.0.0.0.0.0", false)).isFalse()
}
@Test
fun `any`() {
- assertThat(isLocalHost("0.0.0.0")).isTrue()
+ assertThat(isLocalHost("0.0.0.0", false)).isTrue()
}
@Test
fun `localhost`() {
- assertThat(isLocalHost("localhost")).isTrue()
+ 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/built-in-server-api/src/org/jetbrains/ide/HttpRequestHandler.java b/platform/platform-impl/src/org/jetbrains/ide/HttpRequestHandler.java
similarity index 89%
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 18f473c77e8a..23730cbad529 100644
--- a/platform/built-in-server-api/src/org/jetbrains/ide/HttpRequestHandler.java
+++ b/platform/platform-impl/src/org/jetbrains/ide/HttpRequestHandler.java
@@ -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,10 @@ public abstract class HttpRequestHandler {
return false;
}
+ public boolean isAccessible(@NotNull HttpRequest request) {
+ return NettyKt.isLocalOrigin(request);
+ }
+
public boolean isSupported(@NotNull FullHttpRequest request) {
return request.method() == HttpMethod.GET || request.method() == HttpMethod.HEAD;
}
@@ -50,8 +56,4 @@ public abstract class HttpRequestHandler {
*/
public abstract boolean process(@NotNull QueryStringDecoder urlDecoder, @NotNull FullHttpRequest request, @NotNull ChannelHandlerContext context)
throws IOException;
-
- public boolean isAllowRequestOnlyFromLocalOrigin() {
- return true;
- }
-}
+}
\ No newline at end of file
diff --git a/platform/platform-impl/src/org/jetbrains/io/DelegatingHttpRequestHandler.kt b/platform/platform-impl/src/org/jetbrains/io/DelegatingHttpRequestHandler.kt
index 99178d48b310..b500c0294ab5 100644
--- a/platform/platform-impl/src/org/jetbrains/io/DelegatingHttpRequestHandler.kt
+++ b/platform/platform-impl/src/org/jetbrains/io/DelegatingHttpRequestHandler.kt
@@ -38,17 +38,13 @@ internal class DelegatingHttpRequestHandler : DelegatingHttpRequestHandlerBase()
request: FullHttpRequest,
urlDecoder: QueryStringDecoder): Boolean {
fun HttpRequestHandler.checkAndProcess(): Boolean {
- if (isAllowRequestOnlyFromLocalOrigin && !request.isLocalOrigin()) {
- return false
- }
-
- return process(urlDecoder, request, context)
+ return isSupported(request) && isAccessible(request) && process(urlDecoder, request, context)
}
val prevHandlerAttribute = context.attr(PREV_HANDLER)
val connectedHandler = prevHandlerAttribute.get()
if (connectedHandler != null) {
- if (connectedHandler.isSupported(request) && connectedHandler.checkAndProcess()) {
+ if (connectedHandler.checkAndProcess()) {
return true
}
// prev cached connectedHandler is not suitable for this request, so, let's find it again
@@ -57,7 +53,7 @@ internal class DelegatingHttpRequestHandler : DelegatingHttpRequestHandlerBase()
for (handler in HttpRequestHandler.EP_NAME.extensions) {
try {
- if (handler.isSupported(request) && handler.checkAndProcess()) {
+ if (handler.checkAndProcess()) {
prevHandlerAttribute.set(handler)
return true
}
diff --git a/platform/platform-impl/src/org/jetbrains/io/netty.kt b/platform/platform-impl/src/org/jetbrains/io/netty.kt
index d9f355df42ec..c46eceb88541 100644
--- a/platform/platform-impl/src/org/jetbrains/io/netty.kt
+++ b/platform/platform-impl/src/org/jetbrains/io/netty.kt
@@ -15,8 +15,10 @@
*/
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
@@ -123,7 +125,18 @@ inline fun ByteBuf.releaseIfError(task: () -> T): T {
}
}
-fun isLocalHost(host: String): Boolean {
+fun isLocalHost(host: String, onlyAnyOrLoopback: Boolean): Boolean {
+ if (onlyAnyOrLoopback) {
+ if (NetUtils.isLocalhost(host)) {
+ return true
+ }
+
+ if (!InetAddresses.isInetAddress(host)) {
+ return false
+ }
+ // if IP address, it is safe to use getByName (not affected by DNS rebinding)
+ }
+
try {
val address = InetAddress.getByName(host)
return address.isAnyLocalAddress || address.isLoopbackAddress || NetworkInterface.getByInetAddress(address) != null
@@ -133,20 +146,21 @@ fun isLocalHost(host: String): Boolean {
}
}
-fun HttpRequest.isLocalOrigin() = parseAndCheckIsLocalHost(origin) && parseAndCheckIsLocalHost(referrer)
+@JvmOverloads
+fun HttpRequest.isLocalOrigin(onlyAnyOrLoopback: Boolean = true) = parseAndCheckIsLocalHost(origin, onlyAnyOrLoopback) && parseAndCheckIsLocalHost(referrer, onlyAnyOrLoopback)
private fun isTrustedChromeExtension(uri: URI): Boolean {
return uri.scheme == "chrome-extension" && (uri.host == "hmhgeddbohgjknpmjagkdomcpobmllji" || uri.host == "offnedcbhjldheanlbojaefbfbllddna")
}
-private fun parseAndCheckIsLocalHost(uri: String?): Boolean {
+private fun parseAndCheckIsLocalHost(uri: String?, onlyAnyOrLoopback: Boolean): Boolean {
if (uri == null) {
return true
}
try {
val parsedUri = URI(uri)
- return isTrustedChromeExtension(parsedUri) || isLocalHost(parsedUri.host)
+ return isTrustedChromeExtension(parsedUri) || isLocalHost(parsedUri.host, onlyAnyOrLoopback)
}
catch (ignored: Exception) {
}
From 63bf42b6abc2544d37800157a7c8583607ec9c0a Mon Sep 17 00:00:00 2001
From: Vladimir Krivosheev
Date: Wed, 13 Apr 2016 10:30:49 +0200
Subject: [PATCH 13/67] check Host
---
.../src/org/jetbrains/ide/HttpRequestHandler.java | 6 +++++-
platform/platform-impl/src/org/jetbrains/io/netty.kt | 3 ++-
2 files changed, 7 insertions(+), 2 deletions(-)
diff --git a/platform/platform-impl/src/org/jetbrains/ide/HttpRequestHandler.java b/platform/platform-impl/src/org/jetbrains/ide/HttpRequestHandler.java
index 23730cbad529..0f03b280d8c3 100644
--- a/platform/platform-impl/src/org/jetbrains/ide/HttpRequestHandler.java
+++ b/platform/platform-impl/src/org/jetbrains/ide/HttpRequestHandler.java
@@ -43,8 +43,12 @@ public abstract class HttpRequestHandler {
return false;
}
+ @SuppressWarnings("SpellCheckingInspection")
public boolean isAccessible(@NotNull HttpRequest request) {
- return NettyKt.isLocalOrigin(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(host);
}
public boolean isSupported(@NotNull FullHttpRequest request) {
diff --git a/platform/platform-impl/src/org/jetbrains/io/netty.kt b/platform/platform-impl/src/org/jetbrains/io/netty.kt
index c46eceb88541..336205bd7cb8 100644
--- a/platform/platform-impl/src/org/jetbrains/io/netty.kt
+++ b/platform/platform-impl/src/org/jetbrains/io/netty.kt
@@ -153,7 +153,8 @@ private fun isTrustedChromeExtension(uri: URI): Boolean {
return uri.scheme == "chrome-extension" && (uri.host == "hmhgeddbohgjknpmjagkdomcpobmllji" || uri.host == "offnedcbhjldheanlbojaefbfbllddna")
}
-private fun parseAndCheckIsLocalHost(uri: String?, onlyAnyOrLoopback: Boolean): Boolean {
+@JvmOverloads
+fun parseAndCheckIsLocalHost(uri: String?, onlyAnyOrLoopback: Boolean = true): Boolean {
if (uri == null) {
return true
}
From a2340f52ec8b6b951b389096f83ff92da4d26b9d Mon Sep 17 00:00:00 2001
From: Vladimir Krivosheev
Date: Wed, 13 Apr 2016 11:37:31 +0200
Subject: [PATCH 14/67] use InetAddress.getLoopbackAddress()
---
.../src/com/intellij/compiler/server/BuildManager.java | 5 +++--
.../com/intellij/execution/runners/ProcessProxyImpl.java | 6 +++---
.../platform-api/src/com/intellij/util/net/NetUtils.java | 9 ++-------
.../platform-impl/src/com/intellij/idea/SocketLock.java | 6 +++---
.../io/socketConnection/impl/SocketConnectionImpl.java | 7 +++----
.../src/org/jetbrains/io/BuiltInServer.java | 3 +--
.../intellij/lang/xpath/xslt/run/OutputTabAdapter.java | 4 ++--
7 files changed, 17 insertions(+), 23 deletions(-)
diff --git a/java/compiler/impl/src/com/intellij/compiler/server/BuildManager.java b/java/compiler/impl/src/com/intellij/compiler/server/BuildManager.java
index 2d9742863629..3983bf2b36ab 100644
--- a/java/compiler/impl/src/com/intellij/compiler/server/BuildManager.java
+++ b/java/compiler/impl/src/com/intellij/compiler/server/BuildManager.java
@@ -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();
}
diff --git a/java/execution/impl/src/com/intellij/execution/runners/ProcessProxyImpl.java b/java/execution/impl/src/com/intellij/execution/runners/ProcessProxyImpl.java
index 525966359763..f09d7b553639 100644
--- a/java/execution/impl/src/com/intellij/execution/runners/ProcessProxyImpl.java
+++ b/java/execution/impl/src/com/intellij/execution/runners/ProcessProxyImpl.java
@@ -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/platform/platform-api/src/com/intellij/util/net/NetUtils.java b/platform/platform-api/src/com/intellij/util/net/NetUtils.java
index c261cea5d7ef..f62c0f750d91 100644
--- a/platform/platform-api/src/com/intellij/util/net/NetUtils.java
+++ b/platform/platform-api/src/com/intellij/util/net/NetUtils.java
@@ -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,6 +42,7 @@ public class NetUtils {
}
}
+ @Deprecated
public static InetAddress getLoopbackAddress() {
return InetAddress.getLoopbackAddress();
}
@@ -218,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);
}
}
diff --git a/platform/platform-impl/src/com/intellij/idea/SocketLock.java b/platform/platform-impl/src/com/intellij/idea/SocketLock.java
index 0e4585b9f5f3..783e761c1069 100644
--- a/platform/platform-impl/src/com/intellij/idea/SocketLock.java
+++ b/platform/platform-impl/src/com/intellij/idea/SocketLock.java
@@ -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);
diff --git a/platform/platform-impl/src/com/intellij/util/io/socketConnection/impl/SocketConnectionImpl.java b/platform/platform-impl/src/com/intellij/util/io/socketConnection/impl/SocketConnectionImpl.java
index cfe3db1224a6..5fc8fa284a29 100644
--- a/platform/platform-impl/src/com/intellij/util/io/socketConnection/impl/SocketConnectionImpl.java
+++ b/platform/platform-impl/src/com/intellij/util/io/socketConnection/impl/SocketConnectionImpl.java
@@ -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
Date: Wed, 13 Apr 2016 14:17:09 +0200
Subject: [PATCH 15/67] dns rebinging fix for built-in web server
---
.../netty/io/netty/resolver/annotations.xml | 5 +++++
.../builtInWebServer/BuiltInWebServer.kt | 2 +-
.../src/org/jetbrains/io/netty.kt | 22 ++++++++++++++-----
3 files changed, 23 insertions(+), 6 deletions(-)
create mode 100644 lib/annotations/netty/io/netty/resolver/annotations.xml
diff --git a/lib/annotations/netty/io/netty/resolver/annotations.xml b/lib/annotations/netty/io/netty/resolver/annotations.xml
new file mode 100644
index 000000000000..10089671fab8
--- /dev/null
+++ b/lib/annotations/netty/io/netty/resolver/annotations.xml
@@ -0,0 +1,5 @@
+
+ -
+
+
+
\ No newline at end of file
diff --git a/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt b/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt
index 604f2a6dce63..2e2c7395c70a 100644
--- a/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt
+++ b/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt
@@ -42,7 +42,7 @@ import java.nio.file.Path
internal val LOG = Logger.getInstance(BuiltInWebServer::class.java)
class BuiltInWebServer : HttpRequestHandler() {
- override fun isAccessible(request: HttpRequest) = request.isLocalOrigin(false)
+ override fun isAccessible(request: HttpRequest) = request.isLocalOrigin(onlyAnyOrLoopback = false, hostsOnly = true)
override fun isSupported(request: FullHttpRequest) = super.isSupported(request) || request.method() == HttpMethod.POST
diff --git a/platform/platform-impl/src/org/jetbrains/io/netty.kt b/platform/platform-impl/src/org/jetbrains/io/netty.kt
index 336205bd7cb8..9b88f0d50d7f 100644
--- a/platform/platform-impl/src/org/jetbrains/io/netty.kt
+++ b/platform/platform-impl/src/org/jetbrains/io/netty.kt
@@ -31,6 +31,7 @@ import io.netty.channel.socket.oio.OioSocketChannel
import io.netty.handler.codec.http.HttpHeaderNames
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
@@ -125,7 +126,7 @@ inline fun ByteBuf.releaseIfError(task: () -> T): T {
}
}
-fun isLocalHost(host: String, onlyAnyOrLoopback: Boolean): Boolean {
+fun isLocalHost(host: String, onlyAnyOrLoopback: Boolean, hostsOnly: Boolean = false): Boolean {
if (onlyAnyOrLoopback) {
if (NetUtils.isLocalhost(host)) {
return true
@@ -137,9 +138,20 @@ fun isLocalHost(host: String, onlyAnyOrLoopback: Boolean): Boolean {
// if IP address, it is safe to use getByName (not affected by DNS rebinding)
}
+ fun InetAddress.isLocal() = isAnyLocalAddress || isLoopbackAddress || NetworkInterface.getByInetAddress(this) != null
+
try {
val address = InetAddress.getByName(host)
- return address.isAnyLocalAddress || address.isLoopbackAddress || NetworkInterface.getByInetAddress(address) != null
+ if (!address.isLocal()) {
+ return false
+ }
+ // 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
@@ -147,21 +159,21 @@ fun isLocalHost(host: String, onlyAnyOrLoopback: Boolean): Boolean {
}
@JvmOverloads
-fun HttpRequest.isLocalOrigin(onlyAnyOrLoopback: Boolean = true) = parseAndCheckIsLocalHost(origin, onlyAnyOrLoopback) && parseAndCheckIsLocalHost(referrer, onlyAnyOrLoopback)
+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): Boolean {
+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)
+ return isTrustedChromeExtension(parsedUri) || isLocalHost(parsedUri.host, onlyAnyOrLoopback, hostsOnly)
}
catch (ignored: Exception) {
}
From 9f6d9518044a77a4a8f608da0beace4d910858a2 Mon Sep 17 00:00:00 2001
From: Vladimir Krivosheev
Date: Wed, 13 Apr 2016 15:06:32 +0200
Subject: [PATCH 16/67] close connection if message > 8192
---
platform/platform-impl/src/com/intellij/idea/SocketLock.java | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/platform/platform-impl/src/com/intellij/idea/SocketLock.java b/platform/platform-impl/src/com/intellij/idea/SocketLock.java
index 783e761c1069..0fcf78556d4f 100644
--- a/platform/platform-impl/src/com/intellij/idea/SocketLock.java
+++ b/platform/platform-impl/src/com/intellij/idea/SocketLock.java
@@ -325,6 +325,10 @@ public final class SocketLock {
}
contentLength = buffer.readUnsignedShort();
+ if (contentLength > 8192) {
+ context.close();
+ return;
+ }
myState = State.CONTENT;
}
break;
From b83ef506cd5bb599c74fdb130cbe5a65a7b4d5f3 Mon Sep 17 00:00:00 2001
From: Vladimir Krivosheev
Date: Wed, 13 Apr 2016 16:24:14 +0200
Subject: [PATCH 17/67] host doesn't contain scheme
---
.../platform-impl/src/org/jetbrains/ide/HttpRequestHandler.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/platform/platform-impl/src/org/jetbrains/ide/HttpRequestHandler.java b/platform/platform-impl/src/org/jetbrains/ide/HttpRequestHandler.java
index 0f03b280d8c3..5166e95ba8fd 100644
--- a/platform/platform-impl/src/org/jetbrains/ide/HttpRequestHandler.java
+++ b/platform/platform-impl/src/org/jetbrains/ide/HttpRequestHandler.java
@@ -48,7 +48,7 @@ public abstract class HttpRequestHandler {
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(host);
+ return host != null && NettyKt.isLocalOrigin(request) && NettyKt.parseAndCheckIsLocalHost("http://" + host);
}
public boolean isSupported(@NotNull FullHttpRequest request) {
From ee2f9950478ad28bf06134f0dfb411f9441902b1 Mon Sep 17 00:00:00 2001
From: Vladimir Krivosheev
Date: Wed, 13 Apr 2016 18:05:39 +0200
Subject: [PATCH 18/67] cleanup
---
.../platform-impl/src/org/jetbrains/io/NettyUtil.java | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/platform/platform-impl/src/org/jetbrains/io/NettyUtil.java b/platform/platform-impl/src/org/jetbrains/io/NettyUtil.java
index b3541f95b383..8bb15db8e996 100644
--- a/platform/platform-impl/src/org/jetbrains/io/NettyUtil.java
+++ b/platform/platform-impl/src/org/jetbrains/io/NettyUtil.java
@@ -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.
@@ -30,6 +30,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;
@@ -234,8 +235,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)
From 2ddce3be1b80d09e40f646af6b19b1324b9a49b9 Mon Sep 17 00:00:00 2001
From: Vladimir Krivosheev
Date: Thu, 14 Apr 2016 08:16:56 +0200
Subject: [PATCH 19/67] forbid POST requests from browser without Origin
---
.../src/org/jetbrains/ide/HttpRequestHandler.java | 3 +++
.../org/jetbrains/io/DelegatingHttpRequestHandler.kt | 2 +-
platform/platform-impl/src/org/jetbrains/io/netty.kt | 11 +++++++++++
3 files changed, 15 insertions(+), 1 deletion(-)
diff --git a/platform/platform-impl/src/org/jetbrains/ide/HttpRequestHandler.java b/platform/platform-impl/src/org/jetbrains/ide/HttpRequestHandler.java
index 5166e95ba8fd..815264ada9a8 100644
--- a/platform/platform-impl/src/org/jetbrains/ide/HttpRequestHandler.java
+++ b/platform/platform-impl/src/org/jetbrains/ide/HttpRequestHandler.java
@@ -44,6 +44,9 @@ public abstract class HttpRequestHandler {
}
@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.
diff --git a/platform/platform-impl/src/org/jetbrains/io/DelegatingHttpRequestHandler.kt b/platform/platform-impl/src/org/jetbrains/io/DelegatingHttpRequestHandler.kt
index b500c0294ab5..806d3e6a351b 100644
--- a/platform/platform-impl/src/org/jetbrains/io/DelegatingHttpRequestHandler.kt
+++ b/platform/platform-impl/src/org/jetbrains/io/DelegatingHttpRequestHandler.kt
@@ -38,7 +38,7 @@ internal class DelegatingHttpRequestHandler : DelegatingHttpRequestHandlerBase()
request: FullHttpRequest,
urlDecoder: QueryStringDecoder): Boolean {
fun HttpRequestHandler.checkAndProcess(): Boolean {
- return isSupported(request) && isAccessible(request) && process(urlDecoder, request, context)
+ return isSupported(request) && !request.isWriteFromBrowserWithoutOrigin() && isAccessible(request) && process(urlDecoder, request, context)
}
val prevHandlerAttribute = context.attr(PREV_HANDLER)
diff --git a/platform/platform-impl/src/org/jetbrains/io/netty.kt b/platform/platform-impl/src/org/jetbrains/io/netty.kt
index 9b88f0d50d7f..c01fc8febdd8 100644
--- a/platform/platform-impl/src/org/jetbrains/io/netty.kt
+++ b/platform/platform-impl/src/org/jetbrains/io/netty.kt
@@ -29,6 +29,7 @@ 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
@@ -112,6 +113,9 @@ val HttpRequest.origin: String?
val HttpRequest.referrer: String?
get() = headers().getAsString(HttpHeaderNames.REFERER)
+val HttpRequest.userAgent: String?
+ get() = headers().getAsString(HttpHeaderNames.USER_AGENT)
+
inline fun ByteBuf.releaseIfError(task: () -> T): T {
try {
return task()
@@ -178,4 +182,11 @@ fun parseAndCheckIsLocalHost(uri: String?, onlyAnyOrLoopback: Boolean = true, ho
catch (ignored: Exception) {
}
return false
+}
+
+// forbid POST requests from browser without Origin
+fun HttpRequest.isWriteFromBrowserWithoutOrigin(): Boolean {
+ val userAgent = userAgent ?: return false
+ val method = method()
+ return origin.isNullOrEmpty() && userAgent.startsWith("Mozilla/5.0") && (method == HttpMethod.POST || method == HttpMethod.PATCH || method == HttpMethod.PUT || method == HttpMethod.DELETE)
}
\ No newline at end of file
From 235eab07b7ce2358317d69739bc25ea84614fa41 Mon Sep 17 00:00:00 2001
From: Vladimir Krivosheev
Date: Fri, 15 Apr 2016 10:28:38 +0200
Subject: [PATCH 20/67] remove OpenFileXmlRpcHandler
---
.../jetbrains/ide/OpenFileXmlRpcHandler.java | 48 -------------------
.../org/jetbrains/ide/XmlRpcServerImpl.java | 16 +------
.../com/intellij/ide/XmlRpcHandlerBean.java | 30 ------------
.../src/META-INF/PlatformExtensionPoints.xml | 7 +--
.../src/META-INF/built-in-server.xml | 2 -
5 files changed, 3 insertions(+), 100 deletions(-)
delete mode 100644 platform/built-in-server/src/org/jetbrains/ide/OpenFileXmlRpcHandler.java
delete mode 100644 platform/platform-api/src/com/intellij/ide/XmlRpcHandlerBean.java
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
index 410bf62eb9d8..000000000000
--- a/platform/built-in-server/src/org/jetbrains/ide/OpenFileXmlRpcHandler.java
+++ /dev/null
@@ -1,48 +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) {
- OpenFileRequest request = new OpenFileRequest();
- request.setFile(path);
- request.setLine(line);
- request.setColumn(column);
- request.setFocused(false);
- Promise promise = HttpRequestHandler.EP_NAME.findExtension(OpenFileHttpService.class).openFile(request, null, null);
- return promise != null && promise.getState() != Promise.State.REJECTED;
- }
-}
\ No newline at end of file
diff --git a/platform/built-in-server/src/org/jetbrains/ide/XmlRpcServerImpl.java b/platform/built-in-server/src/org/jetbrains/ide/XmlRpcServerImpl.java
index 3f26acc49bfd..782136dd77f9 100644
--- a/platform/built-in-server/src/org/jetbrains/ide/XmlRpcServerImpl.java
+++ b/platform/built-in-server/src/org/jetbrains/ide/XmlRpcServerImpl.java
@@ -15,12 +15,8 @@
*/
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 handlerMapping;
+ private final Map handlerMapping = new THashMap();
public XmlRpcServerImpl() {
- handlerMapping = new THashMap();
- 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 {
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
index 77630f66b30e..000000000000
--- a/platform/platform-api/src/com/intellij/ide/XmlRpcHandlerBean.java
+++ /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 EP_NAME = ExtensionPointName.create("com.intellij.xmlRpcHandler");
-
- @Attribute("name")
- public String name;
-
- @Attribute("implementation")
- public String implementation;
-}
\ No newline at end of file
diff --git a/platform/platform-resources/src/META-INF/PlatformExtensionPoints.xml b/platform/platform-resources/src/META-INF/PlatformExtensionPoints.xml
index 58990d478097..05e5bcead225 100644
--- a/platform/platform-resources/src/META-INF/PlatformExtensionPoints.xml
+++ b/platform/platform-resources/src/META-INF/PlatformExtensionPoints.xml
@@ -190,9 +190,6 @@
-
-
-
@@ -215,11 +212,11 @@
-
+
-
+
diff --git a/platform/platform-resources/src/META-INF/built-in-server.xml b/platform/platform-resources/src/META-INF/built-in-server.xml
index b38e318efdef..cc795eaf1080 100644
--- a/platform/platform-resources/src/META-INF/built-in-server.xml
+++ b/platform/platform-resources/src/META-INF/built-in-server.xml
@@ -22,8 +22,6 @@
-
-
From 0247ed2fe6b560e39288751c45861c9eed98a8c6 Mon Sep 17 00:00:00 2001
From: Vladimir Krivosheev
Date: Fri, 15 Apr 2016 13:03:20 +0200
Subject: [PATCH 21/67] cleanup
---
.../src/org/jetbrains/io/NettyUtil.java | 18 ------------------
1 file changed, 18 deletions(-)
diff --git a/platform/platform-impl/src/org/jetbrains/io/NettyUtil.java b/platform/platform-impl/src/org/jetbrains/io/NettyUtil.java
index 8bb15db8e996..33bd2693a253 100644
--- a/platform/platform-impl/src/org/jetbrains/io/NettyUtil.java
+++ b/platform/platform-impl/src/org/jetbrains/io/NettyUtil.java
@@ -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;
@@ -200,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));
}
From 865c83d5c99719e03865aab6c1bef19b9f8ba03c Mon Sep 17 00:00:00 2001
From: Vladimir Krivosheev
Date: Fri, 15 Apr 2016 13:13:14 +0200
Subject: [PATCH 22/67] =?UTF-8?q?/api/file=20=E2=80=94=20do=20not=20expose?=
=?UTF-8?q?=20status=20in=20any=20case,=20always=20return=20200?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../src/org/jetbrains/builtInWebServer/StaticFileHandler.kt | 6 ++----
.../src/org/jetbrains/ide/OpenFileHttpService.kt | 5 ++---
2 files changed, 4 insertions(+), 7 deletions(-)
diff --git a/platform/built-in-server/src/org/jetbrains/builtInWebServer/StaticFileHandler.kt b/platform/built-in-server/src/org/jetbrains/builtInWebServer/StaticFileHandler.kt
index ef779182b485..2051af71edd4 100644
--- a/platform/built-in-server/src/org/jetbrains/builtInWebServer/StaticFileHandler.kt
+++ b/platform/built-in-server/src/org/jetbrains/builtInWebServer/StaticFileHandler.kt
@@ -13,7 +13,6 @@ 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.isLocalOrigin
import org.jetbrains.io.send
import java.nio.file.Files
import java.nio.file.Path
@@ -108,12 +107,11 @@ private fun sendIoFile(channel: Channel, file: Path, root: Path, request: HttpRe
}
}
-fun checkAccess(channel: Channel, file: Path, request: HttpRequest, root: Path = file.root, doNotExposeStatusIfNotLocalOrigin: Boolean = false): Boolean {
+fun checkAccess(channel: Channel, file: Path, request: HttpRequest, root: Path = file.root, doNotExposeStatus: Boolean = false): Boolean {
var parent = file
do {
if (!hasAccess(parent)) {
- (if (doNotExposeStatusIfNotLocalOrigin && !request.isLocalOrigin()) HttpResponseStatus.OK else HttpResponseStatus.FORBIDDEN)
- .send(channel, request)
+ (if (doNotExposeStatus) HttpResponseStatus.OK else HttpResponseStatus.FORBIDDEN).send(channel, request)
return false
}
parent = parent.parent ?: break
diff --git a/platform/built-in-server/src/org/jetbrains/ide/OpenFileHttpService.kt b/platform/built-in-server/src/org/jetbrains/ide/OpenFileHttpService.kt
index caff2214a43c..d4242a3df3e9 100644
--- a/platform/built-in-server/src/org/jetbrains/ide/OpenFileHttpService.kt
+++ b/platform/built-in-server/src/org/jetbrains/ide/OpenFileHttpService.kt
@@ -42,7 +42,6 @@ import org.jetbrains.concurrency.AsyncPromise
import org.jetbrains.concurrency.Promise
import org.jetbrains.concurrency.catchError
import org.jetbrains.concurrency.rejectedPromise
-import org.jetbrains.io.isLocalOrigin
import java.nio.file.Path
import java.nio.file.Paths
import java.util.concurrent.ConcurrentLinkedQueue
@@ -123,7 +122,7 @@ internal class OpenFileHttpService : RestService() {
.rejected {
if (it === NOT_FOUND) {
// don't expose file status if not local origin
- sendStatus(if (request.isLocalOrigin()) HttpResponseStatus.NOT_FOUND else HttpResponseStatus.OK, keepAlive, channel)
+ sendStatus(HttpResponseStatus.OK, keepAlive, channel)
}
else {
// todo send error
@@ -141,7 +140,7 @@ internal class OpenFileHttpService : RestService() {
if (!file.exists()) {
return rejectedPromise(NOT_FOUND)
}
- return if (context == null || checkAccess(context.channel(), file, httpRequest!!, doNotExposeStatusIfNotLocalOrigin = true)) openAbsolutePath(file, request) else null
+ return if (context == null || checkAccess(context.channel(), file, httpRequest!!, doNotExposeStatus = true)) openAbsolutePath(file, request) else null
}
// we don't want to call refresh for each attempt on findFileByRelativePath call, so, we do what ourSaveAndSyncHandlerImpl does on frame activation
From f765c8387593c6d0025632b375ee0fe594565303 Mon Sep 17 00:00:00 2001
From: Vladimir Krivosheev
Date: Fri, 15 Apr 2016 14:44:08 +0200
Subject: [PATCH 23/67] =?UTF-8?q?set=20X-Frame-Options=20to=20SameOrigin?=
=?UTF-8?q?=20(rest=20api=20=E2=80=94=20Deny)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../built-in-server/src/org/jetbrains/ide/RestService.java | 4 +++-
platform/platform-impl/src/org/jetbrains/io/Responses.kt | 3 +++
2 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/platform/built-in-server/src/org/jetbrains/ide/RestService.java b/platform/built-in-server/src/org/jetbrains/ide/RestService.java
index 2d8beb522d5a..3ea42c11956a 100644
--- a/platform/built-in-server/src/org/jetbrains/ide/RestService.java
+++ b/platform/built-in-server/src/org/jetbrains/ide/RestService.java
@@ -187,12 +187,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);
}
diff --git a/platform/platform-impl/src/org/jetbrains/io/Responses.kt b/platform/platform-impl/src/org/jetbrains/io/Responses.kt
index 3c293b37f0d4..3393b936e7d0 100644
--- a/platform/platform-impl/src/org/jetbrains/io/Responses.kt
+++ b/platform/platform-impl/src/org/jetbrains/io/Responses.kt
@@ -99,6 +99,9 @@ fun HttpResponse.addKeepAliveIfNeed(request: HttpRequest): Boolean {
fun HttpResponse.addCommonHeaders() {
addServer()
setDate()
+ if (!headers().contains("X-Frame-Options")) {
+ headers().set("X-Frame-Options", "SameOrigin")
+ }
}
fun HttpResponse.send(channel: Channel, close: Boolean) {
From 99d82772c62627ff426b6817dbfc4c2a382288d1 Mon Sep 17 00:00:00 2001
From: Vladimir Krivosheev
Date: Fri, 15 Apr 2016 16:45:10 +0200
Subject: [PATCH 24/67] add X-Content-Type-Options: nosniff
---
platform/platform-impl/src/org/jetbrains/io/Responses.kt | 1 +
1 file changed, 1 insertion(+)
diff --git a/platform/platform-impl/src/org/jetbrains/io/Responses.kt b/platform/platform-impl/src/org/jetbrains/io/Responses.kt
index 3393b936e7d0..3efbd2597082 100644
--- a/platform/platform-impl/src/org/jetbrains/io/Responses.kt
+++ b/platform/platform-impl/src/org/jetbrains/io/Responses.kt
@@ -102,6 +102,7 @@ fun HttpResponse.addCommonHeaders() {
if (!headers().contains("X-Frame-Options")) {
headers().set("X-Frame-Options", "SameOrigin")
}
+ headers().set("X-Content-Type-Options", "nosniff")
}
fun HttpResponse.send(channel: Channel, close: Boolean) {
From 6efc6b5e307f40db17eefc9c7f756b571373f38a Mon Sep 17 00:00:00 2001
From: Vladimir Krivosheev
Date: Fri, 15 Apr 2016 16:48:23 +0200
Subject: [PATCH 25/67] fix pubserve
---
.../platform-impl/src/org/jetbrains/io/netty.kt | 15 +++++++--------
1 file changed, 7 insertions(+), 8 deletions(-)
diff --git a/platform/platform-impl/src/org/jetbrains/io/netty.kt b/platform/platform-impl/src/org/jetbrains/io/netty.kt
index c01fc8febdd8..760f54b84bec 100644
--- a/platform/platform-impl/src/org/jetbrains/io/netty.kt
+++ b/platform/platform-impl/src/org/jetbrains/io/netty.kt
@@ -131,15 +131,13 @@ inline fun ByteBuf.releaseIfError(task: () -> T): T {
}
fun isLocalHost(host: String, onlyAnyOrLoopback: Boolean, hostsOnly: Boolean = false): Boolean {
- if (onlyAnyOrLoopback) {
- if (NetUtils.isLocalhost(host)) {
- return true
- }
+ if (NetUtils.isLocalhost(host)) {
+ return true
+ }
- if (!InetAddresses.isInetAddress(host)) {
- return false
- }
- // if IP address, it is safe to use getByName (not affected by DNS rebinding)
+ // 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
@@ -149,6 +147,7 @@ fun isLocalHost(host: String, onlyAnyOrLoopback: Boolean, hostsOnly: Boolean = f
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() }
From 40a7b465c5be73a6202ab5ee2b3dcf45a5a986e8 Mon Sep 17 00:00:00 2001
From: Vladimir Krivosheev
Date: Fri, 15 Apr 2016 18:02:48 +0200
Subject: [PATCH 26/67] log.warn if not found, fix comment
---
.../src/org/jetbrains/ide/OpenFileHttpService.kt | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/platform/built-in-server/src/org/jetbrains/ide/OpenFileHttpService.kt b/platform/built-in-server/src/org/jetbrains/ide/OpenFileHttpService.kt
index d4242a3df3e9..8417d3dbcddb 100644
--- a/platform/built-in-server/src/org/jetbrains/ide/OpenFileHttpService.kt
+++ b/platform/built-in-server/src/org/jetbrains/ide/OpenFileHttpService.kt
@@ -121,8 +121,9 @@ internal class OpenFileHttpService : RestService() {
promise.done { sendStatus(HttpResponseStatus.OK, keepAlive, channel) }
.rejected {
if (it === NOT_FOUND) {
- // don't expose file status if not local origin
+ // don't expose file status
sendStatus(HttpResponseStatus.OK, keepAlive, channel)
+ LOG.warn("File ${apiRequest.file} not found")
}
else {
// todo send error
From 8c949bdba2def6f2cb9932641173256fbec40911 Mon Sep 17 00:00:00 2001
From: Vladimir Krivosheev
Date: Mon, 18 Apr 2016 12:39:16 +0200
Subject: [PATCH 27/67] =?UTF-8?q?checkAccess=20in=20DefaultWebServerPathHa?=
=?UTF-8?q?ndler=20=E2=80=94=20because=20each=20implementation=20of=20WebS?=
=?UTF-8?q?erverFileHandler=20must=20do=20it=20in=20any=20case?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../builtInWebServer/BuiltInWebServer.kt | 3 +-
.../DefaultWebServerPathHandler.kt | 28 +++++++++++++++++++
.../builtInWebServer/StaticFileHandler.kt | 27 ++++--------------
.../org/jetbrains/ide/OpenFileHttpService.kt | 5 ++--
.../src/org/jetbrains/io/Responses.kt | 3 ++
5 files changed, 42 insertions(+), 24 deletions(-)
diff --git a/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt b/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt
index 2e2c7395c70a..fae51c09cf8c 100644
--- a/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt
+++ b/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt
@@ -138,7 +138,8 @@ private fun doProcess(request: FullHttpRequest, context: ChannelHandlerContext,
val path = toIdeaPath(decodedPath, offset)
if (path == null) {
- HttpResponseStatus.BAD_REQUEST.send(context.channel(), request)
+ LOG.warn("$decodedPath is not valid")
+ HttpResponseStatus.NOT_FOUND.send(context.channel(), request)
return true
}
diff --git a/platform/built-in-server/src/org/jetbrains/builtInWebServer/DefaultWebServerPathHandler.kt b/platform/built-in-server/src/org/jetbrains/builtInWebServer/DefaultWebServerPathHandler.kt
index 128e3be572dc..a0bf723a0018 100644
--- a/platform/built-in-server/src/org/jetbrains/builtInWebServer/DefaultWebServerPathHandler.kt
+++ b/platform/built-in-server/src/org/jetbrains/builtInWebServer/DefaultWebServerPathHandler.kt
@@ -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 java.nio.file.Path
+import java.nio.file.Paths
private class DefaultWebServerPathHandler : WebServerPathHandler() {
override fun process(path: String,
@@ -94,6 +99,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 +113,23 @@ 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.NOT_FOUND.send(channel, request)
+ return false
+ }
+ else if (!checkAccess(channel, file, request, Paths.get(pathInfo.root.path))) {
+ return false
+ }
+ }
+ else if (pathInfo.file!!.`is`(VFileProperty.HIDDEN)) {
+ HttpResponseStatus.NOT_FOUND.send(channel, request)
+ return false
+ }
+
+ return true
}
\ No newline at end of file
diff --git a/platform/built-in-server/src/org/jetbrains/builtInWebServer/StaticFileHandler.kt b/platform/built-in-server/src/org/jetbrains/builtInWebServer/StaticFileHandler.kt
index 2051af71edd4..d1b67c2f6cd9 100644
--- a/platform/built-in-server/src/org/jetbrains/builtInWebServer/StaticFileHandler.kt
+++ b/platform/built-in-server/src/org/jetbrains/builtInWebServer/StaticFileHandler.kt
@@ -1,9 +1,7 @@
package org.jetbrains.builtInWebServer
import com.intellij.openapi.project.Project
-import com.intellij.openapi.vfs.VFileProperty
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
@@ -13,6 +11,7 @@ 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.okInSafeMode
import org.jetbrains.io.send
import java.nio.file.Files
import java.nio.file.Path
@@ -31,15 +30,10 @@ private class StaticFileHandler : WebServerFileHandler() {
return true
}
- sendIoFile(channel, ioFile, Paths.get(pathInfo.root.path), request)
+ FileResponses.sendFile(request, channel, ioFile)
}
else {
val file = pathInfo.file!!
- if (file.`is`(VFileProperty.HIDDEN)) {
- HttpResponseStatus.FORBIDDEN.send(channel, request)
- return true
- }
-
val response = FileResponses.prepareSend(request, channel, file.timeStamp, file.name) ?: return true
val keepAlive = response.addKeepAliveIfNeed(request)
@@ -98,20 +92,11 @@ private class StaticFileHandler : WebServerFileHandler() {
}
}
-private fun sendIoFile(channel: Channel, file: Path, root: Path, request: HttpRequest) {
- if (file.isDirectory()) {
- HttpResponseStatus.FORBIDDEN.send(channel, request)
- }
- else if (checkAccess(channel, file, request, root)) {
- FileResponses.sendFile(request, channel, file)
- }
-}
-
-fun checkAccess(channel: Channel, file: Path, request: HttpRequest, root: Path = file.root, doNotExposeStatus: Boolean = false): Boolean {
+internal fun checkAccess(channel: Channel, file: Path, request: HttpRequest, root: Path = file.root): Boolean {
var parent = file
do {
if (!hasAccess(parent)) {
- (if (doNotExposeStatus) HttpResponseStatus.OK else HttpResponseStatus.FORBIDDEN).send(channel, request)
+ HttpResponseStatus.FORBIDDEN.okInSafeMode().send(channel, request)
return false
}
parent = parent.parent ?: break
@@ -120,5 +105,5 @@ fun checkAccess(channel: Channel, file: Path, request: HttpRequest, root: Path =
return true
}
-// deny access to .htaccess files
-private fun hasAccess(result: Path) = 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
diff --git a/platform/built-in-server/src/org/jetbrains/ide/OpenFileHttpService.kt b/platform/built-in-server/src/org/jetbrains/ide/OpenFileHttpService.kt
index 8417d3dbcddb..96ca5601edbb 100644
--- a/platform/built-in-server/src/org/jetbrains/ide/OpenFileHttpService.kt
+++ b/platform/built-in-server/src/org/jetbrains/ide/OpenFileHttpService.kt
@@ -42,6 +42,7 @@ import org.jetbrains.concurrency.AsyncPromise
import org.jetbrains.concurrency.Promise
import org.jetbrains.concurrency.catchError
import org.jetbrains.concurrency.rejectedPromise
+import org.jetbrains.io.okInSafeMode
import java.nio.file.Path
import java.nio.file.Paths
import java.util.concurrent.ConcurrentLinkedQueue
@@ -122,7 +123,7 @@ internal class OpenFileHttpService : RestService() {
.rejected {
if (it === NOT_FOUND) {
// don't expose file status
- sendStatus(HttpResponseStatus.OK, keepAlive, channel)
+ sendStatus(HttpResponseStatus.NOT_FOUND.okInSafeMode(), keepAlive, channel)
LOG.warn("File ${apiRequest.file} not found")
}
else {
@@ -141,7 +142,7 @@ internal class OpenFileHttpService : RestService() {
if (!file.exists()) {
return rejectedPromise(NOT_FOUND)
}
- return if (context == null || checkAccess(context.channel(), file, httpRequest!!, doNotExposeStatus = true)) openAbsolutePath(file, request) else null
+ return if (context == null || checkAccess(context.channel(), file, httpRequest!!)) openAbsolutePath(file, request) else null
}
// we don't want to call refresh for each attempt on findFileByRelativePath call, so, we do what ourSaveAndSyncHandlerImpl does on frame activation
diff --git a/platform/platform-impl/src/org/jetbrains/io/Responses.kt b/platform/platform-impl/src/org/jetbrains/io/Responses.kt
index 3efbd2597082..4a3bd36e3523 100644
--- a/platform/platform-impl/src/org/jetbrains/io/Responses.kt
+++ b/platform/platform-impl/src/org/jetbrains/io/Responses.kt
@@ -103,6 +103,7 @@ fun HttpResponse.addCommonHeaders() {
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) {
@@ -125,6 +126,8 @@ fun HttpResponseStatus.send(channel: Channel, request: HttpRequest? = null, desc
createStatusResponse(this, request, description).send(channel, request)
}
+fun HttpResponseStatus.okInSafeMode() = if (ApplicationManager.getApplication()?.isUnitTestMode ?: false) this else HttpResponseStatus.OK
+
private fun createStatusResponse(responseStatus: HttpResponseStatus, request: HttpRequest?, description: String?): HttpResponse {
if (request != null && request.method() === HttpMethod.HEAD) {
return responseStatus.response()
From 68063e28d1f0e5ac8c590e71cf07b870e1b002fd Mon Sep 17 00:00:00 2001
From: Vladimir Krivosheev
Date: Mon, 18 Apr 2016 13:31:47 +0200
Subject: [PATCH 28/67] any asset must be requested with valid Refer (from
regular browser)
---
.../builtInWebServer/BuiltInWebServer.kt | 21 ++++++++++++++++---
.../builtInWebServer/StaticFileHandler.kt | 2 ++
.../builtInWebServer/WebServerFileHandler.kt | 4 +++-
.../src/org/jetbrains/io/netty.kt | 4 +++-
4 files changed, 26 insertions(+), 5 deletions(-)
diff --git a/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt b/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt
index fae51c09cf8c..a754c1dcb5c6 100644
--- a/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt
+++ b/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt
@@ -22,6 +22,7 @@ import com.intellij.openapi.project.Project
import com.intellij.openapi.project.ProjectManager
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.vfs.VirtualFile
import com.intellij.util.UriUtil
@@ -32,9 +33,7 @@ import com.intellij.util.net.NetUtils
import io.netty.channel.ChannelHandlerContext
import io.netty.handler.codec.http.*
import org.jetbrains.ide.HttpRequestHandler
-import org.jetbrains.io.host
-import org.jetbrains.io.isLocalOrigin
-import org.jetbrains.io.send
+import org.jetbrains.io.*
import java.io.IOException
import java.net.InetAddress
import java.nio.file.Path
@@ -143,6 +142,11 @@ private fun doProcess(request: FullHttpRequest, context: ChannelHandlerContext,
return true
}
+ if (request.origin == null && request.referrer == null && request.isRegularBrowser() && !canBeAccessedDirectly(path)) {
+ HttpResponseStatus.NOT_FOUND.send(context.channel(), request)
+ return true
+ }
+
for (pathHandler in WebServerPathHandler.EP_NAME.extensions) {
LOG.catchAndLog {
if (pathHandler.process(path, project, request, context, projectName, decodedPath, isCustomHost)) {
@@ -243,4 +247,15 @@ internal fun isOwnHostName(host: String): Boolean {
catch (ignored: IOException) {
return false
}
+}
+
+private 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
diff --git a/platform/built-in-server/src/org/jetbrains/builtInWebServer/StaticFileHandler.kt b/platform/built-in-server/src/org/jetbrains/builtInWebServer/StaticFileHandler.kt
index d1b67c2f6cd9..2158b67948d6 100644
--- a/platform/built-in-server/src/org/jetbrains/builtInWebServer/StaticFileHandler.kt
+++ b/platform/built-in-server/src/org/jetbrains/builtInWebServer/StaticFileHandler.kt
@@ -18,6 +18,8 @@ import java.nio.file.Path
import java.nio.file.Paths
private class StaticFileHandler : WebServerFileHandler() {
+ override val pageFileExtensions = arrayOf("html", "htm", "shtml")
+
private var ssiProcessor: SsiProcessor? = null
override fun process(pathInfo: PathInfo, canonicalPath: CharSequence, project: Project, request: FullHttpRequest, channel: Channel, projectNameIfNotCustomHost: String?): Boolean {
diff --git a/platform/built-in-server/src/org/jetbrains/builtInWebServer/WebServerFileHandler.kt b/platform/built-in-server/src/org/jetbrains/builtInWebServer/WebServerFileHandler.kt
index d2eed3717aca..1e823c4861a4 100644
--- a/platform/built-in-server/src/org/jetbrains/builtInWebServer/WebServerFileHandler.kt
+++ b/platform/built-in-server/src/org/jetbrains/builtInWebServer/WebServerFileHandler.kt
@@ -25,6 +25,9 @@ abstract class WebServerFileHandler {
internal val EP_NAME = ExtensionPointName.create("org.jetbrains.webServerFileHandler")
}
+ open val pageFileExtensions: Array
+ 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
diff --git a/platform/platform-impl/src/org/jetbrains/io/netty.kt b/platform/platform-impl/src/org/jetbrains/io/netty.kt
index 760f54b84bec..fd09296971cd 100644
--- a/platform/platform-impl/src/org/jetbrains/io/netty.kt
+++ b/platform/platform-impl/src/org/jetbrains/io/netty.kt
@@ -183,9 +183,11 @@ fun parseAndCheckIsLocalHost(uri: String?, onlyAnyOrLoopback: Boolean = true, ho
return false
}
+fun HttpRequest.isRegularBrowser() = userAgent?.startsWith("Mozilla/5.0") ?: false
+
// forbid POST requests from browser without Origin
fun HttpRequest.isWriteFromBrowserWithoutOrigin(): Boolean {
val userAgent = userAgent ?: return false
val method = method()
- return origin.isNullOrEmpty() && userAgent.startsWith("Mozilla/5.0") && (method == HttpMethod.POST || method == HttpMethod.PATCH || method == HttpMethod.PUT || method == HttpMethod.DELETE)
+ return origin.isNullOrEmpty() && isRegularBrowser() && (method == HttpMethod.POST || method == HttpMethod.PATCH || method == HttpMethod.PUT || method == HttpMethod.DELETE)
}
\ No newline at end of file
From 63ad820e63f9b165c70ec4a545e9332b0e6369b8 Mon Sep 17 00:00:00 2001
From: Vladimir Krivosheev
Date: Mon, 18 Apr 2016 16:01:17 +0200
Subject: [PATCH 29/67] use UUID instead of incremental int id
---
.../git4idea/http/GitAskPassApp.java | 14 ++++----
.../git4idea/http/GitAskPassXmlRpcClient.java | 10 +++---
.../http/GitAskPassXmlRpcHandler.java | 10 +++---
.../jetbrains/git4idea/ssh/GitSSHHandler.java | 34 ++++++++-----------
.../git4idea/ssh/GitSSHXmlRpcClient.java | 30 ++++++++--------
.../org/jetbrains/git4idea/ssh/SSHMain.java | 6 ++--
.../src/git4idea/commands/GitHandler.java | 14 ++++----
.../git4idea/commands/GitHttpAuthService.java | 11 +++---
.../rebase/GitRebaseEditorService.java | 5 +--
.../git4idea/ssh/GitXmlRpcHandlerService.java | 15 ++++----
.../git4idea/ssh/GitXmlRpcSshService.java | 27 ++++++++-------
11 files changed, 86 insertions(+), 90 deletions(-)
diff --git a/plugins/git4idea/rt/src/org/jetbrains/git4idea/http/GitAskPassApp.java b/plugins/git4idea/rt/src/org/jetbrains/git4idea/http/GitAskPassApp.java
index 8c8e425570a2..8ce927804e52 100644
--- a/plugins/git4idea/rt/src/org/jetbrains/git4idea/http/GitAskPassApp.java
+++ b/plugins/git4idea/rt/src/org/jetbrains/git4idea/http/GitAskPassApp.java
@@ -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
diff --git a/plugins/git4idea/rt/src/org/jetbrains/git4idea/http/GitAskPassXmlRpcClient.java b/plugins/git4idea/rt/src/org/jetbrains/git4idea/http/GitAskPassXmlRpcClient.java
index 5d3f8681f099..85e49697416e 100644
--- a/plugins/git4idea/rt/src/org/jetbrains/git4idea/http/GitAskPassXmlRpcClient.java
+++ b/plugins/git4idea/rt/src/org/jetbrains/git4idea/http/GitAskPassXmlRpcClient.java
@@ -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 {
diff --git a/plugins/git4idea/rt/src/org/jetbrains/git4idea/http/GitAskPassXmlRpcHandler.java b/plugins/git4idea/rt/src/org/jetbrains/git4idea/http/GitAskPassXmlRpcHandler.java
index 36cc365c1d80..9c9900a1f3ca 100644
--- a/plugins/git4idea/rt/src/org/jetbrains/git4idea/http/GitAskPassXmlRpcHandler.java
+++ b/plugins/git4idea/rt/src/org/jetbrains/git4idea/http/GitAskPassXmlRpcHandler.java
@@ -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);
}
diff --git a/plugins/git4idea/rt/src/org/jetbrains/git4idea/ssh/GitSSHHandler.java b/plugins/git4idea/rt/src/org/jetbrains/git4idea/ssh/GitSSHHandler.java
index 2bc849d83fb0..4ce4ea31b6a2 100644
--- a/plugins/git4idea/rt/src/org/jetbrains/git4idea/ssh/GitSSHHandler.java
+++ b/plugins/git4idea/rt/src/org/jetbrains/git4idea/ssh/GitSSHHandler.java
@@ -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 replyToChallenge(final int handlerNo,
+ Vector 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);
}
diff --git a/plugins/git4idea/rt/src/org/jetbrains/git4idea/ssh/GitSSHXmlRpcClient.java b/plugins/git4idea/rt/src/org/jetbrains/git4idea/ssh/GitSSHXmlRpcClient.java
index c4ddd216e1cd..593362a3ee8e 100644
--- a/plugins/git4idea/rt/src/org/jetbrains/git4idea/ssh/GitSSHXmlRpcClient.java
+++ b/plugins/git4idea/rt/src/org/jetbrains/git4idea/ssh/GitSSHXmlRpcClient.java
@@ -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 replyToChallenge(final int handlerNo,
+ public Vector 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);
}
}
diff --git a/plugins/git4idea/rt/src/org/jetbrains/git4idea/ssh/SSHMain.java b/plugins/git4idea/rt/src/org/jetbrains/git4idea/ssh/SSHMain.java
index b5bc260b9520..67a74b92b0ac 100644
--- a/plugins/git4idea/rt/src/org/jetbrains/git4idea/ssh/SSHMain.java
+++ b/plugins/git4idea/rt/src/org/jetbrains/git4idea/ssh/SSHMain.java
@@ -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;
diff --git a/plugins/git4idea/src/git4idea/commands/GitHandler.java b/plugins/git4idea/src/git4idea/commands/GitHandler.java
index ef234be88087..52aec9f6388e 100644
--- a/plugins/git4idea/src/git4idea/commands/GitHandler.java
+++ b/plugins/git4idea/src/git4idea/commands/GitHandler.java
@@ -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 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;
diff --git a/plugins/git4idea/src/git4idea/commands/GitHttpAuthService.java b/plugins/git4idea/src/git4idea/commands/GitHttpAuthService.java
index 6ddbcab22147..7cc1a8deb06c 100644
--- a/plugins/git4idea/src/git4idea/commands/GitHttpAuthService.java
+++ b/plugins/git4idea/src/git4idea/commands/GitHttpAuthService.java
@@ -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 GitXmlRpcHandlerServiceThe provider of external application scripts called by Git when a remote operation needs communication with the user.
@@ -57,8 +58,7 @@ public abstract class GitXmlRpcHandlerService {
@Nullable private File myScriptPath;
@NotNull private final Object SCRIPT_FILE_LOCK = new Object();
- @NotNull private final THashMap handlers = new THashMap();
- private int myNextHandlerKey;
+ @NotNull private final THashMap handlers = new THashMap();
@NotNull private final Object HANDLERS_LOCK = new Object();
/**
@@ -111,14 +111,14 @@ public abstract class GitXmlRpcHandlerService {
* @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 {
handlers.remove(key);
}
});
- myNextHandlerKey++;
return key;
}
}
@@ -146,7 +145,7 @@ public abstract class GitXmlRpcHandlerService {
* @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 {
*
* @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");
diff --git a/plugins/git4idea/src/org/jetbrains/git4idea/ssh/GitXmlRpcSshService.java b/plugins/git4idea/src/org/jetbrains/git4idea/ssh/GitXmlRpcSshService.java
index 5d802d36e065..9b03e6db8c94 100644
--- a/plugins/git4idea/src/org/jetbrains/git4idea/ssh/GitXmlRpcSshService.java
+++ b/plugins/git4idea/src/org/jetbrains/git4idea/ssh/GitXmlRpcSshService.java
@@ -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 replyToChallenge(int handlerNo, String username, String name, String instruction, int numPrompts,
+ public Vector replyToChallenge(String token, String username, String name, String instruction, int numPrompts,
Vector prompt, Vector 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);
}
/**
From 6ed814b2a510fa20fe86eb568aa6efd9b5dc03fa Mon Sep 17 00:00:00 2001
From: Vladimir Krivosheev
Date: Tue, 19 Apr 2016 11:06:45 +0200
Subject: [PATCH 30/67] add shtm/stm as html files
---
.../src/org/jetbrains/builtInWebServer/StaticFileHandler.kt | 2 +-
platform/platform-resources/src/META-INF/mime.types | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/platform/built-in-server/src/org/jetbrains/builtInWebServer/StaticFileHandler.kt b/platform/built-in-server/src/org/jetbrains/builtInWebServer/StaticFileHandler.kt
index 2158b67948d6..8ea5c96a3ffb 100644
--- a/platform/built-in-server/src/org/jetbrains/builtInWebServer/StaticFileHandler.kt
+++ b/platform/built-in-server/src/org/jetbrains/builtInWebServer/StaticFileHandler.kt
@@ -18,7 +18,7 @@ import java.nio.file.Path
import java.nio.file.Paths
private class StaticFileHandler : WebServerFileHandler() {
- override val pageFileExtensions = arrayOf("html", "htm", "shtml")
+ override val pageFileExtensions = arrayOf("html", "htm", "shtml", "stm", "shtm")
private var ssiProcessor: SsiProcessor? = null
diff --git a/platform/platform-resources/src/META-INF/mime.types b/platform/platform-resources/src/META-INF/mime.types
index 3ae07a484937..1dbfd66428f9 100644
--- a/platform/platform-resources/src/META-INF/mime.types
+++ b/platform/platform-resources/src/META-INF/mime.types
@@ -101,7 +101,7 @@
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
From 1a5b1228b466a125711f7079755c673760a73ca1 Mon Sep 17 00:00:00 2001
From: Vladimir Krivosheev
Date: Tue, 19 Apr 2016 15:40:03 +0200
Subject: [PATCH 31/67] restore broken / redirection functionality
---
.../builtInWebServer/BuiltInWebServer.kt | 11 ++++------
.../DefaultWebServerPathHandler.kt | 21 +++++++++++++------
2 files changed, 19 insertions(+), 13 deletions(-)
diff --git a/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt b/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt
index a754c1dcb5c6..2dd2b3f14948 100644
--- a/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt
+++ b/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt
@@ -33,7 +33,9 @@ import com.intellij.util.net.NetUtils
import io.netty.channel.ChannelHandlerContext
import io.netty.handler.codec.http.*
import org.jetbrains.ide.HttpRequestHandler
-import org.jetbrains.io.*
+import org.jetbrains.io.host
+import org.jetbrains.io.isLocalOrigin
+import org.jetbrains.io.send
import java.io.IOException
import java.net.InetAddress
import java.nio.file.Path
@@ -142,11 +144,6 @@ private fun doProcess(request: FullHttpRequest, context: ChannelHandlerContext,
return true
}
- if (request.origin == null && request.referrer == null && request.isRegularBrowser() && !canBeAccessedDirectly(path)) {
- HttpResponseStatus.NOT_FOUND.send(context.channel(), request)
- return true
- }
-
for (pathHandler in WebServerPathHandler.EP_NAME.extensions) {
LOG.catchAndLog {
if (pathHandler.process(path, project, request, context, projectName, decodedPath, isCustomHost)) {
@@ -249,7 +246,7 @@ internal fun isOwnHostName(host: String): Boolean {
}
}
-private fun canBeAccessedDirectly(path: String): Boolean {
+internal fun canBeAccessedDirectly(path: String): Boolean {
for (fileHandler in WebServerFileHandler.EP_NAME.extensions) {
for (ext in fileHandler.pageFileExtensions) {
if (FileUtilRt.extensionEquals(path, ext)) {
diff --git a/platform/built-in-server/src/org/jetbrains/builtInWebServer/DefaultWebServerPathHandler.kt b/platform/built-in-server/src/org/jetbrains/builtInWebServer/DefaultWebServerPathHandler.kt
index a0bf723a0018..f40186f447c7 100644
--- a/platform/built-in-server/src/org/jetbrains/builtInWebServer/DefaultWebServerPathHandler.kt
+++ b/platform/built-in-server/src/org/jetbrains/builtInWebServer/DefaultWebServerPathHandler.kt
@@ -29,6 +29,9 @@ 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.isRegularBrowser
+import org.jetbrains.io.origin
+import org.jetbrains.io.referrer
import org.jetbrains.io.send
import java.nio.file.Path
import java.nio.file.Paths
@@ -61,11 +64,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) {
@@ -76,7 +74,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
}
@@ -85,6 +89,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
From 5ab012b1d9c02d00c2bf23f228b73c3c7bbca027 Mon Sep 17 00:00:00 2001
From: Vladimir Krivosheev
Date: Wed, 20 Apr 2016 14:25:54 +0200
Subject: [PATCH 32/67] rest-api returns 200, built-in web server 404
---
.../DefaultWebServerPathHandler.kt | 12 +++++-------
.../builtInWebServer/StaticFileHandler.kt | 10 +++++-----
.../src/org/jetbrains/ide/OpenFileHttpService.kt | 15 +++++++++++----
.../src/org/jetbrains/io/Responses.kt | 2 +-
4 files changed, 22 insertions(+), 17 deletions(-)
diff --git a/platform/built-in-server/src/org/jetbrains/builtInWebServer/DefaultWebServerPathHandler.kt b/platform/built-in-server/src/org/jetbrains/builtInWebServer/DefaultWebServerPathHandler.kt
index f40186f447c7..5f2fefc0950a 100644
--- a/platform/built-in-server/src/org/jetbrains/builtInWebServer/DefaultWebServerPathHandler.kt
+++ b/platform/built-in-server/src/org/jetbrains/builtInWebServer/DefaultWebServerPathHandler.kt
@@ -29,10 +29,7 @@ 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.isRegularBrowser
-import org.jetbrains.io.origin
-import org.jetbrains.io.referrer
-import org.jetbrains.io.send
+import org.jetbrains.io.*
import java.nio.file.Path
import java.nio.file.Paths
@@ -128,15 +125,16 @@ private fun checkAccess(pathInfo: PathInfo, channel: Channel, request: HttpReque
if (pathInfo.ioFile != null || pathInfo.file!!.isInLocalFileSystem) {
val file = pathInfo.ioFile ?: Paths.get(pathInfo.file!!.path)
if (file.isDirectory()) {
- HttpResponseStatus.NOT_FOUND.send(channel, request)
+ HttpResponseStatus.FORBIDDEN.orInSafeMode(HttpResponseStatus.NOT_FOUND).send(channel, request)
return false
}
- else if (!checkAccess(channel, file, request, Paths.get(pathInfo.root.path))) {
+ 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.NOT_FOUND.send(channel, request)
+ HttpResponseStatus.FORBIDDEN.orInSafeMode(HttpResponseStatus.NOT_FOUND).send(channel, request)
return false
}
diff --git a/platform/built-in-server/src/org/jetbrains/builtInWebServer/StaticFileHandler.kt b/platform/built-in-server/src/org/jetbrains/builtInWebServer/StaticFileHandler.kt
index 8ea5c96a3ffb..bd458b234473 100644
--- a/platform/built-in-server/src/org/jetbrains/builtInWebServer/StaticFileHandler.kt
+++ b/platform/built-in-server/src/org/jetbrains/builtInWebServer/StaticFileHandler.kt
@@ -5,14 +5,15 @@ import com.intellij.util.PathUtilRt
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.okInSafeMode
-import org.jetbrains.io.send
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
@@ -94,11 +95,10 @@ private class StaticFileHandler : WebServerFileHandler() {
}
}
-internal fun checkAccess(channel: Channel, file: Path, request: HttpRequest, root: Path = file.root): Boolean {
+internal fun checkAccess(file: Path, root: Path = file.root): Boolean {
var parent = file
do {
if (!hasAccess(parent)) {
- HttpResponseStatus.FORBIDDEN.okInSafeMode().send(channel, request)
return false
}
parent = parent.parent ?: break
diff --git a/platform/built-in-server/src/org/jetbrains/ide/OpenFileHttpService.kt b/platform/built-in-server/src/org/jetbrains/ide/OpenFileHttpService.kt
index 96ca5601edbb..162ea4033321 100644
--- a/platform/built-in-server/src/org/jetbrains/ide/OpenFileHttpService.kt
+++ b/platform/built-in-server/src/org/jetbrains/ide/OpenFileHttpService.kt
@@ -42,7 +42,8 @@ import org.jetbrains.concurrency.AsyncPromise
import org.jetbrains.concurrency.Promise
import org.jetbrains.concurrency.catchError
import org.jetbrains.concurrency.rejectedPromise
-import org.jetbrains.io.okInSafeMode
+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
@@ -123,7 +124,7 @@ internal class OpenFileHttpService : RestService() {
.rejected {
if (it === NOT_FOUND) {
// don't expose file status
- sendStatus(HttpResponseStatus.NOT_FOUND.okInSafeMode(), keepAlive, channel)
+ sendStatus(HttpResponseStatus.NOT_FOUND.orInSafeMode(HttpResponseStatus.OK), keepAlive, channel)
LOG.warn("File ${apiRequest.file} not found")
}
else {
@@ -135,14 +136,20 @@ internal class OpenFileHttpService : RestService() {
return null
}
- fun openFile(request: OpenFileRequest, context: ChannelHandlerContext?, httpRequest: HttpRequest?): Promise? {
+ internal fun openFile(request: OpenFileRequest, context: ChannelHandlerContext, httpRequest: HttpRequest?): Promise? {
val path = FileUtil.expandUserHome(request.file!!)
val file = Paths.get(FileUtil.toSystemDependentName(path))
if (file.isAbsolute) {
if (!file.exists()) {
return rejectedPromise(NOT_FOUND)
}
- return if (context == null || checkAccess(context.channel(), file, httpRequest!!)) openAbsolutePath(file, request) else null
+ if (checkAccess(file)) {
+ 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
diff --git a/platform/platform-impl/src/org/jetbrains/io/Responses.kt b/platform/platform-impl/src/org/jetbrains/io/Responses.kt
index 4a3bd36e3523..b21cfa40ffd1 100644
--- a/platform/platform-impl/src/org/jetbrains/io/Responses.kt
+++ b/platform/platform-impl/src/org/jetbrains/io/Responses.kt
@@ -126,7 +126,7 @@ fun HttpResponseStatus.send(channel: Channel, request: HttpRequest? = null, desc
createStatusResponse(this, request, description).send(channel, request)
}
-fun HttpResponseStatus.okInSafeMode() = if (ApplicationManager.getApplication()?.isUnitTestMode ?: false) this else HttpResponseStatus.OK
+fun HttpResponseStatus.orInSafeMode(safeStatus: HttpResponseStatus) = if (ApplicationManager.getApplication()?.isUnitTestMode ?: false) this else safeStatus
private fun createStatusResponse(responseStatus: HttpResponseStatus, request: HttpRequest?, description: String?): HttpResponse {
if (request != null && request.method() === HttpMethod.HEAD) {
From a11b9a9ce5c0ad5e11630949f7d3e8a72e387fc9 Mon Sep 17 00:00:00 2001
From: Vladimir Krivosheev
Date: Thu, 21 Apr 2016 10:53:02 +0200
Subject: [PATCH 33/67] ask the user to confirm if he wants to open the file
---
.../org/jetbrains/ide/OpenFileHttpService.kt | 18 ++++++++++++++----
1 file changed, 14 insertions(+), 4 deletions(-)
diff --git a/platform/built-in-server/src/org/jetbrains/ide/OpenFileHttpService.kt b/platform/built-in-server/src/org/jetbrains/ide/OpenFileHttpService.kt
index 162ea4033321..d924544ab8b2 100644
--- a/platform/built-in-server/src/org/jetbrains/ide/OpenFileHttpService.kt
+++ b/platform/built-in-server/src/org/jetbrains/ide/OpenFileHttpService.kt
@@ -48,6 +48,7 @@ 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+))?$")
@@ -137,13 +138,22 @@ internal class OpenFileHttpService : RestService() {
}
internal fun openFile(request: OpenFileRequest, context: ChannelHandlerContext, httpRequest: HttpRequest?): Promise? {
- val path = FileUtil.expandUserHome(request.file!!)
- val file = Paths.get(FileUtil.toSystemDependentName(path))
+ 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)
}
- if (checkAccess(file)) {
+
+ 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 {
@@ -155,7 +165,7 @@ internal class OpenFileHttpService : RestService() {
// 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(FileUtil.toSystemIndependentName(path), '/'), request)
+ val mainTask = OpenFileTask(FileUtil.toCanonicalPath(systemIndependentPath, '/'), request)
requests.offer(mainTask)
val session = queue.createSession(true, true, {
while (true) {
From f0aca939329abf1f23d35c2fb7649e31100b3861 Mon Sep 17 00:00:00 2001
From: Vladimir Krivosheev
Date: Thu, 21 Apr 2016 13:49:07 +0200
Subject: [PATCH 34/67] rest api: limit number of requests per minite and check
is host trusted
---
.../src/org/jetbrains/ide/RestService.java | 77 ++++++++++++++++++-
.../com/intellij/ide/impl/ProjectUtil.java | 9 ++-
.../src/org/jetbrains/io/Responses.kt | 10 ++-
.../src/org/jetbrains/io/netty.kt | 1 -
.../src/messages/IdeBundle.properties | 5 +-
.../util/resources/misc/registry.properties | 5 +-
6 files changed, 99 insertions(+), 8 deletions(-)
diff --git a/platform/built-in-server/src/org/jetbrains/ide/RestService.java b/platform/built-in-server/src/org/jetbrains/ide/RestService.java
index 3ea42c11956a..e29d40851114 100644
--- a/platform/built-in-server/src/org/jetbrains/ide/RestService.java
+++ b/platform/built-in-server/src/org/jetbrains/ide/RestService.java
@@ -15,23 +15,33 @@
*/
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 apiDoc. 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 abuseCounter =
+ CacheBuilder.newBuilder().expireAfterWrite(1, TimeUnit.MINUTES).build(CacheLoader.from((Supplier)AtomicInteger::new));
+
+ private final Cache 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 {
+ if (!isHostTrusted(request)) {
+ Responses.send(Responses.orInSafeMode(HttpResponseStatus.FORBIDDEN, HttpResponseStatus.OK), context.channel(), request);
+ return true;
+ }
+
+ AtomicInteger counter = abuseCounter.get(((InetSocketAddress)context.channel().remoteAddress()).getAddress());
+ if (counter.incrementAndGet() > Registry.intValue("ide.rest.api.requests.per.minute", 60)) {
+ Responses.send(Responses.orInSafeMode(HttpResponseStatus.TOO_MANY_REQUESTS, 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 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();
diff --git a/platform/platform-impl/src/com/intellij/ide/impl/ProjectUtil.java b/platform/platform-impl/src/com/intellij/ide/impl/ProjectUtil.java
index e2461d2b2d59..2a58f4691e26 100644
--- a/platform/platform-impl/src/com/intellij/ide/impl/ProjectUtil.java
+++ b/platform/platform-impl/src/com/intellij/ide/impl/ProjectUtil.java
@@ -209,11 +209,14 @@ 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;
}
diff --git a/platform/platform-impl/src/org/jetbrains/io/Responses.kt b/platform/platform-impl/src/org/jetbrains/io/Responses.kt
index b21cfa40ffd1..533cdea6c2cc 100644
--- a/platform/platform-impl/src/org/jetbrains/io/Responses.kt
+++ b/platform/platform-impl/src/org/jetbrains/io/Responses.kt
@@ -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
@@ -126,7 +127,14 @@ fun HttpResponseStatus.send(channel: Channel, request: HttpRequest? = null, desc
createStatusResponse(this, request, description).send(channel, request)
}
-fun HttpResponseStatus.orInSafeMode(safeStatus: HttpResponseStatus) = if (ApplicationManager.getApplication()?.isUnitTestMode ?: false) this else safeStatus
+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) {
diff --git a/platform/platform-impl/src/org/jetbrains/io/netty.kt b/platform/platform-impl/src/org/jetbrains/io/netty.kt
index fd09296971cd..0d0c87f8d090 100644
--- a/platform/platform-impl/src/org/jetbrains/io/netty.kt
+++ b/platform/platform-impl/src/org/jetbrains/io/netty.kt
@@ -187,7 +187,6 @@ fun HttpRequest.isRegularBrowser() = userAgent?.startsWith("Mozilla/5.0") ?: fal
// forbid POST requests from browser without Origin
fun HttpRequest.isWriteFromBrowserWithoutOrigin(): Boolean {
- val userAgent = userAgent ?: return false
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
diff --git a/platform/platform-resources-en/src/messages/IdeBundle.properties b/platform/platform-resources-en/src/messages/IdeBundle.properties
index 88607e0eaf70..824424dda7ae 100644
--- a/platform/platform-resources-en/src/messages/IdeBundle.properties
+++ b/platform/platform-resources-en/src/messages/IdeBundle.properties
@@ -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
diff --git a/platform/util/resources/misc/registry.properties b/platform/util/resources/misc/registry.properties
index a3d6810581ba..a80847d6c2eb 100644
--- a/platform/util/resources/misc/registry.properties
+++ b/platform/util/resources/misc/registry.properties
@@ -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=60
\ No newline at end of file
From e861df08e509664ebe25f783724677255865799c Mon Sep 17 00:00:00 2001
From: Vladimir Krivosheev
Date: Thu, 21 Apr 2016 15:29:14 +0200
Subject: [PATCH 35/67] IDEA-CR-10285 set limit to 60, fix quotes
---
.../src/org/jetbrains/ide/RestService.java | 10 +++++-----
.../src/messages/IdeBundle.properties | 2 +-
platform/util/resources/misc/registry.properties | 2 +-
3 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/platform/built-in-server/src/org/jetbrains/ide/RestService.java b/platform/built-in-server/src/org/jetbrains/ide/RestService.java
index e29d40851114..014364aa48e3 100644
--- a/platform/built-in-server/src/org/jetbrains/ide/RestService.java
+++ b/platform/built-in-server/src/org/jetbrains/ide/RestService.java
@@ -142,14 +142,14 @@ public abstract class RestService extends HttpRequestHandler {
@Override
public final boolean process(@NotNull QueryStringDecoder urlDecoder, @NotNull FullHttpRequest request, @NotNull ChannelHandlerContext context) throws IOException {
try {
- if (!isHostTrusted(request)) {
- Responses.send(Responses.orInSafeMode(HttpResponseStatus.FORBIDDEN, HttpResponseStatus.OK), context.channel(), request);
+ 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;
}
- AtomicInteger counter = abuseCounter.get(((InetSocketAddress)context.channel().remoteAddress()).getAddress());
- if (counter.incrementAndGet() > Registry.intValue("ide.rest.api.requests.per.minute", 60)) {
- Responses.send(Responses.orInSafeMode(HttpResponseStatus.TOO_MANY_REQUESTS, HttpResponseStatus.OK), context.channel(), request);
+ if (!isHostTrusted(request)) {
+ Responses.send(Responses.orInSafeMode(HttpResponseStatus.FORBIDDEN, HttpResponseStatus.OK), context.channel(), request);
return true;
}
diff --git a/platform/platform-resources-en/src/messages/IdeBundle.properties b/platform/platform-resources-en/src/messages/IdeBundle.properties
index 824424dda7ae..d3f826be420a 100644
--- a/platform/platform-resources-en/src/messages/IdeBundle.properties
+++ b/platform/platform-resources-en/src/messages/IdeBundle.properties
@@ -1197,5 +1197,5 @@ 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
-warning.use.rest.api='{0}' API is requested. Do you trust '{1}'?
+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
diff --git a/platform/util/resources/misc/registry.properties b/platform/util/resources/misc/registry.properties
index a80847d6c2eb..d21da51552b6 100644
--- a/platform/util/resources/misc/registry.properties
+++ b/platform/util/resources/misc/registry.properties
@@ -734,4 +734,4 @@ editor.rainbow.identifiers=false
editor.rainbow.identifiers.description=Rainbow identifiers in editor
ide.rest.api.paranoid.mode=true
-ide.rest.api.requests.per.minute=60
\ No newline at end of file
+ide.rest.api.requests.per.minute=30
\ No newline at end of file
From 1c4c39a782e2e973d311b3af65c6fb2b084d1265 Mon Sep 17 00:00:00 2001
From: Vladimir Krivosheev
Date: Fri, 22 Apr 2016 14:53:05 +0200
Subject: [PATCH 36/67] =?UTF-8?q?built-in=20web=20server=20=E2=80=94=20for?=
=?UTF-8?q?bid=20untrusted=20access?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../BuiltInWebBrowserUrlProvider.java | 9 +-
.../builtInWebServer/BuiltInWebServer.kt | 108 ++++++++++++++++--
.../com/intellij/ide/impl/ProjectUtil.java | 2 +-
.../src/com/intellij/util/Urls.java | 7 +-
4 files changed, 111 insertions(+), 15 deletions(-)
diff --git a/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebBrowserUrlProvider.java b/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebBrowserUrlProvider.java
index 1fa74d36be45..1f5b2bffe4bf 100644
--- a/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebBrowserUrlProvider.java
+++ b/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebBrowserUrlProvider.java
@@ -53,19 +53,20 @@ public class BuiltInWebBrowserUrlProvider extends WebBrowserUrlProvider implemen
String path = info.getPath();
String authority = currentAuthority == null ? "localhost:" + effectiveBuiltInServerPort : currentAuthority;
- List urls = new SmartList<>(Urls.newHttpUrl(authority, '/' + project.getName() + '/' + path));
+ String query = "?" + BuiltInWebServerKt.TOKEN_PARAM_NAME + "=" + BuiltInWebServerKt.acquireToken();
+ List 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));
}
}
diff --git a/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt b/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt
index 2dd2b3f14948..d95e84877fe1 100644
--- a/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt
+++ b/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt
@@ -15,30 +15,41 @@
*/
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.project.Project
import com.intellij.openapi.project.ProjectManager
+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.*
+import io.netty.handler.codec.http.cookie.ClientCookieEncoder
+import io.netty.handler.codec.http.cookie.DefaultCookie
+import io.netty.handler.codec.http.cookie.ServerCookieDecoder
import org.jetbrains.ide.HttpRequestHandler
-import org.jetbrains.io.host
-import org.jetbrains.io.isLocalOrigin
-import org.jetbrains.io.send
+import org.jetbrains.io.*
+import java.io.File
import java.io.IOException
import java.net.InetAddress
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)
@@ -73,12 +84,52 @@ class BuiltInWebServer : HttpRequestHandler() {
else {
projectName = host
}
- return doProcess(request, context, projectName)
+ return doProcess(urlDecoder, request, context, projectName)
}
}
-private fun doProcess(request: FullHttpRequest, context: ChannelHandlerContext, projectNameAsHost: String?): Boolean {
- val decodedPath = URLUtil.unescapePercentSequences(UriUtil.trimParameters(request.uri()))
+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!!)
+ }
+
+ val cookie = DefaultCookie(cookieName, token!!)
+ cookie.isHttpOnly = true
+ cookie.setMaxAge(TimeUnit.DAYS.toMillis(365 * 10))
+ cookie.setPath("/")
+ cookie
+}
+
+// expire after access because we reuse tokens
+private val tokens = CacheBuilder.newBuilder().expireAfterAccess(1, TimeUnit.MINUTES).build()
+
+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(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
@@ -139,8 +190,11 @@ private fun doProcess(request: FullHttpRequest, context: ChannelHandlerContext,
val path = toIdeaPath(decodedPath, offset)
if (path == null) {
- LOG.warn("$decodedPath is not valid")
- HttpResponseStatus.NOT_FOUND.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
}
@@ -154,6 +208,42 @@ 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
+ }
+ }
+ }
+
+ val token = urlDecoder.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, ClientCookieEncoder.STRICT.encode(STANDARD_COOKIE))
+ response.send(channel, request)
+ return true
+ }
+
+ SwingUtilities.invokeAndWait {
+ ProjectUtil.focusProjectWindow(null, true)
+ Messages.showMessageDialog(ProjectUtil.getActiveFrameOrWelcomeScreen(), "Page '" + StringUtil.trimMiddle(url, 50) + "' requested without authorization, " +
+ "\nplease open this link to trust it.", "", Messages.getWarningIcon())
+ }
+
+ 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)
diff --git a/platform/platform-impl/src/com/intellij/ide/impl/ProjectUtil.java b/platform/platform-impl/src/com/intellij/ide/impl/ProjectUtil.java
index 2a58f4691e26..4340318b9f69 100644
--- a/platform/platform-impl/src/com/intellij/ide/impl/ProjectUtil.java
+++ b/platform/platform-impl/src/com/intellij/ide/impl/ProjectUtil.java
@@ -220,7 +220,7 @@ public class ProjectUtil {
return answer == Messages.YES;
}
- private static Window getActiveFrameOrWelcomeScreen() {
+ public static Window getActiveFrameOrWelcomeScreen() {
Window window = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusedWindow();
if (window != null) return window;
diff --git a/platform/platform-impl/src/com/intellij/util/Urls.java b/platform/platform-impl/src/com/intellij/util/Urls.java
index 5ae9a7c7117c..c487ab38c41e 100644
--- a/platform/platform-impl/src/com/intellij/util/Urls.java
+++ b/platform/platform-impl/src/com/intellij/util/Urls.java
@@ -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);
From a2cbeb555a7edd52f6ee5a854aa5600dfc59c38d Mon Sep 17 00:00:00 2001
From: Vladimir Krivosheev
Date: Fri, 22 Apr 2016 15:33:26 +0200
Subject: [PATCH 37/67] cleanup
---
.../src/com/intellij/openapi/ui/MessageDialogBuilder.java | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/platform/platform-api/src/com/intellij/openapi/ui/MessageDialogBuilder.java b/platform/platform-api/src/com/intellij/openapi/ui/MessageDialogBuilder.java
index e6a2e956947e..445027fec026 100644
--- a/platform/platform-api/src/com/intellij/openapi/ui/MessageDialogBuilder.java
+++ b/platform/platform-api/src/com/intellij/openapi/ui/MessageDialogBuilder.java
@@ -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 {
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() {
From 6b2ffe1234df3a0a1425ea49c5ee3e1f8df9842e Mon Sep 17 00:00:00 2001
From: Vladimir Krivosheev
Date: Fri, 22 Apr 2016 15:51:06 +0200
Subject: [PATCH 38/67] =?UTF-8?q?built-in=20web=20server=20=E2=80=94=20for?=
=?UTF-8?q?bid=20untrusted=20access?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../jetbrains/builtInWebServer/BuiltInWebServer.kt | 14 ++++++++++++--
1 file changed, 12 insertions(+), 2 deletions(-)
diff --git a/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt b/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt
index d95e84877fe1..16e478a69ec8 100644
--- a/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt
+++ b/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt
@@ -22,8 +22,10 @@ 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
@@ -43,6 +45,7 @@ import io.netty.handler.codec.http.cookie.DefaultCookie
import io.netty.handler.codec.http.cookie.ServerCookieDecoder
import org.jetbrains.ide.HttpRequestHandler
import org.jetbrains.io.*
+import java.awt.datatransfer.StringSelection
import java.io.File
import java.io.IOException
import java.net.InetAddress
@@ -236,8 +239,15 @@ private fun validateToken(request: HttpRequest, channel: Channel, urlDecoder: Qu
SwingUtilities.invokeAndWait {
ProjectUtil.focusProjectWindow(null, true)
- Messages.showMessageDialog(ProjectUtil.getActiveFrameOrWelcomeScreen(), "Page '" + StringUtil.trimMiddle(url, 50) + "' requested without authorization, " +
- "\nplease open this link to trust it.", "", Messages.getWarningIcon())
+
+ 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)
From f1b552e157a610c600768a83da547ecf24253478 Mon Sep 17 00:00:00 2001
From: Vladimir Krivosheev
Date: Fri, 22 Apr 2016 17:55:22 +0200
Subject: [PATCH 39/67] =?UTF-8?q?built-in=20web=20server=20=E2=80=94=20for?=
=?UTF-8?q?bid=20untrusted=20access:=20check=20param=20in=20referer?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../jetbrains/builtInWebServer/BuiltInWebServer.kt | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt b/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt
index 16e478a69ec8..355a998cae51 100644
--- a/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt
+++ b/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt
@@ -40,9 +40,9 @@ import com.intellij.util.net.NetUtils
import io.netty.channel.Channel
import io.netty.channel.ChannelHandlerContext
import io.netty.handler.codec.http.*
-import io.netty.handler.codec.http.cookie.ClientCookieEncoder
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.*
import java.awt.datatransfer.StringSelection
@@ -112,9 +112,11 @@ private val STANDARD_COOKIE by lazy {
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.toMillis(365 * 10))
+ cookie.setMaxAge(TimeUnit.DAYS.toSeconds(365 * 10))
cookie.setPath("/")
cookie
}
@@ -225,14 +227,15 @@ private fun validateToken(request: HttpRequest, channel: Channel, urlDecoder: Qu
}
}
- val token = urlDecoder.parameters().get(TOKEN_PARAM_NAME)?.firstOrNull()
+ // 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, ClientCookieEncoder.STRICT.encode(STANDARD_COOKIE))
+ response.headers().set(HttpHeaderNames.SET_COOKIE, ServerCookieEncoder.STRICT.encode(STANDARD_COOKIE))
response.send(channel, request)
return true
}
From ca1544e6a2e97dd9ad6ddb919960a992d06e28cd Mon Sep 17 00:00:00 2001
From: Vladimir Krivosheev
Date: Mon, 25 Apr 2016 17:16:35 +0200
Subject: [PATCH 40/67] =?UTF-8?q?built-in=20web=20server=20=E2=80=94=20dir?=
=?UTF-8?q?ect=20access=20without=20token,=20fix=20"first=20request=20afte?=
=?UTF-8?q?r=20redirect=20doesn't=20have=20cookies",=20SameSite=20strict?=
=?UTF-8?q?=20cookie?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../builtInWebServer/BuiltInWebServer.kt | 30 ++++++++++---------
.../DefaultWebServerPathHandler.kt | 15 ++++++----
.../builtInWebServer/StaticFileHandler.kt | 17 +++++------
.../builtInWebServer/WebServerFileHandler.kt | 4 ++-
.../builtInWebServer/WebServerPathHandler.kt | 14 ++++-----
.../src/org/jetbrains/io/FileResponses.kt | 13 ++++----
.../src/org/jetbrains/io/Responses.kt | 18 ++++++-----
.../util/resources/misc/registry.properties | 2 +-
8 files changed, 61 insertions(+), 52 deletions(-)
diff --git a/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt b/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt
index 355a998cae51..e88f5fa107d1 100644
--- a/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt
+++ b/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt
@@ -189,7 +189,7 @@ private fun doProcess(urlDecoder: QueryStringDecoder, request: FullHttpRequest,
if (isEmptyPath) {
// we must redirect "jsdebug" to "jsdebug/" as nginx does, otherwise browser will treat it as a file instead of a directory, so, relative path will not work
- redirectToDirectory(request, context.channel(), projectName)
+ redirectToDirectory(request, context.channel(), projectName, null)
return true
}
@@ -199,10 +199,6 @@ private fun doProcess(urlDecoder: QueryStringDecoder, request: FullHttpRequest,
return true
}
- if (!validateToken(request, context.channel(), urlDecoder)) {
- return true
- }
-
for (pathHandler in WebServerPathHandler.EP_NAME.extensions) {
LOG.catchAndLog {
if (pathHandler.process(path, project, request, context, projectName, decodedPath, isCustomHost)) {
@@ -213,31 +209,37 @@ private fun doProcess(urlDecoder: QueryStringDecoder, request: FullHttpRequest,
return false
}
-private fun validateToken(request: HttpRequest, channel: Channel, urlDecoder: QueryStringDecoder): Boolean {
+internal fun validateToken(request: HttpRequest, channel: Channel, redirectToSetCookie: Boolean): HttpHeaders? {
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
+ return EmptyHttpHeaders.INSTANCE
}
break
}
}
}
+ val urlDecoder = QueryStringDecoder(request.uri())
// 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
+ if (redirectToSetCookie) {
+ // we redirect because it is not easy to change and maintain all places where we send response
+ val response = HttpResponseStatus.TEMPORARY_REDIRECT.response(request)
+ response.headers().add(HttpHeaderNames.LOCATION, url)
+ response.headers().set(HttpHeaderNames.SET_COOKIE, ServerCookieEncoder.STRICT.encode(STANDARD_COOKIE) + "; SameSite=strict")
+ response.send(channel, request)
+ return response.headers()
+ }
+ else {
+ return DefaultHttpHeaders().set(HttpHeaderNames.SET_COOKIE, ServerCookieEncoder.STRICT.encode(STANDARD_COOKIE) + "; SameSite=strict")
+ }
}
SwingUtilities.invokeAndWait {
@@ -254,7 +256,7 @@ private fun validateToken(request: HttpRequest, channel: Channel, urlDecoder: Qu
}
HttpResponseStatus.UNAUTHORIZED.orInSafeMode(HttpResponseStatus.NOT_FOUND).send(channel, request)
- return false
+ return null
}
private fun toIdeaPath(decodedPath: String, offset: Int): String? {
diff --git a/platform/built-in-server/src/org/jetbrains/builtInWebServer/DefaultWebServerPathHandler.kt b/platform/built-in-server/src/org/jetbrains/builtInWebServer/DefaultWebServerPathHandler.kt
index 5f2fefc0950a..66a4fe4b3cbc 100644
--- a/platform/built-in-server/src/org/jetbrains/builtInWebServer/DefaultWebServerPathHandler.kt
+++ b/platform/built-in-server/src/org/jetbrains/builtInWebServer/DefaultWebServerPathHandler.kt
@@ -41,6 +41,8 @@ private class DefaultWebServerPathHandler : WebServerPathHandler() {
projectName: String,
decodedRawPath: String,
isCustomHost: Boolean): Boolean {
+ val extraHttpHeaders = validateToken(request, context.channel(), false) ?: return true
+
val channel = context.channel()
val pathToFileManager = WebServerPathToFileManager.getInstance(project)
var pathInfo = pathToFileManager.pathToInfoCache.getIfPresent(path)
@@ -48,7 +50,7 @@ private class DefaultWebServerPathHandler : WebServerPathHandler() {
pathInfo = pathToFileManager.doFindByRelativePath(path)
if (pathInfo == null) {
if (path.isEmpty()) {
- HttpResponseStatus.NOT_FOUND.send(channel, request, "Index file doesn't exist.")
+ HttpResponseStatus.NOT_FOUND.send(channel, request, "Index file doesn't exist.", extraHttpHeaders)
return true
}
else {
@@ -71,13 +73,13 @@ private class DefaultWebServerPathHandler : WebServerPathHandler() {
}
if (indexFile == null && indexVirtualFile == null) {
- HttpResponseStatus.NOT_FOUND.send(channel, request)
+ HttpResponseStatus.NOT_FOUND.send(channel, request, extraHeaders = extraHttpHeaders)
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")
+ redirectToDirectory(request, channel, if (isCustomHost) path else "$projectName/$path", extraHttpHeaders)
return true
}
@@ -86,7 +88,8 @@ private class DefaultWebServerPathHandler : WebServerPathHandler() {
pathToFileManager.pathToInfoCache.put(path, pathInfo)
}
- if (request.origin == null && request.referrer == null && request.isRegularBrowser() && !canBeAccessedDirectly(pathInfo.name)) {
+ // if extraHttpHeaders is not empty, it means that we get request wih token in the query
+ if (extraHttpHeaders.isEmpty && request.origin == null && request.referrer == null && request.isRegularBrowser() && !canBeAccessedDirectly(pathInfo.name)) {
HttpResponseStatus.NOT_FOUND.send(context.channel(), request)
return true
}
@@ -99,7 +102,7 @@ private class DefaultWebServerPathHandler : WebServerPathHandler() {
// FallbackResource feature in action, /login requested, /index.php retrieved, we must not redirect /login to /login/
val parentPath = getParentPath(pathInfo.path)
if (parentPath != null && endsWithName(path, PathUtilRt.getFileName(parentPath))) {
- redirectToDirectory(request, channel, if (isCustomHost) path else "$projectName/$path")
+ redirectToDirectory(request, channel, if (isCustomHost) path else "$projectName/$path", extraHttpHeaders)
return true
}
}
@@ -112,7 +115,7 @@ private class DefaultWebServerPathHandler : WebServerPathHandler() {
val canonicalPath = if (indexUsed) "$path/${pathInfo.name}" else path
for (fileHandler in WebServerFileHandler.EP_NAME.extensions) {
LOG.catchAndLog {
- if (fileHandler.process(pathInfo!!, canonicalPath, project, request, channel, if (isCustomHost) null else projectName)) {
+ if (fileHandler.process(pathInfo!!, canonicalPath, project, request, channel, if (isCustomHost) null else projectName, extraHttpHeaders)) {
return true
}
}
diff --git a/platform/built-in-server/src/org/jetbrains/builtInWebServer/StaticFileHandler.kt b/platform/built-in-server/src/org/jetbrains/builtInWebServer/StaticFileHandler.kt
index bd458b234473..457e39c19f5b 100644
--- a/platform/built-in-server/src/org/jetbrains/builtInWebServer/StaticFileHandler.kt
+++ b/platform/built-in-server/src/org/jetbrains/builtInWebServer/StaticFileHandler.kt
@@ -5,10 +5,7 @@ import com.intellij.util.PathUtilRt
import io.netty.buffer.ByteBufUtf8Writer
import io.netty.channel.Channel
import io.netty.channel.ChannelFutureListener
-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.codec.http.*
import io.netty.handler.stream.ChunkedStream
import org.jetbrains.builtInWebServer.ssi.SsiExternalResolver
import org.jetbrains.builtInWebServer.ssi.SsiProcessor
@@ -23,21 +20,21 @@ private class StaticFileHandler : WebServerFileHandler() {
private var ssiProcessor: SsiProcessor? = null
- override fun process(pathInfo: PathInfo, canonicalPath: CharSequence, project: Project, request: FullHttpRequest, channel: Channel, projectNameIfNotCustomHost: String?): Boolean {
+ override fun process(pathInfo: PathInfo, canonicalPath: CharSequence, project: Project, request: FullHttpRequest, channel: Channel, projectNameIfNotCustomHost: String?, extraHeaders: HttpHeaders): Boolean {
if (pathInfo.ioFile != null || pathInfo.file!!.isInLocalFileSystem) {
val ioFile = pathInfo.ioFile ?: Paths.get(pathInfo.file!!.path)
val nameSequence = ioFile.fileName.toString()
//noinspection SpellCheckingInspection
if (nameSequence.endsWith(".shtml", true) || nameSequence.endsWith(".stm", true) || nameSequence.endsWith(".shtm", true)) {
- processSsi(ioFile, PathUtilRt.getParentPath(canonicalPath.toString()), project, request, channel)
+ processSsi(ioFile, PathUtilRt.getParentPath(canonicalPath.toString()), project, request, channel, extraHeaders)
return true
}
- FileResponses.sendFile(request, channel, ioFile)
+ FileResponses.sendFile(request, channel, ioFile, extraHeaders)
}
else {
val file = pathInfo.file!!
- val response = FileResponses.prepareSend(request, channel, file.timeStamp, file.name) ?: return true
+ val response = FileResponses.prepareSend(request, channel, file.timeStamp, file.name, extraHeaders) ?: return true
val keepAlive = response.addKeepAliveIfNeed(request)
if (request.method() != HttpMethod.HEAD) {
@@ -59,7 +56,7 @@ private class StaticFileHandler : WebServerFileHandler() {
return true
}
- private fun processSsi(file: Path, path: String, project: Project, request: FullHttpRequest, channel: Channel) {
+ private fun processSsi(file: Path, path: String, project: Project, request: FullHttpRequest, channel: Channel, extraHeaders: HttpHeaders) {
if (ssiProcessor == null) {
ssiProcessor = SsiProcessor(false)
}
@@ -69,7 +66,7 @@ private class StaticFileHandler : WebServerFileHandler() {
var releaseBuffer = true
try {
val lastModified = ssiProcessor!!.process(SsiExternalResolver(project, request, path, file.parent), file, ByteBufUtf8Writer(buffer))
- val response = FileResponses.prepareSend(request, channel, lastModified, file.fileName.toString()) ?: return
+ val response = FileResponses.prepareSend(request, channel, lastModified, file.fileName.toString(), extraHeaders) ?: return
keepAlive = response.addKeepAliveIfNeed(request)
if (request.method() != HttpMethod.HEAD) {
HttpUtil.setContentLength(response, buffer.readableBytes().toLong())
diff --git a/platform/built-in-server/src/org/jetbrains/builtInWebServer/WebServerFileHandler.kt b/platform/built-in-server/src/org/jetbrains/builtInWebServer/WebServerFileHandler.kt
index 1e823c4861a4..622322337912 100644
--- a/platform/built-in-server/src/org/jetbrains/builtInWebServer/WebServerFileHandler.kt
+++ b/platform/built-in-server/src/org/jetbrains/builtInWebServer/WebServerFileHandler.kt
@@ -19,6 +19,7 @@ import com.intellij.openapi.extensions.ExtensionPointName
import com.intellij.openapi.project.Project
import io.netty.channel.Channel
import io.netty.handler.codec.http.FullHttpRequest
+import io.netty.handler.codec.http.HttpHeaders
abstract class WebServerFileHandler {
companion object {
@@ -36,7 +37,8 @@ abstract class WebServerFileHandler {
project: Project,
request: FullHttpRequest,
channel: Channel,
- projectNameIfNotCustomHost: String?): Boolean
+ projectNameIfNotCustomHost: String?,
+ extraHeaders: HttpHeaders): Boolean
}
fun getRequestPath(canonicalPath: CharSequence, projectNameIfNotCustomHost: String?) = if (projectNameIfNotCustomHost == null) "/$canonicalPath" else "/$projectNameIfNotCustomHost/$canonicalPath"
\ No newline at end of file
diff --git a/platform/built-in-server/src/org/jetbrains/builtInWebServer/WebServerPathHandler.kt b/platform/built-in-server/src/org/jetbrains/builtInWebServer/WebServerPathHandler.kt
index 037a64cb5e8f..ad4dfc3fb21f 100644
--- a/platform/built-in-server/src/org/jetbrains/builtInWebServer/WebServerPathHandler.kt
+++ b/platform/built-in-server/src/org/jetbrains/builtInWebServer/WebServerPathHandler.kt
@@ -20,10 +20,7 @@ import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VfsUtil
import io.netty.channel.Channel
import io.netty.channel.ChannelHandlerContext
-import io.netty.handler.codec.http.FullHttpRequest
-import io.netty.handler.codec.http.HttpHeaderNames
-import io.netty.handler.codec.http.HttpRequest
-import io.netty.handler.codec.http.HttpResponseStatus
+import io.netty.handler.codec.http.*
import org.jetbrains.io.host
import org.jetbrains.io.response
import org.jetbrains.io.send
@@ -49,9 +46,12 @@ abstract class WebServerPathHandler {
isCustomHost: Boolean): Boolean
}
-fun redirectToDirectory(request: HttpRequest, channel: Channel, path: String) {
- val response = HttpResponseStatus.MOVED_PERMANENTLY.response()
+internal fun redirectToDirectory(request: HttpRequest, channel: Channel, path: String, extraHttpHeader: HttpHeaders?) {
+ val response = HttpResponseStatus.MOVED_PERMANENTLY.response(request)
val url = VfsUtil.toUri("${channel.uriScheme}://${request.host!!}/$path/")!!
response.headers().add(HttpHeaderNames.LOCATION, url.toASCIIString())
- response.send(channel, request)
+ extraHttpHeader?.let {
+
+ }
+ response.send(channel, request, extraHttpHeader)
}
\ No newline at end of file
diff --git a/platform/platform-impl/src/org/jetbrains/io/FileResponses.kt b/platform/platform-impl/src/org/jetbrains/io/FileResponses.kt
index c00648bdb53d..6c083affd380 100644
--- a/platform/platform-impl/src/org/jetbrains/io/FileResponses.kt
+++ b/platform/platform-impl/src/org/jetbrains/io/FileResponses.kt
@@ -35,17 +35,17 @@ object FileResponses {
return FILE_MIMETYPE_MAP.getContentType(path)
}
- private fun checkCache(request: HttpRequest, channel: Channel, lastModified: Long): Boolean {
+ private fun checkCache(request: HttpRequest, channel: Channel, lastModified: Long, extraHeaders: HttpHeaders): Boolean {
val ifModified = request.headers().getTimeMillis(HttpHeaderNames.IF_MODIFIED_SINCE)
if (ifModified != null && ifModified >= lastModified) {
- HttpResponseStatus.NOT_MODIFIED.send(channel, request)
+ HttpResponseStatus.NOT_MODIFIED.send(channel, request, extraHeaders = extraHeaders)
return true
}
return false
}
- fun prepareSend(request: HttpRequest, channel: Channel, lastModified: Long, filename: String): HttpResponse? {
- if (checkCache(request, channel, lastModified)) {
+ fun prepareSend(request: HttpRequest, channel: Channel, lastModified: Long, filename: String, extraHeaders: HttpHeaders): HttpResponse? {
+ if (checkCache(request, channel, lastModified, extraHeaders)) {
return null
}
@@ -54,11 +54,12 @@ object FileResponses {
response.addCommonHeaders()
response.headers().set(HttpHeaderNames.CACHE_CONTROL, "private, must-revalidate")
response.headers().set(HttpHeaderNames.LAST_MODIFIED, Date(lastModified))
+ response.headers().add(extraHeaders)
return response
}
- fun sendFile(request: HttpRequest, channel: Channel, file: Path) {
- val response = prepareSend(request, channel, Files.getLastModifiedTime(file).toMillis(), file.fileName.toString()) ?: return
+ fun sendFile(request: HttpRequest, channel: Channel, file: Path, extraHeaders: HttpHeaders = EmptyHttpHeaders.INSTANCE) {
+ val response = prepareSend(request, channel, Files.getLastModifiedTime(file).toMillis(), file.fileName.toString(), extraHeaders) ?: return
val keepAlive = response.addKeepAliveIfNeed(request)
diff --git a/platform/platform-impl/src/org/jetbrains/io/Responses.kt b/platform/platform-impl/src/org/jetbrains/io/Responses.kt
index 533cdea6c2cc..718f51aef0ff 100644
--- a/platform/platform-impl/src/org/jetbrains/io/Responses.kt
+++ b/platform/platform-impl/src/org/jetbrains/io/Responses.kt
@@ -33,8 +33,6 @@ import java.util.*
private var SERVER_HEADER_VALUE: String? = null
-fun HttpResponseStatus.response(): FullHttpResponse = DefaultFullHttpResponse(HttpVersion.HTTP_1_1, this, Unpooled.EMPTY_BUFFER)
-
fun response(contentType: String?, content: ByteBuf?): FullHttpResponse {
val response = if (content == null)
DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK)
@@ -79,13 +77,17 @@ fun HttpResponse.addServer() {
}
}
-fun HttpResponse.send(channel: Channel, request: HttpRequest?) {
+@JvmOverloads
+fun HttpResponse.send(channel: Channel, request: HttpRequest?, extraHeaders: HttpHeaders? = null) {
if (status() !== HttpResponseStatus.NOT_MODIFIED && !HttpUtil.isContentLengthSet(this)) {
HttpUtil.setContentLength(this,
(if (this is FullHttpResponse) content().readableBytes() else 0).toLong())
}
addCommonHeaders()
+ extraHeaders?.let {
+ headers().add(it)
+ }
send(channel, request != null && !addKeepAliveIfNeed(request))
}
@@ -122,13 +124,15 @@ fun HttpResponse.send(channel: Channel, close: Boolean) {
}
}
+fun HttpResponseStatus.response(request: HttpRequest? = null, description: String? = null): HttpResponse = createStatusResponse(this, request, description)
+
@JvmOverloads
-fun HttpResponseStatus.send(channel: Channel, request: HttpRequest? = null, description: String? = null) {
- createStatusResponse(this, request, description).send(channel, request)
+fun HttpResponseStatus.send(channel: Channel, request: HttpRequest? = null, description: String? = null, extraHeaders: HttpHeaders? = null) {
+ createStatusResponse(this, request, description).send(channel, request, extraHeaders)
}
fun HttpResponseStatus.orInSafeMode(safeStatus: HttpResponseStatus): HttpResponseStatus {
- if (!Registry.`is`("ide.rest.api.paranoid.mode", true) || (ApplicationManager.getApplication()?.isUnitTestMode ?: false)) {
+ if (!Registry.`is`("ide.http.server.response.actual.status", true) || (ApplicationManager.getApplication()?.isUnitTestMode ?: false)) {
return this
}
else {
@@ -138,7 +142,7 @@ fun HttpResponseStatus.orInSafeMode(safeStatus: HttpResponseStatus): HttpRespons
private fun createStatusResponse(responseStatus: HttpResponseStatus, request: HttpRequest?, description: String?): HttpResponse {
if (request != null && request.method() === HttpMethod.HEAD) {
- return responseStatus.response()
+ return DefaultFullHttpResponse(HttpVersion.HTTP_1_1, responseStatus, Unpooled.EMPTY_BUFFER)
}
val builder = StringBuilder()
diff --git a/platform/util/resources/misc/registry.properties b/platform/util/resources/misc/registry.properties
index d21da51552b6..f60e407dfff3 100644
--- a/platform/util/resources/misc/registry.properties
+++ b/platform/util/resources/misc/registry.properties
@@ -733,5 +733,5 @@ ide.screenreader.autodetect.accessibility.description=Automatically detect wheth
editor.rainbow.identifiers=false
editor.rainbow.identifiers.description=Rainbow identifiers in editor
-ide.rest.api.paranoid.mode=true
+ide.http.server.response.actual.status=true
ide.rest.api.requests.per.minute=30
\ No newline at end of file
From 7869ab6797fef3a0d0757b47fa434fe9dfcd1b4e Mon Sep 17 00:00:00 2001
From: Vladimir Krivosheev
Date: Mon, 25 Apr 2016 17:28:50 +0200
Subject: [PATCH 41/67] constant user token name
---
.../org/jetbrains/builtInWebServer/BuiltInWebServer.kt | 10 ++++++----
.../openapi/application/ConfigImportHelper.java | 9 ++++++---
2 files changed, 12 insertions(+), 7 deletions(-)
diff --git a/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt b/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt
index e88f5fa107d1..c2b81b41d379 100644
--- a/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt
+++ b/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt
@@ -56,6 +56,9 @@ import javax.swing.SwingUtilities
internal val LOG = Logger.getInstance(BuiltInWebServer::class.java)
+// name is duplicated in the ConfigImportHelper
+private const val IDE_TOKEN_FILE = "user.token"
+
class BuiltInWebServer : HttpRequestHandler() {
override fun isAccessible(request: HttpRequest) = request.isLocalOrigin(onlyAnyOrLoopback = false, hostsOnly = true)
@@ -91,13 +94,12 @@ class BuiltInWebServer : HttpRequestHandler() {
}
}
-const val TOKEN_PARAM_NAME = "__ij-st"
+internal 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)
+ val file = File(configPath, IDE_TOKEN_FILE)
var token: String? = null
if (file.exists()) {
try {
@@ -114,7 +116,7 @@ private val STANDARD_COOKIE by lazy {
// 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!!)
+ val cookie = DefaultCookie(productName + "-" + Integer.toHexString(configPath.hashCode()), token!!)
cookie.isHttpOnly = true
cookie.setMaxAge(TimeUnit.DAYS.toSeconds(365 * 10))
cookie.setPath("/")
diff --git a/platform/platform-impl/src/com/intellij/openapi/application/ConfigImportHelper.java b/platform/platform-impl/src/com/intellij/openapi/application/ConfigImportHelper.java
index 2329e369d9d9..a34689ad76df 100644
--- a/platform/platform-impl/src/com/intellij/openapi/application/ConfigImportHelper.java
+++ b/platform/platform-impl/src/com/intellij/openapi/application/ConfigImportHelper.java
@@ -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.
@@ -196,6 +196,9 @@ public class ConfigImportHelper {
// Copy old plugins. If some of them are incompatible PluginManager will deal with it
FileUtil.copyDir(src, dest);
+ // delete old user token - we must not reuse it
+ FileUtil.delete(new File(dest, "user.token"));
+
File oldPluginsDir = new File(src, PLUGINS_PATH);
if (!oldPluginsDir.isDirectory() && SystemInfo.isMac) {
oldPluginsDir = getSettingsPath(oldInstallationHome, settings, PathManager.PROPERTY_PLUGINS_PATH,
@@ -340,14 +343,14 @@ public class ConfigImportHelper {
}
if (bundle.containsKey(propertyName)) {
return bundle.getString(propertyName);
- }
+ }
return null;
}
catch (IOException e) {
return null;
}
}
-
+
final String fileContent = getContent(file);
// try to find custom config path
From 9449116e4ed14d8ade33ee4119eecd25d858f1d8 Mon Sep 17 00:00:00 2001
From: Vladimir Krivosheev
Date: Mon, 25 Apr 2016 17:36:21 +0200
Subject: [PATCH 42/67] ide.http.server.response.actual.status false by default
---
platform/platform-impl/src/org/jetbrains/io/Responses.kt | 2 +-
platform/util/resources/misc/registry.properties | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/platform/platform-impl/src/org/jetbrains/io/Responses.kt b/platform/platform-impl/src/org/jetbrains/io/Responses.kt
index 718f51aef0ff..3230f28b7d7b 100644
--- a/platform/platform-impl/src/org/jetbrains/io/Responses.kt
+++ b/platform/platform-impl/src/org/jetbrains/io/Responses.kt
@@ -132,7 +132,7 @@ fun HttpResponseStatus.send(channel: Channel, request: HttpRequest? = null, desc
}
fun HttpResponseStatus.orInSafeMode(safeStatus: HttpResponseStatus): HttpResponseStatus {
- if (!Registry.`is`("ide.http.server.response.actual.status", true) || (ApplicationManager.getApplication()?.isUnitTestMode ?: false)) {
+ if (Registry.`is`("ide.http.server.response.actual.status", true) || (ApplicationManager.getApplication()?.isUnitTestMode ?: false)) {
return this
}
else {
diff --git a/platform/util/resources/misc/registry.properties b/platform/util/resources/misc/registry.properties
index f60e407dfff3..424e502522f0 100644
--- a/platform/util/resources/misc/registry.properties
+++ b/platform/util/resources/misc/registry.properties
@@ -733,5 +733,5 @@ ide.screenreader.autodetect.accessibility.description=Automatically detect wheth
editor.rainbow.identifiers=false
editor.rainbow.identifiers.description=Rainbow identifiers in editor
-ide.http.server.response.actual.status=true
+ide.http.server.response.actual.status=false
ide.rest.api.requests.per.minute=30
\ No newline at end of file
From a325ad365903ee5ff8e6dbf636b350da006ee601 Mon Sep 17 00:00:00 2001
From: Vladimir Krivosheev
Date: Tue, 26 Apr 2016 10:29:48 +0200
Subject: [PATCH 43/67] =?UTF-8?q?built-in=20web=20server=20=E2=80=94=20use?=
=?UTF-8?q?=20own=20file=20to=20store=20token?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt | 2 +-
.../com/intellij/openapi/application/ConfigImportHelper.java | 1 +
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt b/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt
index c2b81b41d379..39bd6c7dc740 100644
--- a/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt
+++ b/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt
@@ -57,7 +57,7 @@ import javax.swing.SwingUtilities
internal val LOG = Logger.getInstance(BuiltInWebServer::class.java)
// name is duplicated in the ConfigImportHelper
-private const val IDE_TOKEN_FILE = "user.token"
+private const val IDE_TOKEN_FILE = "user.web.token"
class BuiltInWebServer : HttpRequestHandler() {
override fun isAccessible(request: HttpRequest) = request.isLocalOrigin(onlyAnyOrLoopback = false, hostsOnly = true)
diff --git a/platform/platform-impl/src/com/intellij/openapi/application/ConfigImportHelper.java b/platform/platform-impl/src/com/intellij/openapi/application/ConfigImportHelper.java
index a34689ad76df..bbcd4bf3ebbc 100644
--- a/platform/platform-impl/src/com/intellij/openapi/application/ConfigImportHelper.java
+++ b/platform/platform-impl/src/com/intellij/openapi/application/ConfigImportHelper.java
@@ -198,6 +198,7 @@ public class ConfigImportHelper {
// delete old user token - we must not reuse it
FileUtil.delete(new File(dest, "user.token"));
+ FileUtil.delete(new File(dest, "user.web.token"));
File oldPluginsDir = new File(src, PLUGINS_PATH);
if (!oldPluginsDir.isDirectory() && SystemInfo.isMac) {
From f49be7a05bd3568bc888be425fa8eae5d19527be Mon Sep 17 00:00:00 2001
From: Vladimir Krivosheev
Date: Tue, 26 Apr 2016 11:38:50 +0200
Subject: [PATCH 44/67] activatable built-in web server (by default false)
---
.../BuiltInWebBrowserUrlProvider.java | 5 +++++
.../builtInWebServer/BuiltInWebServer.kt | 16 ++++++++++++++++
platform/util/resources/misc/registry.properties | 3 ++-
3 files changed, 23 insertions(+), 1 deletion(-)
diff --git a/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebBrowserUrlProvider.java b/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebBrowserUrlProvider.java
index 1f5b2bffe4bf..97c561c0e086 100644
--- a/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebBrowserUrlProvider.java
+++ b/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebBrowserUrlProvider.java
@@ -18,6 +18,7 @@ package org.jetbrains.builtInWebServer;
import com.intellij.ide.browsers.OpenInBrowserRequest;
import com.intellij.ide.browsers.WebBrowserService;
import com.intellij.ide.browsers.WebBrowserUrlProvider;
+import com.intellij.ide.util.PropertiesComponent;
import com.intellij.lang.Language;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.Project;
@@ -70,6 +71,10 @@ public class BuiltInWebBrowserUrlProvider extends WebBrowserUrlProvider implemen
}
}
+ if (BuiltInWebServerKt.isActivatable()) {
+ PropertiesComponent.getInstance().setValue("ide.built.in.web.server.active", true);
+ }
+
return urls;
}
diff --git a/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt b/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt
index 39bd6c7dc740..7185f37c72bd 100644
--- a/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt
+++ b/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt
@@ -18,6 +18,8 @@ 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.ide.util.PropertiesComponent
+import com.intellij.notification.NotificationType
import com.intellij.openapi.application.ApplicationNamesInfo
import com.intellij.openapi.application.PathManager
import com.intellij.openapi.diagnostic.Logger
@@ -31,6 +33,7 @@ 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.registry.Registry
import com.intellij.openapi.util.text.StringUtil
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.util.directoryStreamIfExists
@@ -43,8 +46,10 @@ 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.BuiltInServerManagerImpl
import org.jetbrains.ide.HttpRequestHandler
import org.jetbrains.io.*
+import org.jetbrains.notification.SingletonNotificationManager
import java.awt.datatransfer.StringSelection
import java.io.File
import java.io.IOException
@@ -59,6 +64,10 @@ internal val LOG = Logger.getInstance(BuiltInWebServer::class.java)
// name is duplicated in the ConfigImportHelper
private const val IDE_TOKEN_FILE = "user.web.token"
+private val notificationManager by lazy {
+ SingletonNotificationManager(BuiltInServerManagerImpl.NOTIFICATION_GROUP.getValue(), NotificationType.INFORMATION, null)
+}
+
class BuiltInWebServer : HttpRequestHandler() {
override fun isAccessible(request: HttpRequest) = request.isLocalOrigin(onlyAnyOrLoopback = false, hostsOnly = true)
@@ -94,6 +103,8 @@ class BuiltInWebServer : HttpRequestHandler() {
}
}
+internal fun isActivatable() = Registry.`is`("ide.built.in.web.server.activatable", false)
+
internal const val TOKEN_PARAM_NAME = "__ij-st"
private val STANDARD_COOKIE by lazy {
@@ -189,6 +200,11 @@ private fun doProcess(urlDecoder: QueryStringDecoder, request: FullHttpRequest,
return false
}) ?: candidateByDirectoryName ?: return false
+ if (isActivatable() && !PropertiesComponent.getInstance().getBoolean("ide.built.in.web.server.active")) {
+ notificationManager.notify("Built-in web server is deactivated, to activate, please use Open in Browser", null)
+ return false
+ }
+
if (isEmptyPath) {
// we must redirect "jsdebug" to "jsdebug/" as nginx does, otherwise browser will treat it as a file instead of a directory, so, relative path will not work
redirectToDirectory(request, context.channel(), projectName, null)
diff --git a/platform/util/resources/misc/registry.properties b/platform/util/resources/misc/registry.properties
index 424e502522f0..e497dfe258bf 100644
--- a/platform/util/resources/misc/registry.properties
+++ b/platform/util/resources/misc/registry.properties
@@ -734,4 +734,5 @@ editor.rainbow.identifiers=false
editor.rainbow.identifiers.description=Rainbow identifiers in editor
ide.http.server.response.actual.status=false
-ide.rest.api.requests.per.minute=30
\ No newline at end of file
+ide.rest.api.requests.per.minute=30
+ide.built.in.web.server.activatable=false
\ No newline at end of file
From 74d8f3d799283ec8e6023e921cc40a9a929b0a17 Mon Sep 17 00:00:00 2001
From: Vladimir Krivosheev
Date: Tue, 26 Apr 2016 12:55:27 +0200
Subject: [PATCH 45/67] cleanup
---
.../org/jetbrains/builtInWebServer/WebServerPathHandler.kt | 7 ++-----
1 file changed, 2 insertions(+), 5 deletions(-)
diff --git a/platform/built-in-server/src/org/jetbrains/builtInWebServer/WebServerPathHandler.kt b/platform/built-in-server/src/org/jetbrains/builtInWebServer/WebServerPathHandler.kt
index ad4dfc3fb21f..772c9c35a6a1 100644
--- a/platform/built-in-server/src/org/jetbrains/builtInWebServer/WebServerPathHandler.kt
+++ b/platform/built-in-server/src/org/jetbrains/builtInWebServer/WebServerPathHandler.kt
@@ -46,12 +46,9 @@ abstract class WebServerPathHandler {
isCustomHost: Boolean): Boolean
}
-internal fun redirectToDirectory(request: HttpRequest, channel: Channel, path: String, extraHttpHeader: HttpHeaders?) {
+internal fun redirectToDirectory(request: HttpRequest, channel: Channel, path: String, extraHeaders: HttpHeaders?) {
val response = HttpResponseStatus.MOVED_PERMANENTLY.response(request)
val url = VfsUtil.toUri("${channel.uriScheme}://${request.host!!}/$path/")!!
response.headers().add(HttpHeaderNames.LOCATION, url.toASCIIString())
- extraHttpHeader?.let {
-
- }
- response.send(channel, request, extraHttpHeader)
+ response.send(channel, request, extraHeaders)
}
\ No newline at end of file
From 94fc868cf29d523d8015a637efad474f96e69633 Mon Sep 17 00:00:00 2001
From: Vladimir Krivosheev
Date: Tue, 26 Apr 2016 15:40:30 +0200
Subject: [PATCH 46/67] cleanup
---
.../org/jetbrains/ide/XmlRpcServerImpl.java | 67 +++++++++----------
.../src/org/jetbrains/io/SubServer.java | 3 +-
.../src/com/intellij/ide/XmlRpcServer.java | 5 +-
.../io/DelegatingHttpRequestHandlerBase.kt | 6 +-
.../io/PortUnificationServerHandler.java | 6 +-
5 files changed, 39 insertions(+), 48 deletions(-)
diff --git a/platform/built-in-server/src/org/jetbrains/ide/XmlRpcServerImpl.java b/platform/built-in-server/src/org/jetbrains/ide/XmlRpcServerImpl.java
index 782136dd77f9..4ad9a213b925 100644
--- a/platform/built-in-server/src/org/jetbrains/ide/XmlRpcServerImpl.java
+++ b/platform/built-in-server/src/org/jetbrains/ide/XmlRpcServerImpl.java
@@ -75,48 +75,45 @@ public class XmlRpcServerImpl implements XmlRpcServer {
}
@Override
- public boolean process(@NotNull String path, @NotNull FullHttpRequest request, @NotNull ChannelHandlerContext context, @Nullable Map handlers) throws IOException {
+ public boolean process(@NotNull String path, @NotNull FullHttpRequest request, @NotNull ChannelHandlerContext context, @Nullable Map handlers) {
if (!(path.isEmpty() || (path.length() == 1 && path.charAt(0) == '/') || path.equalsIgnoreCase("/rpc2"))) {
return false;
}
- if (request.method() == HttpMethod.POST) {
- ByteBuf result;
- ByteBuf content = request.content();
- if (content.readableBytes() == 0) {
- Responses.send(HttpResponseStatus.BAD_REQUEST, context.channel(), request);
- return true;
- }
+ if (request.method() != HttpMethod.POST) {
+ return false;
+ }
- ByteBufInputStream in = new ByteBufInputStream(content);
- try {
- XmlRpcServerRequest xmlRpcServerRequest = new XmlRpcRequestProcessor().decodeRequest(in);
- if (StringUtil.isEmpty(xmlRpcServerRequest.getMethodName())) {
- LOG.warn("method name empty");
- return false;
- }
-
- Object response = invokeHandler(getHandler(xmlRpcServerRequest.getMethodName(), handlers == null ? handlerMapping : handlers), xmlRpcServerRequest);
- result = Unpooled.wrappedBuffer(new XmlRpcResponseProcessor().encodeResponse(response, CharsetToolkit.UTF8));
- }
- catch (SAXParseException e) {
- LOG.warn(e);
- Responses.send(HttpResponseStatus.BAD_REQUEST, context.channel(), request);
- return true;
- }
- catch (Throwable e) {
- context.channel().close();
- LOG.error(e);
- return true;
- }
- finally {
- in.close();
- }
-
- Responses.send(Responses.response("text/xml", result), context.channel(), request);
+ ByteBuf content = request.content();
+ if (content.readableBytes() == 0) {
+ Responses.send(HttpResponseStatus.BAD_REQUEST, context.channel(), request);
return true;
}
- return false;
+
+ ByteBuf result;
+ try (ByteBufInputStream in = new ByteBufInputStream(content)) {
+ XmlRpcServerRequest xmlRpcServerRequest = new XmlRpcRequestProcessor().decodeRequest(in);
+ if (StringUtil.isEmpty(xmlRpcServerRequest.getMethodName())) {
+ LOG.warn("method name empty");
+ return false;
+ }
+
+ Object response = invokeHandler(getHandler(xmlRpcServerRequest.getMethodName(), handlers == null ? handlerMapping : handlers), xmlRpcServerRequest);
+ result = Unpooled.wrappedBuffer(new XmlRpcResponseProcessor().encodeResponse(response, CharsetToolkit.UTF8));
+ }
+ catch (SAXParseException e) {
+ LOG.warn(e);
+ Responses.send(HttpResponseStatus.BAD_REQUEST, context.channel(), request);
+ return true;
+ }
+ catch (Throwable e) {
+ context.channel().close();
+ LOG.error(e);
+ return true;
+ }
+
+ Responses.send(Responses.response("text/xml", result), context.channel(), request);
+ return true;
}
private static Object getHandler(@NotNull String methodName, @NotNull Map handlers) {
diff --git a/platform/built-in-server/src/org/jetbrains/io/SubServer.java b/platform/built-in-server/src/org/jetbrains/io/SubServer.java
index c5e58d805f36..67a52d832d21 100644
--- a/platform/built-in-server/src/org/jetbrains/io/SubServer.java
+++ b/platform/built-in-server/src/org/jetbrains/io/SubServer.java
@@ -31,7 +31,6 @@ import io.netty.handler.codec.http.QueryStringDecoder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.ide.CustomPortServerManager;
-import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.Map;
@@ -123,7 +122,7 @@ public final class SubServer implements CustomPortServerManager.CustomPortServic
}
@Override
- protected boolean process(@NotNull ChannelHandlerContext context, @NotNull FullHttpRequest request, @NotNull QueryStringDecoder urlDecoder) throws IOException {
+ protected boolean process(@NotNull ChannelHandlerContext context, @NotNull FullHttpRequest request, @NotNull QueryStringDecoder urlDecoder) {
if (handlers.isEmpty()) {
// not yet initialized, for example, P2PTransport could add handlers after we bound.
return false;
diff --git a/platform/platform-api/src/com/intellij/ide/XmlRpcServer.java b/platform/platform-api/src/com/intellij/ide/XmlRpcServer.java
index 16dafdcd8775..eed4583c5ab0 100644
--- a/platform/platform-api/src/com/intellij/ide/XmlRpcServer.java
+++ b/platform/platform-api/src/com/intellij/ide/XmlRpcServer.java
@@ -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.
@@ -21,7 +21,6 @@ import io.netty.handler.codec.http.FullHttpRequest;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
-import java.io.IOException;
import java.util.Map;
public interface XmlRpcServer {
@@ -31,7 +30,7 @@ public interface XmlRpcServer {
void removeHandler(String name);
- boolean process(@NotNull String path, @NotNull FullHttpRequest request, @NotNull ChannelHandlerContext context, @Nullable Map handlers) throws IOException;
+ boolean process(@NotNull String path, @NotNull FullHttpRequest request, @NotNull ChannelHandlerContext context, @Nullable Map handlers);
final class SERVICE {
private SERVICE() {
diff --git a/platform/platform-impl/src/org/jetbrains/io/DelegatingHttpRequestHandlerBase.kt b/platform/platform-impl/src/org/jetbrains/io/DelegatingHttpRequestHandlerBase.kt
index bfe0014ae145..d8c1914c8e39 100644
--- a/platform/platform-impl/src/org/jetbrains/io/DelegatingHttpRequestHandlerBase.kt
+++ b/platform/platform-impl/src/org/jetbrains/io/DelegatingHttpRequestHandlerBase.kt
@@ -16,6 +16,7 @@
package org.jetbrains.io
import com.intellij.openapi.diagnostic.Logger
+import com.intellij.openapi.diagnostic.debug
import io.netty.channel.ChannelHandlerContext
import io.netty.handler.codec.http.FullHttpRequest
import io.netty.handler.codec.http.HttpResponseStatus
@@ -23,16 +24,13 @@ import io.netty.handler.codec.http.QueryStringDecoder
internal abstract class DelegatingHttpRequestHandlerBase : SimpleChannelInboundHandlerAdapter() {
override fun messageReceived(context: ChannelHandlerContext, message: FullHttpRequest) {
- if (Logger.getInstance(BuiltInServer::class.java).isDebugEnabled) {
- Logger.getInstance(BuiltInServer::class.java).debug("IN HTTP: " + message.uri())
- }
+ Logger.getInstance(BuiltInServer::class.java).debug { "IN HTTP: $message" }
if (!process(context, message, QueryStringDecoder(message.uri()))) {
HttpResponseStatus.NOT_FOUND.send(context.channel(), message)
}
}
- @Throws(Exception::class)
protected abstract fun process(context: ChannelHandlerContext,
request: FullHttpRequest,
urlDecoder: QueryStringDecoder): Boolean
diff --git a/platform/platform-impl/src/org/jetbrains/io/PortUnificationServerHandler.java b/platform/platform-impl/src/org/jetbrains/io/PortUnificationServerHandler.java
index 749487e22890..2ccd269c6fc8 100644
--- a/platform/platform-impl/src/org/jetbrains/io/PortUnificationServerHandler.java
+++ b/platform/platform-impl/src/org/jetbrains/io/PortUnificationServerHandler.java
@@ -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.
@@ -34,8 +34,6 @@ import java.security.KeyStore;
import java.security.Security;
import java.util.UUID;
-import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE;
-
@ChannelHandler.Sharable
class PortUnificationServerHandler extends Decoder {
// keytool -genkey -keyalg RSA -alias selfsigned -keystore cert.jks -storepass jetbrains -validity 10000 -keysize 2048
@@ -114,7 +112,7 @@ class PortUnificationServerHandler extends Decoder {
public void write(ChannelHandlerContext context, Object message, ChannelPromise promise) throws Exception {
if (message instanceof HttpResponse) {
HttpResponse response = (HttpResponse)message;
- logger.debug("OUT HTTP: " + response.status().code() + " " + response.headers().getAsString(CONTENT_TYPE));
+ logger.debug("OUT HTTP: " + response.toString());
}
super.write(context, message, promise);
}
From 40a1c99000747e3f1feea6fe62a86c8698599be4 Mon Sep 17 00:00:00 2001
From: Vladimir Krivosheev
Date: Tue, 26 Apr 2016 15:42:21 +0200
Subject: [PATCH 47/67] use MOVED_PERMANENTLY instead of TEMPORARY_REDIRECT,
short temp token
---
.../builtInWebServer/BuiltInWebServer.kt | 17 +++++++++++++----
1 file changed, 13 insertions(+), 4 deletions(-)
diff --git a/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt b/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt
index 7185f37c72bd..c5ea9abecb67 100644
--- a/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt
+++ b/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt
@@ -53,8 +53,10 @@ import org.jetbrains.notification.SingletonNotificationManager
import java.awt.datatransfer.StringSelection
import java.io.File
import java.io.IOException
+import java.math.BigInteger
import java.net.InetAddress
import java.nio.file.Path
+import java.security.SecureRandom
import java.util.*
import java.util.concurrent.TimeUnit
import javax.swing.SwingUtilities
@@ -65,7 +67,7 @@ internal val LOG = Logger.getInstance(BuiltInWebServer::class.java)
private const val IDE_TOKEN_FILE = "user.web.token"
private val notificationManager by lazy {
- SingletonNotificationManager(BuiltInServerManagerImpl.NOTIFICATION_GROUP.getValue(), NotificationType.INFORMATION, null)
+ SingletonNotificationManager(BuiltInServerManagerImpl.NOTIFICATION_GROUP.value, NotificationType.INFORMATION, null)
}
class BuiltInWebServer : HttpRequestHandler() {
@@ -105,7 +107,7 @@ class BuiltInWebServer : HttpRequestHandler() {
internal fun isActivatable() = Registry.`is`("ide.built.in.web.server.activatable", false)
-internal const val TOKEN_PARAM_NAME = "__ij-st"
+internal const val TOKEN_PARAM_NAME = "_ijt"
private val STANDARD_COOKIE by lazy {
val productName = ApplicationNamesInfo.getInstance().lowercaseProductName
@@ -140,12 +142,19 @@ private val tokens = CacheBuilder.newBuilder().expireAfterAccess(1, TimeUnit.MIN
internal fun acquireToken(): String {
var token = tokens.asMap().keys.firstOrNull()
if (token == null) {
- token = UUID.randomUUID().toString()
+ token = TokenGenerator.generate()
tokens.put(token, java.lang.Boolean.TRUE)
}
return token
}
+// http://stackoverflow.com/a/41156 - shorter than UUID, but secure
+private object TokenGenerator {
+ private val random = SecureRandom()
+
+ fun generate(): String = BigInteger(130, random).toString(32)
+}
+
private fun doProcess(urlDecoder: QueryStringDecoder, request: FullHttpRequest, context: ChannelHandlerContext, projectNameAsHost: String?): Boolean {
val decodedPath = URLUtil.unescapePercentSequences(urlDecoder.path())
var offset: Int
@@ -249,7 +258,7 @@ internal fun validateToken(request: HttpRequest, channel: Channel, redirectToSet
tokens.invalidate(token)
if (redirectToSetCookie) {
// we redirect because it is not easy to change and maintain all places where we send response
- val response = HttpResponseStatus.TEMPORARY_REDIRECT.response(request)
+ val response = HttpResponseStatus.MOVED_PERMANENTLY.response(request)
response.headers().add(HttpHeaderNames.LOCATION, url)
response.headers().set(HttpHeaderNames.SET_COOKIE, ServerCookieEncoder.STRICT.encode(STANDARD_COOKIE) + "; SameSite=strict")
response.send(channel, request)
From 69f590c81a0da73b83252a2e4b8cb9f2504f6e55 Mon Sep 17 00:00:00 2001
From: Vladimir Krivosheev
Date: Tue, 26 Apr 2016 16:16:35 +0200
Subject: [PATCH 48/67] cleanup
---
.../org/jetbrains/io/jsonRpc/ClientManager.kt | 24 ++++++-------------
1 file changed, 7 insertions(+), 17 deletions(-)
diff --git a/platform/built-in-server/src/org/jetbrains/io/jsonRpc/ClientManager.kt b/platform/built-in-server/src/org/jetbrains/io/jsonRpc/ClientManager.kt
index 5bed61f85081..4eb035b48410 100644
--- a/platform/built-in-server/src/org/jetbrains/io/jsonRpc/ClientManager.kt
+++ b/platform/built-in-server/src/org/jetbrains/io/jsonRpc/ClientManager.kt
@@ -10,22 +10,16 @@ import io.netty.util.AttributeKey
import org.jetbrains.concurrency.Promise
import org.jetbrains.io.webSocket.WebSocketServerOptions
-val CLIENT = AttributeKey.valueOf("SocketHandler.client")
+internal val CLIENT = AttributeKey.valueOf("SocketHandler.client")
class ClientManager(private val listener: ClientListener?, val exceptionHandler: ExceptionHandler, options: WebSocketServerOptions? = null) : Disposable {
- private val heartbeatTimer = SimpleTimer.getInstance().setUp(Runnable {
- synchronized (clients) {
- if (clients.isEmpty) {
- return@Runnable
+ private val heartbeatTimer = SimpleTimer.getInstance().setUp({
+ forEachClient(TObjectProcedure {
+ if (it.channel.isActive) {
+ it.sendHeartbeat()
}
-
- clients.forEach { client ->
- if (client.channel.isActive) {
- client.sendHeartbeat()
- }
- true
- }
- }
+ true
+ })
}, (options ?: WebSocketServerOptions()).heartbeatDelay.toLong())
private val clients = THashSet()
@@ -94,10 +88,6 @@ class ClientManager(private val listener: ClientListener?, val exceptionHandler:
fun forEachClient(procedure: TObjectProcedure) {
synchronized (clients) {
- if (clients.isEmpty) {
- return
- }
-
clients.forEach(procedure)
}
}
From 2d15b5d3077f377a43c73cfbf2f057cbcc4651f1 Mon Sep 17 00:00:00 2001
From: Vladimir Krivosheev
Date: Tue, 26 Apr 2016 16:20:50 +0200
Subject: [PATCH 49/67] cleanup
---
.../src/org/jetbrains/io/fastCgi/FastCgiService.kt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/platform/built-in-server/src/org/jetbrains/io/fastCgi/FastCgiService.kt b/platform/built-in-server/src/org/jetbrains/io/fastCgi/FastCgiService.kt
index 75d090042f7f..437c0ea48c8f 100644
--- a/platform/built-in-server/src/org/jetbrains/io/fastCgi/FastCgiService.kt
+++ b/platform/built-in-server/src/org/jetbrains/io/fastCgi/FastCgiService.kt
@@ -30,7 +30,7 @@ import org.jetbrains.concurrency.doneRun
import org.jetbrains.io.*
import java.util.concurrent.atomic.AtomicInteger
-val LOG: Logger = Logger.getInstance(FastCgiService::class.java)
+val LOG = Logger.getInstance(FastCgiService::class.java)
// todo send FCGI_ABORT_REQUEST if client channel disconnected
abstract class FastCgiService(project: Project) : SingleConnectionNetService(project) {
From 21df259d755e4b7deb96004110d07d1b239fc2f7 Mon Sep 17 00:00:00 2001
From: Vladimir Krivosheev
Date: Tue, 26 Apr 2016 16:30:01 +0200
Subject: [PATCH 50/67] =?UTF-8?q?dart/php=20handler=20=E2=80=94=20don't=20?=
=?UTF-8?q?use=20redirect=20to=20set=20cookie?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../builtInWebServer/BuiltInWebServer.kt | 14 ++-------
.../DefaultWebServerPathHandler.kt | 2 +-
.../jetbrains/io/fastCgi/FastCgiService.kt | 30 ++++++++++---------
3 files changed, 19 insertions(+), 27 deletions(-)
diff --git a/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt b/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt
index c5ea9abecb67..0f3d9b2a31a3 100644
--- a/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt
+++ b/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt
@@ -236,7 +236,7 @@ private fun doProcess(urlDecoder: QueryStringDecoder, request: FullHttpRequest,
return false
}
-internal fun validateToken(request: HttpRequest, channel: Channel, redirectToSetCookie: Boolean): HttpHeaders? {
+internal fun validateToken(request: HttpRequest, channel: Channel): HttpHeaders? {
val cookieString = request.headers().get(HttpHeaderNames.COOKIE)
if (cookieString != null) {
val cookies = ServerCookieDecoder.STRICT.decode(cookieString)
@@ -256,17 +256,7 @@ internal fun validateToken(request: HttpRequest, channel: Channel, redirectToSet
val url = "${channel.uriScheme}://${request.host!!}${urlDecoder.path()}"
if (token != null && tokens.getIfPresent(token) != null) {
tokens.invalidate(token)
- if (redirectToSetCookie) {
- // we redirect because it is not easy to change and maintain all places where we send response
- val response = HttpResponseStatus.MOVED_PERMANENTLY.response(request)
- response.headers().add(HttpHeaderNames.LOCATION, url)
- response.headers().set(HttpHeaderNames.SET_COOKIE, ServerCookieEncoder.STRICT.encode(STANDARD_COOKIE) + "; SameSite=strict")
- response.send(channel, request)
- return response.headers()
- }
- else {
- return DefaultHttpHeaders().set(HttpHeaderNames.SET_COOKIE, ServerCookieEncoder.STRICT.encode(STANDARD_COOKIE) + "; SameSite=strict")
- }
+ return DefaultHttpHeaders().set(HttpHeaderNames.SET_COOKIE, ServerCookieEncoder.STRICT.encode(STANDARD_COOKIE) + "; SameSite=strict")
}
SwingUtilities.invokeAndWait {
diff --git a/platform/built-in-server/src/org/jetbrains/builtInWebServer/DefaultWebServerPathHandler.kt b/platform/built-in-server/src/org/jetbrains/builtInWebServer/DefaultWebServerPathHandler.kt
index 66a4fe4b3cbc..e88bb2c98193 100644
--- a/platform/built-in-server/src/org/jetbrains/builtInWebServer/DefaultWebServerPathHandler.kt
+++ b/platform/built-in-server/src/org/jetbrains/builtInWebServer/DefaultWebServerPathHandler.kt
@@ -41,7 +41,7 @@ private class DefaultWebServerPathHandler : WebServerPathHandler() {
projectName: String,
decodedRawPath: String,
isCustomHost: Boolean): Boolean {
- val extraHttpHeaders = validateToken(request, context.channel(), false) ?: return true
+ val extraHttpHeaders = validateToken(request, context.channel()) ?: return true
val channel = context.channel()
val pathToFileManager = WebServerPathToFileManager.getInstance(project)
diff --git a/platform/built-in-server/src/org/jetbrains/io/fastCgi/FastCgiService.kt b/platform/built-in-server/src/org/jetbrains/io/fastCgi/FastCgiService.kt
index 437c0ea48c8f..a295c1ca12fa 100644
--- a/platform/built-in-server/src/org/jetbrains/io/fastCgi/FastCgiService.kt
+++ b/platform/built-in-server/src/org/jetbrains/io/fastCgi/FastCgiService.kt
@@ -18,7 +18,6 @@ package org.jetbrains.io.fastCgi
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.project.Project
import com.intellij.util.Consumer
-import com.intellij.util.containers.ConcurrentIntObjectMap
import com.intellij.util.containers.ContainerUtil
import io.netty.bootstrap.Bootstrap
import io.netty.buffer.ByteBuf
@@ -35,7 +34,7 @@ val LOG = Logger.getInstance(FastCgiService::class.java)
// todo send FCGI_ABORT_REQUEST if client channel disconnected
abstract class FastCgiService(project: Project) : SingleConnectionNetService(project) {
private val requestIdCounter = AtomicInteger()
- protected val requests: ConcurrentIntObjectMap = ContainerUtil.createConcurrentIntObjectMap()
+ private val requests = ContainerUtil.createConcurrentIntObjectMap()
override fun configureBootstrap(bootstrap: Bootstrap, errorOutputConsumer: Consumer) {
bootstrap.handler {
@@ -47,8 +46,8 @@ abstract class FastCgiService(project: Project) : SingleConnectionNetService(pro
if (!requests.isEmpty) {
val waitingClients = requests.elements().toList()
requests.clear()
- for (channel in waitingClients) {
- sendBadGateway(channel)
+ for (client in waitingClients) {
+ sendBadGateway(client.channel, client.extraHeaders)
}
}
}
@@ -103,30 +102,30 @@ abstract class FastCgiService(project: Project) : SingleConnectionNetService(pro
}
}
finally {
- val channel = requests.remove(fastCgiRequest.requestId)
- if (channel != null) {
- sendBadGateway(channel)
+ requests.remove(fastCgiRequest.requestId)?.let {
+ sendBadGateway(it.channel, it.extraHeaders)
}
}
}
- fun allocateRequestId(channel: Channel): Int {
+ fun allocateRequestId(channel: Channel, extraHeaders: HttpHeaders): Int {
var requestId = requestIdCounter.getAndIncrement()
if (requestId >= java.lang.Short.MAX_VALUE) {
requestIdCounter.set(0)
requestId = requestIdCounter.getAndDecrement()
}
- requests.put(requestId, channel)
+ requests.put(requestId, ClientInfo(channel, extraHeaders))
return requestId
}
fun responseReceived(id: Int, buffer: ByteBuf?) {
- val channel = requests.remove(id)
- if (channel == null || !channel.isActive) {
+ val client = requests.remove(id)
+ if (client == null || !client.channel.isActive) {
buffer?.release()
return
}
+ val channel = client.channel
if (buffer == null) {
HttpResponseStatus.BAD_GATEWAY.send(channel)
return
@@ -139,6 +138,7 @@ abstract class FastCgiService(project: Project) : SingleConnectionNetService(pro
if (!HttpUtil.isContentLengthSet(httpResponse)) {
HttpUtil.setContentLength(httpResponse, buffer.readableBytes().toLong())
}
+ httpResponse.headers().add(client.extraHeaders)
}
catch (e: Throwable) {
buffer.release()
@@ -155,10 +155,10 @@ abstract class FastCgiService(project: Project) : SingleConnectionNetService(pro
}
}
-private fun sendBadGateway(channel: Channel) {
+private fun sendBadGateway(channel: Channel, extraHeaders: HttpHeaders) {
try {
if (channel.isActive) {
- HttpResponseStatus.BAD_GATEWAY.send(channel)
+ HttpResponseStatus.BAD_GATEWAY.send(channel, extraHeaders = extraHeaders)
}
}
catch (e: Throwable) {
@@ -218,4 +218,6 @@ private fun parseHeaders(response: HttpResponse, buffer: ByteBuf) {
response.headers().add(key, value)
}
}
-}
\ No newline at end of file
+}
+
+private class ClientInfo(val channel: Channel, val extraHeaders: HttpHeaders)
\ No newline at end of file
From 332cdcf97d77dd53821e55e04ca014f8805e2e91 Mon Sep 17 00:00:00 2001
From: Vladimir Krivosheev
Date: Wed, 27 Apr 2016 11:29:00 +0200
Subject: [PATCH 51/67] activate built-in web server only on real open
invocation
---
.../BuiltInWebBrowserUrlProvider.java | 7 +--
.../ide/browsers/BrowserLauncherImpl.java | 62 ++++++++++++++++++-
xml/impl/xml.iml | 4 +-
3 files changed, 63 insertions(+), 10 deletions(-)
diff --git a/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebBrowserUrlProvider.java b/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebBrowserUrlProvider.java
index 97c561c0e086..61e5db5ca9c3 100644
--- a/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebBrowserUrlProvider.java
+++ b/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebBrowserUrlProvider.java
@@ -18,7 +18,6 @@ package org.jetbrains.builtInWebServer;
import com.intellij.ide.browsers.OpenInBrowserRequest;
import com.intellij.ide.browsers.WebBrowserService;
import com.intellij.ide.browsers.WebBrowserUrlProvider;
-import com.intellij.ide.util.PropertiesComponent;
import com.intellij.lang.Language;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.Project;
@@ -70,11 +69,7 @@ public class BuiltInWebBrowserUrlProvider extends WebBrowserUrlProvider implemen
urls.add(Urls.newHttpUrl(defaultAuthority, '/' + project.getName() + '/' + path2, query));
}
}
-
- if (BuiltInWebServerKt.isActivatable()) {
- PropertiesComponent.getInstance().setValue("ide.built.in.web.server.active", true);
- }
-
+
return urls;
}
diff --git a/xml/impl/src/com/intellij/ide/browsers/BrowserLauncherImpl.java b/xml/impl/src/com/intellij/ide/browsers/BrowserLauncherImpl.java
index 65da7b30722c..7a62abe0b33f 100644
--- a/xml/impl/src/com/intellij/ide/browsers/BrowserLauncherImpl.java
+++ b/xml/impl/src/com/intellij/ide/browsers/BrowserLauncherImpl.java
@@ -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.
@@ -20,22 +20,80 @@ import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.execution.util.ExecUtil;
import com.intellij.ide.GeneralSettings;
import com.intellij.ide.IdeBundle;
+import com.intellij.ide.util.PropertiesComponent;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.options.ShowSettingsUtil;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.SystemInfo;
+import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.ui.AppUIUtil;
import com.intellij.util.ArrayUtil;
+import com.intellij.util.Url;
+import com.intellij.util.Urls;
+import com.intellij.util.net.NetUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
+import org.jetbrains.builtInWebServer.BuiltInServerOptions;
+import org.jetbrains.ide.BuiltInServerManager;
+import java.net.InetAddress;
import java.net.URI;
+import java.net.UnknownHostException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
-final class BrowserLauncherImpl extends BrowserLauncherAppless {
+public final class BrowserLauncherImpl extends BrowserLauncherAppless {
+ @Override
+ public void browse(@NotNull String url, @Nullable WebBrowser browser, @Nullable Project project) {
+ if (Registry.is("ide.built.in.web.server.activatable", false)) {
+ Url parsedUrl = Urls.parse(url, false);
+ if (parsedUrl != null && parsedUrl.getAuthority() != null && isOnBuiltInWebServerByAuthority(parsedUrl.getAuthority())) {
+ PropertiesComponent.getInstance().setValue("ide.built.in.web.server.active", true);
+ }
+ }
+
+ super.browse(url, browser, project);
+ }
+
+ public static boolean isOnBuiltInWebServerByAuthority(@NotNull String authority) {
+ int portIndex = authority.indexOf(':');
+ if (portIndex < 0 || portIndex == authority.length() - 1) {
+ return false;
+ }
+
+ int port;
+ try {
+ port = Integer.parseInt(authority.substring(portIndex + 1));
+ }
+ catch (NumberFormatException ignored) {
+ return false;
+ }
+
+ if (BuiltInServerOptions.getInstance().builtInServerPort != port && BuiltInServerManager.getInstance().getPort() != port) {
+ return false;
+ }
+
+ String host = authority.substring(0, portIndex);
+ if (NetUtils.isLocalhost(host)) {
+ return true;
+ }
+
+ InetAddress inetAddress;
+ try {
+ inetAddress = InetAddress.getByName(host);
+ }
+ catch (UnknownHostException ignored) {
+ return false;
+ }
+
+ if (inetAddress == null) {
+ return false;
+ }
+ return inetAddress.isLoopbackAddress() || inetAddress.isAnyLocalAddress();
+ }
+
@Override
protected void browseUsingNotSystemDefaultBrowserPolicy(@NotNull URI uri, @NotNull GeneralSettings settings, @Nullable Project project) {
WebBrowserManager browserManager = WebBrowserManager.getInstance();
diff --git a/xml/impl/xml.iml b/xml/impl/xml.iml
index 2fb3ee7ea262..fb6c556fefcf 100644
--- a/xml/impl/xml.iml
+++ b/xml/impl/xml.iml
@@ -23,11 +23,11 @@
+
-
-
+
\ No newline at end of file
From 275c227e83ea14b73159780b368ed0e991a52996 Mon Sep 17 00:00:00 2001
From: Vladimir Krivosheev
Date: Wed, 27 Apr 2016 12:53:35 +0200
Subject: [PATCH 52/67] pass token in the headers in debug mode
---
.../BuiltInWebBrowserUrlProvider.java | 9 +++++--
.../builtInWebServer/BuiltInWebServer.kt | 7 ++++--
.../io/DelegatingHttpRequestHandlerBase.kt | 2 +-
.../browsers/impl/WebBrowserServiceImpl.java | 11 ++++++--
.../ide/browsers/OpenInBrowserRequest.java | 25 +++++++++++++++++++
5 files changed, 47 insertions(+), 7 deletions(-)
diff --git a/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebBrowserUrlProvider.java b/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebBrowserUrlProvider.java
index 61e5db5ca9c3..471da19667f8 100644
--- a/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebBrowserUrlProvider.java
+++ b/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebBrowserUrlProvider.java
@@ -40,6 +40,11 @@ import java.util.List;
public class BuiltInWebBrowserUrlProvider extends WebBrowserUrlProvider implements DumbAware {
@NotNull
public static List getUrls(@NotNull VirtualFile file, @NotNull Project project, @Nullable String currentAuthority) {
+ return getUrls(file, project, currentAuthority, true);
+ }
+
+ @NotNull
+ public static List getUrls(@NotNull VirtualFile file, @NotNull Project project, @Nullable String currentAuthority, boolean appendAccessToken) {
if (currentAuthority != null && !compareAuthority(currentAuthority)) {
return Collections.emptyList();
}
@@ -53,7 +58,7 @@ public class BuiltInWebBrowserUrlProvider extends WebBrowserUrlProvider implemen
String path = info.getPath();
String authority = currentAuthority == null ? "localhost:" + effectiveBuiltInServerPort : currentAuthority;
- String query = "?" + BuiltInWebServerKt.TOKEN_PARAM_NAME + "=" + BuiltInWebServerKt.acquireToken();
+ String query = appendAccessToken ? "?" + BuiltInWebServerKt.TOKEN_PARAM_NAME + "=" + BuiltInWebServerKt.acquireToken() : "";
List urls = new SmartList<>(Urls.newHttpUrl(authority, '/' + project.getName() + '/' + path, query));
String path2 = info.getRootLessPathIfPossible();
@@ -118,7 +123,7 @@ public class BuiltInWebBrowserUrlProvider extends WebBrowserUrlProvider implemen
return Urls.newFromVirtualFile(file);
}
else {
- return ContainerUtil.getFirstItem(getUrls(file, request.getProject(), null));
+ return ContainerUtil.getFirstItem(getUrls(file, request.getProject(), null, request.isAppendAccessToken()));
}
}
}
diff --git a/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt b/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt
index 0f3d9b2a31a3..308937b079c9 100644
--- a/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt
+++ b/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt
@@ -108,6 +108,7 @@ class BuiltInWebServer : HttpRequestHandler() {
internal fun isActivatable() = Registry.`is`("ide.built.in.web.server.activatable", false)
internal const val TOKEN_PARAM_NAME = "_ijt"
+const val TOKEN_HEADER_NAME = "x-ijt"
private val STANDARD_COOKIE by lazy {
val productName = ApplicationNamesInfo.getInstance().lowercaseProductName
@@ -139,7 +140,7 @@ private val STANDARD_COOKIE by lazy {
// expire after access because we reuse tokens
private val tokens = CacheBuilder.newBuilder().expireAfterAccess(1, TimeUnit.MINUTES).build()
-internal fun acquireToken(): String {
+fun acquireToken(): String {
var token = tokens.asMap().keys.firstOrNull()
if (token == null) {
token = TokenGenerator.generate()
@@ -252,7 +253,9 @@ internal fun validateToken(request: HttpRequest, channel: Channel): HttpHeaders?
val urlDecoder = QueryStringDecoder(request.uri())
// 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 token = request.headers().get(TOKEN_HEADER_NAME)
+ ?: 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)
diff --git a/platform/platform-impl/src/org/jetbrains/io/DelegatingHttpRequestHandlerBase.kt b/platform/platform-impl/src/org/jetbrains/io/DelegatingHttpRequestHandlerBase.kt
index d8c1914c8e39..b760cddf1561 100644
--- a/platform/platform-impl/src/org/jetbrains/io/DelegatingHttpRequestHandlerBase.kt
+++ b/platform/platform-impl/src/org/jetbrains/io/DelegatingHttpRequestHandlerBase.kt
@@ -24,7 +24,7 @@ import io.netty.handler.codec.http.QueryStringDecoder
internal abstract class DelegatingHttpRequestHandlerBase : SimpleChannelInboundHandlerAdapter() {
override fun messageReceived(context: ChannelHandlerContext, message: FullHttpRequest) {
- Logger.getInstance(BuiltInServer::class.java).debug { "IN HTTP: $message" }
+ Logger.getInstance(BuiltInServer::class.java).debug { "\n\nIN HTTP: $message\n\n" }
if (!process(context, message, QueryStringDecoder(message.uri()))) {
HttpResponseStatus.NOT_FOUND.send(context.channel(), message)
diff --git a/xml/impl/src/com/intellij/ide/browsers/impl/WebBrowserServiceImpl.java b/xml/impl/src/com/intellij/ide/browsers/impl/WebBrowserServiceImpl.java
index be5151bb06a5..9f23e779ed19 100644
--- a/xml/impl/src/com/intellij/ide/browsers/impl/WebBrowserServiceImpl.java
+++ b/xml/impl/src/com/intellij/ide/browsers/impl/WebBrowserServiceImpl.java
@@ -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.
@@ -94,7 +94,14 @@ public class WebBrowserServiceImpl extends WebBrowserService {
public static Collection getDebuggableUrls(@Nullable PsiElement context) {
try {
OpenInBrowserRequest request = context == null ? null : OpenInBrowserRequest.create(context);
- return request == null || request.getFile().getViewProvider().getBaseLanguage() == XMLLanguage.INSTANCE ? Collections.emptyList() : getUrls(getProvider(request), request);
+ if (request == null || request.getFile().getViewProvider().getBaseLanguage() == XMLLanguage.INSTANCE) {
+ return Collections.emptyList();
+ }
+ else {
+ // it is client responsibility to set token
+ request.setAppendAccessToken(false);
+ return getUrls(getProvider(request), request);
+ }
}
catch (WebBrowserUrlProvider.BrowserException ignored) {
return Collections.emptyList();
diff --git a/xml/openapi/src/com/intellij/ide/browsers/OpenInBrowserRequest.java b/xml/openapi/src/com/intellij/ide/browsers/OpenInBrowserRequest.java
index c4119d10c8c0..3f8d70e8522d 100644
--- a/xml/openapi/src/com/intellij/ide/browsers/OpenInBrowserRequest.java
+++ b/xml/openapi/src/com/intellij/ide/browsers/OpenInBrowserRequest.java
@@ -1,3 +1,18 @@
+/*
+ * 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.
+ * 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.browsers;
import com.intellij.openapi.application.AccessToken;
@@ -15,6 +30,8 @@ import java.util.Collection;
public abstract class OpenInBrowserRequest {
private Collection result;
protected PsiFile file;
+
+ private boolean appendAccessToken = true;
public OpenInBrowserRequest(@NotNull PsiFile file) {
this.file = file;
@@ -72,4 +89,12 @@ public abstract class OpenInBrowserRequest {
public Collection getResult() {
return result;
}
+
+ public boolean isAppendAccessToken() {
+ return appendAccessToken;
+ }
+
+ public void setAppendAccessToken(boolean value) {
+ this.appendAccessToken = value;
+ }
}
\ No newline at end of file
From 63d6cdeba7b5ea6bb77c68fb443557a45baa314f Mon Sep 17 00:00:00 2001
From: Vladimir Krivosheev
Date: Wed, 27 Apr 2016 15:54:01 +0200
Subject: [PATCH 53/67] DefaultRemoteContentProvider must explicitly set token
in the headers (Open URL and load remote script by http)
---
.../jetbrains/ide/BuiltInServerManager.java | 8 +++
.../builtInWebServer/BuiltInWebServer.kt | 20 ++++---
.../ide/BuiltInServerManagerImpl.java | 55 +++++++++++++++++++
.../http/DefaultRemoteContentProvider.java | 7 +++
.../ide/browsers/BrowserLauncherImpl.java | 50 +----------------
xml/impl/xml.iml | 2 +-
6 files changed, 85 insertions(+), 57 deletions(-)
diff --git a/platform/built-in-server-api/src/org/jetbrains/ide/BuiltInServerManager.java b/platform/built-in-server-api/src/org/jetbrains/ide/BuiltInServerManager.java
index fff0a1564531..448ede483363 100644
--- a/platform/built-in-server-api/src/org/jetbrains/ide/BuiltInServerManager.java
+++ b/platform/built-in-server-api/src/org/jetbrains/ide/BuiltInServerManager.java
@@ -18,8 +18,12 @@ package org.jetbrains.ide;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.ApplicationComponent;
+import com.intellij.util.Url;
+import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
+import java.net.URLConnection;
+
public abstract class BuiltInServerManager extends ApplicationComponent.Adapter {
public static BuiltInServerManager getInstance() {
return ApplicationManager.getApplication().getComponent(BuiltInServerManager.class);
@@ -31,4 +35,8 @@ public abstract class BuiltInServerManager extends ApplicationComponent.Adapter
@Nullable
public abstract Disposable getServerDisposable();
+
+ public abstract boolean isOnBuiltInWebServer(@Nullable Url url);
+
+ public abstract void configureRequestToWebServer(@NotNull URLConnection connection);
}
\ No newline at end of file
diff --git a/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt b/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt
index 308937b079c9..334cf2ede98f 100644
--- a/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt
+++ b/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt
@@ -262,16 +262,18 @@ internal fun validateToken(request: HttpRequest, channel: Channel): HttpHeaders?
return DefaultHttpHeaders().set(HttpHeaderNames.SET_COOKIE, ServerCookieEncoder.STRICT.encode(STANDARD_COOKIE) + "; SameSite=strict")
}
- SwingUtilities.invokeAndWait {
- ProjectUtil.focusProjectWindow(null, true)
+ if (!urlDecoder.path().endsWith("/favicon.ico")) {
+ 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()))
+ 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()))
+ }
}
}
diff --git a/platform/built-in-server/src/org/jetbrains/ide/BuiltInServerManagerImpl.java b/platform/built-in-server/src/org/jetbrains/ide/BuiltInServerManagerImpl.java
index 6e33b54cbe24..941a0cb6566b 100644
--- a/platform/built-in-server/src/org/jetbrains/ide/BuiltInServerManagerImpl.java
+++ b/platform/built-in-server/src/org/jetbrains/ide/BuiltInServerManagerImpl.java
@@ -10,13 +10,21 @@ import com.intellij.openapi.application.ApplicationNamesInfo;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.NotNullLazyValue;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.util.Url;
+import com.intellij.util.net.NetUtils;
import io.netty.channel.oio.OioEventLoopGroup;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
+import org.jetbrains.builtInWebServer.BuiltInServerOptions;
+import org.jetbrains.builtInWebServer.BuiltInWebServerKt;
import org.jetbrains.io.BuiltInServer;
import org.jetbrains.io.SubServer;
+import java.net.InetAddress;
+import java.net.URLConnection;
+import java.net.UnknownHostException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -116,6 +124,16 @@ public class BuiltInServerManagerImpl extends BuiltInServerManager {
return server;
}
+ @Override
+ public boolean isOnBuiltInWebServer(@Nullable Url url) {
+ return url != null && !StringUtil.isEmpty(url.getAuthority()) && isOnBuiltInWebServerByAuthority(url.getAuthority());
+ }
+
+ @Override
+ public void configureRequestToWebServer(@NotNull URLConnection connection) {
+ connection.setRequestProperty(BuiltInWebServerKt.TOKEN_HEADER_NAME, BuiltInWebServerKt.acquireToken());
+ }
+
private static void bindCustomPorts(@NotNull BuiltInServer server) {
if (ApplicationManager.getApplication().isUnitTestMode()) {
return;
@@ -130,4 +148,41 @@ public class BuiltInServerManagerImpl extends BuiltInServerManager {
}
}
}
+
+ public static boolean isOnBuiltInWebServerByAuthority(@NotNull String authority) {
+ int portIndex = authority.indexOf(':');
+ if (portIndex < 0 || portIndex == authority.length() - 1) {
+ return false;
+ }
+
+ int port;
+ try {
+ port = Integer.parseInt(authority.substring(portIndex + 1));
+ }
+ catch (NumberFormatException ignored) {
+ return false;
+ }
+
+ if (BuiltInServerOptions.getInstance().builtInServerPort != port && BuiltInServerManager.getInstance().getPort() != port) {
+ return false;
+ }
+
+ String host = authority.substring(0, portIndex);
+ if (NetUtils.isLocalhost(host)) {
+ return true;
+ }
+
+ InetAddress inetAddress;
+ try {
+ inetAddress = InetAddress.getByName(host);
+ }
+ catch (UnknownHostException ignored) {
+ return false;
+ }
+
+ if (inetAddress == null) {
+ return false;
+ }
+ return inetAddress.isLoopbackAddress() || inetAddress.isAnyLocalAddress();
+ }
}
\ No newline at end of file
diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/impl/http/DefaultRemoteContentProvider.java b/platform/platform-impl/src/com/intellij/openapi/vfs/impl/http/DefaultRemoteContentProvider.java
index c74cecb41df7..4f0baf1bd92f 100644
--- a/platform/platform-impl/src/com/intellij/openapi/vfs/impl/http/DefaultRemoteContentProvider.java
+++ b/platform/platform-impl/src/com/intellij/openapi/vfs/impl/http/DefaultRemoteContentProvider.java
@@ -29,6 +29,7 @@ import com.intellij.util.Url;
import com.intellij.util.io.HttpRequests;
import com.intellij.util.net.ssl.CertificateManager;
import org.jetbrains.annotations.NotNull;
+import org.jetbrains.ide.BuiltInServerManager;
import java.io.File;
import java.io.IOException;
@@ -60,6 +61,12 @@ public class DefaultRemoteContentProvider extends RemoteContentProvider {
.connectTimeout(60 * 1000)
.productNameAsUserAgent()
.hostNameVerifier(CertificateManager.HOSTNAME_VERIFIER)
+ .tuner(connection -> {
+ BuiltInServerManager builtInServerManager = BuiltInServerManager.getInstance();
+ if (builtInServerManager.isOnBuiltInWebServer(url)) {
+ builtInServerManager.configureRequestToWebServer(connection);
+ }
+ })
.connect(new HttpRequests.RequestProcessor