]> git.basschouten.com Git - openhab-addons.git/blob
93097f624ef2fcaebb2ae3655782765d319a9db8
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.folderwatcher.internal.api;
14
15 import static org.eclipse.jetty.http.HttpHeader.*;
16 import static org.eclipse.jetty.http.HttpMethod.*;
17
18 import java.io.IOException;
19 import java.io.StringReader;
20 import java.net.MalformedURLException;
21 import java.net.URL;
22 import java.time.Duration;
23 import java.util.ArrayList;
24 import java.util.HashMap;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.concurrent.ExecutionException;
28 import java.util.concurrent.TimeUnit;
29 import java.util.concurrent.TimeoutException;
30
31 import javax.xml.parsers.DocumentBuilder;
32 import javax.xml.parsers.DocumentBuilderFactory;
33 import javax.xml.parsers.ParserConfigurationException;
34
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;
49
50 /**
51  * The {@link S3Actions} class contains AWS S3 API implementation.
52  *
53  * @author Alexandr Salamatov - Initial contribution
54  */
55 @NonNullByDefault
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;
64
65     public S3Actions(HttpClientFactory httpClientFactory, String bucketName, String region) throws APIException {
66         this(httpClientFactory, bucketName, region, "", "");
67     }
68
69     public S3Actions(HttpClientFactory httpClientFactory, String bucketName, String region, String awsAccessKey,
70             String awsSecretKey) throws APIException {
71         this.httpClient = httpClientFactory.getCommonHttpClient();
72         try {
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());
76         }
77         this.region = region;
78         this.awsAccessKey = awsAccessKey;
79         this.awsSecretKey = awsSecretKey;
80     }
81
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);
86     }
87
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",
95                     region);
96             String authorization;
97             try {
98                 authorization = signer.computeSignature(headers, params, AWS4SignerBase.EMPTY_BODY_SHA256, awsAccessKey,
99                         awsSecretKey);
100             } catch (HttpUtilException e) {
101                 throw new AuthException(e);
102             }
103             headers.put("Authorization", authorization);
104         }
105
106         headers.put(ACCEPT.toString(), CONTENT_TYPE);
107         Request request = httpClient.newRequest(this.bucketUri.toString()) //
108                 .method(GET) //
109                 .timeout(REQUEST_TIMEOUT.toNanos(), TimeUnit.NANOSECONDS); //
110
111         for (String headerKey : headers.keySet()) {
112             request.header(headerKey, headers.get(headerKey));
113         }
114         for (String paramKey : params.keySet()) {
115             request.param(paramKey, params.get(paramKey));
116         }
117
118         ContentResponse contentResponse;
119         try {
120             contentResponse = request.send();
121         } catch (InterruptedException | TimeoutException | ExecutionException e) {
122             throw new APIException(e);
123         }
124
125         if (contentResponse.getStatus() != 200) {
126             throw new APIException("HTTP Response is not 200");
127         }
128
129         DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
130         DocumentBuilder docBuilder;
131         try {
132             docBuilder = docBuilderFactory.newDocumentBuilder();
133         } catch (ParserConfigurationException e) {
134             throw new APIException(e);
135         }
136         InputSource is = new InputSource(new StringReader(contentResponse.getContentAsString()));
137         Document doc;
138         try {
139             doc = docBuilder.parse(is);
140         } catch (SAXException | IOException e) {
141             throw new APIException(e);
142         }
143         NodeList nameNodesList = doc.getElementsByTagName("Key");
144         List<String> returnList = new ArrayList<>();
145
146         if (nameNodesList.getLength() == 0) {
147             return returnList;
148         }
149
150         for (int i = 0; i < nameNodesList.getLength(); i++) {
151             returnList.add(nameNodesList.item(i).getFirstChild().getTextContent());
152         }
153
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();
160                     params.clear();
161                     headers.clear();
162                     params.put("continuation-token", continueToken);
163                     returnList.addAll(listObjectsV2(prefix, headers, params));
164                 }
165             }
166         }
167         return returnList;
168     }
169 }