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;
catch (Throwable e) {
LOG.error(e);
}
-
+
final OSProcessHandler processHandler = new OSProcessHandler(cmdLine) {
@Override
protected boolean shouldDestroyProcessRecursively() {
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();
}
/*
- * 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.
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;
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())));
}
--- /dev/null
+<root>
+ <item name='io.netty.resolver.HostsFileEntriesResolver java.net.InetAddress address(java.lang.String)'>
+ <annotation name='org.jetbrains.annotations.Nullable'/>
+ </item>
+</root>
\ No newline at end of file
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);
@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
public class BuiltInWebBrowserUrlProvider extends WebBrowserUrlProvider implements DumbAware {
@NotNull
public static List<Url> getUrls(@NotNull VirtualFile file, @NotNull Project project, @Nullable String currentAuthority) {
+ return getUrls(file, project, currentAuthority, true);
+ }
+
+ @NotNull
+ public static List<Url> getUrls(@NotNull VirtualFile file, @NotNull Project project, @Nullable String currentAuthority, boolean appendAccessToken) {
if (currentAuthority != null && !compareAuthority(currentAuthority)) {
return Collections.emptyList();
}
String path = info.getPath();
String authority = currentAuthority == null ? "localhost:" + effectiveBuiltInServerPort : currentAuthority;
- List<Url> urls = new SmartList<>(Urls.newHttpUrl(authority, '/' + project.getName() + '/' + path));
+ String query = appendAccessToken ? "?" + BuiltInWebServerKt.TOKEN_PARAM_NAME + "=" + BuiltInWebServerKt.acquireToken() : "";
+ List<Url> urls = new SmartList<>(Urls.newHttpUrl(authority, '/' + project.getName() + '/' + path, query));
String path2 = info.getRootLessPathIfPossible();
if (path2 != null) {
- urls.add(Urls.newHttpUrl(authority, '/' + project.getName() + '/' + path2));
+ urls.add(Urls.newHttpUrl(authority, '/' + project.getName() + '/' + path2, query));
}
int defaultPort = BuiltInServerManager.getInstance().getPort();
if (currentAuthority == null && defaultPort != effectiveBuiltInServerPort) {
String defaultAuthority = "localhost:" + defaultPort;
- urls.add(Urls.newHttpUrl(defaultAuthority, '/' + project.getName() + '/' + path));
+ urls.add(Urls.newHttpUrl(defaultAuthority, '/' + project.getName() + '/' + path, query));
if (path2 != null) {
- urls.add(Urls.newHttpUrl(defaultAuthority, '/' + project.getName() + '/' + path2));
+ urls.add(Urls.newHttpUrl(defaultAuthority, '/' + project.getName() + '/' + path2, query));
}
}
-
+
return urls;
}
return Urls.newFromVirtualFile(file);
}
else {
- return ContainerUtil.getFirstItem(getUrls(file, request.getProject(), null));
+ return ContainerUtil.getFirstItem(getUrls(file, request.getProject(), null, request.isAppendAccessToken()));
}
}
}
*/
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
import com.intellij.openapi.diagnostic.catchAndLog
+import com.intellij.openapi.ide.CopyPasteManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.ProjectManager
+import com.intellij.openapi.ui.MessageDialogBuilder
+import com.intellij.openapi.ui.Messages
import com.intellij.openapi.util.SystemInfoRt
import com.intellij.openapi.util.io.FileUtil
+import com.intellij.openapi.util.io.FileUtilRt
import com.intellij.openapi.util.io.endsWithName
+import com.intellij.openapi.util.registry.Registry
+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.*
import com.intellij.util.io.URLUtil
-import com.intellij.util.isDirectory
import com.intellij.util.net.NetUtils
+import io.netty.channel.Channel
import io.netty.channel.ChannelHandlerContext
-import io.netty.handler.codec.http.FullHttpRequest
-import io.netty.handler.codec.http.HttpMethod
-import io.netty.handler.codec.http.HttpResponseStatus
-import io.netty.handler.codec.http.QueryStringDecoder
+import io.netty.handler.codec.http.*
+import io.netty.handler.codec.http.cookie.DefaultCookie
+import io.netty.handler.codec.http.cookie.ServerCookieDecoder
+import io.netty.handler.codec.http.cookie.ServerCookieEncoder
+import org.jetbrains.ide.BuiltInServerManagerImpl
import org.jetbrains.ide.HttpRequestHandler
-import org.jetbrains.io.host
-import org.jetbrains.io.send
+import org.jetbrains.io.*
+import org.jetbrains.notification.SingletonNotificationManager
+import java.awt.datatransfer.StringSelection
+import java.io.IOException
+import java.math.BigInteger
import java.net.InetAddress
-import java.net.UnknownHostException
+import java.nio.file.Files
import java.nio.file.Path
+import java.nio.file.Paths
+import java.nio.file.attribute.PosixFileAttributeView
+import java.nio.file.attribute.PosixFilePermission
+import java.security.SecureRandom
+import java.util.*
+import java.util.concurrent.TimeUnit
+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.web.token"
+
+private val notificationManager by lazy {
+ SingletonNotificationManager(BuiltInServerManagerImpl.NOTIFICATION_GROUP.value, NotificationType.INFORMATION, null)
+}
+
class BuiltInWebServer : HttpRequestHandler() {
+ override fun isAccessible(request: HttpRequest) = request.isLocalOrigin(onlyAnyOrLoopback = false, hostsOnly = true)
+
override fun isSupported(request: FullHttpRequest) = super.isSupported(request) || request.method() == HttpMethod.POST
override fun process(urlDecoder: QueryStringDecoder, request: FullHttpRequest, context: ChannelHandlerContext): Boolean {
else {
projectName = host
}
- return doProcess(request, context, projectName)
+ return doProcess(urlDecoder, request, context, projectName)
+ }
+}
+
+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
+ val configPath = PathManager.getConfigPath()
+ val file = Paths.get(configPath, IDE_TOKEN_FILE)
+ var token: String? = null
+ if (file.exists()) {
+ try {
+ token = UUID.fromString(file.readText()).toString()
+ }
+ catch (e: Exception) {
+ LOG.warn(e)
+ }
+ }
+ if (token == null) {
+ token = UUID.randomUUID().toString()
+ file.write(token!!)
+ val view = Files.getFileAttributeView(file, PosixFileAttributeView::class.java)
+ if (view != null) {
+ try {
+ view.setPermissions(setOf(PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE))
+ }
+ catch (e: IOException) {
+ LOG.warn(e)
+ }
+ }
}
+
+ // 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(productName + "-" + Integer.toHexString(configPath.hashCode()), token!!)
+ cookie.isHttpOnly = true
+ cookie.setMaxAge(TimeUnit.DAYS.toSeconds(365 * 10))
+ cookie.setPath("/")
+ cookie
+}
+
+// expire after access because we reuse tokens
+private val tokens = CacheBuilder.newBuilder().expireAfterAccess(1, TimeUnit.MINUTES).build<String, Boolean>()
+
+fun acquireToken(): String {
+ var token = tokens.asMap().keys.firstOrNull()
+ if (token == null) {
+ 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(request: FullHttpRequest, context: ChannelHandlerContext, projectNameAsHost: String?): Boolean {
- val decodedPath = URLUtil.unescapePercentSequences(UriUtil.trimParameters(request.uri()))
+private fun doProcess(urlDecoder: QueryStringDecoder, request: FullHttpRequest, context: ChannelHandlerContext, projectNameAsHost: String?): Boolean {
+ val decodedPath = URLUtil.unescapePercentSequences(urlDecoder.path())
var offset: Int
var isEmptyPath: Boolean
val isCustomHost = projectNameAsHost != null
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)
+ redirectToDirectory(request, context.channel(), projectName, null)
return true
}
val path = toIdeaPath(decodedPath, offset)
if (path == null) {
- HttpResponseStatus.BAD_REQUEST.send(context.channel(), request)
+ HttpResponseStatus.BAD_REQUEST.orInSafeMode(HttpResponseStatus.NOT_FOUND).send(context.channel(), request)
return true
}
return false
}
+internal fun HttpRequest.isSignedRequest(): Boolean {
+ // we must check referrer - if html cached, browser will send request without query
+ val token = headers().get(TOKEN_HEADER_NAME)
+ ?: QueryStringDecoder(uri()).parameters().get(TOKEN_PARAM_NAME)?.firstOrNull()
+ ?: referrer?.let { QueryStringDecoder(it).parameters().get(TOKEN_PARAM_NAME)?.firstOrNull() }
+
+ if (token != null && tokens.getIfPresent(token) != null) {
+ tokens.invalidate(token)
+ return true
+ }
+ else {
+ return false
+ }
+}
+
+@JvmOverloads
+internal fun validateToken(request: HttpRequest, channel: Channel, isSignedRequest: Boolean = request.isSignedRequest()): HttpHeaders? {
+ request.headers().get(HttpHeaderNames.COOKIE)?.let {
+ for (cookie in ServerCookieDecoder.STRICT.decode(it)) {
+ if (cookie.name() == STANDARD_COOKIE.name()) {
+ if (cookie.value() == STANDARD_COOKIE.value()) {
+ return EmptyHttpHeaders.INSTANCE
+ }
+ break
+ }
+ }
+ }
+
+ if (isSignedRequest) {
+ return DefaultHttpHeaders().set(HttpHeaderNames.SET_COOKIE, ServerCookieEncoder.STRICT.encode(STANDARD_COOKIE) + "; SameSite=strict")
+ }
+
+ val urlDecoder = QueryStringDecoder(request.uri())
+ if (!urlDecoder.path().endsWith("/favicon.ico")) {
+ val url = "${channel.uriScheme}://${request.host!!}${urlDecoder.path()}"
+ SwingUtilities.invokeAndWait {
+ ProjectUtil.focusProjectWindow(null, true)
+
+ if (MessageDialogBuilder
+ .yesNo("", "Page '" + StringUtil.trimMiddle(url, 50) + "' requested without authorization, " +
+ "\nyou can copy URL and open it in browser to trust it.")
+ .icon(Messages.getWarningIcon())
+ .yesText("Copy authorization URL to clipboard")
+ .show() == Messages.YES) {
+ CopyPasteManager.getInstance().setContents(StringSelection(url + "?" + TOKEN_PARAM_NAME + "=" + acquireToken()))
+ }
+ }
+ }
+
+ HttpResponseStatus.UNAUTHORIZED.orInSafeMode(HttpResponseStatus.NOT_FOUND).send(channel, request)
+ return null
+}
+
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)
return null
}
-fun isOwnHostName(host: String): Boolean {
+// is host loopback/any or network interface address (i.e. not custom domain)
+// must be not used to check is host on local machine
+internal fun isOwnHostName(host: String): Boolean {
if (NetUtils.isLocalhost(host)) {
return true
}
// develar.local is own host name: develar. equals to "develar.labs.intellij.net" (canonical host name)
return localHostName.equals(host, ignoreCase = true) || (host.endsWith(".local") && localHostName.regionMatches(0, host, 0, host.length - ".local".length, true))
}
- catch (ignored: UnknownHostException) {
+ catch (ignored: IOException) {
return false
}
+}
+
+internal fun canBeAccessedDirectly(path: String): Boolean {
+ for (fileHandler in WebServerFileHandler.EP_NAME.extensions) {
+ for (ext in fileHandler.pageFileExtensions) {
+ if (FileUtilRt.extensionEquals(path, ext)) {
+ return true
+ }
+ }
+ }
+ return false
}
\ No newline at end of file
import com.intellij.openapi.util.io.endsWithName
import com.intellij.openapi.util.io.endsWithSlash
import com.intellij.openapi.util.io.getParentPath
+import com.intellij.openapi.vfs.VFileProperty
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.util.PathUtilRt
+import com.intellij.util.isDirectory
+import io.netty.channel.Channel
import io.netty.channel.ChannelHandlerContext
import io.netty.handler.codec.http.FullHttpRequest
+import io.netty.handler.codec.http.HttpRequest
import io.netty.handler.codec.http.HttpResponseStatus
-import org.jetbrains.io.send
+import org.jetbrains.io.*
import java.nio.file.Path
+import java.nio.file.Paths
private class DefaultWebServerPathHandler : WebServerPathHandler() {
override fun process(path: String,
decodedRawPath: String,
isCustomHost: Boolean): Boolean {
val channel = context.channel()
+
+ val isSignedRequest = request.isSignedRequest()
+ val extraHttpHeaders = validateToken(request, channel, isSignedRequest) ?: return true
+
val pathToFileManager = WebServerPathToFileManager.getInstance(project)
var pathInfo = pathToFileManager.pathToInfoCache.getIfPresent(path)
if (pathInfo == null || !pathInfo.isValid) {
pathInfo = pathToFileManager.doFindByRelativePath(path)
if (pathInfo == null) {
- if (path.isEmpty()) {
- HttpResponseStatus.NOT_FOUND.send(channel, request, "Index file doesn't exist.")
- return true
- }
- else {
- return false
- }
+ HttpResponseStatus.NOT_FOUND.send(channel, request, if (path.isEmpty()) "Index file doesn't exist." else null, extraHttpHeaders)
+ return true
}
pathToFileManager.pathToInfoCache.put(path, pathInfo)
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) {
}
if (indexFile == null && indexVirtualFile == null) {
- HttpResponseStatus.NOT_FOUND.send(channel, request, "Index file doesn't exist.")
+ 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", extraHttpHeaders)
return true
}
pathToFileManager.pathToInfoCache.put(path, pathInfo)
}
+ // if extraHttpHeaders is not empty, it means that we get request wih token in the query
+ if (!isSignedRequest && request.origin == null && request.referrer == null && request.isRegularBrowser() && !canBeAccessedDirectly(pathInfo.name)) {
+ HttpResponseStatus.NOT_FOUND.send(channel, request)
+ return true
+ }
+
if (!indexUsed && !endsWithName(path, pathInfo.name)) {
if (endsWithSlash(decodedRawPath)) {
indexUsed = true
// 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
}
}
}
+ 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 {
- 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
}
}
}
+
+ // we registered as a last handler, so, we should just return 404 and send extra headers
+ HttpResponseStatus.NOT_FOUND.send(channel, request, extraHeaders = extraHttpHeaders)
+ return true
+ }
+}
+
+private fun checkAccess(pathInfo: PathInfo, channel: Channel, request: HttpRequest): Boolean {
+ if (pathInfo.ioFile != null || pathInfo.file!!.isInLocalFileSystem) {
+ val file = pathInfo.ioFile ?: Paths.get(pathInfo.file!!.path)
+ if (file.isDirectory()) {
+ HttpResponseStatus.FORBIDDEN.orInSafeMode(HttpResponseStatus.NOT_FOUND).send(channel, request)
+ return false
+ }
+ else if (!checkAccess(file, Paths.get(pathInfo.root.path))) {
+ HttpResponseStatus.FORBIDDEN.orInSafeMode(HttpResponseStatus.NOT_FOUND).send(channel, request)
+ return false
+ }
+ }
+ else if (pathInfo.file!!.`is`(VFileProperty.HIDDEN)) {
+ HttpResponseStatus.FORBIDDEN.orInSafeMode(HttpResponseStatus.NOT_FOUND).send(channel, request)
return false
}
+
+ return true
}
\ No newline at end of file
package org.jetbrains.builtInWebServer
import com.intellij.openapi.project.Project
-import com.intellij.openapi.util.text.StringUtilRt
import com.intellij.util.PathUtilRt
-import com.intellij.util.isDirectory
import io.netty.buffer.ByteBufUtf8Writer
import io.netty.channel.Channel
import io.netty.channel.ChannelFutureListener
import org.jetbrains.builtInWebServer.ssi.SsiProcessor
import org.jetbrains.io.FileResponses
import org.jetbrains.io.addKeepAliveIfNeed
-import org.jetbrains.io.send
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
private class StaticFileHandler : WebServerFileHandler() {
+ override val pageFileExtensions = arrayOf("html", "htm", "shtml", "stm", "shtm")
+
private var ssiProcessor: SsiProcessor? = null
- override fun process(pathInfo: PathInfo, canonicalPath: CharSequence, project: Project, request: FullHttpRequest, channel: Channel, projectNameIfNotCustomHost: String?): Boolean {
+ 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 = pathInfo.name
+ val nameSequence = ioFile.fileName.toString()
//noinspection SpellCheckingInspection
- if (StringUtilRt.endsWithIgnoreCase(nameSequence, ".shtml") || StringUtilRt.endsWithIgnoreCase(nameSequence, ".stm") || StringUtilRt.endsWithIgnoreCase(nameSequence, ".shtm")) {
- processSsi(ioFile, PathUtilRt.getParentPath(canonicalPath.toString()), project, request, channel)
+ if (nameSequence.endsWith(".shtml", true) || nameSequence.endsWith(".stm", true) || nameSequence.endsWith(".shtm", true)) {
+ processSsi(ioFile, PathUtilRt.getParentPath(canonicalPath.toString()), project, request, channel, extraHeaders)
return true
}
- sendIoFile(channel, ioFile, request)
+ 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) {
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)
}
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())
}
}
-fun sendIoFile(channel: Channel, ioFile: Path, request: HttpRequest) {
- if (hasAccess(ioFile)) {
- FileResponses.sendFile(request, channel, ioFile)
- }
- else {
- HttpResponseStatus.FORBIDDEN.send(channel, request)
+internal fun checkAccess(file: Path, root: Path = file.root): Boolean {
+ var parent = file
+ do {
+ if (!hasAccess(parent)) {
+ return false
+ }
+ parent = parent.parent ?: break
}
+ while (parent != root)
+ return true
}
-// deny access to .htaccess files
-private fun hasAccess(result: Path) = !result.isDirectory() && Files.isReadable(result) && !(Files.isHidden(result) || result.fileName.toString().startsWith(".ht"))
\ No newline at end of file
+// deny access to any dot prefixed file
+private fun hasAccess(result: Path) = Files.isReadable(result) && !(Files.isHidden(result) || result.fileName.toString().startsWith('.'))
\ No newline at end of file
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 {
internal val EP_NAME = ExtensionPointName.create<WebServerFileHandler>("org.jetbrains.webServerFileHandler")
}
+ open val pageFileExtensions: Array<String>
+ get() = emptyArray()
+
/**
* canonicalRequestPath contains index file name (if not specified in the request)
*/
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
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
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, 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())
- response.send(channel, request)
+ response.send(channel, request, extraHeaders)
}
\ No newline at end of file
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.io.IOException;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.URLConnection;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicBoolean;
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;
}
}
}
+
+ public static boolean isOnBuiltInWebServerByAuthority(@NotNull String authority) {
+ int portIndex = authority.indexOf(':');
+ if (portIndex < 0 || portIndex == authority.length() - 1) {
+ return false;
+ }
+
+ int port = StringUtil.parseInt(authority.substring(portIndex + 1), -1);
+ if (port == -1) {
+ return false;
+ }
+
+ BuiltInServerOptions options = BuiltInServerOptions.getInstance();
+ int idePort = BuiltInServerManager.getInstance().getPort();
+ if (options.builtInServerPort != port && idePort != port) {
+ return false;
+ }
+
+ String host = authority.substring(0, portIndex);
+ if (NetUtils.isLocalhost(host)) {
+ return true;
+ }
+
+ try {
+ InetAddress inetAddress = InetAddress.getByName(host);
+ return inetAddress.isLoopbackAddress() ||
+ inetAddress.isAnyLocalAddress() ||
+ (options.builtInServerAvailableExternally && idePort != port && NetworkInterface.getByInetAddress(inetAddress) != null);
+ }
+ catch (IOException e) {
+ return false;
+ }
+ }
}
\ No newline at end of file
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;
reader.endArray();
return null;
}
+
+ @Override
+ public boolean isAccessible(@NotNull HttpRequest request) {
+ return true;
+ }
}
+++ /dev/null
-/*
- * Copyright 2000-2015 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.jetbrains.ide;
-
-import com.intellij.openapi.application.AccessToken;
-import com.intellij.openapi.application.ApplicationManager;
-import com.intellij.openapi.application.ModalityState;
-import com.intellij.openapi.application.WriteAction;
-import com.intellij.openapi.fileEditor.OpenFileDescriptor;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.project.ProjectManager;
-import com.intellij.openapi.project.ProjectUtil;
-import com.intellij.openapi.util.Ref;
-import com.intellij.openapi.util.io.FileUtil;
-import com.intellij.openapi.util.text.StringUtil;
-import com.intellij.openapi.util.text.StringUtilRt;
-import com.intellij.openapi.vcs.ProjectLevelVcsManager;
-import com.intellij.openapi.vcs.VcsRoot;
-import com.intellij.openapi.vfs.LocalFileSystem;
-import com.intellij.openapi.vfs.VirtualFile;
-import com.intellij.openapi.vfs.newvfs.ManagingFS;
-import com.intellij.openapi.vfs.newvfs.RefreshQueue;
-import com.intellij.openapi.vfs.newvfs.RefreshSession;
-import com.intellij.ui.AppUIUtil;
-import com.intellij.util.Consumer;
-import io.netty.channel.Channel;
-import io.netty.channel.ChannelHandlerContext;
-import io.netty.handler.codec.http.*;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-import org.jetbrains.builtInWebServer.WebServerPathToFileManager;
-import org.jetbrains.concurrency.AsyncPromise;
-import org.jetbrains.concurrency.Promise;
-
-import javax.swing.*;
-import java.io.File;
-import java.io.IOException;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * @api {get} /file Open file
- * @apiName file
- * @apiGroup Platform
- *
- * @apiParam {String} file The path of the file. Relative (to project base dir, VCS root, module source or content root) or absolute.
- * @apiParam {Integer} [line] The line number of the file (1-based).
- * @apiParam {Integer} [column] The column number of the file (1-based).
- * @apiParam {Boolean} [focused=true] Whether to focus project window.
- *
- * @apiExample {curl} Absolute path
- * curl http://localhost:63342/api/file//absolute/path/to/file.kt
- *
- * @apiExample {curl} Relative path
- * curl http://localhost:63342/api/file/relative/to/module/root/path/to/file.kt
- *
- * @apiExample {curl} With line and column
- * curl http://localhost:63342/api/file/relative/to/module/root/path/to/file.kt:100:34
- *
- * @apiExample {curl} Query parameters
- * curl http://localhost:63342/api/file?file=path/to/file.kt&line=100&column=34
- */
-class OpenFileHttpService extends RestService {
- private static final RuntimeException NOT_FOUND = Promise.createError("not found");
- private static final Pattern LINE_AND_COLUMN = Pattern.compile("^(.*?)(?::(\\d+))?(?::(\\d+))?$");
- private long lastTimeRejected = -1;
- private long waitUntilNextRequestTimeout = 0;
-
- private volatile long refreshSessionId = 0;
- private final ConcurrentLinkedQueue<OpenFileTask> requests = new ConcurrentLinkedQueue<OpenFileTask>();
-
- @NotNull
- @Override
- protected String getServiceName() {
- return "file";
- }
-
- @Override
- protected boolean isMethodSupported(@NotNull HttpMethod method) {
- return method == HttpMethod.GET || method == HttpMethod.POST;
- }
-
- @Nullable
- @Override
- public String execute(@NotNull QueryStringDecoder urlDecoder, @NotNull FullHttpRequest request, @NotNull ChannelHandlerContext context) throws IOException {
- final boolean keepAlive = HttpUtil.isKeepAlive(request);
- final Channel channel = context.channel();
-
- OpenFileRequest apiRequest;
- if (request.method() == HttpMethod.POST) {
- apiRequest = gson.getValue().fromJson(createJsonReader(request), OpenFileRequest.class);
- }
- else {
- apiRequest = new OpenFileRequest();
- apiRequest.file = StringUtil.nullize(getStringParameter("file", urlDecoder), true);
- apiRequest.line = getIntParameter("line", urlDecoder);
- apiRequest.column = getIntParameter("column", urlDecoder);
- apiRequest.focused = getBooleanParameter("focused", urlDecoder, true);
- }
-
- int prefixLength = 1 + PREFIX.length() + 1 + getServiceName().length() + 1;
- String path = urlDecoder.path();
- if (path.length() > prefixLength) {
- Matcher matcher = LINE_AND_COLUMN.matcher(path).region(prefixLength, path.length());
- LOG.assertTrue(matcher.matches());
- if (apiRequest.file == null) {
- apiRequest.file = matcher.group(1).trim();
- }
- if (apiRequest.line == -1) {
- apiRequest.line = StringUtilRt.parseInt(matcher.group(2), 1);
- }
- if (apiRequest.column == -1) {
- apiRequest.column = StringUtilRt.parseInt(matcher.group(3), 1);
- }
- }
-
- if (apiRequest.file == null) {
- sendStatus(HttpResponseStatus.BAD_REQUEST, keepAlive, channel);
- return null;
- }
-
- openFile(apiRequest)
- .done(new Consumer<Void>() {
- @Override
- public void consume(Void aVoid) {
- sendStatus(HttpResponseStatus.OK, keepAlive, channel);
- }
- })
- .rejected(new Consumer<Throwable>() {
- @Override
- public void consume(Throwable throwable) {
- if (throwable == NOT_FOUND) {
- sendStatus(HttpResponseStatus.NOT_FOUND, keepAlive, channel);
- }
- else {
- // todo send error
- sendStatus(HttpResponseStatus.INTERNAL_SERVER_ERROR, keepAlive, channel);
- LOG.error(throwable);
- }
- }
- });
- return null;
- }
-
- private static void navigate(@Nullable Project project, @NotNull VirtualFile file, @NotNull OpenFileRequest request) {
- if (project == null) {
- project = getLastFocusedOrOpenedProject();
- if (project == null) {
- project = ProjectManager.getInstance().getDefaultProject();
- }
- }
-
- // OpenFileDescriptor line and column number are 0-based.
- new OpenFileDescriptor(project, file, Math.max(request.line - 1, 0), Math.max(request.column - 1, 0)).navigate(true);
- if (request.focused) {
- com.intellij.ide.impl.ProjectUtil.focusProjectWindow(project, true);
- }
- }
-
- @NotNull
- Promise<Void> openFile(@NotNull OpenFileRequest request) {
- String systemIndependentName = FileUtil.toSystemIndependentName(FileUtil.expandUserHome(request.file));
- final File file = new File(systemIndependentName);
-
- if (file.isAbsolute()) {
- if (com.intellij.ide.impl.ProjectUtil.isRemotePath(systemIndependentName)) {
- final Ref<Promise<Void>> result = new Ref<>();
- try {
- SwingUtilities.invokeAndWait(new Runnable() {
- @Override
- public void run() {
- if (System.currentTimeMillis() - lastTimeRejected < waitUntilNextRequestTimeout) {
- return;
- }
- boolean value = com.intellij.ide.impl.ProjectUtil
- .confirmLoadingFromRemotePath(systemIndependentName, "warning.load.file.from.share", "title.load.file.from.share");
-
- if (value != Boolean.TRUE) {
- lastTimeRejected = System.currentTimeMillis();
- waitUntilNextRequestTimeout = Math.min(2 * Math.max(waitUntilNextRequestTimeout, 2000), 60 * 60 * 1000); //to avoid negative values
- result.set(Promise.reject(NOT_FOUND));
- } else {
- waitUntilNextRequestTimeout = 0;
- }
- }
- });
- } catch (Throwable ignored) {}
-
- if (result.get() != null) {
- return result.get();
- }
- }
-
- return openAbsolutePath(file, request);
- }
-
- // we don't want to call refresh for each attempt on findFileByRelativePath call, so, we do what ourSaveAndSyncHandlerImpl does on frame activation
- RefreshQueue queue = RefreshQueue.getInstance();
- queue.cancelSession(refreshSessionId);
- OpenFileTask task = new OpenFileTask(FileUtil.toCanonicalPath(systemIndependentName, '/'), request);
- requests.offer(task);
- RefreshSession session = queue.createSession(true, true, new Runnable() {
- @Override
- public void run() {
- OpenFileTask task;
- while ((task = requests.poll()) != null) {
- try {
- if (openRelativePath(task.path, task.request)) {
- task.promise.setResult(null);
- }
- else {
- task.promise.setError(NOT_FOUND);
- }
- }
- catch (Throwable e) {
- task.promise.setError(e);
- }
- }
- }
- }, ModalityState.NON_MODAL);
-
- session.addAllFiles(ManagingFS.getInstance().getLocalRoots());
- refreshSessionId = session.getId();
- session.launch();
- return task.promise;
- }
-
- // path must be normalized
- private static boolean openRelativePath(@NotNull final String path, @NotNull final OpenFileRequest request) {
- VirtualFile virtualFile = null;
- Project project = null;
-
- Project[] projects = ProjectManager.getInstance().getOpenProjects();
- for (Project openedProject : projects) {
- VirtualFile openedProjectBaseDir = openedProject.getBaseDir();
- if (openedProjectBaseDir != null) {
- virtualFile = openedProjectBaseDir.findFileByRelativePath(path);
- }
-
- if (virtualFile == null) {
- virtualFile = WebServerPathToFileManager.getInstance(openedProject).findVirtualFile(path);
- }
- if (virtualFile != null) {
- project = openedProject;
- break;
- }
- }
-
- if (virtualFile == null) {
- for (Project openedProject : projects) {
- for (VcsRoot vcsRoot : ProjectLevelVcsManager.getInstance(openedProject).getAllVcsRoots()) {
- VirtualFile root = vcsRoot.getPath();
- if (root != null) {
- virtualFile = root.findFileByRelativePath(path);
- if (virtualFile != null) {
- project = openedProject;
- break;
- }
- }
- }
- }
- }
-
- if (virtualFile == null) {
- return false;
- }
-
- final Project finalProject = project;
- final VirtualFile finalVirtualFile = virtualFile;
- AppUIUtil.invokeLaterIfProjectAlive(project, new Runnable() {
- @Override
- public void run() {
- navigate(finalProject, finalVirtualFile, request);
- }
- });
- return true;
- }
-
- @NotNull
- private static Promise<Void> openAbsolutePath(@NotNull final File file, @NotNull final OpenFileRequest request) {
- if (!file.exists()) {
- return Promise.reject(NOT_FOUND);
- }
-
- final AsyncPromise<Void> promise = new AsyncPromise<Void>();
- ApplicationManager.getApplication().invokeLater(new Runnable() {
- @Override
- public void run() {
- try {
- VirtualFile virtualFile;
- AccessToken token = WriteAction.start();
- try {
- virtualFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(file);
- }
- finally {
- token.finish();
- }
-
- if (virtualFile == null) {
- promise.setError(NOT_FOUND);
- }
- else {
- navigate(ProjectUtil.guessProjectForContentFile(virtualFile), virtualFile, request);
- promise.setResult(null);
- }
- }
- catch (Throwable e) {
- promise.setError(e);
- }
- }
- });
- return promise;
- }
-
- private static final class OpenFileTask {
- final String path;
- final OpenFileRequest request;
-
- final AsyncPromise<Void> promise = new AsyncPromise<Void>();
-
- OpenFileTask(@NotNull String path, @NotNull OpenFileRequest request) {
- this.path = path;
- this.request = request;
- }
- }
-
- static final class OpenFileRequest {
- public String file;
- // The line number of the file (1-based)
- public int line;
- // The column number of the file (1-based)
- public int column;
-
- public boolean focused = true;
- }
-}
--- /dev/null
+/*
+ * Copyright 2000-2015 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.ide
+
+import com.intellij.ide.impl.ProjectUtil.focusProjectWindow
+import com.intellij.openapi.application.ApplicationManager
+import com.intellij.openapi.application.ModalityState
+import com.intellij.openapi.application.runWriteAction
+import com.intellij.openapi.fileEditor.OpenFileDescriptor
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.project.ProjectManager
+import com.intellij.openapi.project.ProjectUtil
+import com.intellij.openapi.util.io.FileUtil
+import com.intellij.openapi.util.text.StringUtil
+import com.intellij.openapi.util.text.StringUtilRt
+import com.intellij.openapi.vcs.ProjectLevelVcsManager
+import com.intellij.openapi.vfs.LocalFileSystem
+import com.intellij.openapi.vfs.VirtualFile
+import com.intellij.openapi.vfs.newvfs.ManagingFS
+import com.intellij.openapi.vfs.newvfs.RefreshQueue
+import com.intellij.ui.AppUIUtil
+import com.intellij.util.exists
+import com.intellij.util.systemIndependentPath
+import io.netty.channel.ChannelHandlerContext
+import io.netty.handler.codec.http.*
+import org.jetbrains.builtInWebServer.WebServerPathToFileManager
+import org.jetbrains.builtInWebServer.checkAccess
+import org.jetbrains.concurrency.AsyncPromise
+import org.jetbrains.concurrency.Promise
+import org.jetbrains.concurrency.catchError
+import org.jetbrains.concurrency.rejectedPromise
+import org.jetbrains.io.orInSafeMode
+import org.jetbrains.io.send
+import java.nio.file.Path
+import java.nio.file.Paths
+import java.util.concurrent.ConcurrentLinkedQueue
+import java.util.regex.Pattern
+import javax.swing.SwingUtilities
+
+private val NOT_FOUND = Promise.createError("not found")
+private val LINE_AND_COLUMN = Pattern.compile("^(.*?)(?::(\\d+))?(?::(\\d+))?$")
+
+/**
+ * @api {get} /file Open file
+ * @apiName file
+ * @apiGroup Platform
+ *
+ * @apiParam {String} file The path of the file. Relative (to project base dir, VCS root, module source or content root) or absolute.
+ * @apiParam {Integer} [line] The line number of the file (1-based).
+ * @apiParam {Integer} [column] The column number of the file (1-based).
+ * @apiParam {Boolean} [focused=true] Whether to focus project window.
+ *
+ * @apiExample {curl} Absolute path
+ * curl http://localhost:63342/api/file//absolute/path/to/file.kt
+ *
+ * @apiExample {curl} Relative path
+ * curl http://localhost:63342/api/file/relative/to/module/root/path/to/file.kt
+ *
+ * @apiExample {curl} With line and column
+ * curl http://localhost:63342/api/file/relative/to/module/root/path/to/file.kt:100:34
+ *
+ * @apiExample {curl} Query parameters
+ * curl http://localhost:63342/api/file?file=path/to/file.kt&line=100&column=34
+ */
+internal class OpenFileHttpService : RestService() {
+ @Volatile private var refreshSessionId: Long = 0
+ private val requests = ConcurrentLinkedQueue<OpenFileTask>()
+
+ override fun getServiceName() = "file"
+
+ override fun isMethodSupported(method: HttpMethod) = method === HttpMethod.GET || method === HttpMethod.POST
+
+ override fun execute(urlDecoder: QueryStringDecoder, request: FullHttpRequest, context: ChannelHandlerContext): String? {
+ val keepAlive = HttpUtil.isKeepAlive(request)
+ val channel = context.channel()
+
+ val apiRequest: OpenFileRequest
+ if (request.method() === HttpMethod.POST) {
+ apiRequest = gson.value.fromJson(createJsonReader(request), OpenFileRequest::class.java)
+ }
+ else {
+ apiRequest = OpenFileRequest()
+ apiRequest.file = StringUtil.nullize(getStringParameter("file", urlDecoder), true)
+ apiRequest.line = getIntParameter("line", urlDecoder)
+ apiRequest.column = getIntParameter("column", urlDecoder)
+ apiRequest.focused = getBooleanParameter("focused", urlDecoder, true)
+ }
+
+ val prefixLength = 1 + PREFIX.length + 1 + serviceName.length + 1
+ val path = urlDecoder.path()
+ if (path.length > prefixLength) {
+ val matcher = LINE_AND_COLUMN.matcher(path).region(prefixLength, path.length)
+ LOG.assertTrue(matcher.matches())
+ if (apiRequest.file == null) {
+ apiRequest.file = matcher.group(1).trim { it <= ' ' }
+ }
+ if (apiRequest.line == -1) {
+ apiRequest.line = StringUtilRt.parseInt(matcher.group(2), 1)
+ }
+ if (apiRequest.column == -1) {
+ apiRequest.column = StringUtilRt.parseInt(matcher.group(3), 1)
+ }
+ }
+
+ if (apiRequest.file == null) {
+ sendStatus(HttpResponseStatus.BAD_REQUEST, keepAlive, channel)
+ return null
+ }
+
+ val promise = openFile(apiRequest, context, request) ?: return null
+ promise.done { sendStatus(HttpResponseStatus.OK, keepAlive, channel) }
+ .rejected {
+ if (it === NOT_FOUND) {
+ // don't expose file status
+ sendStatus(HttpResponseStatus.NOT_FOUND.orInSafeMode(HttpResponseStatus.OK), keepAlive, channel)
+ LOG.warn("File ${apiRequest.file} not found")
+ }
+ else {
+ // todo send error
+ sendStatus(HttpResponseStatus.INTERNAL_SERVER_ERROR, keepAlive, channel)
+ LOG.error(it)
+ }
+ }
+ return null
+ }
+
+ internal fun openFile(request: OpenFileRequest, context: ChannelHandlerContext, httpRequest: HttpRequest?): Promise<Void>? {
+ val systemIndependentPath = FileUtil.toSystemIndependentName(FileUtil.expandUserHome(request.file!!))
+ val file = Paths.get(FileUtil.toSystemDependentName(systemIndependentPath))
+ if (file.isAbsolute) {
+ if (!file.exists()) {
+ return rejectedPromise(NOT_FOUND)
+ }
+
+ var isAllowed = checkAccess(file)
+ if (isAllowed && com.intellij.ide.impl.ProjectUtil.isRemotePath(systemIndependentPath)) {
+ // invokeAndWait is added to avoid processing many requests in this place: e.g. to prevent abuse of opening many remote files
+ SwingUtilities.invokeAndWait {
+ isAllowed = com.intellij.ide.impl.ProjectUtil.confirmLoadingFromRemotePath(systemIndependentPath, "warning.load.file.from.share", "title.load.file.from.share")
+ }
+ }
+
+ if (isAllowed) {
+ return openAbsolutePath(file, request)
+ }
+ else {
+ HttpResponseStatus.FORBIDDEN.orInSafeMode(HttpResponseStatus.OK).send(context.channel(), httpRequest)
+ return null
+ }
+ }
+
+ // we don't want to call refresh for each attempt on findFileByRelativePath call, so, we do what ourSaveAndSyncHandlerImpl does on frame activation
+ val queue = RefreshQueue.getInstance()
+ queue.cancelSession(refreshSessionId)
+ val mainTask = OpenFileTask(FileUtil.toCanonicalPath(systemIndependentPath, '/'), request)
+ requests.offer(mainTask)
+ val session = queue.createSession(true, true, {
+ while (true) {
+ val task = requests.poll() ?: break
+ task.promise.catchError {
+ if (openRelativePath(task.path, task.request)) {
+ task.promise.setResult(null)
+ }
+ else {
+ task.promise.setError(NOT_FOUND)
+ }
+ }
+ }
+ }, ModalityState.NON_MODAL)
+
+ session.addAllFiles(*ManagingFS.getInstance().localRoots)
+ refreshSessionId = session.id
+ session.launch()
+ return mainTask.promise
+ }
+
+ override fun isAccessible(request: HttpRequest) = true
+}
+
+internal class OpenFileRequest {
+ var file: String? = null
+ // The line number of the file (1-based)
+ var line = 0
+ // The column number of the file (1-based)
+ var column = 0
+
+ var focused = true
+}
+
+private class OpenFileTask(internal val path: String, internal val request: OpenFileRequest) {
+ internal val promise = AsyncPromise<Void>()
+}
+
+private fun navigate(project: Project?, file: VirtualFile, request: OpenFileRequest) {
+ val effectiveProject = project ?: RestService.getLastFocusedOrOpenedProject() ?: ProjectManager.getInstance().defaultProject
+ // OpenFileDescriptor line and column number are 0-based.
+ OpenFileDescriptor(effectiveProject, file, Math.max(request.line - 1, 0), Math.max(request.column - 1, 0)).navigate(true)
+ if (request.focused) {
+ focusProjectWindow(project, true)
+ }
+}
+
+// path must be normalized
+private fun openRelativePath(path: String, request: OpenFileRequest): Boolean {
+ var virtualFile: VirtualFile? = null
+ var project: Project? = null
+
+ val projects = ProjectManager.getInstance().openProjects
+ for (openedProject in projects) {
+ openedProject.baseDir?.let {
+ virtualFile = it.findFileByRelativePath(path)
+ }
+
+ if (virtualFile == null) {
+ virtualFile = WebServerPathToFileManager.getInstance(openedProject).findVirtualFile(path)
+ }
+ if (virtualFile != null) {
+ project = openedProject
+ break
+ }
+ }
+
+ if (virtualFile == null) {
+ for (openedProject in projects) {
+ for (vcsRoot in ProjectLevelVcsManager.getInstance(openedProject).allVcsRoots) {
+ val root = vcsRoot.path
+ if (root != null) {
+ virtualFile = root.findFileByRelativePath(path)
+ if (virtualFile != null) {
+ project = openedProject
+ break
+ }
+ }
+ }
+ }
+ }
+
+ return virtualFile?.let {
+ AppUIUtil.invokeLaterIfProjectAlive(project!!, Runnable { navigate(project, it, request) })
+ true
+ } ?: false
+}
+
+private fun openAbsolutePath(file: Path, request: OpenFileRequest): Promise<Void> {
+ val promise = AsyncPromise<Void>()
+ ApplicationManager.getApplication().invokeLater {
+ promise.catchError {
+ val virtualFile = runWriteAction { LocalFileSystem.getInstance().refreshAndFindFileByPath(file.systemIndependentPath) }
+ if (virtualFile == null) {
+ promise.setError(NOT_FOUND)
+ }
+ else {
+ navigate(ProjectUtil.guessProjectForContentFile(virtualFile), virtualFile, request)
+ promise.setResult(null)
+ }
+ }
+ }
+ return promise
+}
+++ /dev/null
-/*
- * Copyright 2000-2015 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.jetbrains.ide;
-
-import com.intellij.openapi.diagnostic.Logger;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.concurrency.Promise;
-
-class OpenFileXmlRpcHandler {
- private static final Logger LOG = Logger.getInstance(OpenFileXmlRpcHandler.class);
-
- // XML-RPC interface method - keep the signature intact
- @SuppressWarnings("UnusedDeclaration")
- public boolean open(String path) {
- LOG.debug("open(" + path + ")");
- return doOpen(path, -1, -1);
- }
-
- // XML-RPC interface method - keep the signature intact
- @SuppressWarnings("UnusedDeclaration")
- public boolean openAndNavigate(String path, int line, int column) {
- LOG.debug("openAndNavigate(" + path + ", " + line + ", " + column + ")");
- return doOpen(path, line, column);
- }
-
- private static boolean doOpen(@NotNull String path, int line, int column) {
- OpenFileHttpService.OpenFileRequest request = new OpenFileHttpService.OpenFileRequest();
- request.file = path;
- request.line = line;
- request.column = column;
- request.focused = false;
- return HttpRequestHandler.EP_NAME.findExtension(OpenFileHttpService.class).openFile(request).getState() != Promise.State.REJECTED;
- }
-}
\ No newline at end of file
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;
sendOk(request, context);
return null;
}
+
+ @Override
+ public boolean isAccessible(@NotNull HttpRequest request) {
+ return true;
+ }
}
*/
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;
import io.netty.handler.codec.http.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
+import org.jetbrains.io.NettyKt;
import org.jetbrains.io.Responses;
+import javax.swing.*;
import java.awt.*;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
+import java.lang.reflect.InvocationTargetException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.URI;
+import java.net.URISyntaxException;
import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static com.intellij.ide.impl.ProjectUtil.showYesNoDialog;
/**
* Document your service using <a href="http://apidocjs.com">apiDoc</a>. To extract big example from source code, consider to use *.coffee file near your source file.
}
};
+ private final LoadingCache<InetAddress, AtomicInteger> abuseCounter =
+ CacheBuilder.newBuilder().expireAfterWrite(1, TimeUnit.MINUTES).build(CacheLoader.from((Supplier<AtomicInteger>)AtomicInteger::new));
+
+ private final Cache<String, Boolean> trustedOrigins =
+ CacheBuilder.newBuilder().maximumSize(1024).expireAfterWrite(1, TimeUnit.DAYS).build();
+
@Override
public final boolean isSupported(@NotNull FullHttpRequest request) {
if (!isMethodSupported(request.method())) {
@Override
public final boolean process(@NotNull QueryStringDecoder urlDecoder, @NotNull FullHttpRequest request, @NotNull ChannelHandlerContext context) throws IOException {
try {
+ AtomicInteger counter = abuseCounter.get(((InetSocketAddress)context.channel().remoteAddress()).getAddress());
+ if (counter.incrementAndGet() > Registry.intValue("ide.rest.api.requests.per.minute", 30)) {
+ Responses.send(Responses.orInSafeMode(HttpResponseStatus.TOO_MANY_REQUESTS, HttpResponseStatus.OK), context.channel(), request);
+ return true;
+ }
+
+ if (!isHostTrusted(request)) {
+ Responses.send(Responses.orInSafeMode(HttpResponseStatus.FORBIDDEN, HttpResponseStatus.OK), context.channel(), request);
+ return true;
+ }
+
String error = execute(urlDecoder, request, context);
if (error != null) {
Responses.send(HttpResponseStatus.BAD_REQUEST, context.channel(), request, error);
return true;
}
- protected final void activateLastFocusedFrame() {
+ private boolean isHostTrusted(@NotNull FullHttpRequest request) throws InterruptedException, InvocationTargetException {
+ String referrer = NettyKt.getOrigin(request);
+ if (referrer == null) {
+ referrer = NettyKt.getReferrer(request);
+ }
+
+ String host;
+ try {
+ host = StringUtil.nullize(referrer == null ? null : new URI(referrer).getHost());
+ }
+ catch (URISyntaxException ignored) {
+ return false;
+ }
+
+ Ref<Boolean> isTrusted = Ref.create();
+ if (host != null) {
+ if (NetUtils.isLocalhost(host)) {
+ isTrusted.set(true);
+ }
+ else {
+ isTrusted.set(trustedOrigins.getIfPresent(host));
+ }
+ }
+
+ if (isTrusted.isNull()) {
+ SwingUtilities.invokeAndWait(() -> {
+ isTrusted.set(showYesNoDialog(
+ IdeBundle.message("warning.use.rest.api", getServiceName(), ObjectUtils.chooseNotNull(host, "unknown host")),
+ "title.use.rest.api"));
+ if (host != null) {
+ trustedOrigins.put(host, isTrusted.get());
+ }
+ });
+ }
+ return isTrusted.get();
+ }
+
+ protected static void activateLastFocusedFrame() {
IdeFrame frame = IdeFocusManager.getGlobalInstance().getLastFocusedFrame();
if (frame instanceof Window) {
((Window)frame).toFront();
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);
}
*/
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;
public class XmlRpcServerImpl implements XmlRpcServer {
private static final Logger LOG = Logger.getInstance(XmlRpcServerImpl.class);
- private final Map<String, Object> handlerMapping;
+ private final Map<String, Object> handlerMapping = new THashMap<String, Object>();
public XmlRpcServerImpl() {
- handlerMapping = new THashMap<String, Object>();
- for (XmlRpcHandlerBean handlerBean : Extensions.getExtensions(XmlRpcHandlerBean.EP_NAME)) {
- try {
- handlerMapping.put(handlerBean.name, AbstractExtensionPointBean.instantiate(handlerBean.findClass(handlerBean.implementation), ApplicationManager.getApplication().getPicoContainer(), true));
- }
- catch (ClassNotFoundException e) {
- LOG.error(e);
- }
- }
- LOG.debug("XmlRpcServerImpl instantiated, handlers " + handlerMapping);
}
static final class XmlRpcRequestHandler extends HttpRequestHandler {
}
@Override
- public boolean process(@NotNull String path, @NotNull FullHttpRequest request, @NotNull ChannelHandlerContext context, @Nullable Map<String, Object> handlers) throws IOException {
+ public boolean process(@NotNull String path, @NotNull FullHttpRequest request, @NotNull ChannelHandlerContext context, @Nullable Map<String, Object> 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;
- }
+ ByteBuf content = request.content();
+ if (content.readableBytes() == 0) {
+ Responses.send(HttpResponseStatus.BAD_REQUEST, context.channel(), request);
+ return true;
+ }
- 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();
+ 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;
}
- Responses.send(Responses.response("text/xml", result), context.channel(), request);
+ 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;
}
- return false;
+
+ Responses.send(Responses.response("text/xml", result), context.channel(), request);
+ return true;
}
private static Object getHandler(@NotNull String methodName, @NotNull Map<String, Object> handlers) {
import org.jetbrains.annotations.NotNull;
import org.jetbrains.ide.CustomPortServerManager;
-import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.Map;
}
@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;
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
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) {
private val requestIdCounter = AtomicInteger()
- protected val requests: ConcurrentIntObjectMap<Channel> = ContainerUtil.createConcurrentIntObjectMap<Channel>()
+ private val requests = ContainerUtil.createConcurrentIntObjectMap<ClientInfo>()
override fun configureBootstrap(bootstrap: Bootstrap, errorOutputConsumer: Consumer<String>) {
bootstrap.handler {
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)
}
}
}
}
}
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
if (!HttpUtil.isContentLengthSet(httpResponse)) {
HttpUtil.setContentLength(httpResponse, buffer.readableBytes().toLong())
}
+ httpResponse.headers().add(client.extraHeaders)
}
catch (e: Throwable) {
buffer.release()
}
}
-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) {
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
import org.jetbrains.concurrency.Promise
import org.jetbrains.io.webSocket.WebSocketServerOptions
-val CLIENT = AttributeKey.valueOf<Client>("SocketHandler.client")
+internal val CLIENT = AttributeKey.valueOf<Client>("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
- }
-
- clients.forEach { client ->
- if (client.channel.isActive) {
- client.sendHeartbeat()
- }
- true
+ private val heartbeatTimer = SimpleTimer.getInstance().setUp({
+ forEachClient(TObjectProcedure {
+ if (it.channel.isActive) {
+ it.sendHeartbeat()
}
- }
+ true
+ })
}, (options ?: WebSocketServerOptions()).heartbeatDelay.toLong())
private val clients = THashSet<Client>()
fun forEachClient(procedure: TObjectProcedure<Client>) {
synchronized (clients) {
- if (clients.isEmpty) {
- return
- }
-
clients.forEach(procedure)
}
}
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
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
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
--- /dev/null
+package org.jetbrains.io
+
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.Test
+
+class IsLocalHostTest {
+ @Test
+ fun `google ip`() {
+ assertThat(isLocalHost("37.29.1.113", false)).isFalse()
+ }
+
+ @Test
+ fun `google name`() {
+ assertThat(isLocalHost("google.com", false)).isFalse()
+ }
+
+ @Test
+ fun `jetbrains name`() {
+ assertThat(isLocalHost("jetbrains.com", false)).isFalse()
+ }
+
+ @Test
+ fun `unknown name`() {
+ assertThat(isLocalHost("foo.com", false)).isFalse()
+ }
+
+ @Test
+ fun `unknown unqualified name`() {
+ assertThat(isLocalHost("local", false)).isFalse()
+ }
+
+ @Test
+ fun `invalid ip`() {
+ assertThat(isLocalHost("0.0.0.0.0.0.0", false)).isFalse()
+ }
+
+ @Test
+ fun `any`() {
+ assertThat(isLocalHost("0.0.0.0", false)).isTrue()
+ }
+
+ @Test
+ fun `localhost`() {
+ assertThat(isLocalHost("localhost", false)).isTrue()
+ }
+
+ @Test
+ fun `localhost only loopback`() {
+ assertThat(isLocalHost("localhost", true)).isTrue()
+ }
+}
\ No newline at end of file
+++ /dev/null
-/*
- * Copyright 2000-2015 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.intellij.ide;
-
-import com.intellij.openapi.extensions.AbstractExtensionPointBean;
-import com.intellij.openapi.extensions.ExtensionPointName;
-import com.intellij.util.xmlb.annotations.Attribute;
-
-public class XmlRpcHandlerBean extends AbstractExtensionPointBean {
- public static final ExtensionPointName<XmlRpcHandlerBean> EP_NAME = ExtensionPointName.create("com.intellij.xmlRpcHandler");
-
- @Attribute("name")
- public String name;
-
- @Attribute("implementation")
- public String implementation;
-}
\ No newline at end of file
/*
- * 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.
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
-import java.io.IOException;
import java.util.Map;
public interface XmlRpcServer {
void removeHandler(String name);
- boolean process(@NotNull String path, @NotNull FullHttpRequest request, @NotNull ChannelHandlerContext context, @Nullable Map<String, Object> handlers) throws IOException;
+ boolean process(@NotNull String path, @NotNull FullHttpRequest request, @NotNull ChannelHandlerContext context, @Nullable Map<String, Object> handlers);
final class SERVICE {
private SERVICE() {
/*
- * 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.
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() {
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);
}
}
+ @Deprecated
public static InetAddress getLoopbackAddress() {
- try {
- // todo use JDK 7 InetAddress.getLoopbackAddress()
- return InetAddress.getByName(null);
- }
- catch (UnknownHostException e) {
- throw new RuntimeException(e);
- }
+ return InetAddress.getLoopbackAddress();
}
public static boolean isLocalhost(@NotNull String host) {
}
public static boolean isSniEnabled() {
- return SystemInfo.isJavaVersionAtLeast("1.7") && SystemProperties.getBooleanProperty("jsse.enableSNIExtension", true);
+ return SystemProperties.getBooleanProperty("jsse.enableSNIExtension", true);
}
}
public static boolean confirmLoadingFromRemotePath(@NotNull String path,
@NotNull @PropertyKey(resourceBundle = IdeBundle.BUNDLE) String msgKey,
@NotNull @PropertyKey(resourceBundle = IdeBundle.BUNDLE) String titleKey) {
+ return showYesNoDialog(IdeBundle.message(msgKey, path), titleKey);
+ }
+
+ public static boolean showYesNoDialog(@NotNull String message, @NotNull @PropertyKey(resourceBundle = IdeBundle.BUNDLE) String titleKey) {
final Window window = getActiveFrameOrWelcomeScreen();
- final String msg = IdeBundle.message(msgKey, path);
- final String title = IdeBundle.message(titleKey);
final Icon icon = Messages.getWarningIcon();
- final int answer = window == null ? Messages.showYesNoDialog(msg, title, icon) : Messages.showYesNoDialog(window, msg, title, icon);
+ String title = IdeBundle.message(titleKey);
+ final int answer = window == null ? Messages.showYesNoDialog(message, title, icon) : Messages.showYesNoDialog(window, message, title, icon);
return answer == Messages.YES;
}
- private static Window getActiveFrameOrWelcomeScreen() {
+ public static Window getActiveFrameOrWelcomeScreen() {
Window window = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusedWindow();
if (window != null) return window;
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;
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;
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);
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);
}
contentLength = buffer.readUnsignedShort();
+ if (contentLength > 8192) {
+ context.close();
+ return;
+ }
myState = State.CONTENT;
}
break;
/*
- * 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.
// 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"));
+ FileUtil.delete(new File(dest, "user.web.token"));
+
File oldPluginsDir = new File(src, PLUGINS_PATH);
if (!oldPluginsDir.isDirectory() && SystemInfo.isMac) {
oldPluginsDir = getSettingsPath(oldInstallationHome, settings, PathManager.PROPERTY_PLUGINS_PATH,
}
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
import com.intellij.util.PathUtilRt;
import com.intellij.util.Url;
import com.intellij.util.io.HttpRequests;
+import com.intellij.util.io.RequestBuilder;
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;
public class DefaultRemoteContentProvider extends RemoteContentProvider {
private static final Logger LOG = Logger.getInstance(DefaultRemoteContentProvider.class);
+ @NotNull
+ public static RequestBuilder addRequestTuner(@NotNull Url url, @NotNull RequestBuilder requestBuilder) {
+ BuiltInServerManager builtInServerManager = BuiltInServerManager.getInstance();
+ if (builtInServerManager.isOnBuiltInWebServer(url)) {
+ requestBuilder.tuner(builtInServerManager::configureRequestToWebServer);
+ }
+ return requestBuilder;
+ }
+
@Override
public boolean canProvideContent(@NotNull Url url) {
return true;
final String presentableUrl = StringUtil.trimMiddle(url.trimParameters().toDecodedForm(), 40);
callback.setProgressText(VfsBundle.message("download.progress.connecting", presentableUrl), true);
try {
- HttpRequests.request(url.toExternalForm())
+ addRequestTuner(url, HttpRequests.request(url.toExternalForm()))
.connectTimeout(60 * 1000)
.productNameAsUserAgent()
.hostNameVerifier(CertificateManager.HOSTNAME_VERIFIER)
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);
/*
- * 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.
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;
host = InetAddress.getLocalHost();
}
catch (UnknownHostException ignored) {
- host = NetUtils.getLoopbackAddress();
+ host = InetAddress.getLoopbackAddress();
}
}
Exception exception = null;
InetAddress host = myHost;
if (host == null) {
- host = NetUtils.getLoopbackAddress();
+ host = InetAddress.getLoopbackAddress();
}
for (int attempt = 0; attempt < MAX_CONNECTION_ATTEMPTS; attempt++) {
/*
- * 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.
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;
return false;
}
+ @SuppressWarnings("SpellCheckingInspection")
+ /**
+ * Write request from browser without Origin will be always blocked regardles of your implementation.
+ */
+ public boolean isAccessible(@NotNull HttpRequest request) {
+ String host = NettyKt.getHost(request);
+ // If attacker.com DNS rebound to 127.0.0.1 and user open site directly — no Origin or Referer headers.
+ // So we should check Host header.
+ return host != null && NettyKt.isLocalOrigin(request) && NettyKt.parseAndCheckIsLocalHost("http://" + host);
+ }
+
public boolean isSupported(@NotNull FullHttpRequest request) {
return request.method() == HttpMethod.GET || request.method() == HttpMethod.HEAD;
}
*/
public abstract boolean process(@NotNull QueryStringDecoder urlDecoder, @NotNull FullHttpRequest request, @NotNull ChannelHandlerContext context)
throws IOException;
-}
+}
\ No newline at end of file
import com.intellij.util.ExceptionUtil;
import com.intellij.util.NotNullProducer;
import com.intellij.util.SystemProperties;
-import com.intellij.util.net.NetUtils;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
boolean tryAnyPort,
@NotNull ServerBootstrap bootstrap,
@NotNull ChannelRegistrar channelRegistrar) throws Exception {
- InetAddress address = NetUtils.getLoopbackAddress();
+ InetAddress address = InetAddress.getLoopbackAddress();
for (int i = 0; i < portsCount; i++) {
int port = firstPort + i;
/*
- * 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.
import org.apache.sanselan.ImageFormat
import org.apache.sanselan.Sanselan
import org.jetbrains.ide.HttpRequestHandler
-
import java.awt.image.BufferedImage
private val PREV_HANDLER = AttributeKey.valueOf<HttpRequestHandler>("DelegatingHttpRequestHandler.handler")
override fun process(context: ChannelHandlerContext,
request: FullHttpRequest,
urlDecoder: QueryStringDecoder): Boolean {
+ fun HttpRequestHandler.checkAndProcess(): Boolean {
+ return isSupported(request) && !request.isWriteFromBrowserWithoutOrigin() && isAccessible(request) && process(urlDecoder, request, context)
+ }
+
val prevHandlerAttribute = context.attr(PREV_HANDLER)
val connectedHandler = prevHandlerAttribute.get()
if (connectedHandler != null) {
- if (connectedHandler.isSupported(request) && connectedHandler.process(urlDecoder, request, context)) {
+ if (connectedHandler.checkAndProcess()) {
return true
}
// prev cached connectedHandler is not suitable for this request, so, let's find it again
for (handler in HttpRequestHandler.EP_NAME.extensions) {
try {
- if (handler.isSupported(request) && handler.process(urlDecoder, request, context)) {
+ if (handler.checkAndProcess()) {
prevHandlerAttribute.set(handler)
return true
}
catch (e: Throwable) {
Logger.getInstance(BuiltInServer::class.java).error(e)
}
-
}
if (urlDecoder.path() == "/favicon.ico") {
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
internal abstract class DelegatingHttpRequestHandlerBase : SimpleChannelInboundHandlerAdapter<FullHttpRequest>() {
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 { "\n\nIN HTTP: $message\n\n" }
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
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
}
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)
/*
- * 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.
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;
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;
(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));
}
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)
/*
- * 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.
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
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);
}
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
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)
}
}
-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))
}
fun HttpResponse.addCommonHeaders() {
addServer()
setDate()
+ if (!headers().contains("X-Frame-Options")) {
+ headers().set("X-Frame-Options", "SameOrigin")
+ }
+ headers().set("X-Content-Type-Options", "nosniff")
+ headers().set("x-xss-protection", "1; mode=block")
}
fun HttpResponse.send(channel: Channel, close: Boolean) {
}
}
+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.http.server.response.actual.status", true) || (ApplicationManager.getApplication()?.isUnitTestMode ?: false)) {
+ return this
+ }
+ else {
+ return safeStatus
+ }
}
private fun createStatusResponse(responseStatus: HttpResponseStatus, request: HttpRequest?, description: String?): HttpResponse {
if (request != null && request.method() === HttpMethod.HEAD) {
- return responseStatus.response()
+ return DefaultFullHttpResponse(HttpVersion.HTTP_1_1, responseStatus, Unpooled.EMPTY_BUFFER)
}
val builder = StringBuilder()
*/
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.Url
+import com.intellij.util.Urls
+import com.intellij.util.net.NetUtils
import io.netty.bootstrap.Bootstrap
import io.netty.bootstrap.ServerBootstrap
import io.netty.buffer.ByteBuf
import io.netty.channel.socket.oio.OioServerSocketChannel
import io.netty.channel.socket.oio.OioSocketChannel
import io.netty.handler.codec.http.HttpHeaderNames
+import io.netty.handler.codec.http.HttpMethod
import io.netty.handler.codec.http.HttpRequest
import io.netty.handler.ssl.SslHandler
+import io.netty.resolver.HostsFileEntriesResolver
import io.netty.util.concurrent.GenericFutureListener
import org.jetbrains.concurrency.AsyncPromise
import org.jetbrains.ide.PooledThreadExecutor
+import java.io.IOException
+import java.net.InetAddress
import java.net.InetSocketAddress
+import java.net.NetworkInterface
import java.util.concurrent.TimeUnit
inline fun Bootstrap.handler(crossinline task: (Channel) -> Unit): Bootstrap {
}
inline fun ChannelFuture.addChannelListener(crossinline listener: (future: ChannelFuture) -> Unit) {
- addListener(GenericFutureListener<ChannelFuture> { listener(it) })
+ addListener(GenericFutureListener<io.netty.channel.ChannelFuture> { listener(it) })
}
// if NIO, so, it is shared and we must not shutdown it
val HttpRequest.host: String?
get() = headers().getAsString(HttpHeaderNames.HOST)
+val HttpRequest.origin: String?
+ get() = headers().getAsString(HttpHeaderNames.ORIGIN)
+
+val HttpRequest.referrer: String?
+ get() = headers().getAsString(HttpHeaderNames.REFERER)
+
+val HttpRequest.userAgent: String?
+ get() = headers().getAsString(HttpHeaderNames.USER_AGENT)
+
inline fun <T> ByteBuf.releaseIfError(task: () -> T): T {
try {
return task()
throw e
}
}
+}
+
+fun isLocalHost(host: String, onlyAnyOrLoopback: Boolean, hostsOnly: Boolean = false): Boolean {
+ if (NetUtils.isLocalhost(host)) {
+ return true
+ }
+
+ // if IP address, it is safe to use getByName (not affected by DNS rebinding)
+ if (onlyAnyOrLoopback && !InetAddresses.isInetAddress(host)) {
+ return false
+ }
+
+ fun InetAddress.isLocal() = isAnyLocalAddress || isLoopbackAddress || NetworkInterface.getByInetAddress(this) != null
+
+ try {
+ val address = InetAddress.getByName(host)
+ if (!address.isLocal()) {
+ return false
+ }
+ // be aware - on windows hosts file doesn't contain localhost
+ // hosts can contain remote addresses, so, we check it
+ if (hostsOnly && !InetAddresses.isInetAddress(host)) {
+ return HostsFileEntriesResolver.DEFAULT.address(host).let { it != null && it.isLocal() }
+ }
+ else {
+ return true
+ }
+ }
+ catch (ignored: IOException) {
+ return false
+ }
+}
+
+@JvmOverloads
+fun HttpRequest.isLocalOrigin(onlyAnyOrLoopback: Boolean = true, hostsOnly: Boolean = false) = parseAndCheckIsLocalHost(origin, onlyAnyOrLoopback, hostsOnly) && parseAndCheckIsLocalHost(referrer, onlyAnyOrLoopback, hostsOnly)
+
+private fun isTrustedChromeExtension(url: Url): Boolean {
+ return url.scheme == "chrome-extension" && (url.authority == "hmhgeddbohgjknpmjagkdomcpobmllji" || url.authority == "offnedcbhjldheanlbojaefbfbllddna")
+}
+
+private val Url.host: String?
+ get() = authority?.let {
+ val portIndex = it.indexOf(':')
+ if (portIndex > 0) it.substring(0, portIndex) else it
+ }
+
+@JvmOverloads
+fun parseAndCheckIsLocalHost(uri: String?, onlyAnyOrLoopback: Boolean = true, hostsOnly: Boolean = false): Boolean {
+ if (uri == null) {
+ return true
+ }
+
+ try {
+ val parsedUri = Urls.parse(uri, false) ?: return false
+ val host = parsedUri.host
+ return host != null && (isTrustedChromeExtension(parsedUri) || isLocalHost(host, onlyAnyOrLoopback, hostsOnly))
+ }
+ catch (ignored: Exception) {
+ }
+ return false
+}
+
+fun HttpRequest.isRegularBrowser() = userAgent?.startsWith("Mozilla/5.0") ?: false
+
+// forbid POST requests from browser without Origin
+fun HttpRequest.isWriteFromBrowserWithoutOrigin(): Boolean {
+ val method = method()
+ return origin.isNullOrEmpty() && isRegularBrowser() && (method == HttpMethod.POST || method == HttpMethod.PATCH || method == HttpMethod.PUT || method == HttpMethod.DELETE)
}
\ No newline at end of file
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
<extensionPoint name="statistics.usagesCollector" interface="com.intellij.internal.statistic.UsagesCollector"/>
- <!--suppress ExtensionPointBeanClass -->
- <extensionPoint name="xmlRpcHandler" beanClass="com.intellij.ide.XmlRpcHandlerBean"/>
-
<extensionPoint name="editorHighlighterProvider" beanClass="com.intellij.openapi.fileTypes.FileTypeExtensionPoint">
<with attribute="implementationClass" implements="com.intellij.openapi.fileTypes.EditorHighlighterProvider"/>
</extensionPoint>
<extensionPoint name="colorPickerListenerFactory" interface="com.intellij.ui.ColorPickerListenerFactory"/>
<extensionPoint name="search.topHitProvider" interface="com.intellij.ide.SearchTopHitProvider"/>
-
+
<extensionPoint name="search.optionContributor" interface="com.intellij.ide.ui.search.SearchableOptionContributor"/>
<extensionPoint name="ui.suitableFontProvider" interface="com.intellij.ui.SuitableFontProvider"/>
-
+
<extensionPoint name="schemeImporter" beanClass="com.intellij.openapi.options.SchemeImporterEP">
<with attribute="schemeClass" implements="com.intellij.openapi.options.Scheme"/>
<with attribute="implementationClass" implements="com.intellij.openapi.options.SchemeImporter"/>
<applicationService serviceInterface="com.intellij.ide.XmlRpcServer" serviceImplementation="org.jetbrains.ide.XmlRpcServerImpl"/>
- <xmlRpcHandler name="fileOpener" implementation="org.jetbrains.ide.OpenFileXmlRpcHandler"/>
-
<httpRequestHandler implementation="org.jetbrains.ide.XmlRpcServerImpl$XmlRpcRequestHandler"/>
<httpRequestHandler implementation="org.jetbrains.ide.ProjectSetRequestHandler"/>
<httpRequestHandler implementation="org.jetbrains.ide.DiffHttpService"/>
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
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.http.server.response.actual.status=false
+ide.rest.api.requests.per.minute=30
+ide.built.in.web.server.activatable=false
\ No newline at end of file
/*
- * 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.
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);
}
}
@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
/*
- * 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.
// 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 {
// 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 {
/*
- * 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.
/**
* 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);
}
/*
- * 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.
*/
@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
/**
* 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
* @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,
/**
* 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
* @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
*/
@SuppressWarnings({"UseOfObsoleteCollectionType"})
@Nullable
- Vector<String> replyToChallenge(final int handlerNo,
+ Vector<String> replyToChallenge(String token,
final String userName,
final String name,
final String instruction,
/**
* 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
* @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);
}
/*
- * 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.
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
- public boolean verifyServerHostKey(final int handler,
+ public boolean verifyServerHostKey(String token,
final String hostname,
final int port,
final String serverHostKeyAlgorithm,
return false;
}
Vector parameters = new Vector();
- parameters.add(handler);
+ parameters.add(token);
parameters.add(hostname);
parameters.add(port);
parameters.add(serverHostKeyAlgorithm);
*/
@Nullable
@SuppressWarnings("unchecked")
- public String askPassphrase(final int handler,
+ public String askPassphrase(String token,
final String username,
final String keyPath,
final boolean resetPassword,
return null;
}
Vector parameters = new Vector();
- parameters.add(handler);
+ parameters.add(token);
parameters.add(username);
parameters.add(keyPath);
parameters.add(resetPassword);
*/
@Nullable
@SuppressWarnings("unchecked")
- public Vector<String> replyToChallenge(final int handlerNo,
+ public Vector<String> replyToChallenge(String token,
final String username,
final String name,
final String instruction,
return null;
}
Vector parameters = new Vector();
- parameters.add(handlerNo);
+ parameters.add(token);
parameters.add(username);
parameters.add(name);
parameters.add(instruction);
*/
@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);
@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);
*/
@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);
}
}
/*
- * 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.
/**
* Handler number
*/
- private final int myHandlerNo;
+ private final String myHandlerNo;
/**
* the xml RPC port
*/
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;
/*
- * 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.
private final File myWorkingDirectory;
private boolean myEnvironmentCleanedUp = true; // the flag indicating that environment has been cleaned up, by default is true because there is nothing to clean
- private int mySshHandler = -1;
- private int myHttpHandler = -1;
+ private UUID mySshHandler;
+ private UUID myHttpHandler;
private Processor<OutputStream> myInputProcessor; // The processor for stdin
// if true process might be cancelled
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));
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));
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;
/*
- * 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.
import org.jetbrains.git4idea.util.ScriptGenerator;
import java.util.Collection;
+import java.util.UUID;
/**
* Provides the authentication mechanism for Git HTTP connections.
public class InternalRequestHandlerDelegate implements GitAskPassXmlRpcHandler {
@NotNull
@Override
- public String askUsername(int handler, @NotNull String url) {
- return getHandler(handler).askUsername(url);
+ public String askUsername(String token, @NotNull String url) {
+ return getHandler(UUID.fromString(token)).askUsername(url);
}
@NotNull
@Override
- public String askPassword(int handler, @NotNull String url) {
- return getHandler(handler).askPassword(url);
+ public String askPassword(String token, @NotNull String url) {
+ return getHandler(UUID.fromString(token)).askPassword(url);
}
}
/*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * Copyright 2000-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
import org.jetbrains.git4idea.util.ScriptGenerator;
import org.jetbrains.ide.BuiltInServerManager;
+import java.security.SecureRandom;
import java.util.Map;
import java.util.Random;
/**
* Random number generator
*/
- private static final Random oursRandom = new Random();
+ private static final Random oursRandom = new SecureRandom();
/**
* The prefix for rebase editors
*/
/*
- * 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.
import java.io.File;
import java.io.IOException;
+import java.util.UUID;
/**
* <p>The provider of external application scripts called by Git when a remote operation needs communication with the user.</p>
@Nullable private File myScriptPath;
@NotNull private final Object SCRIPT_FILE_LOCK = new Object();
- @NotNull private final THashMap<Integer, T> handlers = new THashMap<Integer, T>();
- private int myNextHandlerKey;
+ @NotNull private final THashMap<UUID, T> handlers = new THashMap<UUID, T>();
@NotNull private final Object HANDLERS_LOCK = new Object();
/**
* @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
handlers.remove(key);
}
});
- myNextHandlerKey++;
return key;
}
}
* @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) {
*
* @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");
/*
- * 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.
import org.jetbrains.annotations.NotNull;
import org.jetbrains.git4idea.util.ScriptGenerator;
+import java.util.UUID;
import java.util.Vector;
/**
public class InternalRequestHandler implements GitSSHHandler {
@Override
- public boolean verifyServerHostKey(int handler, String hostname, int port, String serverHostKeyAlgorithm, String serverHostKey,
+ public boolean verifyServerHostKey(String handler, String hostname, int port, String serverHostKeyAlgorithm, String serverHostKey,
boolean isNew) {
- return getHandler(handler).verifyServerHostKey(hostname, port, serverHostKeyAlgorithm, serverHostKey, isNew);
+ return getHandler(UUID.fromString(handler)).verifyServerHostKey(hostname, port, serverHostKeyAlgorithm, serverHostKey, isNew);
}
@Override
- public String askPassphrase(int handler, String username, String keyPath, boolean resetPassword, String lastError) {
- return adjustNull(getHandler(handler).askPassphrase(username, keyPath, resetPassword, lastError));
+ public String askPassphrase(String handler, String username, String keyPath, boolean resetPassword, String lastError) {
+ return adjustNull(getHandler(UUID.fromString(handler)).askPassphrase(username, keyPath, resetPassword, lastError));
}
@Override
@SuppressWarnings({"UseOfObsoleteCollectionType"})
- public Vector<String> replyToChallenge(int handlerNo, String username, String name, String instruction, int numPrompts,
+ public Vector<String> replyToChallenge(String token, String username, String name, String instruction, int numPrompts,
Vector<String> prompt, Vector<Boolean> echo, String lastError) {
- return adjustNull(getHandler(handlerNo).replyToChallenge(username, name, instruction, numPrompts, prompt, echo, lastError));
+ return adjustNull(getHandler(UUID.fromString(token)).replyToChallenge(username, name, instruction, numPrompts, prompt, echo, lastError));
}
@Override
- public String askPassword(int handlerNo, String username, boolean resetPassword, String lastError) {
- return adjustNull(getHandler(handlerNo).askPassword(username, resetPassword, lastError));
+ public String askPassword(String token, String username, boolean resetPassword, String lastError) {
+ return adjustNull(getHandler(UUID.fromString(token)).askPassword(username, resetPassword, lastError));
}
@Override
- public String setLastSuccessful(int handlerNo, String userName, String method, String error) {
- getHandler(handlerNo).setLastSuccessful(userName, method, error);
+ public String setLastSuccessful(String token, String userName, String method, String error) {
+ getHandler(UUID.fromString(token)).setLastSuccessful(userName, method, error);
return "";
}
@Override
- public String getLastSuccessful(int handlerNo, String userName) {
- return getHandler(handlerNo).getLastSuccessful(userName);
+ public String getLastSuccessful(String token, String userName) {
+ return getHandler(UUID.fromString(token)).getLastSuccessful(userName);
}
/**
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.vfs.CharsetToolkit;
-import com.intellij.util.net.NetUtils;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.ConnectException;
+import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
@Nullable
private InputStream connect(int port) throws IOException {
final long s = System.currentTimeMillis();
- final InetSocketAddress endpoint = new InetSocketAddress(NetUtils.getLoopbackAddress(), port);
+ final InetSocketAddress endpoint = new InetSocketAddress(InetAddress.getLoopbackAddress(), port);
myStartedProcess.notifyTextAvailable("Connecting to XSLT runner on " + endpoint + "\n", ProcessOutputTypes.SYSTEM);
/*
- * 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.
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.Urls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
+import org.jetbrains.ide.BuiltInServerManager;
import java.net.URI;
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) &&
+ BuiltInServerManager.getInstance().isOnBuiltInWebServer(Urls.parse(url, false))) {
+ PropertiesComponent.getInstance().setValue("ide.built.in.web.server.active", true);
+ }
+
+ super.browse(url, browser, project);
+ }
+
@Override
protected void browseUsingNotSystemDefaultBrowserPolicy(@NotNull URI uri, @NotNull GeneralSettings settings, @Nullable Project project) {
WebBrowserManager browserManager = WebBrowserManager.getInstance();
/*
- * 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.
public static Collection<Url> getDebuggableUrls(@Nullable PsiElement context) {
try {
OpenInBrowserRequest request = context == null ? null : OpenInBrowserRequest.create(context);
- return request == null || request.getFile().getViewProvider().getBaseLanguage() == XMLLanguage.INSTANCE ? Collections.<Url>emptyList() : getUrls(getProvider(request), request);
+ if (request == null || request.getFile().getViewProvider().getBaseLanguage() == XMLLanguage.INSTANCE) {
+ return Collections.<Url>emptyList();
+ }
+ else {
+ // it is client responsibility to set token
+ request.setAppendAccessToken(false);
+ return getUrls(getProvider(request), request);
+ }
}
catch (WebBrowserUrlProvider.BrowserException ignored) {
return Collections.emptyList();
<orderEntry type="module" module-name="xml-structure-view-impl" exported="" />
<orderEntry type="library" name="Netty" level="project" />
<orderEntry type="module" module-name="xdebugger-api" />
+ <orderEntry type="module" module-name="built-in-server-api" />
</component>
<component name="copyright">
<Base>
<setting name="state" value="1" />
</Base>
</component>
-</module>
-
+</module>
\ No newline at end of file
+/*
+ * 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;
public abstract class OpenInBrowserRequest {
private Collection<Url> result;
protected PsiFile file;
+
+ private boolean appendAccessToken = true;
public OpenInBrowserRequest(@NotNull PsiFile file) {
this.file = file;
public Collection<Url> getResult() {
return result;
}
+
+ public boolean isAppendAccessToken() {
+ return appendAccessToken;
+ }
+
+ public void setAppendAccessToken(boolean value) {
+ this.appendAccessToken = value;
+ }
}
\ No newline at end of file