Skip to content

Commit d7648fc

Browse files
author
Vladimir Kotal
authored
file content/genre API (#3050)
fixes #3048
1 parent d016a90 commit d7648fc

File tree

8 files changed

+461
-33
lines changed

8 files changed

+461
-33
lines changed

apiary.apib

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,35 @@ Besides `/suggester` and `/search` endpoints, everything is accessible from `loc
6767

6868
+ Response 204
6969

70+
## File content [/file/content{?path}]
71+
72+
### get file content [GET]
73+
74+
Honors the Accept header. The text type works for plain text files only.
75+
76+
+ Parameters
77+
+ path (string) - path of file, relative to source root
78+
79+
+ Response 200 (text/plain)
80+
+ Body
81+
82+
foo
83+
bar
84+
85+
+ Response 200 (application/octet-stream)
86+
87+
## File genre [/file/genre{?path}]
88+
89+
### get file genre [GET]
90+
91+
+ Parameters
92+
+ path (string) - path of file, relative to source root
93+
94+
+ Response 200 (text/plain)
95+
+ Body
96+
97+
genre as identified by analyzer, could be PLAIN, XREFABLE, IMAGE, DATA, HTML
98+
7099
## History [/history{?path,withFiles,start,max}]
71100

72101
### get history entries [GET]

opengrok-indexer/src/main/java/org/opengrok/indexer/index/IndexDatabase.java

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1356,7 +1356,9 @@ private boolean isInterrupted() {
13561356
* @param listener the object to receive the events
13571357
*/
13581358
public void addIndexChangedListener(IndexChangedListener listener) {
1359-
listeners.add(listener);
1359+
if (listener != null) {
1360+
listeners.add(listener);
1361+
}
13601362
}
13611363

13621364
/**
@@ -1568,8 +1570,29 @@ public static IndexReader getIndexReader(String path) {
15681570
* @throws ClassNotFoundException if the class for the stored definitions
15691571
* instance cannot be found
15701572
*/
1571-
public static Definitions getDefinitions(File file)
1572-
throws IOException, ParseException, ClassNotFoundException {
1573+
public static Definitions getDefinitions(File file) throws ParseException, IOException, ClassNotFoundException {
1574+
Document doc = getDocument(file);
1575+
if (doc == null) {
1576+
return null;
1577+
}
1578+
1579+
IndexableField tags = doc.getField(QueryBuilder.TAGS);
1580+
if (tags != null) {
1581+
return Definitions.deserialize(tags.binaryValue().bytes);
1582+
}
1583+
1584+
// Didn't find any definitions.
1585+
return null;
1586+
}
1587+
1588+
/**
1589+
* @param file File object of a file under source root
1590+
* @return Document object for the file or {@code null}
1591+
* @throws IOException
1592+
* @throws ParseException
1593+
*/
1594+
public static Document getDocument(File file)
1595+
throws IOException, ParseException {
15731596
RuntimeEnvironment env = RuntimeEnvironment.getInstance();
15741597
String path;
15751598
try {
@@ -1584,34 +1607,31 @@ public static Definitions getDefinitions(File file)
15841607
IndexReader ireader = getIndexReader(path);
15851608

15861609
if (ireader == null) {
1587-
// No index, no definitions...
1610+
// No index, no document..
15881611
return null;
15891612
}
15901613

15911614
try {
1615+
Document doc;
15921616
Query q = new QueryBuilder().setPath(path).build();
15931617
IndexSearcher searcher = new IndexSearcher(ireader);
15941618
TopDocs top = searcher.search(q, 1);
15951619
if (top.totalHits.value == 0) {
1596-
// No hits, no definitions...
1620+
// No hits, no document...
15971621
return null;
15981622
}
1599-
Document doc = searcher.doc(top.scoreDocs[0].doc);
1623+
doc = searcher.doc(top.scoreDocs[0].doc);
16001624
String foundPath = doc.get(QueryBuilder.PATH);
16011625

1602-
// Only use the definitions if we found an exact match.
1603-
if (path.equals(foundPath)) {
1604-
IndexableField tags = doc.getField(QueryBuilder.TAGS);
1605-
if (tags != null) {
1606-
return Definitions.deserialize(tags.binaryValue().bytes);
1607-
}
1626+
// Only use the document if we found an exact match.
1627+
if (!path.equals(foundPath)) {
1628+
return null;
16081629
}
1630+
1631+
return doc;
16091632
} finally {
16101633
ireader.close();
16111634
}
1612-
1613-
// Didn't find any definitions.
1614-
return null;
16151635
}
16161636

16171637
@Override
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
/*
2+
* CDDL HEADER START
3+
*
4+
* The contents of this file are subject to the terms of the
5+
* Common Development and Distribution License (the "License").
6+
* You may not use this file except in compliance with the License.
7+
*
8+
* See LICENSE.txt included in this distribution for the specific
9+
* language governing permissions and limitations under the License.
10+
*
11+
* When distributing Covered Code, include this CDDL HEADER in each
12+
* file and include the License file at LICENSE.txt.
13+
* If applicable, add the following below this CDDL HEADER, with the
14+
* fields enclosed by brackets "[]" replaced with your own identifying
15+
* information: Portions Copyright [yyyy] [name of copyright owner]
16+
*
17+
* CDDL HEADER END
18+
*/
19+
20+
/*
21+
* Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved.
22+
*/
23+
24+
package org.opengrok.web.api.v1.controller;
25+
26+
import org.apache.lucene.document.Document;
27+
import org.apache.lucene.queryparser.classic.ParseException;
28+
import org.opengrok.indexer.analysis.AbstractAnalyzer;
29+
import org.opengrok.indexer.configuration.RuntimeEnvironment;
30+
import org.opengrok.indexer.search.QueryBuilder;
31+
import org.opengrok.web.api.v1.filter.CorsEnable;
32+
import org.opengrok.web.api.v1.filter.PathAuthorized;
33+
34+
import javax.servlet.http.HttpServletRequest;
35+
import javax.servlet.http.HttpServletResponse;
36+
import javax.ws.rs.GET;
37+
import javax.ws.rs.Path;
38+
import javax.ws.rs.Produces;
39+
import javax.ws.rs.QueryParam;
40+
import javax.ws.rs.core.Context;
41+
import javax.ws.rs.core.MediaType;
42+
import javax.ws.rs.core.StreamingOutput;
43+
import java.io.File;
44+
import java.io.FileInputStream;
45+
import java.io.FileNotFoundException;
46+
import java.io.IOException;
47+
import java.io.InputStream;
48+
49+
import static org.opengrok.indexer.index.IndexDatabase.getDocument;
50+
51+
@Path(FileController.PATH)
52+
public class FileController {
53+
54+
public static final String PATH = "/file";
55+
56+
private static final RuntimeEnvironment env = RuntimeEnvironment.getInstance();
57+
58+
private static File getFile(String path, HttpServletResponse response) throws IOException {
59+
if (path == null) {
60+
if (response != null) {
61+
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Missing path parameter");
62+
}
63+
return null;
64+
}
65+
66+
File file = new File(env.getSourceRootFile(), path);
67+
if (!file.isFile()) {
68+
if (response != null) {
69+
response.sendError(HttpServletResponse.SC_NOT_FOUND, "File not found");
70+
}
71+
return null;
72+
}
73+
74+
return file;
75+
}
76+
77+
private StreamingOutput transfer(File file) throws FileNotFoundException {
78+
InputStream in = new FileInputStream(file);
79+
return out -> {
80+
byte[] buffer = new byte[1024];
81+
int len = in.read(buffer);
82+
while (len != -1) {
83+
out.write(buffer, 0, len);
84+
len = in.read(buffer);
85+
}
86+
};
87+
}
88+
89+
@GET
90+
@CorsEnable
91+
@PathAuthorized
92+
@Path("/content")
93+
@Produces(MediaType.TEXT_PLAIN)
94+
public StreamingOutput getContentPlain(@Context HttpServletRequest request,
95+
@Context HttpServletResponse response,
96+
@QueryParam("path") final String path) throws IOException, ParseException {
97+
98+
File file = getFile(path, response);
99+
if (file == null) {
100+
// error already set in the response
101+
return null;
102+
}
103+
104+
Document doc;
105+
if ((doc = getDocument(file)) == null) {
106+
response.sendError(HttpServletResponse.SC_NOT_FOUND, "Cannot get document for file");
107+
return null;
108+
}
109+
110+
String fileType = doc.get(QueryBuilder.T);
111+
if (!AbstractAnalyzer.Genre.PLAIN.typeName().equals(fileType)) {
112+
response.sendError(HttpServletResponse.SC_NOT_ACCEPTABLE, "Not a text file");
113+
return null;
114+
}
115+
116+
return transfer(file);
117+
}
118+
119+
@GET
120+
@CorsEnable
121+
@PathAuthorized
122+
@Path("/content")
123+
@Produces(MediaType.APPLICATION_OCTET_STREAM)
124+
public StreamingOutput getContentOctets(@Context HttpServletRequest request,
125+
@Context HttpServletResponse response,
126+
@QueryParam("path") final String path) throws IOException, ParseException {
127+
128+
File file = getFile(path, response);
129+
if (file == null) {
130+
// error already set in the response
131+
return null;
132+
}
133+
134+
Document doc;
135+
if ((doc = getDocument(file)) == null) {
136+
response.sendError(HttpServletResponse.SC_NOT_FOUND, "Cannot get document for file");
137+
return null;
138+
}
139+
140+
try {
141+
return transfer(file);
142+
} catch (FileNotFoundException e) {
143+
response.sendError(HttpServletResponse.SC_NOT_FOUND, "Cannot find file");
144+
return null;
145+
}
146+
}
147+
148+
@GET
149+
@CorsEnable
150+
@PathAuthorized
151+
@Path("/genre")
152+
@Produces(MediaType.TEXT_PLAIN)
153+
public String getGenre(@Context HttpServletRequest request,
154+
@Context HttpServletResponse response,
155+
@QueryParam("path") final String path) throws IOException, ParseException {
156+
157+
File file = getFile(path, response);
158+
if (file == null) {
159+
// error already set in the response
160+
return null;
161+
}
162+
163+
Document doc;
164+
if ((doc = getDocument(file)) == null) {
165+
response.sendError(HttpServletResponse.SC_NOT_FOUND, "Cannot get document for file");
166+
return null;
167+
}
168+
169+
AbstractAnalyzer.Genre genre = AbstractAnalyzer.Genre.get(doc.get(QueryBuilder.T));
170+
if (genre == null) {
171+
response.sendError(HttpServletResponse.SC_NOT_FOUND, "Cannot get genre from the document");
172+
return null;
173+
}
174+
175+
return genre.toString();
176+
}
177+
}

opengrok-web/src/main/java/org/opengrok/web/api/v1/controller/HistoryController.java

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,14 @@
2424
package org.opengrok.web.api.v1.controller;
2525

2626
import com.fasterxml.jackson.annotation.JsonProperty;
27-
import org.opengrok.indexer.authorization.AuthorizationFramework;
28-
import org.opengrok.indexer.configuration.Project;
2927
import org.opengrok.indexer.configuration.RuntimeEnvironment;
3028
import org.opengrok.indexer.history.History;
3129
import org.opengrok.indexer.history.HistoryEntry;
3230
import org.opengrok.indexer.history.HistoryException;
3331
import org.opengrok.indexer.history.HistoryGuru;
3432
import org.opengrok.indexer.web.messages.JSONable;
3533
import org.opengrok.web.api.v1.filter.CorsEnable;
34+
import org.opengrok.web.api.v1.filter.PathAuthorized;
3635

3736
import javax.servlet.http.HttpServletRequest;
3837
import javax.servlet.http.HttpServletResponse;
@@ -43,9 +42,7 @@
4342
import javax.ws.rs.QueryParam;
4443
import javax.ws.rs.core.Context;
4544
import javax.ws.rs.core.MediaType;
46-
import javax.ws.rs.core.Response;
4745
import java.io.File;
48-
import java.io.IOException;
4946
import java.util.ArrayList;
5047
import java.util.Date;
5148
import java.util.List;
@@ -193,26 +190,15 @@ static HistoryDTO getHistoryDTO(List<HistoryEntry> historyEntries, int start, in
193190

194191
@GET
195192
@CorsEnable
193+
@PathAuthorized
196194
@Produces(MediaType.APPLICATION_JSON)
197195
public HistoryDTO get(@Context HttpServletRequest request,
198196
@Context HttpServletResponse response,
199197
@QueryParam("path") final String path,
200198
@QueryParam("withFiles") final boolean withFiles,
201199
@QueryParam("max") @DefaultValue(MAX_RESULTS + "") final int maxEntries,
202200
@QueryParam("start") @DefaultValue(0 + "") final int startIndex)
203-
throws HistoryException, IOException {
204-
205-
if (request != null) {
206-
AuthorizationFramework auth = env.getAuthorizationFramework();
207-
if (auth != null) {
208-
Project p = Project.getProject(path.startsWith("/") ? path : "/" + path);
209-
if (p != null && !auth.isAllowed(request, p)) {
210-
response.sendError(Response.status(Response.Status.FORBIDDEN).build().getStatus(),
211-
"not authorized");
212-
return null;
213-
}
214-
}
215-
}
201+
throws HistoryException {
216202

217203
History history = HistoryGuru.getInstance().getHistory(new File(env.getSourceRootFile(), path),
218204
withFiles, true);

opengrok-web/src/main/java/org/opengrok/web/api/v1/filter/LocalhostFilter.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
package org.opengrok.web.api.v1.filter;
2424

2525
import org.opengrok.indexer.logger.LoggerFactory;
26+
import org.opengrok.web.api.v1.controller.FileController;
2627
import org.opengrok.web.api.v1.controller.HistoryController;
2728
import org.opengrok.web.api.v1.controller.SearchController;
2829
import org.opengrok.web.api.v1.controller.SuggesterController;
@@ -58,7 +59,7 @@ public class LocalhostFilter implements ContainerRequestFilter {
5859
*/
5960
private static final Set<String> allowedPaths = new HashSet<>(Arrays.asList(
6061
SearchController.PATH, SuggesterController.PATH, SuggesterController.PATH + "/config",
61-
HistoryController.PATH));
62+
HistoryController.PATH, FileController.PATH));
6263

6364
@Context
6465
private HttpServletRequest request;

0 commit comments

Comments
 (0)