Skip to content

Commit 8f2fddc

Browse files
committed
Add a resource handler to allow us to read files off the disk.
Woooo.
1 parent c0dd147 commit 8f2fddc

File tree

5 files changed

+373
-0
lines changed

5 files changed

+373
-0
lines changed

java/server/src/org/openqa/selenium/grid/web/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ java_library(
77
"//java/server/src/org/openqa/selenium/grid:__subpackages__",
88
"//java/server/src/org/openqa/selenium/netty/server:__pkg__",
99
"//java/server/src/org/openqa/selenium/remote/server:__subpackages__",
10+
"//java/server/src/org/openqa/selenium/server/htmlrunner:__pkg__",
1011
"//java/server/test/org/openqa/selenium:__subpackages__",
1112
],
1213
deps = [
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
// Licensed to the Software Freedom Conservancy (SFC) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The SFC licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package org.openqa.selenium.grid.web;
19+
20+
import com.google.common.collect.ImmutableSet;
21+
22+
import java.util.Objects;
23+
import java.util.Optional;
24+
import java.util.Set;
25+
26+
public class MergedResource implements Resource {
27+
28+
private final Resource base;
29+
private final Optional<Resource> next;
30+
31+
public MergedResource(Resource base) {
32+
this(base, null);
33+
}
34+
35+
private MergedResource(Resource base, Resource next) {
36+
this.base = Objects.requireNonNull(base);
37+
this.next = Optional.ofNullable(next);
38+
}
39+
40+
public MergedResource alsoCheck(Resource resource) {
41+
Objects.requireNonNull(resource);
42+
return new MergedResource(this, resource);
43+
}
44+
45+
@Override
46+
public String name() {
47+
return base.name();
48+
}
49+
50+
@Override
51+
public Optional<Resource> get(String path) {
52+
Optional<Resource> resource = base.get(path);
53+
if (resource.isPresent()) {
54+
return resource;
55+
}
56+
57+
if (!next.isPresent()) {
58+
return Optional.empty();
59+
}
60+
61+
return next.get().get(path);
62+
}
63+
64+
@Override
65+
public boolean isDirectory() {
66+
return base.isDirectory() || next.map(Resource::isDirectory).orElse(false);
67+
}
68+
69+
@Override
70+
public Set<Resource> list() {
71+
ImmutableSet.Builder<Resource> resources = ImmutableSet.builder();
72+
resources.addAll(base.list());
73+
next.ifPresent(res -> resources.addAll(res.list()));
74+
return resources.build();
75+
}
76+
77+
@Override
78+
public Optional<byte[]> read() {
79+
Optional<byte[]> data = base.read();
80+
if (data.isPresent()) {
81+
return data;
82+
}
83+
84+
if (!next.isPresent()) {
85+
return Optional.empty();
86+
}
87+
88+
return next.get().read();
89+
}
90+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// Licensed to the Software Freedom Conservancy (SFC) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The SFC licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package org.openqa.selenium.grid.web;
19+
20+
import java.io.IOException;
21+
import java.io.UncheckedIOException;
22+
import java.nio.file.Files;
23+
import java.nio.file.Path;
24+
import java.util.Objects;
25+
import java.util.Optional;
26+
import java.util.Set;
27+
28+
import static com.google.common.collect.ImmutableSet.toImmutableSet;
29+
30+
public class PathResource implements Resource {
31+
32+
private final Path base;
33+
34+
public PathResource(Path base) {
35+
this.base = Objects.requireNonNull(base).normalize();
36+
}
37+
38+
@Override
39+
public String name() {
40+
return base.getFileName().toString();
41+
}
42+
43+
@Override
44+
public Optional<Resource> get(String path) {
45+
Path normalized = base.resolve(path).normalize();
46+
if (!normalized.startsWith(base)) {
47+
throw new RuntimeException("Attempt to navigate away from the parent directory");
48+
}
49+
50+
if (Files.exists(normalized)) {
51+
return Optional.of(new PathResource(normalized));
52+
}
53+
54+
return Optional.empty();
55+
}
56+
57+
@Override
58+
public boolean isDirectory() {
59+
return Files.isDirectory(base);
60+
}
61+
62+
@Override
63+
public Set<Resource> list() {
64+
try {
65+
return Files.list(base).map(PathResource::new).collect(toImmutableSet());
66+
} catch (IOException e) {
67+
throw new UncheckedIOException(e);
68+
}
69+
}
70+
71+
@Override
72+
public Optional<byte[]> read() {
73+
if (!Files.exists(base)) {
74+
return Optional.empty();
75+
}
76+
77+
try {
78+
return Optional.of(Files.readAllBytes(base));
79+
} catch (IOException e) {
80+
throw new UncheckedIOException(e);
81+
}
82+
}
83+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Licensed to the Software Freedom Conservancy (SFC) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The SFC licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package org.openqa.selenium.grid.web;
19+
20+
import java.util.Optional;
21+
import java.util.Set;
22+
23+
public interface Resource {
24+
25+
String name();
26+
27+
Optional<Resource> get(String path);
28+
29+
boolean isDirectory();
30+
31+
Set<Resource> list();
32+
33+
Optional<byte[]> read();
34+
}
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
// Licensed to the Software Freedom Conservancy (SFC) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The SFC licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package org.openqa.selenium.grid.web;
19+
20+
import com.google.common.net.MediaType;
21+
import org.openqa.selenium.remote.http.Contents;
22+
import org.openqa.selenium.remote.http.HttpRequest;
23+
import org.openqa.selenium.remote.http.HttpResponse;
24+
import org.openqa.selenium.remote.http.Routable;
25+
26+
import java.io.UncheckedIOException;
27+
import java.util.Objects;
28+
import java.util.Optional;
29+
import java.util.stream.Collectors;
30+
31+
import static com.google.common.net.MediaType.CSS_UTF_8;
32+
import static com.google.common.net.MediaType.GIF;
33+
import static com.google.common.net.MediaType.HTML_UTF_8;
34+
import static com.google.common.net.MediaType.JAVASCRIPT_UTF_8;
35+
import static com.google.common.net.MediaType.JPEG;
36+
import static com.google.common.net.MediaType.OCTET_STREAM;
37+
import static com.google.common.net.MediaType.PLAIN_TEXT_UTF_8;
38+
import static com.google.common.net.MediaType.PNG;
39+
import static com.google.common.net.MediaType.SVG_UTF_8;
40+
import static com.google.common.net.MediaType.WOFF;
41+
import static com.google.common.net.MediaType.XHTML_UTF_8;
42+
import static com.google.common.net.MediaType.XML_UTF_8;
43+
import static java.net.HttpURLConnection.HTTP_NOT_FOUND;
44+
import static java.nio.charset.StandardCharsets.UTF_8;
45+
import static org.openqa.selenium.remote.http.HttpMethod.GET;
46+
47+
public class ResourceHandler implements Routable {
48+
49+
private final Resource resource;
50+
51+
public ResourceHandler(Resource resource) {
52+
this.resource = Objects.requireNonNull(resource);
53+
}
54+
55+
@Override
56+
public boolean matches(HttpRequest req) {
57+
return GET == req.getMethod() && resource.get(req.getUri()).isPresent();
58+
}
59+
60+
@Override
61+
public HttpResponse execute(HttpRequest req) throws UncheckedIOException {
62+
Optional<Resource> result = resource.get(req.getUri());
63+
64+
if (!result.isPresent()) {
65+
return new HttpResponse()
66+
.setStatus(HTTP_NOT_FOUND)
67+
.setContent(Contents.string("Unable to find " + req.getUri(), UTF_8));
68+
}
69+
70+
Resource resource = result.get();
71+
72+
if (resource.isDirectory()) {
73+
String links = resource.list().stream()
74+
.map(res -> String.format("<a href=\"%s\">%s</a>", res.name(), res.name()))
75+
.sorted()
76+
.collect(Collectors.joining("\n"));
77+
78+
String html = String.format(
79+
"<html><title>Listing of %s</title><body><h1>%s</h1>%s",
80+
resource.name(),
81+
resource.name(),
82+
resource.name());
83+
84+
return new HttpResponse()
85+
.addHeader("Content-Type", HTML_UTF_8.toString())
86+
.setContent(Contents.string(html, UTF_8));
87+
}
88+
89+
Optional<byte[]> bytes = resource.read();
90+
if (bytes.isPresent()) {
91+
return new HttpResponse()
92+
.addHeader("Content-Type", mediaType(req.getUri()))
93+
.setContent(Contents.bytes(bytes.get()));
94+
}
95+
96+
return new HttpResponse()
97+
.setStatus(HTTP_NOT_FOUND)
98+
.setContent(Contents.string("Unable to read " + req.getUri(), UTF_8));
99+
}
100+
101+
private String mediaType(String uri) {
102+
int index = uri.lastIndexOf(".");
103+
String extension = (index == -1 || uri.length() == index) ? "" : uri.substring(index + 1);
104+
105+
MediaType type;
106+
switch (extension.toLowerCase()) {
107+
case "dll":
108+
case "ttf":
109+
type = OCTET_STREAM;
110+
break;
111+
112+
case "css":
113+
type = CSS_UTF_8;
114+
break;
115+
116+
case "gif":
117+
type = GIF;
118+
break;
119+
120+
case "jpeg":
121+
case "jpg":
122+
type = JPEG;
123+
break;
124+
125+
case "js":
126+
type = JAVASCRIPT_UTF_8;
127+
break;
128+
129+
case "md":
130+
case "txt":
131+
type = PLAIN_TEXT_UTF_8;
132+
break;
133+
134+
case "png":
135+
type = PNG;
136+
break;
137+
138+
case "svg":
139+
type = SVG_UTF_8;
140+
break;
141+
142+
case "woff":
143+
type = WOFF;
144+
break;
145+
146+
case "xhtml":
147+
type = XHTML_UTF_8;
148+
break;
149+
150+
case "xml":
151+
type = XML_UTF_8;
152+
break;
153+
154+
case "xsl":
155+
type = MediaType.create("application", "xslt+xml").withCharset(UTF_8);
156+
break;
157+
158+
default:
159+
type = HTML_UTF_8;
160+
break;
161+
}
162+
163+
return type.toString();
164+
}
165+
}

0 commit comments

Comments
 (0)