From 8a4659f30dc3fbad4215a2cd6475fa89d40e2e4e Mon Sep 17 00:00:00 2001 From: Vladimir Krivosheev Date: Mon, 8 Jun 2020 19:16:26 +0200 Subject: [PATCH] cleanup, JetBrains Academy site as trusted origin for about service GitOrigin-RevId: 5fd1f46b931d698a3e3d54330757cae3bb87ec81 --- .../intellij.platform.builtInServer.impl.iml | 1 + .../builtInWebServer/BuiltInWebServer.kt | 2 +- .../src/org/jetbrains/ide/AboutHttpService.kt | 153 ++++++++++-------- .../org/jetbrains/ide/InstallPluginService.kt | 7 +- .../ide/StartUpMeasurementService.kt | 6 +- .../com/intellij/ide/IdeAboutInfoUtil.java | 32 ---- .../src/com/intellij/util/io/netty.kt | 11 +- .../org/jetbrains/ide/HttpRequestHandler.kt | 4 +- 8 files changed, 104 insertions(+), 112 deletions(-) delete mode 100644 platform/platform-impl/src/com/intellij/ide/IdeAboutInfoUtil.java diff --git a/platform/built-in-server/intellij.platform.builtInServer.impl.iml b/platform/built-in-server/intellij.platform.builtInServer.impl.iml index 1321f4d140c0..bc1fc2407149 100644 --- a/platform/built-in-server/intellij.platform.builtInServer.impl.iml +++ b/platform/built-in-server/intellij.platform.builtInServer.impl.iml @@ -27,5 +27,6 @@ + \ 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 9b1cf66f4362..712c0e97227b 100644 --- a/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt +++ b/platform/built-in-server/src/org/jetbrains/builtInWebServer/BuiltInWebServer.kt @@ -66,7 +66,7 @@ class BuiltInWebServer : HttpRequestHandler() { override fun isSupported(request: FullHttpRequest): Boolean = super.isSupported(request) || request.method() == HttpMethod.POST override fun process(urlDecoder: QueryStringDecoder, request: FullHttpRequest, context: ChannelHandlerContext): Boolean { - var hostName = request.hostName ?: return false + var hostName = getHostName(request) ?: return false val projectName: String? val isIpv6 = hostName[0] == '[' && hostName.length > 2 && hostName[hostName.length - 1] == ']' if (isIpv6) { diff --git a/platform/built-in-server/src/org/jetbrains/ide/AboutHttpService.kt b/platform/built-in-server/src/org/jetbrains/ide/AboutHttpService.kt index 0d45f5917d49..8a8f87bb8f29 100644 --- a/platform/built-in-server/src/org/jetbrains/ide/AboutHttpService.kt +++ b/platform/built-in-server/src/org/jetbrains/ide/AboutHttpService.kt @@ -1,21 +1,24 @@ -// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. -package org.jetbrains.ide; +// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package org.jetbrains.ide -import com.google.gson.stream.JsonWriter; -import com.intellij.ide.IdeAboutInfoUtil; -import com.intellij.openapi.application.PathManager; -import com.intellij.openapi.application.ex.ApplicationInfoEx; -import com.intellij.openapi.fileTypes.FileType; -import com.intellij.openapi.fileTypes.FileTypeRegistry; -import com.intellij.openapi.util.io.BufferExposingByteArrayOutputStream; -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.http.FullHttpRequest; -import io.netty.handler.codec.http.QueryStringDecoder; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.io.IOException; -import java.io.OutputStream; +import com.fasterxml.jackson.core.JsonFactory +import com.fasterxml.jackson.core.JsonGenerator +import com.intellij.openapi.application.ApplicationInfo +import com.intellij.openapi.application.ApplicationNamesInfo +import com.intellij.openapi.application.PathManager +import com.intellij.openapi.application.ex.ApplicationInfoEx +import com.intellij.openapi.fileTypes.FileTypeRegistry +import com.intellij.openapi.util.io.BufferExposingByteArrayOutputStream +import com.intellij.util.PlatformUtils +import com.intellij.util.io.jackson.array +import com.intellij.util.io.jackson.obj +import com.intellij.util.io.origin +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 org.jetbrains.ide.RestService.Companion.getBooleanParameter +import java.io.OutputStream /** * @api {get} /about The application info @@ -35,61 +38,83 @@ import java.io.OutputStream; * @apiSuccess {String} registeredFileTypes.description The user-readable description of the file type. * @apiSuccess {Boolean} registeredFileTypes.isBinary Whether files of the specified type contain binary data. * - * * @apiExample Request-Example: + * * @apiExample Request-Example: * /rest/about?registeredFileTypes * * @apiUse SuccessExample * @apiUse SuccessExampleWithRegisteredFileTypes */ -public class AboutHttpService extends RestService { - @NotNull - @Override - protected String getServiceName() { - return "about"; +internal class AboutHttpService : RestService() { + override fun getServiceName() = "about" + + override fun isHostTrusted(request: FullHttpRequest, urlDecoder: QueryStringDecoder): Boolean { + return isTrustedOrigin(request) || super.isHostTrusted(request, urlDecoder) } - @Nullable - @Override - public String execute(@NotNull QueryStringDecoder urlDecoder, @NotNull FullHttpRequest request, @NotNull ChannelHandlerContext context) throws IOException { - BufferExposingByteArrayOutputStream byteOut = new BufferExposingByteArrayOutputStream(); - getAbout(byteOut, urlDecoder); - send(byteOut, request, context); - return null; + override fun isAccessible(request: HttpRequest): Boolean { + return isTrustedOrigin(request) || super.isAccessible(request) } - public static void getAbout(@NotNull OutputStream out, @Nullable QueryStringDecoder urlDecoder) throws IOException { - JsonWriter writer = createJsonWriter(out); - writer.beginObject(); - - IdeAboutInfoUtil.writeAboutJson(writer); - - if (urlDecoder != null && getBooleanParameter("registeredFileTypes", urlDecoder)) { - writer.name("registeredFileTypes").beginArray(); - for (FileType fileType : FileTypeRegistry.getInstance().getRegisteredFileTypes()) { - writer.beginObject(); - writer.name("name").value(fileType.getName()); - writer.name("description").value(fileType.getDescription()); - writer.name("isBinary").value(fileType.isBinary()); - writer.endObject(); - } - writer.endArray(); - } - - if (urlDecoder != null && getBooleanParameter("more", urlDecoder)) { - ApplicationInfoEx appInfo = ApplicationInfoEx.getInstanceEx(); - writer.name("vendor").value(appInfo.getCompanyName()); - writer.name("isEAP").value(appInfo.isEAP()); - writer.name("productCode").value(appInfo.getBuild().getProductCode()); - writer.name("buildDate").value(appInfo.getBuildDate().getTime().getTime()); - writer.name("isSnapshot").value(appInfo.getBuild().isSnapshot()); - writer.name("configPath").value(PathManager.getConfigPath()); - writer.name("systemPath").value(PathManager.getSystemPath()); - writer.name("binPath").value(PathManager.getBinPath()); - writer.name("logPath").value(PathManager.getLogPath()); - writer.name("homePath").value(PathManager.getHomePath()); - } - - writer.endObject(); - writer.close(); + override fun execute(urlDecoder: QueryStringDecoder, request: FullHttpRequest, context: ChannelHandlerContext): String? { + val byteOut = BufferExposingByteArrayOutputStream() + writeApplicationInfoJson(byteOut, urlDecoder) + send(byteOut, request, context) + return null } } + +private fun isTrustedOrigin(request: HttpRequest): Boolean { + val origin = request.origin ?: return false + @Suppress("SpellCheckingInspection") + return origin == "hyperskill.org" +} + +fun writeApplicationInfoJson(out: OutputStream, urlDecoder: QueryStringDecoder?) { + val writer = JsonFactory().createGenerator(out).useDefaultPrettyPrinter() + writer.obj { + writeAboutJson(writer) + if (urlDecoder != null && getBooleanParameter("registeredFileTypes", urlDecoder)) { + writer.array("registeredFileTypes") { + for (fileType in FileTypeRegistry.getInstance().registeredFileTypes) { + writer.obj { + writer.writeStringField("name", fileType.name) + writer.writeStringField("description", fileType.description) + writer.writeBooleanField("isBinary", fileType.isBinary) + } + } + } + } + if (urlDecoder != null && getBooleanParameter("more", urlDecoder)) { + val appInfo = ApplicationInfoEx.getInstanceEx() + writer.writeStringField("vendor", appInfo.companyName) + writer.writeBooleanField("isEAP", appInfo.isEAP) + writer.writeStringField("productCode", appInfo.build.productCode) + writer.writeNumberField("buildDate", appInfo.buildDate.time.time) + writer.writeBooleanField("isSnapshot", appInfo.build.isSnapshot) + writer.writeStringField("configPath", PathManager.getConfigPath()) + writer.writeStringField("systemPath", PathManager.getSystemPath()) + writer.writeStringField("binPath", PathManager.getBinPath()) + writer.writeStringField("logPath", PathManager.getLogPath()) + writer.writeStringField("homePath", PathManager.getHomePath()) + } + } + writer.close() +} + +fun writeAboutJson(writer: JsonGenerator) { + var appName = ApplicationInfoEx.getInstanceEx().fullApplicationName + if (!PlatformUtils.isIdeaUltimate()) { + val productName = ApplicationNamesInfo.getInstance().productName + appName = appName + .replace("$productName ($productName)", productName) + .removePrefix("JetBrains ") + } + writer.writeStringField("name", appName) + writer.writeStringField("productName", ApplicationNamesInfo.getInstance().productName) + + val build = ApplicationInfo.getInstance().build + writer.writeNumberField("baselineVersion", build.baselineVersion) + if (!build.isSnapshot) { + writer.writeStringField("buildNumber", build.asStringWithoutProductCode()) + } +} \ No newline at end of file diff --git a/platform/built-in-server/src/org/jetbrains/ide/InstallPluginService.kt b/platform/built-in-server/src/org/jetbrains/ide/InstallPluginService.kt index 74426a7ccd68..ed8892be0985 100644 --- a/platform/built-in-server/src/org/jetbrains/ide/InstallPluginService.kt +++ b/platform/built-in-server/src/org/jetbrains/ide/InstallPluginService.kt @@ -1,4 +1,4 @@ -// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package org.jetbrains.ide import com.intellij.ide.plugins.marketplace.MarketplaceRequests @@ -10,12 +10,11 @@ import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.extensions.PluginId import com.intellij.openapi.project.ProjectManager import com.intellij.openapi.updateSettings.impl.pluginsAdvertisement.PluginsAdvertiser -import com.intellij.openapi.util.BuildNumber import com.intellij.openapi.util.io.BufferExposingByteArrayOutputStream import com.intellij.openapi.util.text.StringUtil import com.intellij.ui.AppIcon import com.intellij.util.PlatformUtils -import com.intellij.util.io.hostName +import com.intellij.util.io.getHostName import com.intellij.util.io.origin import com.intellij.util.net.NetUtils import com.intellij.util.text.nullize @@ -127,7 +126,7 @@ internal class InstallPluginService : RestService() { return false } - val hostName = request.hostName + val hostName = getHostName(request) if (hostName != null && !NetUtils.isLocalhost(hostName)) { LOG.error("Expected 'request.hostName' to be localhost. hostName='$hostName', origin='$origin'") } diff --git a/platform/built-in-server/src/org/jetbrains/ide/StartUpMeasurementService.kt b/platform/built-in-server/src/org/jetbrains/ide/StartUpMeasurementService.kt index 57033477f98f..f0a0fadd1b2f 100644 --- a/platform/built-in-server/src/org/jetbrains/ide/StartUpMeasurementService.kt +++ b/platform/built-in-server/src/org/jetbrains/ide/StartUpMeasurementService.kt @@ -1,11 +1,11 @@ -// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package org.jetbrains.ide import com.intellij.diagnostic.StartUpPerformanceService import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.ex.ApplicationInfoEx import com.intellij.openapi.diagnostic.logger -import com.intellij.util.io.hostName +import com.intellij.util.io.getHostName import com.intellij.util.io.origin import com.intellij.util.net.NetUtils import io.netty.buffer.Unpooled @@ -45,7 +45,7 @@ internal class StartUpMeasurementService : RestService() { } private fun isTrustedHostName(request: HttpRequest): Boolean { - val hostName = request.hostName ?: return false + val hostName = getHostName(request) ?: return false if (!NetUtils.isLocalhost(hostName)) { LOG.error("Expected 'request.hostName' to be localhost. hostName=$hostName, origin=${request.origin}") } diff --git a/platform/platform-impl/src/com/intellij/ide/IdeAboutInfoUtil.java b/platform/platform-impl/src/com/intellij/ide/IdeAboutInfoUtil.java deleted file mode 100644 index 3c695b572931..000000000000 --- a/platform/platform-impl/src/com/intellij/ide/IdeAboutInfoUtil.java +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. -package com.intellij.ide; - -import com.google.gson.stream.JsonWriter; -import com.intellij.openapi.application.ApplicationInfo; -import com.intellij.openapi.application.ApplicationNamesInfo; -import com.intellij.openapi.application.ex.ApplicationInfoEx; -import com.intellij.openapi.util.BuildNumber; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.util.PlatformUtils; - -import java.io.IOException; - -public final class IdeAboutInfoUtil { - public static void writeAboutJson(JsonWriter writer) throws IOException { - String appName = ApplicationInfoEx.getInstanceEx().getFullApplicationName(); - BuildNumber build = ApplicationInfo.getInstance().getBuild(); - - if (!PlatformUtils.isIdeaUltimate()) { - String productName = ApplicationNamesInfo.getInstance().getProductName(); - appName = appName.replace(productName + " (" + productName + ")", productName); - appName = StringUtil.trimStart(appName, "JetBrains "); - } - - writer.name("name").value(appName); - writer.name("productName").value(ApplicationNamesInfo.getInstance().getProductName()); - writer.name("baselineVersion").value(build.getBaselineVersion()); - if (!build.isSnapshot()) { - writer.name("buildNumber").value(build.asStringWithoutProductCode()); - } - } -} diff --git a/platform/platform-util-io/src/com/intellij/util/io/netty.kt b/platform/platform-util-io/src/com/intellij/util/io/netty.kt index b7af1733291c..b3b5be5c3599 100644 --- a/platform/platform-util-io/src/com/intellij/util/io/netty.kt +++ b/platform/platform-util-io/src/com/intellij/util/io/netty.kt @@ -197,12 +197,11 @@ val Channel.uriScheme: String val HttpRequest.host: String? get() = headers().getAsString(HttpHeaderNames.HOST) -val HttpRequest.hostName: String? - get() { - val hostAndPort = headers().getAsString(HttpHeaderNames.HOST)?.ifBlank { null } ?: return null - val portIndex = hostAndPort.lastIndexOf(':') - return if (portIndex > 0) hostAndPort.substring(0, portIndex).ifBlank { null } else hostAndPort - } +fun getHostName(httpRequest: HttpRequest): String? { + val hostAndPort = httpRequest.headers().getAsString(HttpHeaderNames.HOST)?.ifBlank { null } ?: return null + val portIndex = hostAndPort.lastIndexOf(':') + return if (portIndex > 0) hostAndPort.substring(0, portIndex).ifBlank { null } else hostAndPort +} val HttpRequest.origin: String? get() = headers().getAsString(HttpHeaderNames.ORIGIN) diff --git a/platform/platform-util-io/src/org/jetbrains/ide/HttpRequestHandler.kt b/platform/platform-util-io/src/org/jetbrains/ide/HttpRequestHandler.kt index c1696e0cb6a7..334d8b35128a 100644 --- a/platform/platform-util-io/src/org/jetbrains/ide/HttpRequestHandler.kt +++ b/platform/platform-util-io/src/org/jetbrains/ide/HttpRequestHandler.kt @@ -2,7 +2,7 @@ package org.jetbrains.ide import com.intellij.openapi.extensions.ExtensionPointName -import com.intellij.util.io.hostName +import com.intellij.util.io.getHostName import com.intellij.util.io.isLocalHost import com.intellij.util.io.isLocalOrigin import io.netty.channel.Channel @@ -42,7 +42,7 @@ abstract class HttpRequestHandler { */ @SuppressWarnings("SpellCheckingInspection") open fun isAccessible(request: HttpRequest): Boolean { - val hostName = request.hostName + val hostName = getHostName(request) // If attacker.com DNS rebound to 127.0.0.1 and user open site directly - no Origin or Referrer headers. // So we should check Host header. return hostName != null && request.isLocalOrigin() && isLocalHost(hostName)