diff --git a/.gitignore b/.gitignore
index c9b58b2ef..6ad5f8103 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,8 +1,13 @@
+.DS_Store
target
/.settings
/.classpath
/.project
+.settings
+.classpath
+.project
.idea
*.iml
/.run
/logs/
+/venv/
diff --git a/README.md b/README.md
index acccdb8ce..4fa1b999b 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,7 @@ This crawler helps to index binary documents such as PDF, Open Office, MS Office
**Main features**:
* Local file system (or a mounted drive) crawling and index new files, update existing ones and removes old ones.
-* Remote file system over SSH crawling.
+* Remote file system over SSH/FTP/SMB crawling.
* REST interface to let you "upload" your binary documents to elasticsearch.
You need to install a version matching your Elasticsearch version:
diff --git a/cli/src/main/java/fr/pilato/elasticsearch/crawler/fs/cli/FsCrawlerCli.java b/cli/src/main/java/fr/pilato/elasticsearch/crawler/fs/cli/FsCrawlerCli.java
index 5f13de4ea..b0c4a208a 100644
--- a/cli/src/main/java/fr/pilato/elasticsearch/crawler/fs/cli/FsCrawlerCli.java
+++ b/cli/src/main/java/fr/pilato/elasticsearch/crawler/fs/cli/FsCrawlerCli.java
@@ -34,6 +34,7 @@
import fr.pilato.elasticsearch.crawler.fs.settings.FsSettings;
import fr.pilato.elasticsearch.crawler.fs.settings.FsSettingsFileHandler;
import fr.pilato.elasticsearch.crawler.fs.settings.FsSettingsParser;
+import fr.pilato.elasticsearch.crawler.fs.settings.Server.PROTOCOL;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
@@ -208,6 +209,20 @@ public static void main(String[] args) throws Exception {
if (fsSettings.getFs() == null) {
fsSettings.setFs(Fs.DEFAULT);
}
+
+ if (fsSettings.getServer() != null) {
+ if (fsSettings.getServer().getProtocol().equals(PROTOCOL.FTP) && fsSettings.getServer().getPort() == PROTOCOL.SSH_PORT) {
+ fsSettings.getServer().setPort(PROTOCOL.FTP_PORT);
+ }
+ if (fsSettings.getServer().getProtocol().equals(PROTOCOL.FTP) && StringUtils.isEmpty(fsSettings.getServer().getUsername())) {
+ fsSettings.getServer().setUsername("anonymous");
+ }
+
+ if (fsSettings.getServer().getProtocol().equals(PROTOCOL.SMB) && StringUtils.isEmpty(fsSettings.getServer().getUsername())) {
+ fsSettings.getServer().setUsername("Guest");
+ }
+ }
+
if (fsSettings.getElasticsearch() == null) {
fsSettings.setElasticsearch(Elasticsearch.DEFAULT());
}
diff --git a/core/pom.xml b/core/pom.xml
index 5f1bdfe4e..e4253b57f 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -69,10 +69,18 @@
fr.pilato.elasticsearch.crawler
fscrawler-crawler-fs
+
+ fr.pilato.elasticsearch.crawler
+ fscrawler-crawler-ftp
+
fr.pilato.elasticsearch.crawler
fscrawler-crawler-ssh
+
+ fr.pilato.elasticsearch.crawler
+ fscrawler-crawler-smb
+
diff --git a/core/src/main/java/fr/pilato/elasticsearch/crawler/fs/FsCrawlerImpl.java b/core/src/main/java/fr/pilato/elasticsearch/crawler/fs/FsCrawlerImpl.java
index f8bdf4f12..97b9b0e2c 100644
--- a/core/src/main/java/fr/pilato/elasticsearch/crawler/fs/FsCrawlerImpl.java
+++ b/core/src/main/java/fr/pilato/elasticsearch/crawler/fs/FsCrawlerImpl.java
@@ -123,9 +123,15 @@ public void start() throws Exception {
if (settings.getServer() == null || Server.PROTOCOL.LOCAL.equals(settings.getServer().getProtocol())) {
// Local FS
fsParser = new FsParserLocal(settings, config, managementService, documentService, loop);
+ } else if (Server.PROTOCOL.SMB.equals(settings.getServer().getProtocol())) {
+ // Remote SMB FS
+ fsParser = new FsParserSmb(settings, config, managementService, documentService, loop);
} else if (Server.PROTOCOL.SSH.equals(settings.getServer().getProtocol())) {
// Remote SSH FS
fsParser = new FsParserSsh(settings, config, managementService, documentService, loop);
+ } else if (Server.PROTOCOL.FTP.equals(settings.getServer().getProtocol())) {
+ // Remote FTP FS
+ fsParser = new FsParserFTP(settings, config, managementService, documentService, loop);
} else {
// Non supported protocol
throw new RuntimeException(settings.getServer().getProtocol() + " is not supported yet. Please use " +
@@ -146,7 +152,7 @@ public void close() throws InterruptedException, IOException {
if (fsParser != null) {
fsParser.close();
- synchronized(fsParser.getSemaphore()) {
+ synchronized (fsParser.getSemaphore()) {
fsParser.getSemaphore().notifyAll();
}
}
diff --git a/core/src/main/java/fr/pilato/elasticsearch/crawler/fs/FsParserAbstract.java b/core/src/main/java/fr/pilato/elasticsearch/crawler/fs/FsParserAbstract.java
index afdc2ede9..6908ec8dc 100644
--- a/core/src/main/java/fr/pilato/elasticsearch/crawler/fs/FsParserAbstract.java
+++ b/core/src/main/java/fr/pilato/elasticsearch/crawler/fs/FsParserAbstract.java
@@ -28,21 +28,23 @@
import fr.pilato.elasticsearch.crawler.fs.beans.ScanStatistic;
import fr.pilato.elasticsearch.crawler.fs.crawler.FileAbstractModel;
import fr.pilato.elasticsearch.crawler.fs.crawler.FileAbstractor;
+import fr.pilato.elasticsearch.crawler.fs.crawler.fs.FileAbstractorFile;
import fr.pilato.elasticsearch.crawler.fs.framework.ByteSizeValue;
import fr.pilato.elasticsearch.crawler.fs.framework.FSCrawlerLogger;
+import fr.pilato.elasticsearch.crawler.fs.framework.FsCrawlerUtil;
import fr.pilato.elasticsearch.crawler.fs.framework.OsValidator;
import fr.pilato.elasticsearch.crawler.fs.framework.SignTool;
import fr.pilato.elasticsearch.crawler.fs.service.FsCrawlerDocumentService;
import fr.pilato.elasticsearch.crawler.fs.service.FsCrawlerManagementService;
import fr.pilato.elasticsearch.crawler.fs.service.FsCrawlerService;
import fr.pilato.elasticsearch.crawler.fs.settings.FsSettings;
+import fr.pilato.elasticsearch.crawler.fs.settings.Server.PROTOCOL;
import fr.pilato.elasticsearch.crawler.fs.tika.TikaDocParser;
import fr.pilato.elasticsearch.crawler.fs.tika.XmlDocParser;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.BufferedReader;
-import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
@@ -97,12 +99,10 @@ public abstract class FsParserAbstract extends FsParser {
messageDigest = null;
}
- // On Windows, when using SSH server, we need to force the "Linux" separator
- if (OsValidator.WINDOWS && fsSettings.getServer() != null) {
- logger.debug("We are running on Windows with SSH Server settings so we need to force the Linux separator.");
- pathSeparator = "/";
- } else {
- pathSeparator = File.separator;
+ pathSeparator = FsCrawlerUtil.getPathSeparator(fsSettings.getFs().getUrl());
+ if (OsValidator.WINDOWS && fsSettings.getServer() == null) {
+ logger.debug("We are running on Windows without Server settings so we use the separator in accordance with fs.url");
+ FileAbstractorFile.separator = pathSeparator;
}
}
@@ -257,7 +257,7 @@ private void addFilesRecursively(FileAbstractor> path, String filepath, LocalD
logger.trace("FileAbstractModel = {}", child);
String filename = child.getName();
- String virtualFileName = computeVirtualPathName(stats.getRootPath(), new File(filepath, filename).toString());
+ String virtualFileName = computeVirtualPathName(stats.getRootPath(), computeRealPathName(filepath, filename));
// https://github.com/dadoonet/fscrawler/issues/1 : Filter documents
boolean isIndexable = isIndexable(child.isDirectory(), virtualFileName, fsSettings.getFs().getIncludes(), fsSettings.getFs().getExcludes());
@@ -274,9 +274,9 @@ private void addFilesRecursively(FileAbstractor> path, String filepath, LocalD
indexFile(child, stats, filepath,
fsSettings.getFs().isIndexContent() || fsSettings.getFs().isStoreSource() ? path.getInputStream(child) : null, child.getSize());
stats.addFile();
- } catch (java.io.FileNotFoundException e) {
+ } catch (Exception e) {
if (fsSettings.getFs().isContinueOnError()) {
- logger.warn("Unable to open Input Stream for {}, skipping...: {}", filename, e.getMessage());
+ logger.warn("Unable to index {}, skipping...: {}", filename, e.getMessage());
} else {
throw e;
}
@@ -319,7 +319,7 @@ private void addFilesRecursively(FileAbstractor> path, String filepath, LocalD
for (String esfile : esFiles) {
logger.trace("Checking file [{}]", esfile);
- String virtualFileName = computeVirtualPathName(stats.getRootPath(), new File(filepath, esfile).toString());
+ String virtualFileName = computeVirtualPathName(stats.getRootPath(), computeRealPathName(filepath, esfile));
if (isIndexable(false, virtualFileName, fsSettings.getFs().getIncludes(), fsSettings.getFs().getExcludes())
&& !fsFiles.contains(esfile)) {
logger.trace("Removing file [{}] in elasticsearch/workplace", esfile);
@@ -334,7 +334,7 @@ private void addFilesRecursively(FileAbstractor> path, String filepath, LocalD
// for the delete folder
for (String esfolder : esFolders) {
- String virtualFileName = computeVirtualPathName(stats.getRootPath(), new File(filepath, esfolder).toString());
+ String virtualFileName = computeVirtualPathName(stats.getRootPath(), computeRealPathName(filepath, esfolder));
if (isIndexable(true, virtualFileName, fsSettings.getFs().getIncludes(), fsSettings.getFs().getExcludes())) {
logger.trace("Checking directory [{}]", esfolder);
if (!fsFolders.contains(esfolder)) {
@@ -377,7 +377,7 @@ private void indexFile(FileAbstractModel fileAbstractModel, ScanStatistic stats,
final long size = fileAbstractModel.getSize();
logger.debug("fetching content from [{}],[{}]", dirname, filename);
- String fullFilename = new File(dirname, filename).toString();
+ String fullFilename = computeRealPathName(dirname, filename);
try {
// Create the Doc object (only needed when we have add_as_inner_object: true (default) or when we don't index json or xml)
@@ -393,7 +393,11 @@ private void indexFile(FileAbstractModel fileAbstractModel, ScanStatistic stats,
doc.getFile().setLastModified(localDateTimeToDate(lastModified));
doc.getFile().setLastAccessed(localDateTimeToDate(lastAccessed));
doc.getFile().setIndexingDate(localDateTimeToDate(LocalDateTime.now()));
- doc.getFile().setUrl("file://" + fullFilename);
+ if (fsSettings.getServer() == null) {
+ doc.getFile().setUrl("file://" + fullFilename);
+ } else if (fsSettings.getServer().getProtocol().equals(PROTOCOL.FTP)) {
+ doc.getFile().setUrl(String.format("ftp://%s:%d%s", fsSettings.getServer().getHostname(), fsSettings.getServer().getPort(), fullFilename));
+ }
doc.getFile().setExtension(extension);
if (fsSettings.getFs().isAddFilesize()) {
doc.getFile().setFilesize(size);
@@ -493,7 +497,10 @@ private void indexFile(FileAbstractModel fileAbstractModel, ScanStatistic stats,
}
private String generateIdFromFilename(String filename, String filepath) throws NoSuchAlgorithmException {
- return fsSettings.getFs().isFilenameAsId() ? filename : SignTool.sign((new File(filepath, filename)).toString());
+ String filepathForId = filepath.replace("\\", "/");
+ String filenameForId = filename.replace("\\", "").replace("/", "");
+ String idSource = filepathForId.endsWith("/") ? filepathForId.concat(filenameForId) : filepathForId.concat("/").concat(filenameForId);
+ return fsSettings.getFs().isFilenameAsId() ? filename : SignTool.sign(idSource);
}
private String read(InputStream input) throws IOException {
@@ -518,7 +525,7 @@ private void indexDirectory(String id, Folder folder) throws IOException {
/**
* Index a directory
- * @param path complete path like /path/to/subdir
+ * @param path complete path like "/", "/path/to/subdir", "C:\\dir", "C:/dir", "/C:/dir", "//SOMEONE/dir"
*/
private void indexDirectory(String path) throws Exception {
String name = path.substring(path.lastIndexOf(pathSeparator) + 1);
diff --git a/core/src/main/java/fr/pilato/elasticsearch/crawler/fs/FsParserFTP.java b/core/src/main/java/fr/pilato/elasticsearch/crawler/fs/FsParserFTP.java
new file mode 100644
index 000000000..d04934503
--- /dev/null
+++ b/core/src/main/java/fr/pilato/elasticsearch/crawler/fs/FsParserFTP.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to David Pilato (the "Author") under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. Author licenses this
+ * file to you 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 fr.pilato.elasticsearch.crawler.fs;
+
+import fr.pilato.elasticsearch.crawler.fs.crawler.FileAbstractor;
+import fr.pilato.elasticsearch.crawler.fs.crawler.ftp.FileAbstractorFTP;
+import fr.pilato.elasticsearch.crawler.fs.service.FsCrawlerDocumentService;
+import fr.pilato.elasticsearch.crawler.fs.service.FsCrawlerManagementService;
+import fr.pilato.elasticsearch.crawler.fs.settings.FsSettings;
+import java.nio.file.Path;
+
+public class FsParserFTP extends FsParserAbstract {
+
+ public FsParserFTP(FsSettings fsSettings, Path config, FsCrawlerManagementService managementService,
+ FsCrawlerDocumentService documentService, Integer loop) {
+ super(fsSettings, config, managementService, documentService, loop);
+ }
+
+ protected FileAbstractor> buildFileAbstractor() {
+ return new FileAbstractorFTP(fsSettings);
+ }
+}
diff --git a/core/src/main/java/fr/pilato/elasticsearch/crawler/fs/FsParserSmb.java b/core/src/main/java/fr/pilato/elasticsearch/crawler/fs/FsParserSmb.java
new file mode 100644
index 000000000..b319b4a5f
--- /dev/null
+++ b/core/src/main/java/fr/pilato/elasticsearch/crawler/fs/FsParserSmb.java
@@ -0,0 +1,22 @@
+package fr.pilato.elasticsearch.crawler.fs;
+
+import fr.pilato.elasticsearch.crawler.fs.crawler.FileAbstractor;
+import fr.pilato.elasticsearch.crawler.fs.crawler.smb.FileAbstractorSMB;
+import fr.pilato.elasticsearch.crawler.fs.crawler.ssh.FileAbstractorSSH;
+import fr.pilato.elasticsearch.crawler.fs.service.FsCrawlerDocumentService;
+import fr.pilato.elasticsearch.crawler.fs.service.FsCrawlerManagementService;
+import fr.pilato.elasticsearch.crawler.fs.settings.FsSettings;
+import java.nio.file.Path;
+
+public class FsParserSmb extends FsParserAbstract{
+
+ public FsParserSmb(FsSettings fsSettings, Path config, FsCrawlerManagementService managementService,
+ FsCrawlerDocumentService documentService, Integer loop){
+ super(fsSettings, config, managementService, documentService, loop);
+ }
+
+ @Override
+ protected FileAbstractor> buildFileAbstractor() {
+ return new FileAbstractorSMB(fsSettings);
+ }
+}
diff --git a/core/src/test/java/fr/pilato/elasticsearch/crawler/fs/FsCrawlerUtilTest.java b/core/src/test/java/fr/pilato/elasticsearch/crawler/fs/FsCrawlerUtilTest.java
deleted file mode 100644
index bcd812888..000000000
--- a/core/src/test/java/fr/pilato/elasticsearch/crawler/fs/FsCrawlerUtilTest.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Licensed to David Pilato (the "Author") under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. Author licenses this
- * file to you 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 fr.pilato.elasticsearch.crawler.fs;
-
-import fr.pilato.elasticsearch.crawler.fs.test.framework.AbstractFSCrawlerMetadataTestCase;
-import org.junit.Test;
-
-import java.io.File;
-import java.time.LocalDateTime;
-import java.util.TimeZone;
-
-import static fr.pilato.elasticsearch.crawler.fs.framework.FsCrawlerUtil.computeVirtualPathName;
-import static fr.pilato.elasticsearch.crawler.fs.framework.FsCrawlerUtil.getFileExtension;
-import static fr.pilato.elasticsearch.crawler.fs.framework.FsCrawlerUtil.localDateTimeToDate;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.is;
-
-/**
- * We want to test some utilities
- */
-public class FsCrawlerUtilTest extends AbstractFSCrawlerMetadataTestCase {
-
- @Test
- public void testComputePathLinux() {
- testHelper("/tmp", "/tmp", "/");
- testHelper("/tmp", "/tmp/dir", "/dir");
- testHelper("/tmp", "/tmp/dir/subdir", "/dir/subdir");
- testHelper("/tmp", "/tmp/file.txt", "/file.txt");
- testHelper("/tmp", "/tmp/dir/file.txt", "/dir/file.txt");
- testHelper("/tmp", "/tmp/dir/subdir/file.txt", "/dir/subdir/file.txt");
- }
-
- @Test
- public void testComputePathWindows() {
- testHelper("C:\\tmp", "C:\\tmp", "/");
- testHelper("C:\\tmp", "C:\\tmp\\dir", "/dir");
- testHelper("C:\\tmp", "C:\\tmp\\dir\\subdir", "/dir/subdir");
- testHelper("C:\\tmp", "C:\\tmp\\file.txt", "/file.txt");
- testHelper("C:\\tmp", "C:\\tmp\\dir\\file.txt", "/dir/file.txt");
- testHelper("C:\\tmp", "C:\\tmp\\dir\\subdir\\file.txt", "/dir/subdir/file.txt");
- }
-
- private void testHelper(String rootPath, String realPath, String expectedPath) {
- assertThat(computeVirtualPathName(rootPath, realPath), is(expectedPath));
- }
-
- @Test
- public void testGetFileExtension() {
- assertThat(getFileExtension(new File("foo.bar")), is("bar"));
- assertThat(getFileExtension(new File("foo")), is(""));
- assertThat(getFileExtension(new File("foo.bar.baz")), is("baz"));
- }
-
- @Test
- public void testLocalDateToDate() {
- LocalDateTime now = LocalDateTime.now();
- logger.info("Current Time [{}] in [{}] is actually [{}]", now, TimeZone.getDefault().getDisplayName(), localDateTimeToDate(now));
- }
-}
diff --git a/crawler/crawler-fs/src/main/java/fr/pilato/elasticsearch/crawler/fs/crawler/fs/FileAbstractorFile.java b/crawler/crawler-fs/src/main/java/fr/pilato/elasticsearch/crawler/fs/crawler/fs/FileAbstractorFile.java
index 4453f6a84..55b864ad8 100644
--- a/crawler/crawler-fs/src/main/java/fr/pilato/elasticsearch/crawler/fs/crawler/fs/FileAbstractorFile.java
+++ b/crawler/crawler-fs/src/main/java/fr/pilato/elasticsearch/crawler/fs/crawler/fs/FileAbstractorFile.java
@@ -48,6 +48,15 @@ public FileAbstractorFile(FsSettings fsSettings) {
super(fsSettings);
}
+ public static String separator = File.separator;
+
+ private String resolveSeparator(String path) {
+ if (separator.equals("/")) {
+ return path.replace("\\", "/");
+ }
+ return path.replace("/", "\\");
+ }
+
@Override
public FileAbstractModel toFileAbstractModel(String path, File file) {
return new FileAbstractModel(
@@ -57,8 +66,8 @@ public FileAbstractModel toFileAbstractModel(String path, File file) {
getCreationTime(file),
getLastAccessTime(file),
getFileExtension(file),
- path,
- file.getAbsolutePath(),
+ resolveSeparator(path),
+ resolveSeparator(file.getAbsolutePath()),
file.length(),
getOwnerName(file),
getGroupName(file),
@@ -67,7 +76,7 @@ public FileAbstractModel toFileAbstractModel(String path, File file) {
@Override
public InputStream getInputStream(FileAbstractModel file) throws Exception {
- return new FileInputStream(new File(file.getFullpath()));
+ return new FileInputStream(file.getFullpath());
}
@Override
diff --git a/crawler/crawler-ftp/pom.xml b/crawler/crawler-ftp/pom.xml
new file mode 100644
index 000000000..f02dd6a18
--- /dev/null
+++ b/crawler/crawler-ftp/pom.xml
@@ -0,0 +1,37 @@
+
+
+
+ fscrawler-crawler
+ fr.pilato.elasticsearch.crawler
+ 2.7-SNAPSHOT
+
+ 4.0.0
+
+ fscrawler-crawler-ftp
+ FSCrawler Crawlers: FTP
+
+
+
+ fr.pilato.elasticsearch.crawler
+ fscrawler-crawler-abstract
+
+
+
+
+ commons-net
+ commons-net
+
+
+ org.mockftpserver
+ MockFtpServer
+ test
+
+
+ org.apache.logging.log4j
+ log4j-iostreams
+
+
+
+
diff --git a/crawler/crawler-ftp/src/main/java/fr/pilato/elasticsearch/crawler/fs/crawler/ftp/FTPUtils.java b/crawler/crawler-ftp/src/main/java/fr/pilato/elasticsearch/crawler/fs/crawler/ftp/FTPUtils.java
new file mode 100644
index 000000000..96cda2ba8
--- /dev/null
+++ b/crawler/crawler-ftp/src/main/java/fr/pilato/elasticsearch/crawler/fs/crawler/ftp/FTPUtils.java
@@ -0,0 +1,37 @@
+package fr.pilato.elasticsearch.crawler.fs.crawler.ftp;
+
+import static fr.pilato.elasticsearch.crawler.fs.framework.FsCrawlerUtil.toOctalPermission;
+
+import org.apache.commons.net.ftp.FTPFile;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+public class FTPUtils {
+ private static final Logger logger = LogManager.getLogger(FTPUtils.class);
+
+ /**
+ * Determines FTPFile permissions.
+ */
+ public static int getFilePermissions(final FTPFile file) {
+ try {
+ int user = toOctalPermission(
+ file.hasPermission(FTPFile.USER_ACCESS, FTPFile.READ_PERMISSION),
+ file.hasPermission(FTPFile.USER_ACCESS, FTPFile.WRITE_PERMISSION),
+ file.hasPermission(FTPFile.USER_ACCESS, FTPFile.EXECUTE_PERMISSION));
+ int group = toOctalPermission(
+ file.hasPermission(FTPFile.GROUP_ACCESS, FTPFile.READ_PERMISSION),
+ file.hasPermission(FTPFile.GROUP_ACCESS, FTPFile.WRITE_PERMISSION),
+ file.hasPermission(FTPFile.GROUP_ACCESS, FTPFile.EXECUTE_PERMISSION));
+ int others = toOctalPermission(
+ file.hasPermission(FTPFile.WORLD_ACCESS, FTPFile.READ_PERMISSION),
+ file.hasPermission(FTPFile.WORLD_ACCESS, FTPFile.WRITE_PERMISSION),
+ file.hasPermission(FTPFile.WORLD_ACCESS, FTPFile.EXECUTE_PERMISSION));
+
+ return user * 100 + group * 10 + others;
+ } catch (Exception e) {
+ logger.warn("Failed to determine 'permissions' of {}: {}", file, e.getMessage());
+ return -1;
+ }
+ }
+
+}
diff --git a/crawler/crawler-ftp/src/main/java/fr/pilato/elasticsearch/crawler/fs/crawler/ftp/FileAbstractorFTP.java b/crawler/crawler-ftp/src/main/java/fr/pilato/elasticsearch/crawler/fs/crawler/ftp/FileAbstractorFTP.java
new file mode 100644
index 000000000..cfbe7e4aa
--- /dev/null
+++ b/crawler/crawler-ftp/src/main/java/fr/pilato/elasticsearch/crawler/fs/crawler/ftp/FileAbstractorFTP.java
@@ -0,0 +1,208 @@
+/*
+ * Licensed to David Pilato (the "Author") under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. Author licenses this
+ * file to you 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 fr.pilato.elasticsearch.crawler.fs.crawler.ftp;
+
+import fr.pilato.elasticsearch.crawler.fs.crawler.FileAbstractModel;
+import fr.pilato.elasticsearch.crawler.fs.crawler.FileAbstractor;
+import fr.pilato.elasticsearch.crawler.fs.settings.FsSettings;
+import fr.pilato.elasticsearch.crawler.fs.settings.Server;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.List;
+import org.apache.commons.io.FilenameUtils;
+import org.apache.commons.net.PrintCommandListener;
+import org.apache.commons.net.ftp.FTP;
+import org.apache.commons.net.ftp.FTPClient;
+import org.apache.commons.net.ftp.FTPFile;
+import org.apache.commons.net.ftp.FTPReply;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.io.IoBuilder;
+
+import java.io.InputStream;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.stream.Collectors;
+
+public class FileAbstractorFTP extends FileAbstractor {
+ private final Logger logger = LogManager.getLogger(FileAbstractorFTP.class);
+
+ private FTPClient ftp;
+
+ private final OutputStream loggerOutputStream = IoBuilder.forLogger(logger).buildOutputStream();
+
+ private final PrintCommandListener ftpListener = new PrintCommandListener(new PrintWriter(loggerOutputStream));
+
+ private boolean isUtf8 = false;
+
+ private static final String ALTERNATIVE_ENCODING = "GBK";
+
+ public FileAbstractorFTP(FsSettings fsSettings) {
+ super(fsSettings);
+ }
+
+ @Override
+ public FileAbstractModel toFileAbstractModel(String path, FTPFile file) {
+ String filename = file.getName();
+ String extension = FilenameUtils.getExtension(filename);
+
+ String toEncoding = ALTERNATIVE_ENCODING;
+ if (isUtf8) {
+ toEncoding = StandardCharsets.UTF_8.displayName();
+ }
+ try {
+ filename = new String(filename.getBytes(FTP.DEFAULT_CONTROL_ENCODING), toEncoding);
+ path = new String(path.getBytes(FTP.DEFAULT_CONTROL_ENCODING), toEncoding);
+ } catch (UnsupportedEncodingException e) {
+ logger.error("Error during encoding: {}", e.getMessage());
+ }
+
+ return new FileAbstractModel(
+ filename,
+ file.isFile(),
+ // We are using here the local TimeZone as a reference. If the remote system is under another TZ, this might cause issues
+ LocalDateTime.ofInstant(Instant.ofEpochMilli(file.getTimestamp().getTimeInMillis()), ZoneId.systemDefault()),
+ // We don't have the creation date
+ null,
+ // We don't have the access date
+ null,
+ extension,
+ path,
+ path.equals("/") ? path.concat(filename) : path.concat("/").concat(filename),
+ file.getSize(),
+ file.getUser(),
+ file.getGroup(),
+ FTPUtils.getFilePermissions(file));
+ }
+
+ @Override
+ public InputStream getInputStream(FileAbstractModel file) throws IOException {
+ String fullPath = file.getFullpath();
+ if (isUtf8) {
+ fullPath = new String(fullPath.getBytes(StandardCharsets.UTF_8), FTP.DEFAULT_CONTROL_ENCODING);
+ } else {
+ fullPath = new String(fullPath.getBytes(ALTERNATIVE_ENCODING), FTP.DEFAULT_CONTROL_ENCODING);
+ }
+
+ InputStream inputStream = ftp.retrieveFileStream(fullPath);
+ if (inputStream != null) {
+ ftp.completePendingCommand();
+ return inputStream;
+ } else {
+ throw new IOException(String.format("FTP client can not retrieve stream for [%s]", file.getFullpath()));
+ }
+ }
+
+ @Override
+ public Collection getFiles(String dir) throws IOException {
+ logger.debug("Listing files from {}", dir);
+ if (isUtf8) {
+ dir = new String(dir.getBytes(StandardCharsets.UTF_8), FTP.DEFAULT_CONTROL_ENCODING);
+ } else {
+ dir = new String(dir.getBytes(ALTERNATIVE_ENCODING), FTP.DEFAULT_CONTROL_ENCODING);
+ }
+ ftp.enterLocalPassiveMode();
+ FTPFile[] ftpFiles = ftp.listFiles(dir);
+ if (ftpFiles == null) return null;
+ List files = Arrays.stream(ftpFiles).filter(file -> {
+ if (fsSettings.getFs().isFollowSymlinks()) return true;
+ return !file.isSymbolicLink();
+ }).collect(Collectors.toList());
+
+ Collection result = new ArrayList<>(files.size());
+ // Iterate other files
+ // We ignore here all files like . and ..
+ String finalDir = dir;
+ result.addAll(files.stream().filter(file -> !".".equals(file.getName()) &&
+ !"..".equals(file.getName()))
+ .map(file -> toFileAbstractModel(finalDir, file))
+ .collect(Collectors.toList()));
+
+ logger.debug("{} local files found", result.size());
+ return result;
+ }
+
+ @Override
+ public boolean exists(String dir) {
+ try {
+ logger.debug("Checking dir existence: " + dir);
+ if (isUtf8) {
+ dir = new String(dir.getBytes(StandardCharsets.UTF_8), FTP.DEFAULT_CONTROL_ENCODING);
+ } else {
+ dir = new String(dir.getBytes(ALTERNATIVE_ENCODING), FTP.DEFAULT_CONTROL_ENCODING);
+ }
+ return ftp.changeWorkingDirectory(dir);
+ } catch (IOException e) {
+ return false;
+ }
+ }
+
+ @Override
+ public void open() throws IOException {
+ Server server = fsSettings.getServer();
+ logger.debug("Opening FTP connection to {}@{}", server.getUsername(), server.getHostname());
+
+ ftp = new FTPClient();
+ ftp.addProtocolCommandListener(ftpListener);
+ // send a safe command (i.e. NOOP) over the control connection to reset the router's idle timer
+ ftp.setControlKeepAliveTimeout(300);
+ openFTPConnection();
+ }
+
+ @Override
+ public void close() throws IOException {
+ ftp.logout();
+ ftp.disconnect();
+ }
+
+ private void openFTPConnection() throws IOException {
+ Server server = fsSettings.getServer();
+ ftp.connect(server.getHostname(), server.getPort());
+
+ // checking FTP client connection.
+ int reply = ftp.getReplyCode();
+ if (!FTPReply.isPositiveCompletion(reply)) {
+ ftp.disconnect();
+ logger.warn("Cannot connect with FTP to {}@{}", server.getUsername(),
+ server.getHostname());
+ throw new RuntimeException("Can not connect to " + server.getUsername() + "@" + server.getHostname());
+ }
+
+ if (!ftp.login(server.getUsername(), server.getPassword())) {
+ ftp.disconnect();
+ throw new RuntimeException("Please check ftp user or password");
+ }
+
+ int utf8Reply = ftp.sendCommand("OPTS UTF8", "ON");
+ if (FTPReply.isPositiveCompletion(utf8Reply)) {
+ isUtf8 = true;
+ }
+ ftp.setFileType(FTPClient.BINARY_FILE_TYPE);
+
+ logger.debug("FTP connection successful");
+ }
+}
diff --git a/crawler/crawler-ftp/src/test/java/fr/pilato/elasticsearch/crawler/fs/crawler/ftp/FileAbstractorFTPTest.java b/crawler/crawler-ftp/src/test/java/fr/pilato/elasticsearch/crawler/fs/crawler/ftp/FileAbstractorFTPTest.java
new file mode 100644
index 000000000..1b893f379
--- /dev/null
+++ b/crawler/crawler-ftp/src/test/java/fr/pilato/elasticsearch/crawler/fs/crawler/ftp/FileAbstractorFTPTest.java
@@ -0,0 +1,225 @@
+/*
+ * Licensed to David Pilato (the "Author") under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. Author licenses this
+ * file to you 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 fr.pilato.elasticsearch.crawler.fs.crawler.ftp;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+import fr.pilato.elasticsearch.crawler.fs.crawler.FileAbstractModel;
+import fr.pilato.elasticsearch.crawler.fs.settings.FsSettings;
+import fr.pilato.elasticsearch.crawler.fs.settings.Server;
+import fr.pilato.elasticsearch.crawler.fs.test.framework.AbstractFSCrawlerTestCase;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Collection;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import org.apache.commons.io.IOUtils;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.mockftpserver.fake.FakeFtpServer;
+import org.mockftpserver.fake.UserAccount;
+import org.mockftpserver.fake.filesystem.DirectoryEntry;
+import org.mockftpserver.fake.filesystem.FileEntry;
+import org.mockftpserver.fake.filesystem.FileSystem;
+import org.mockftpserver.fake.filesystem.Permissions;
+import org.mockftpserver.fake.filesystem.UnixFakeFileSystem;
+
+public class FileAbstractorFTPTest extends AbstractFSCrawlerTestCase {
+ private FakeFtpServer fakeFtpServer;
+ private final String nestedDir = "/nested";
+ private final String permissionDir = "/permission";
+ private final String user = "user";
+ private final String pass = "pass";
+
+ @Before
+ public void setup() {
+ fakeFtpServer = new FakeFtpServer();
+ fakeFtpServer.setServerControlPort(5968);
+ fakeFtpServer.addUserAccount(new UserAccount(user, pass, "/"));
+ FileSystem fileSystem = new UnixFakeFileSystem();
+
+ fileSystem.add(new DirectoryEntry(nestedDir));
+ fileSystem.add(new FileEntry(nestedDir + "/foo.txt", "文件名不支持中文"));
+ fileSystem.add(new FileEntry(nestedDir + "/bar.txt", "filename doesn't support utf-8"));
+
+ fileSystem.add(new DirectoryEntry(nestedDir + "/buzz"));
+ fileSystem.add(new FileEntry(nestedDir + "/buzz/hello.txt", "hello"));
+ fileSystem.add(new FileEntry(nestedDir + "/buzz/world.txt", "world"));
+
+ fileSystem.add(new DirectoryEntry(permissionDir));
+ FileEntry fileAllPermissions = new FileEntry(permissionDir + "/all.txt", "123");
+ fileAllPermissions.setPermissions(Permissions.ALL);
+ fileSystem.add(fileAllPermissions);
+ FileEntry fileNonePermissions = new FileEntry(permissionDir + "/none.txt", "456");
+ fileNonePermissions.setPermissions(Permissions.NONE);
+ fileSystem.add(fileNonePermissions);
+
+ fakeFtpServer.setFileSystem(fileSystem);
+ fakeFtpServer.start();
+ }
+
+ @After
+ public void shutDown() {
+ fakeFtpServer.stop();
+ }
+
+ @Test
+ public void testConnectToFakeFTPServer() throws Exception {
+ int port = fakeFtpServer.getServerControlPort();
+ FsSettings fsSettings = FsSettings.builder("fake")
+ .setServer(
+ Server.builder()
+ .setHostname("localhost")
+ .setUsername(user)
+ .setPassword(pass)
+ .setPort(port)
+ .build()
+ )
+ .build();
+
+ FileAbstractorFTP ftp = new FileAbstractorFTP(fsSettings);
+ ftp.open();
+ boolean exists = ftp.exists(nestedDir);
+ assertThat(exists, is(true));
+ Collection files = ftp.getFiles(nestedDir);
+ assertThat(files.size(), is(3));
+
+ for (FileAbstractModel file : files) {
+ if (file.isDirectory()) {
+ assertThat(file.getName(), is("buzz"));
+ Collection subDirFiles = ftp.getFiles(file.getFullpath());
+ assertThat(subDirFiles.size(), is(2));
+ logger.debug("Found {} files in sub dir", subDirFiles.size());
+ for (FileAbstractModel subDirFile : subDirFiles) {
+ try (InputStream inputStream = ftp.getInputStream(subDirFile)) {
+ String content = IOUtils.toString(inputStream, StandardCharsets.UTF_8);
+ logger.debug("[{}] - {}: {}", file.getName(), subDirFile.getName(), content);
+ }
+ }
+ } else {
+ try (InputStream inputStream = ftp.getInputStream(file)) {
+ String content = IOUtils.toString(inputStream, StandardCharsets.UTF_8);
+ logger.debug(" - {}: {}", file.getName(), content);
+ }
+ }
+ }
+
+ ftp.close();
+ }
+
+ /**
+ * FakeFtpServer doesn't support utf-8
+ * You have to adapt this test to your own system
+ * So this test is disabled by default
+ */
+ @Test @Ignore
+ public void testConnectToFTPServer() throws Exception {
+ String path = "/中文目录";
+ FsSettings fsSettings = FsSettings.builder("local_utf8_test")
+ .setServer(
+ Server.builder()
+ .setHostname("192.168.18.207")
+ .setUsername("helsonxiao")
+ .setPassword("123456")
+ .setPort(21)
+ .build()
+ )
+ .build();
+
+ FileAbstractorFTP ftp = new FileAbstractorFTP(fsSettings);
+ ftp.open();
+ boolean exists = ftp.exists(path);
+ assertThat(exists, is(true));
+ Collection files = ftp.getFiles(path);
+ logger.debug("Found {} files", files.size());
+
+ for (FileAbstractModel file : files) {
+ if (file.isDirectory()) {
+ Collection subDirFiles = ftp.getFiles(file.getFullpath());
+ logger.debug("Found {} files in sub dir", subDirFiles.size());
+ for (FileAbstractModel subDirFile : subDirFiles) {
+ if (subDirFile.isFile()) {
+ try (InputStream inputStream = ftp.getInputStream(subDirFile)) {
+ String content = IOUtils.toString(inputStream, StandardCharsets.UTF_8);
+ logger.debug("[{}] - {}: {}", file.getName(), subDirFile.getName(), content);
+ }
+ }
+ }
+ } else {
+ try (InputStream inputStream = ftp.getInputStream(file)) {
+ String content = IOUtils.toString(inputStream, StandardCharsets.UTF_8);
+ logger.debug(" - {}: {}", file.getName(), content);
+ }
+ }
+ }
+
+ ftp.close();
+ }
+
+ @Test
+ public void testFTPFilePermissions() throws IOException {
+ int port = fakeFtpServer.getServerControlPort();
+ FsSettings fsSettings = FsSettings.builder("fake")
+ .setServer(
+ Server.builder()
+ .setHostname("localhost")
+ .setUsername(user)
+ .setPassword(pass)
+ .setPort(port)
+ .build()
+ )
+ .build();
+
+ FileAbstractorFTP ftp = new FileAbstractorFTP(fsSettings);
+ ftp.open();
+
+ Collection files = ftp.getFiles(permissionDir);
+ assertThat(files.size(), is(2));
+ List filenames = files.stream().map(FileAbstractModel::getName).collect(Collectors.toList());
+ assertThat(filenames.contains("all.txt"), is(true));
+ assertThat(filenames.contains("none.txt"), is(true));
+ for (FileAbstractModel file : files) {
+ if (file.getName().equals("all.txt")) {
+ assertThat(file.getPermissions(), is(777));
+ try (InputStream inputStream = ftp.getInputStream(file)) {
+ String content = IOUtils.toString(inputStream, StandardCharsets.UTF_8);
+ logger.debug(" - {}: {}", file.getName(), content);
+ }
+ } else if (file.getName().equals("none.txt")) {
+ assertThat(file.getPermissions(), is(0));
+ boolean errorOccurred = false;
+ try (InputStream ignored = ftp.getInputStream(file)) {
+ logger.error(ignored);
+ } catch (IOException e) {
+ errorOccurred = true;
+ logger.error(e.getMessage());
+ }
+ assertThat(errorOccurred, is(true));
+ }
+ }
+
+ ftp.close();
+ }
+}
diff --git a/crawler/crawler-ftp/src/test/resources/log4j2.xml b/crawler/crawler-ftp/src/test/resources/log4j2.xml
new file mode 100644
index 000000000..c551aa429
--- /dev/null
+++ b/crawler/crawler-ftp/src/test/resources/log4j2.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/crawler/crawler-smb/pom.xml b/crawler/crawler-smb/pom.xml
new file mode 100644
index 000000000..a8043eb15
--- /dev/null
+++ b/crawler/crawler-smb/pom.xml
@@ -0,0 +1,38 @@
+
+
+
+ fscrawler-crawler
+ fr.pilato.elasticsearch.crawler
+ 2.7-SNAPSHOT
+
+ 4.0.0
+
+
+ fscrawler-crawler-smb
+ FSCrawler Crawlers: SMB
+
+
+
+
+ fr.pilato.elasticsearch.crawler
+ fscrawler-crawler-abstract
+
+
+
+
+ com.hierynomus
+ smbj
+ 0.11.1
+
+
+ org.slf4j
+ slf4j-api
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/crawler/crawler-smb/src/main/java/fr/pilato/elasticsearch/crawler/fs/crawler/smb/FileAbstractorSMB.java b/crawler/crawler-smb/src/main/java/fr/pilato/elasticsearch/crawler/fs/crawler/smb/FileAbstractorSMB.java
new file mode 100644
index 000000000..0385487dc
--- /dev/null
+++ b/crawler/crawler-smb/src/main/java/fr/pilato/elasticsearch/crawler/fs/crawler/smb/FileAbstractorSMB.java
@@ -0,0 +1,194 @@
+package fr.pilato.elasticsearch.crawler.fs.crawler.smb;
+
+import com.hierynomus.msdtyp.AccessMask;
+import com.hierynomus.msdtyp.SecurityInformation;
+import com.hierynomus.msfscc.fileinformation.FileIdBothDirectoryInformation;
+import com.hierynomus.mssmb2.SMB2CreateDisposition;
+import com.hierynomus.mssmb2.SMB2ShareAccess;
+import com.hierynomus.mssmb2.SMBApiException;
+import com.hierynomus.protocol.commons.EnumWithValue;
+import com.hierynomus.security.bc.BCSecurityProvider;
+import com.hierynomus.smbj.SMBClient;
+import com.hierynomus.smbj.SmbConfig;
+import com.hierynomus.smbj.auth.AuthenticationContext;
+import com.hierynomus.smbj.connection.Connection;
+import com.hierynomus.smbj.session.Session;
+import com.hierynomus.smbj.share.Directory;
+import com.hierynomus.smbj.share.DiskEntry;
+import com.hierynomus.smbj.share.DiskShare;
+import fr.pilato.elasticsearch.crawler.fs.crawler.FileAbstractModel;
+import fr.pilato.elasticsearch.crawler.fs.crawler.FileAbstractor;
+import fr.pilato.elasticsearch.crawler.fs.framework.FsCrawlerUtil;
+import fr.pilato.elasticsearch.crawler.fs.settings.FsSettings;
+import fr.pilato.elasticsearch.crawler.fs.settings.Server;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+import org.apache.commons.io.FilenameUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+public class FileAbstractorSMB extends FileAbstractor {
+
+
+ private final Logger logger = LogManager.getLogger(FileAbstractorSMB.class);
+
+ public FileAbstractorSMB(FsSettings fsSettings) {
+ super(fsSettings);
+ }
+
+ private DiskShare share;
+
+ private SMBClient client;
+
+ @Override
+ public FileAbstractModel toFileAbstractModel(String path, DiskEntry file) {
+
+ int permissions = 777;
+
+ //TODO 修正权限 file.getFileInformation().getAccessInformation().getAccessFlags()
+
+ EnumSet list = EnumWithValue.EnumUtils.toEnumSet(file.getFileInformation().getAccessInformation().getAccessFlags(), AccessMask.class);
+
+
+ //此处这样取文件/文件夹名的原因为:file.getFileInformation().getNameInformation() 取到的值永远为null
+ String fileName = FsCrawlerUtil.getFileName(file.getUncPath());
+ String extension = FilenameUtils.getExtension(fileName);
+
+ return new FileAbstractModel(
+ fileName,
+ !file.getFileInformation().getStandardInformation().isDirectory(),
+ // We are using here the local TimeZone as a reference. If the remote system is under another TZ, this might cause issues
+ LocalDateTime.ofInstant(Instant.ofEpochMilli(file.getFileInformation().getBasicInformation().getLastWriteTime().toEpochMillis()), ZoneId.systemDefault()),
+ // We don't have the creation date
+ null,
+ // We are using here the local TimeZone as a reference. If the remote system is under another TZ, this might cause issues
+ LocalDateTime.ofInstant(Instant.ofEpochMilli(file.getFileInformation().getBasicInformation().getCreationTime().toEpochMillis()), ZoneId.systemDefault()),
+ extension,
+ path,
+ path.concat("/").concat(fileName),
+ file.getFileInformation().getStandardInformation().getAllocationSize(),
+ file.getSecurityInformation(Collections.singleton(SecurityInformation.OWNER_SECURITY_INFORMATION)).getOwnerSid().toString(),
+ file.getSecurityInformation(Collections.singleton(SecurityInformation.GROUP_SECURITY_INFORMATION)).getGroupSid().toString(),
+ permissions);
+ }
+
+ @Override
+ public InputStream getInputStream(FileAbstractModel file) throws IOException {
+ if (file.isFile()) {
+ String fullPath = file.getFullpath();
+ fullPath = FsCrawlerUtil.getRelativePath(fullPath);
+
+ try {
+ return share.openFile(fullPath, EnumSet.of(AccessMask.GENERIC_READ),
+ null,
+ SMB2ShareAccess.ALL,
+ SMB2CreateDisposition.FILE_OPEN,
+ null).getInputStream();
+ } catch (SMBApiException e) {
+ logger.error("SMB client can not retrieve stream for {} , e: {}", fullPath , e);
+ throw new IOException(String.format("SMB client can not retrieve stream for [%s] ",fullPath));
+ }
+ } else {
+ return new ByteArrayInputStream(file.getName().getBytes());
+ }
+ }
+
+ @Override
+ public Collection getFiles(String dir) {
+
+ String relativeDir = FsCrawlerUtil.getRelativePath(dir);
+ logger.debug("Listing smb files from {}", relativeDir);
+ List ls;
+
+ Directory directory = share.openDirectory(relativeDir, EnumSet.of(AccessMask.GENERIC_READ),
+ null,
+ SMB2ShareAccess.ALL,
+ SMB2CreateDisposition.FILE_OPEN,
+ null);
+
+ ls = directory.list();
+ if (ls == null) {
+ return null;
+ }
+
+ Collection result = new ArrayList<>(ls.size());
+ // Iterate other files
+ // We ignore here all files like . and ..
+ result.addAll(ls.stream().filter(file -> !".".equals(file.getFileName()) &&
+ !"..".equals(file.getFileName()))
+ .map(file -> toFileAbstractModel(dir, share.open(relativeDir + "/" + file.getFileName(), EnumSet.of(AccessMask.GENERIC_READ),
+ null,
+ SMB2ShareAccess.ALL,
+ SMB2CreateDisposition.FILE_OPEN,
+ null)))
+ .collect(Collectors.toList()));
+
+ logger.debug("{} local files found", result.size());
+ return result;
+ }
+
+ @Override
+ public boolean exists(String dir) {
+ dir = FsCrawlerUtil.getRelativePath(dir);
+ return share.folderExists(dir);
+ }
+
+ @Override
+ public void open() throws Exception {
+ share = openSMBConnection(fsSettings.getServer());
+ }
+
+ @Override
+ public void close() throws Exception {
+ share.close();
+ client.close();
+ }
+
+
+ private DiskShare openSMBConnection(Server server) throws IOException {
+ logger.debug("Opening SMB connection to {}@{}", server.getUsername(), server.getHostname());
+
+
+ Session session;
+ AuthenticationContext ac = new AuthenticationContext(server.getUsername(), server.getPassword().toCharArray(), server.getHostname());
+ try {
+ logger.debug("Start trying to connect through SMB2");
+ SmbConfig smbConfig = SmbConfig.builder()
+ .withTimeout(12, TimeUnit.SECONDS) // Timeout sets Read, Write, and Transact timeouts (default is 60 seconds)
+ .withSoTimeout(18, TimeUnit.SECONDS) // Socket Timeout (default is 0 seconds, blocks forever)
+ .build();
+
+ client = new SMBClient(smbConfig);
+ Connection connection = client.connect(server.getHostname());
+ session = connection.authenticate(ac);
+ } catch (UnsupportedOperationException | SMBApiException e) {
+ logger.debug("Start trying to connect through SMB3");
+ //close client
+ client.close();
+ SmbConfig smbConfig = SmbConfig.builder()
+ //SMB3.0 use BCSecurityProvider
+ .withSecurityProvider(new BCSecurityProvider())
+ .withTimeout(12, TimeUnit.SECONDS) // Timeout sets Read, Write, and Transact timeouts (default is 60 seconds)
+ .withSoTimeout(18, TimeUnit.SECONDS) // Socket Timeout (default is 0 seconds, blocks forever)
+ .build();
+ client = new SMBClient(smbConfig);
+ Connection connection = client.connect(server.getHostname());
+ session = connection.authenticate(ac);
+ }
+ String url = fsSettings.getFs().getUrl();
+ String serverName = FsCrawlerUtil.getServerName(url);
+ return (DiskShare) session.connectShare(serverName);
+
+ }
+}
diff --git a/crawler/crawler-smb/src/test/java/fr/pilato/elasticsearch/crawler/fs/crawler/smb/FileAbstractorSMBTest.java b/crawler/crawler-smb/src/test/java/fr/pilato/elasticsearch/crawler/fs/crawler/smb/FileAbstractorSMBTest.java
new file mode 100644
index 000000000..062480253
--- /dev/null
+++ b/crawler/crawler-smb/src/test/java/fr/pilato/elasticsearch/crawler/fs/crawler/smb/FileAbstractorSMBTest.java
@@ -0,0 +1,49 @@
+package fr.pilato.elasticsearch.crawler.fs.crawler.smb;
+
+import fr.pilato.elasticsearch.crawler.fs.crawler.FileAbstractModel;
+import fr.pilato.elasticsearch.crawler.fs.settings.Fs;
+import fr.pilato.elasticsearch.crawler.fs.settings.FsSettings;
+import fr.pilato.elasticsearch.crawler.fs.settings.Server;
+import fr.pilato.elasticsearch.crawler.fs.test.framework.AbstractFSCrawlerTestCase;
+import java.util.Collection;
+import junit.framework.TestCase;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class FileAbstractorSMBTest extends AbstractFSCrawlerTestCase {
+
+ @Test
+ @Ignore
+ public void testConnectToWindows() throws Exception {
+ String[] paths = {"","folder","文件夹","folder/文件夹","文件夹/folder"};
+ String host = "192.168.31.45";
+ String user = "lzwcyd";
+ String pass = "123456";
+ String url = "//Desktop/win10_share_test";
+ FsSettings fsSettings = FsSettings.builder("foo")
+ .setServer(
+ Server.builder()
+ .setHostname(host)
+ .setUsername(user)
+ .setPassword(pass)
+ .build()
+ ).setFs(Fs.builder()
+ .setUrl(url)
+ .build())
+ .build();
+ FileAbstractorSMB smb = new FileAbstractorSMB(fsSettings);
+ smb.open();
+ for (String path : paths) {
+ boolean exists = smb.exists(path);
+ assertThat(exists, is(true));
+ Collection files = smb.getFiles(path);
+ logger.debug("Found {} files", files.size());
+ for (FileAbstractModel file : files) {
+ logger.debug(" - {}", file);
+ }
+ }
+ smb.close();
+ }
+}
\ No newline at end of file
diff --git a/crawler/crawler-ssh/src/main/java/fr/pilato/elasticsearch/crawler/fs/crawler/ssh/FileAbstractorSSH.java b/crawler/crawler-ssh/src/main/java/fr/pilato/elasticsearch/crawler/fs/crawler/ssh/FileAbstractorSSH.java
index 2c95c94f8..a872def6e 100644
--- a/crawler/crawler-ssh/src/main/java/fr/pilato/elasticsearch/crawler/fs/crawler/ssh/FileAbstractorSSH.java
+++ b/crawler/crawler-ssh/src/main/java/fr/pilato/elasticsearch/crawler/fs/crawler/ssh/FileAbstractorSSH.java
@@ -62,7 +62,7 @@ public FileAbstractModel toFileAbstractModel(String path, ChannelSftp.LsEntry fi
LocalDateTime.ofInstant(Instant.ofEpochMilli(file.getAttrs().getATime()*1000L), ZoneId.systemDefault()),
FilenameUtils.getExtension(file.getFilename()),
path,
- path.concat("/").concat(file.getFilename()),
+ path.equals("/") ? path.concat(file.getFilename()) : path.concat("/").concat(file.getFilename()),
file.getAttrs().getSize(),
Integer.toString(file.getAttrs().getUId()),
Integer.toString(file.getAttrs().getGId()),
diff --git a/crawler/pom.xml b/crawler/pom.xml
index 64d02cb9f..ee7874a63 100644
--- a/crawler/pom.xml
+++ b/crawler/pom.xml
@@ -16,7 +16,9 @@
crawler-abstract
crawler-fs
+ crawler-ftp
crawler-ssh
+ crawler-smb
diff --git a/distribution/pom.xml b/distribution/pom.xml
index c021f95d7..898a411b2 100644
--- a/distribution/pom.xml
+++ b/distribution/pom.xml
@@ -21,6 +21,7 @@
+
${env.DOCKER_SKIP}
build
dadoonet
diff --git a/docs/source/admin/fs/ftp.rst b/docs/source/admin/fs/ftp.rst
new file mode 100644
index 000000000..c8b570b4d
--- /dev/null
+++ b/docs/source/admin/fs/ftp.rst
@@ -0,0 +1,48 @@
+.. _ftp-settings:
+
+FTP settings
+------------
+
+You can index files remotely using FTP.
+
+Here is a list of FTP settings (under ``server.`` prefix):
+
++-----------------------+-----------------------+-----------------------+
+| Name | Default value | Documentation |
++=======================+=======================+=======================+
+| ``server.hostname`` | ``null`` | Hostname |
++-----------------------+-----------------------+-----------------------+
+| ``server.port`` | ``21`` | Port |
++-----------------------+-----------------------+-----------------------+
+| ``server.username`` | ``anonymous`` | :ref:`ftp_login` |
++-----------------------+-----------------------+-----------------------+
+| ``server.password`` | ``null`` | :ref:`ftp_login` |
++-----------------------+-----------------------+-----------------------+
+| ``server.protocol`` | ``"local"`` | Set it to ``ftp`` |
++-----------------------+-----------------------+-----------------------+
+
+.. _ftp_login:
+
+Username / Password
+~~~~~~~~~~~~~~~~~~~
+
+Let’s say you want to index from a remote server using FTP:
+
+- FS URL: ``/path/to/data/dir/on/server``
+- Server: ``mynode.mydomain.com``
+- Username: ``username`` (default to ``anonymous``)
+- Password: ``password``
+- Protocol: ``ftp`` (default to ``local``)
+- Port: ``21`` (default to ``21``)
+
+.. code:: yaml
+
+ name: "test"
+ fs:
+ url: "/path/to/data/dir/on/server"
+ server:
+ hostname: "mynode.mydomain.com"
+ port: 21
+ username: "username"
+ password: "password"
+ protocol: "ftp"
diff --git a/docs/source/admin/fs/smb.rst b/docs/source/admin/fs/smb.rst
new file mode 100644
index 000000000..facc31445
--- /dev/null
+++ b/docs/source/admin/fs/smb.rst
@@ -0,0 +1,44 @@
+.. _smb-settings:
+
+SMB settings
+------------
+
+You can index files remotely using SMB.
+
+Here is a list of SMB settings (under ``server.`` prefix):
+
++-----------------------+-----------------------+-----------------------+
+| Name | Default value | Documentation |
++=======================+=======================+=======================+
+| ``server.hostname`` | ``null`` | Hostname |
++-----------------------+-----------------------+-----------------------+
+| ``server.username`` | ``Guest`` | :ref:`smb_login` |
++-----------------------+-----------------------+-----------------------+
+| ``server.password`` | ``null`` | :ref:`smb_login` |
++-----------------------+-----------------------+-----------------------+
+| ``server.protocol`` | ``"local"`` | Set it to ``smb`` |
++-----------------------+-----------------------+-----------------------+
+
+.. _smb_login:
+
+Username / Password
+~~~~~~~~~~~~~~~~~~~
+
+Let’s say you want to index from a remote server using SMB:
+
+- FS URL: ``/path/to/data/dir/on/server``
+- Server: ``mynode.mydomain.com``
+- Username: ``username`` (default to ``anonymous``)
+- Password: ``password``
+- Protocol: ``smb`` (default to ``local``)
+
+.. code:: yaml
+
+ name: "test"
+ fs:
+ url: "/path/to/data/dir/on/server"
+ server:
+ hostname: "mynode.mydomain.com"
+ username: "username"
+ password: "password"
+ protocol: "smb"
diff --git a/docs/source/admin/fs/ssh.rst b/docs/source/admin/fs/ssh.rst
index 2653deb3a..48f7683cd 100644
--- a/docs/source/admin/fs/ssh.rst
+++ b/docs/source/admin/fs/ssh.rst
@@ -5,7 +5,7 @@ SSH settings
You can index files remotely using SSH.
-Here is a list of SSH settings (under ``server.`` prefix)`:
+Here is a list of SSH settings (under ``server.`` prefix):
+-----------------------+-----------------------+-----------------------+
| Name | Default value | Documentation |
@@ -93,3 +93,20 @@ To specify the drive, you need to use the following format:
username: "username"
password: "password"
protocol: "ssh"
+
+Windows shared folder
+~~~~~~~~~~~~~~~~~~~~~
+
+When using Windows shared folder, you need to use the following format:
+
+.. code:: yaml
+
+ name: "test"
+ fs:
+ url: "//DESKTOP-NAME/path/to/data/dir/on/server"
+ server:
+ hostname: "mynode.mydomain.com"
+ port: 22
+ username: "username"
+ password: "password"
+ protocol: "ssh"
diff --git a/docs/source/conf.py b/docs/source/conf.py
index a3c5c0aad..93d39326e 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -16,7 +16,7 @@
# import sys
# sys.path.insert(0, os.path.abspath('.'))
import os
-import ConfigParser
+import configparser
from datetime import date
from os.path import join, dirname
@@ -38,7 +38,7 @@
# built documents.
#
-config = ConfigParser.RawConfigParser()
+config = configparser.RawConfigParser()
config.read(join(dirname(__file__), "fscrawler.ini"))
# development versions always have the suffix '-SNAPSHOT'
diff --git a/docs/source/dev/build.rst b/docs/source/dev/build.rst
index 8a3964e28..cc23c7017 100644
--- a/docs/source/dev/build.rst
+++ b/docs/source/dev/build.rst
@@ -51,6 +51,12 @@ But you need first to specify the Maven profile to use and rebuild the project.
* ``es-7x`` for Elasticsearch 7.x
* ``es-6x`` for Elasticsearch 6.x
+Run a specific test from your Terminal
+""""""""""""""""""""""""""""""""""""""
+
+To run a specific integration test, just run::
+
+ mvn verify -am -Dtests.class=fr.pilato.elasticsearch.crawler.fs.test.integration.CLASS_NAME -Dtests.method="METHOD_NAME"
Run tests with an external cluster
""""""""""""""""""""""""""""""""""
diff --git a/docs/source/dev/doc.rst b/docs/source/dev/doc.rst
index cf002380b..5226e75ab 100644
--- a/docs/source/dev/doc.rst
+++ b/docs/source/dev/doc.rst
@@ -4,12 +4,11 @@ Writing documentation
This project uses `ReadTheDocs `_ to build and serve the documentation.
If you want to run the generation of documentation (recommended!), you need
-to have Python installed. Then install ``sphinx``
-$ pip install sphinx sphinx-autobuild
+to have Python3 installed.
-Assuming you have `Python `_ already, install `Sphinx `_::
+Assuming you have `Python3 `_ already, install `Sphinx `_::
- $ pip install sphinx sphinx-autobuild
+ $ pip install sphinx sphinx-autobuild sphinx_rtd_theme recommonmark
Go to the ``docs`` directory and build the html documentation::
diff --git a/docs/source/index.rst b/docs/source/index.rst
index 7ae8598fc..738029d12 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -15,7 +15,7 @@ This crawler helps to index binary documents such as PDF, Open Office, MS Office
**Main features**:
* Local file system (or a mounted drive) crawling and index new files, update existing ones and removes old ones.
-* Remote file system over SSH crawling.
+* Remote file system over SSH/FTP/SMB crawling.
* REST interface to let you "upload" your binary documents to elasticsearch.
.. note::
@@ -56,6 +56,7 @@ This crawler helps to index binary documents such as PDF, Open Office, MS Office
admin/fs/simple
admin/fs/local-fs
admin/fs/ssh
+ admin/fs/ftp
admin/fs/elasticsearch
admin/fs/wpsearch
admin/fs/rest
diff --git a/docs/source/installation.rst b/docs/source/installation.rst
index 955dd3287..1ede539ae 100644
--- a/docs/source/installation.rst
+++ b/docs/source/installation.rst
@@ -184,7 +184,7 @@ Then, you can run Elasticsearch.
.. code:: sh
- docker-compose up -d elasticsearch elasticsearch2
+ docker-compose up -d elasticsearch
docker-compose logs -f elasticsearch
Wait for elasticsearch to be started:
diff --git a/docs/source/user/options.rst b/docs/source/user/options.rst
index aebc6d9f8..2984a0f01 100644
--- a/docs/source/user/options.rst
+++ b/docs/source/user/options.rst
@@ -40,5 +40,7 @@ You will find more information about settings in the following sections:
- :ref:`cli-options`
- :ref:`local-fs-settings`
- :ref:`ssh-settings`
+- :ref:`ftp-settings`
+- :ref:`smb-settings`
- :ref:`elasticsearch-settings`
diff --git a/framework/src/main/java/fr/pilato/elasticsearch/crawler/fs/framework/FsCrawlerUtil.java b/framework/src/main/java/fr/pilato/elasticsearch/crawler/fs/framework/FsCrawlerUtil.java
index 1f74a541d..bc32b3dfd 100644
--- a/framework/src/main/java/fr/pilato/elasticsearch/crawler/fs/framework/FsCrawlerUtil.java
+++ b/framework/src/main/java/fr/pilato/elasticsearch/crawler/fs/framework/FsCrawlerUtil.java
@@ -29,7 +29,6 @@
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
-import java.nio.charset.StandardCharsets;
import java.nio.file.CopyOption;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.FileSystem;
@@ -282,11 +281,34 @@ public static boolean isIndexable(String content, List filters) {
return true;
}
+ public static String getPathSeparator(String path) {
+ if (path.contains("/") && !path.contains("\\")) {
+ return "/";
+ }
+
+ if (!path.contains("/") && (path.contains("\\") || path.contains(":"))) {
+ return "\\";
+ }
+
+ return File.separator;
+ }
+
+ public static String computeRealPathName(String _dirname, String filename) {
+ // new File(dirname, filename).toString() is not suitable for server
+ String separator = getPathSeparator(_dirname);
+ String dirname = _dirname.endsWith(separator) ? _dirname : _dirname.concat(separator);
+ return dirname + filename;
+ }
+
public static String computeVirtualPathName(String rootPath, String realPath) {
- String result = "/";
- if (realPath != null && realPath.length() > rootPath.length()) {
- result = realPath.substring(rootPath.length())
- .replace("\\", "/");
+ String result = getPathSeparator(rootPath);
+ if (realPath.startsWith(rootPath) && realPath.length() > rootPath.length()) {
+ if (rootPath.equals("/")) {
+ // "/" is very common for FTP
+ result = realPath;
+ } else {
+ result = realPath.substring(rootPath.length());
+ }
}
logger.debug("computeVirtualPathName({}, {}) = {}", rootPath, realPath, result);
@@ -415,12 +437,22 @@ public static int getFilePermissions(final File file) {
return user * 100 + group * 10 + others;
}
catch(Exception e) {
- logger.warn("Failed to determine 'owner' of {}: {}", file, e.getMessage());
+ logger.warn("Failed to determine 'permissions' of {}: {}", file, e.getMessage());
return -1;
}
}
- private static int toOctalPermission(boolean read, boolean write, boolean execute) {
+ /**
+ * This method is used to get the file/folder name from the path, only for SMB Crawler
+ * because file.getFileInformation().getNameInformation() always equal null
+ * @param uncPath file uncPath
+ * @return fileName
+ */
+ public static String getFileName(String uncPath) {
+ return uncPath.substring(uncPath.lastIndexOf("\\") + 1);
+ }
+
+ public static int toOctalPermission(boolean read, boolean write, boolean execute) {
return (read ? 4 : 0) + (write ? 2 : 0) + (execute ? 1 : 0);
}
@@ -576,7 +608,9 @@ public static void createDirIfMissing(Path root) {
if (Files.notExists(root)) {
Files.createDirectory(root);
}
- } catch (IOException ignored) { }
+ } catch (IOException ignored) {
+ logger.error("Failed to create config dir");
+ }
}
/**
@@ -629,4 +663,29 @@ public static String extractMajorVersion(String version) {
public static String extractMinorVersion(String version) {
return version.split("\\.")[1];
}
+
+ /**
+ * obtain server name through server url(SMB)
+ * @param url serverUrl
+ * @return serverName
+ */
+ public static String getServerName(String url) {
+ return url.split("/")[url.startsWith("//") ? 3 : url.startsWith("/") ? 1 : 0];
+ }
+
+
+ /**
+ * get relative path (SMB) //desktopName/shareName //desktopName/shareName/test /shareName /shareName/test
+ * @param dir dir
+ * @return relative path
+ */
+ public static String getRelativePath(String dir) {
+ String[] path = dir.split("/");
+ if (dir.startsWith("//")) {
+ dir = dir.substring(3 + path[2].length() + path[3].length());
+ } else if (dir.startsWith("/") && path.length >= 2) {
+ dir = dir.substring(1 + path[1].length());
+ }
+ return dir;
+ }
}
diff --git a/framework/src/test/java/fr/pilato/elasticsearch/crawler/fs/framework/FsCrawlerUtilTest.java b/framework/src/test/java/fr/pilato/elasticsearch/crawler/fs/framework/FsCrawlerUtilTest.java
index d420837b5..4f4e9d675 100644
--- a/framework/src/test/java/fr/pilato/elasticsearch/crawler/fs/framework/FsCrawlerUtilTest.java
+++ b/framework/src/test/java/fr/pilato/elasticsearch/crawler/fs/framework/FsCrawlerUtilTest.java
@@ -20,6 +20,8 @@
package fr.pilato.elasticsearch.crawler.fs.framework;
import fr.pilato.elasticsearch.crawler.fs.test.framework.AbstractFSCrawlerTestCase;
+import java.time.LocalDateTime;
+import java.util.TimeZone;
import org.junit.BeforeClass;
import org.junit.Test;
@@ -33,12 +35,19 @@
import java.util.Set;
import static com.carrotsearch.randomizedtesting.RandomizedTest.randomIntBetween;
+import static fr.pilato.elasticsearch.crawler.fs.framework.FsCrawlerUtil.computeRealPathName;
+import static fr.pilato.elasticsearch.crawler.fs.framework.FsCrawlerUtil.computeVirtualPathName;
import static fr.pilato.elasticsearch.crawler.fs.framework.FsCrawlerUtil.extractMajorVersion;
import static fr.pilato.elasticsearch.crawler.fs.framework.FsCrawlerUtil.extractMinorVersion;
+import static fr.pilato.elasticsearch.crawler.fs.framework.FsCrawlerUtil.getFileExtension;
+import static fr.pilato.elasticsearch.crawler.fs.framework.FsCrawlerUtil.getFileName;
import static fr.pilato.elasticsearch.crawler.fs.framework.FsCrawlerUtil.getFilePermissions;
import static fr.pilato.elasticsearch.crawler.fs.framework.FsCrawlerUtil.getGroupName;
import static fr.pilato.elasticsearch.crawler.fs.framework.FsCrawlerUtil.getOwnerName;
+import static fr.pilato.elasticsearch.crawler.fs.framework.FsCrawlerUtil.getServerName;
+import static fr.pilato.elasticsearch.crawler.fs.framework.FsCrawlerUtil.getRelativePath;
import static fr.pilato.elasticsearch.crawler.fs.framework.FsCrawlerUtil.isFileSizeUnderLimit;
+import static fr.pilato.elasticsearch.crawler.fs.framework.FsCrawlerUtil.localDateTimeToDate;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.isEmptyOrNullString;
@@ -78,6 +87,17 @@ public void testPermissions() {
assertThat(permissions, is(700));
}
+ @Test
+ public void testGetFileName(){
+ assertThat(getFileName("\\test\\test.txt"),is("test.txt"));
+ assertThat(getFileName("\\test.txt"),is("test.txt"));
+ assertThat(getFileName("test.txt"),is("test.txt"));
+ assertThat(getFileName("\\test\\test\\test.txt"),is("test.txt"));
+ assertThat(getFileName("\\test\\test"),is("test"));
+ assertThat(getFileName("\\test"),is("test"));
+ assertThat(getFileName("test"),is("test"));
+ }
+
@Test
public void testIsFileSizeUnderLimit() {
assertThat(isFileSizeUnderLimit(ByteSizeValue.parseBytesSizeValue("1mb"), 1), is(true));
@@ -97,4 +117,141 @@ public void testExtractMinorVersion() {
assertThat(extractMinorVersion("7.2.0"), is("2"));
assertThat(extractMinorVersion("10.1.0"), is("1"));
}
+
+ @Test
+ public void testGetRealPathNameWindows() {
+ testRealPath("/C:", "test-windows.txt", "/C:/test-windows.txt");
+ testRealPath("/C:/", "test-windows.txt", "/C:/test-windows.txt");
+ testRealPath("/C:/dir", "test-windows.txt", "/C:/dir/test-windows.txt");
+ testRealPath("/C:/dir/", "test-windows.txt", "/C:/dir/test-windows.txt");
+
+ testRealPath("C:/", "test-windows.txt", "C:/test-windows.txt");
+ testRealPath("C:/dir", "test-windows.txt", "C:/dir/test-windows.txt");
+ testRealPath("C:/dir/", "test-windows.txt", "C:/dir/test-windows.txt");
+
+ testRealPath("C:", "test-windows.txt", "C:\\test-windows.txt");
+ testRealPath("C:\\", "test-windows.txt", "C:\\test-windows.txt");
+ testRealPath("C:\\dir", "test-windows.txt", "C:\\dir\\test-windows.txt");
+ testRealPath("C:\\dir\\", "test-windows.txt", "C:\\dir\\test-windows.txt");
+
+ testRealPath("\\\\SOMEONE", "test-smb.txt", "\\\\SOMEONE\\test-smb.txt");
+ testRealPath("\\\\SOMEONE\\", "test-smb.txt", "\\\\SOMEONE\\test-smb.txt");
+ testRealPath("\\\\SOMEONE\\share", "test-smb.txt", "\\\\SOMEONE\\share\\test-smb.txt");
+ testRealPath("\\\\SOMEONE\\share\\", "test-smb.txt", "\\\\SOMEONE\\share\\test-smb.txt");
+ }
+
+ @Test
+ public void testGetRealPathNameLinux() {
+ // Local Linux / FTP
+ testRealPath("/", "test-linux.txt", "/test-linux.txt");
+ testRealPath("/dir", "test-linux.txt", "/dir/test-linux.txt");
+ testRealPath("/dir/", "test-linux.txt", "/dir/test-linux.txt");
+
+ // SMB
+ testRealPath("//SOMEONE", "test-smb.txt", "//SOMEONE/test-smb.txt");
+ testRealPath("//SOMEONE/", "test-smb.txt", "//SOMEONE/test-smb.txt");
+ testRealPath("//SOMEONE/share", "test-smb.txt", "//SOMEONE/share/test-smb.txt");
+ testRealPath("//SOMEONE/share/", "test-smb.txt", "//SOMEONE/share/test-smb.txt");
+ }
+
+ private void testRealPath(String dirname, String filename, String expectedPath) {
+ assertThat(computeRealPathName(dirname, filename), is(expectedPath));
+ }
+
+ @Test
+ public void testComputePathLinux() {
+ // Local Linux / FTP
+ testVirtualPath("/", "/", "/");
+ testVirtualPath("/", "/dir", "/dir");
+ testVirtualPath("/", "/dir/subdir", "/dir/subdir");
+ testVirtualPath("/", "/file.txt", "/file.txt");
+ testVirtualPath("/", "/dir/file.txt", "/dir/file.txt");
+ testVirtualPath("/", "/dir/subdir/file.txt", "/dir/subdir/file.txt");
+
+ testVirtualPath("/tmp", "/tmp", "/");
+ testVirtualPath("/tmp", "/tmp/dir", "/dir");
+ testVirtualPath("/tmp", "/tmp/dir/subdir", "/dir/subdir");
+ testVirtualPath("/tmp", "/tmp/file.txt", "/file.txt");
+ testVirtualPath("/tmp", "/tmp/dir/file.txt", "/dir/file.txt");
+ testVirtualPath("/tmp", "/tmp/dir/subdir/file.txt", "/dir/subdir/file.txt");
+
+ // SMB
+ testVirtualPath("//SOMEONE/share", "//SOMEONE/share", "/");
+ testVirtualPath("//SOMEONE/share", "//SOMEONE/share/dir", "/dir");
+ testVirtualPath("//SOMEONE/share", "//SOMEONE/share/dir/subdir", "/dir/subdir");
+ testVirtualPath("//SOMEONE/share", "//SOMEONE/share/file.txt", "/file.txt");
+ testVirtualPath("//SOMEONE/share", "//SOMEONE/share/dir/file.txt", "/dir/file.txt");
+ testVirtualPath("//SOMEONE/share", "//SOMEONE/share/dir/subdir/file.txt", "/dir/subdir/file.txt");
+ }
+
+ @Test
+ public void testComputePathWindows() {
+ testVirtualPath("C:", "C:", "\\");
+ testVirtualPath("C:", "C:\\dir", "\\dir");
+ testVirtualPath("C:", "C:\\dir\\subdir", "\\dir\\subdir");
+ testVirtualPath("C:", "C:\\file.txt", "\\file.txt");
+ testVirtualPath("C:", "C:\\dir\\file.txt", "\\dir\\file.txt");
+ testVirtualPath("C:", "C:\\dir\\subdir\\file.txt", "\\dir\\subdir\\file.txt");
+
+ testVirtualPath("C:\\tmp", "C:\\tmp", "\\");
+ testVirtualPath("C:\\tmp", "C:\\tmp\\dir", "\\dir");
+ testVirtualPath("C:\\tmp", "C:\\tmp\\dir\\subdir", "\\dir\\subdir");
+ testVirtualPath("C:\\tmp", "C:\\tmp\\file.txt", "\\file.txt");
+ testVirtualPath("C:\\tmp", "C:\\tmp\\dir\\file.txt", "\\dir\\file.txt");
+ testVirtualPath("C:\\tmp", "C:\\tmp\\dir\\subdir\\file.txt", "\\dir\\subdir\\file.txt");
+
+ testVirtualPath("C:/tmp", "C:/tmp", "/");
+ testVirtualPath("C:/tmp", "C:/tmp/dir", "/dir");
+ testVirtualPath("C:/tmp", "C:/tmp/dir/subdir", "/dir/subdir");
+ testVirtualPath("C:/tmp", "C:/tmp/file.txt", "/file.txt");
+ testVirtualPath("C:/tmp", "C:/tmp/dir/file.txt", "/dir/file.txt");
+ testVirtualPath("C:/tmp", "C:/tmp/dir/subdir/file.txt", "/dir/subdir/file.txt");
+
+ testVirtualPath("/C:/tmp", "/C:/tmp", "/");
+ testVirtualPath("/C:/tmp", "/C:/tmp/dir", "/dir");
+ testVirtualPath("/C:/tmp", "/C:/tmp/dir/subdir", "/dir/subdir");
+ testVirtualPath("/C:/tmp", "/C:/tmp/file.txt", "/file.txt");
+ testVirtualPath("/C:/tmp", "/C:/tmp/dir/file.txt", "/dir/file.txt");
+ testVirtualPath("/C:/tmp", "/C:/tmp/dir/subdir/file.txt", "/dir/subdir/file.txt");
+
+ testVirtualPath("\\\\SOMEONE\\share", "\\\\SOMEONE\\share", "\\");
+ testVirtualPath("\\\\SOMEONE\\share", "\\\\SOMEONE\\share\\dir", "\\dir");
+ testVirtualPath("\\\\SOMEONE\\share", "\\\\SOMEONE\\share\\dir\\subdir", "\\dir\\subdir");
+ testVirtualPath("\\\\SOMEONE\\share", "\\\\SOMEONE\\share\\file.txt", "\\file.txt");
+ testVirtualPath("\\\\SOMEONE\\share", "\\\\SOMEONE\\share\\dir\\file.txt", "\\dir\\file.txt");
+ testVirtualPath("\\\\SOMEONE\\share", "\\\\SOMEONE\\share\\dir\\subdir\\file.txt", "\\dir\\subdir\\file.txt");
+ }
+
+ private void testVirtualPath(String rootPath, String realPath, String expectedPath) {
+ assertThat(computeVirtualPathName(rootPath, realPath), is(expectedPath));
+ }
+
+ @Test
+ public void testGetFileExtension() {
+ assertThat(getFileExtension(new File("foo.bar")), is("bar"));
+ assertThat(getFileExtension(new File("foo")), is(""));
+ assertThat(getFileExtension(new File("foo.bar.baz")), is("baz"));
+ }
+
+ @Test
+ public void testLocalDateToDate() {
+ LocalDateTime now = LocalDateTime.now();
+ logger.info("Current Time [{}] in [{}] is actually [{}]", now, TimeZone.getDefault().getDisplayName(), localDateTimeToDate(now));
+ }
+
+ @Test
+ public void testGetServerName(){
+ assertThat(getServerName("//desttop-123/test"), is("test"));
+ }
+
+ @Test
+ public void testGetRelativePath(){
+ assertThat(getRelativePath("//desktopName/shareName"), is(""));
+ assertThat(getRelativePath("//desktopName/shareName/test"), is("/test"));
+ assertThat(getRelativePath("//desktopName/shareName/test.txt"), is("/test.txt"));
+ assertThat(getRelativePath("//desktopName/shareName/folder/test.txt"), is("/folder/test.txt"));
+ assertThat(getRelativePath("/shareName"), is(""));
+ assertThat(getRelativePath("/shareName/test"), is("/test"));
+ }
+
}
diff --git a/integration-tests/it-common/pom.xml b/integration-tests/it-common/pom.xml
index ceeb5174a..952cdddde 100644
--- a/integration-tests/it-common/pom.xml
+++ b/integration-tests/it-common/pom.xml
@@ -31,6 +31,11 @@
fr.pilato.elasticsearch.crawler
fscrawler-test-documents
+
+ org.mockftpserver
+ MockFtpServer
+ compile
+
+
${DOCKER_USERNAME}
+
${DOCKER_PASSWORD}
${env.DOCKER_USERNAME}
${env.DOCKER_PASSWORD}
@@ -551,11 +553,21 @@
fscrawler-crawler-fs
2.7-SNAPSHOT
+
+ fr.pilato.elasticsearch.crawler
+ fscrawler-crawler-ftp
+ 2.7-SNAPSHOT
+
fr.pilato.elasticsearch.crawler
fscrawler-crawler-ssh
2.7-SNAPSHOT
+
+ fr.pilato.elasticsearch.crawler
+ fscrawler-crawler-smb
+ 2.7-SNAPSHOT
+
fr.pilato.elasticsearch.crawler
fscrawler-tika
@@ -794,13 +806,33 @@
-
+
+
com.jcraft
jsch
0.1.55
+
+
+ commons-net
+ commons-net
+ 3.8.0
+
+
+ org.mockftpserver
+ MockFtpServer
+ 2.8.0
+ test
+
+
+ org.slf4j
+ slf4j-api
+
+
+
+
com.beust
@@ -888,6 +920,12 @@
${log4j.version}
true
+
+ org.apache.logging.log4j
+ log4j-iostreams
+ ${log4j.version}
+ true
+
org.fusesource.jansi
jansi
diff --git a/settings/src/main/java/fr/pilato/elasticsearch/crawler/fs/settings/FsCrawlerValidator.java b/settings/src/main/java/fr/pilato/elasticsearch/crawler/fs/settings/FsCrawlerValidator.java
index 081e441e9..4b6f086b4 100644
--- a/settings/src/main/java/fr/pilato/elasticsearch/crawler/fs/settings/FsCrawlerValidator.java
+++ b/settings/src/main/java/fr/pilato/elasticsearch/crawler/fs/settings/FsCrawlerValidator.java
@@ -62,19 +62,26 @@ public static boolean validateSettings(Logger logger, FsSettings settings, boole
// Checking protocol
if (settings.getServer() != null) {
if (!Server.PROTOCOL.LOCAL.equals(settings.getServer().getProtocol()) &&
- !Server.PROTOCOL.SSH.equals(settings.getServer().getProtocol())) {
+ !Server.PROTOCOL.SSH.equals(settings.getServer().getProtocol()) && !Server.PROTOCOL.FTP.equals(settings.getServer().getProtocol()) && !Server.PROTOCOL.SMB.equals(settings.getServer().getProtocol())) {
// Non supported protocol
logger.error(settings.getServer().getProtocol() + " is not supported yet. Please use " +
- Server.PROTOCOL.LOCAL + " or " + Server.PROTOCOL.SSH + ". Disabling crawler");
+ Server.PROTOCOL.LOCAL + " or " + Server.PROTOCOL.SSH + " or " + Server.PROTOCOL.FTP + " or " + Server.PROTOCOL.SMB + ". Disabling crawler");
return true;
}
// Checking username/password
if (Server.PROTOCOL.SSH.equals(settings.getServer().getProtocol()) &&
FsCrawlerUtil.isNullOrEmpty(settings.getServer().getUsername())) {
- // Non supported protocol
logger.error("When using SSH, you need to set a username and probably a password or a pem file. Disabling crawler");
return true;
+ } else if (Server.PROTOCOL.FTP.equals(settings.getServer().getProtocol()) &&
+ FsCrawlerUtil.isNullOrEmpty(settings.getServer().getUsername())) {
+ logger.error("When using FTP, you need to set a username and probably a password. Disabling crawler");
+ return true;
+ } else if (Server.PROTOCOL.SMB.equals(settings.getServer().getProtocol()) &&
+ FsCrawlerUtil.isNullOrEmpty(settings.getServer().getUsername())) {
+ logger.error("When using SMB, you need to set a username and probably a password. Disabling crawler");
+ return true;
}
}
diff --git a/settings/src/main/java/fr/pilato/elasticsearch/crawler/fs/settings/Server.java b/settings/src/main/java/fr/pilato/elasticsearch/crawler/fs/settings/Server.java
index 270392e0e..5bf059f84 100644
--- a/settings/src/main/java/fr/pilato/elasticsearch/crawler/fs/settings/Server.java
+++ b/settings/src/main/java/fr/pilato/elasticsearch/crawler/fs/settings/Server.java
@@ -28,8 +28,11 @@ public class Server {
public static final class PROTOCOL {
public static final String LOCAL = "local";
+ public static final String SMB = "smb";
public static final String SSH = "ssh";
+ public static final String FTP = "ftp";
public static final int SSH_PORT = 22;
+ public static final int FTP_PORT = 21;
}
public Server() {
diff --git a/settings/src/test/java/fr/pilato/elasticsearch/crawler/fs/settings/FsCrawlerValidatorTest.java b/settings/src/test/java/fr/pilato/elasticsearch/crawler/fs/settings/FsCrawlerValidatorTest.java
index b59164255..70214a59a 100644
--- a/settings/src/test/java/fr/pilato/elasticsearch/crawler/fs/settings/FsCrawlerValidatorTest.java
+++ b/settings/src/test/java/fr/pilato/elasticsearch/crawler/fs/settings/FsCrawlerValidatorTest.java
@@ -66,6 +66,10 @@ public void testSettingsValidation() {
settings = buildSettings(null, Server.builder().setProtocol(Server.PROTOCOL.SSH).build());
assertThat(FsCrawlerValidator.validateSettings(logger, settings, false), is(true));
+ // Checking username / password when FTP
+ settings = buildSettings(null, Server.builder().setProtocol(Server.PROTOCOL.FTP).build());
+ assertThat(FsCrawlerValidator.validateSettings(logger, settings, false), is(true));
+
// Checking That we don't try to do both xml and json
settings = buildSettings(Fs.builder().setJsonSupport(true).setXmlSupport(true).build(), null);
assertThat(FsCrawlerValidator.validateSettings(logger, settings, false), is(true));