2 * Copyright (c) 2010-2024 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.folderwatcher.internal.api;
15 import static org.eclipse.jetty.http.HttpHeader.*;
16 import static org.eclipse.jetty.http.HttpMethod.*;
18 import java.io.IOException;
19 import java.io.StringReader;
20 import java.net.MalformedURLException;
22 import java.time.Duration;
23 import java.util.ArrayList;
24 import java.util.HashMap;
25 import java.util.List;
27 import java.util.concurrent.ExecutionException;
28 import java.util.concurrent.TimeUnit;
29 import java.util.concurrent.TimeoutException;
31 import javax.xml.parsers.DocumentBuilder;
32 import javax.xml.parsers.DocumentBuilderFactory;
33 import javax.xml.parsers.ParserConfigurationException;
35 import org.eclipse.jdt.annotation.NonNullByDefault;
36 import org.eclipse.jetty.client.HttpClient;
37 import org.eclipse.jetty.client.api.ContentResponse;
38 import org.eclipse.jetty.client.api.Request;
39 import org.openhab.binding.folderwatcher.internal.api.auth.AWS4SignerBase;
40 import org.openhab.binding.folderwatcher.internal.api.auth.AWS4SignerForAuthorizationHeader;
41 import org.openhab.binding.folderwatcher.internal.api.exception.APIException;
42 import org.openhab.binding.folderwatcher.internal.api.exception.AuthException;
43 import org.openhab.binding.folderwatcher.internal.api.util.HttpUtilException;
44 import org.openhab.core.io.net.http.HttpClientFactory;
45 import org.w3c.dom.Document;
46 import org.w3c.dom.NodeList;
47 import org.xml.sax.InputSource;
48 import org.xml.sax.SAXException;
51 * The {@link S3Actions} class contains AWS S3 API implementation.
53 * @author Alexandr Salamatov - Initial contribution
56 public class S3Actions {
57 private final HttpClient httpClient;
58 private static final Duration REQUEST_TIMEOUT = Duration.ofMinutes(1);
59 private static final String CONTENT_TYPE = "application/xml";
60 private URL bucketUri;
61 private String region;
62 private String awsAccessKey;
63 private String awsSecretKey;
65 public S3Actions(HttpClientFactory httpClientFactory, String bucketName, String region) throws APIException {
66 this(httpClientFactory, bucketName, region, "", "");
69 public S3Actions(HttpClientFactory httpClientFactory, String bucketName, String region, String awsAccessKey,
70 String awsSecretKey) throws APIException {
71 this.httpClient = httpClientFactory.getCommonHttpClient();
73 this.bucketUri = new URL("http://" + bucketName + ".s3." + region + ".amazonaws.com");
74 } catch (MalformedURLException e) {
75 throw new APIException("Unable to parse service endpoint: " + e.getMessage());
78 this.awsAccessKey = awsAccessKey;
79 this.awsSecretKey = awsSecretKey;
82 public List<String> listBucket(String prefix) throws APIException, AuthException {
83 Map<String, String> headers = new HashMap<String, String>();
84 Map<String, String> params = new HashMap<String, String>();
85 return listObjectsV2(prefix, headers, params);
88 private List<String> listObjectsV2(String prefix, Map<String, String> headers, Map<String, String> params)
89 throws APIException, AuthException {
90 params.put("list-type", "2");
91 params.put("prefix", prefix);
92 if (!awsAccessKey.isEmpty() || !awsSecretKey.isEmpty()) {
93 headers.put("x-amz-content-sha256", AWS4SignerBase.EMPTY_BODY_SHA256);
94 AWS4SignerForAuthorizationHeader signer = new AWS4SignerForAuthorizationHeader(this.bucketUri, "GET", "s3",
98 authorization = signer.computeSignature(headers, params, AWS4SignerBase.EMPTY_BODY_SHA256, awsAccessKey,
100 } catch (HttpUtilException e) {
101 throw new AuthException(e);
103 headers.put("Authorization", authorization);
106 headers.put(ACCEPT.toString(), CONTENT_TYPE);
107 Request request = httpClient.newRequest(this.bucketUri.toString()) //
109 .timeout(REQUEST_TIMEOUT.toNanos(), TimeUnit.NANOSECONDS); //
111 for (String headerKey : headers.keySet()) {
112 request.header(headerKey, headers.get(headerKey));
114 for (String paramKey : params.keySet()) {
115 request.param(paramKey, params.get(paramKey));
118 ContentResponse contentResponse;
120 contentResponse = request.send();
121 } catch (InterruptedException | TimeoutException | ExecutionException e) {
122 throw new APIException(e);
125 if (contentResponse.getStatus() != 200) {
126 throw new APIException("HTTP Response is not 200");
129 DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
130 DocumentBuilder docBuilder;
132 docBuilder = docBuilderFactory.newDocumentBuilder();
133 } catch (ParserConfigurationException e) {
134 throw new APIException(e);
136 InputSource is = new InputSource(new StringReader(contentResponse.getContentAsString()));
139 doc = docBuilder.parse(is);
140 } catch (SAXException | IOException e) {
141 throw new APIException(e);
143 NodeList nameNodesList = doc.getElementsByTagName("Key");
144 List<String> returnList = new ArrayList<>();
146 if (nameNodesList.getLength() == 0) {
150 for (int i = 0; i < nameNodesList.getLength(); i++) {
151 returnList.add(nameNodesList.item(i).getFirstChild().getTextContent());
154 nameNodesList = doc.getElementsByTagName("IsTruncated");
155 if (nameNodesList.getLength() > 0) {
156 if ("true".equals(nameNodesList.item(0).getFirstChild().getTextContent())) {
157 nameNodesList = doc.getElementsByTagName("NextContinuationToken");
158 if (nameNodesList.getLength() > 0) {
159 String continueToken = nameNodesList.item(0).getFirstChild().getTextContent();
162 params.put("continuation-token", continueToken);
163 returnList.addAll(listObjectsV2(prefix, headers, params));