summaryrefslogtreecommitdiff
path: root/xml/impl/src/org/jetbrains/builtInWebServer/BuiltInWebServer.java
diff options
context:
space:
mode:
Diffstat (limited to 'xml/impl/src/org/jetbrains/builtInWebServer/BuiltInWebServer.java')
-rw-r--r--xml/impl/src/org/jetbrains/builtInWebServer/BuiltInWebServer.java231
1 files changed, 231 insertions, 0 deletions
diff --git a/xml/impl/src/org/jetbrains/builtInWebServer/BuiltInWebServer.java b/xml/impl/src/org/jetbrains/builtInWebServer/BuiltInWebServer.java
new file mode 100644
index 000000000000..2dc24ba01706
--- /dev/null
+++ b/xml/impl/src/org/jetbrains/builtInWebServer/BuiltInWebServer.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright 2000-2014 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.builtInWebServer;
+
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.project.ProjectManager;
+import com.intellij.openapi.util.SystemInfoRt;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vfs.VfsUtilCore;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.UriUtil;
+import com.intellij.util.io.URLUtil;
+import com.intellij.util.net.NetUtils;
+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.ide.HttpRequestHandler;
+import org.jetbrains.io.FileResponses;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+import static org.jetbrains.io.Responses.sendOptionsResponse;
+import static org.jetbrains.io.Responses.sendStatus;
+
+public final class BuiltInWebServer extends HttpRequestHandler {
+ static final Logger LOG = Logger.getInstance(BuiltInWebServer.class);
+
+ @Nullable
+ public static VirtualFile findIndexFile(@NotNull VirtualFile basedir) {
+ VirtualFile[] children = basedir.getChildren();
+ if (children == null || children.length == 0) {
+ return null;
+ }
+
+ for (String indexNamePrefix : new String[]{"index.", "default."}) {
+ VirtualFile index = null;
+ String preferredName = indexNamePrefix + "html";
+ for (VirtualFile child : children) {
+ if (!child.isDirectory()) {
+ String name = child.getName();
+ if (name.equals(preferredName)) {
+ return child;
+ }
+ else if (index == null && name.startsWith(indexNamePrefix)) {
+ index = child;
+ }
+ }
+ }
+ if (index != null) {
+ return index;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public boolean isSupported(@NotNull FullHttpRequest request) {
+ return super.isSupported(request) || request.method() == HttpMethod.POST || request.method() == HttpMethod.OPTIONS;
+ }
+
+ @Override
+ public boolean process(@NotNull QueryStringDecoder urlDecoder, @NotNull FullHttpRequest request, @NotNull ChannelHandlerContext context) {
+ if (request.method() == HttpMethod.OPTIONS) {
+ sendOptionsResponse("GET, POST, HEAD, OPTIONS", request, context);
+ return true;
+ }
+
+ String host = HttpHeaders.getHost(request);
+ if (StringUtil.isEmpty(host)) {
+ return false;
+ }
+
+ int portIndex = host.indexOf(':');
+ if (portIndex > 0) {
+ host = host.substring(0, portIndex);
+ }
+
+ String projectName;
+ boolean isIpv6 = host.charAt(0) == '[' && host.length() > 2 && host.charAt(host.length() - 1) == ']';
+ if (isIpv6) {
+ host = host.substring(1, host.length() - 1);
+ }
+
+ if (isIpv6 || Character.digit(host.charAt(0), 10) != -1 || host.charAt(0) == ':' || isOwnHostName(host)) {
+ if (urlDecoder.path().length() < 2) {
+ return false;
+ }
+ projectName = null;
+ }
+ else {
+ projectName = host;
+ }
+ return doProcess(request, context.channel(), projectName);
+ }
+
+ public static boolean isOwnHostName(@NotNull String host) {
+ if (NetUtils.isLocalhost(host)) {
+ return true;
+ }
+
+ try {
+ InetAddress address = InetAddress.getByName(host);
+ if (host.equals(address.getHostAddress()) || host.equalsIgnoreCase(address.getCanonicalHostName())) {
+ return true;
+ }
+
+ String localHostName = InetAddress.getLocalHost().getHostName();
+ // WEB-8889
+ // develar.local is own host name: develar. equals to "develar.labs.intellij.net" (canonical host name)
+ return localHostName.equalsIgnoreCase(host) ||
+ (host.endsWith(".local") && localHostName.regionMatches(true, 0, host, 0, host.length() - ".local".length()));
+ }
+ catch (UnknownHostException ignored) {
+ return false;
+ }
+ }
+
+ private static boolean doProcess(@NotNull FullHttpRequest request, @NotNull Channel channel, @Nullable String projectName) {
+ final String decodedPath = URLUtil.unescapePercentSequences(UriUtil.trimParameters(request.uri()));
+ int offset;
+ boolean emptyPath;
+ boolean isCustomHost = projectName != null;
+ if (isCustomHost) {
+ // host mapped to us
+ offset = 0;
+ emptyPath = decodedPath.isEmpty();
+ }
+ else {
+ offset = decodedPath.indexOf('/', 1);
+ projectName = decodedPath.substring(1, offset == -1 ? decodedPath.length() : offset);
+ emptyPath = offset == -1;
+ }
+
+ Project project = findProject(projectName, isCustomHost);
+ if (project == null) {
+ return false;
+ }
+
+ if (emptyPath) {
+ if (!SystemInfoRt.isFileSystemCaseSensitive) {
+ // may be passed path is not correct
+ projectName = project.getName();
+ }
+
+ // we must redirect "jsdebug" to "jsdebug/" as nginx does, otherwise browser will treat it as file instead of directory, so, relative path will not work
+ WebServerPathHandler.redirectToDirectory(request, channel, projectName);
+ return true;
+ }
+
+ final String path = FileUtil.toCanonicalPath(decodedPath.substring(offset + 1), '/');
+ LOG.assertTrue(path != null);
+
+ for (WebServerPathHandler pathHandler : WebServerPathHandler.EP_NAME.getExtensions()) {
+ try {
+ if (pathHandler.process(path, project, request, channel, projectName, decodedPath, isCustomHost)) {
+ return true;
+ }
+ }
+ catch (Throwable e) {
+ LOG.error(e);
+ }
+ }
+ return false;
+ }
+
+ static final class StaticFileHandler extends WebServerFileHandler {
+ @Override
+ public boolean process(@NotNull VirtualFile file,
+ @NotNull CharSequence canonicalRequestPath,
+ @NotNull Project project,
+ @NotNull FullHttpRequest request,
+ @NotNull Channel channel) throws IOException {
+ File ioFile = VfsUtilCore.virtualToIoFile(file);
+ if (hasAccess(ioFile)) {
+ FileResponses.sendFile(request, channel, ioFile);
+ }
+ else {
+ sendStatus(HttpResponseStatus.FORBIDDEN, channel, request);
+ }
+ return true;
+ }
+
+ private static boolean hasAccess(File result) {
+ // deny access to .htaccess files
+ return !result.isDirectory() && result.canRead() && !(result.isHidden() || result.getName().startsWith(".ht"));
+ }
+ }
+
+ @Nullable
+ private static Project findProject(String projectName, boolean isCustomHost) {
+ // user can rename project directory, so, we should support this case - find project by base directory name
+ Project candidateByDirectoryName = null;
+ for (Project project : ProjectManager.getInstance().getOpenProjects()) {
+ String name = project.getName();
+ // domain name is case-insensitive
+ if (!project.isDisposed() && ((isCustomHost || !SystemInfoRt.isFileSystemCaseSensitive) ? projectName.equalsIgnoreCase(name) : projectName.equals(name))) {
+ return project;
+ }
+
+ if (candidateByDirectoryName == null && compareNameAndProjectBasePath(projectName, project)) {
+ candidateByDirectoryName = project;
+ }
+ }
+ return candidateByDirectoryName;
+ }
+
+ public static boolean compareNameAndProjectBasePath(String projectName, Project project) {
+ String basePath = project.getBasePath();
+ return basePath != null && basePath.length() > projectName.length() && basePath.endsWith(projectName) && basePath.charAt(basePath.length() - projectName.length() - 1) == '/';
+ }
+} \ No newline at end of file