• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ebourg / jsign / #361

07 Feb 2025 02:04PM UTC coverage: 83.469% (+0.3%) from 83.213%
#361

push

ebourg
SignPath support

69 of 69 new or added lines in 2 files covered. (100.0%)

4 existing lines in 1 file now uncovered.

4827 of 5783 relevant lines covered (83.47%)

0.83 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.56
/jsign-crypto/src/main/java/net/jsign/jca/RESTClient.java
1
/**
2
 * Copyright 2021 Emmanuel Bourg
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16

17
package net.jsign.jca;
18

19
import java.io.IOException;
20
import java.net.HttpURLConnection;
21
import java.net.URL;
22
import java.net.URLEncoder;
23
import java.nio.charset.StandardCharsets;
24
import java.util.HashMap;
25
import java.util.List;
26
import java.util.Map;
27
import java.util.Random;
28
import java.util.function.BiConsumer;
29
import java.util.function.Consumer;
30
import java.util.function.Function;
31
import java.util.logging.Level;
32
import java.util.logging.Logger;
33

34
import com.cedarsoftware.util.io.JsonReader;
35
import org.apache.commons.io.IOUtils;
36

37
class RESTClient {
38

39
    private final Logger log = Logger.getLogger(getClass().getName());
1✔
40

41
    /** Base URL of the REST service for relative resources */
42
    private final String endpoint;
43

44
    /** Callback setting the authentication headers for the request */
45
    private BiConsumer<HttpURLConnection, byte[]> authenticationHandler;
46

47
    /** Callback building an error message from the JSON formatted error response */
48
    private Function<Map<String, ?>, String> errorHandler;
49

50
    public RESTClient(String endpoint) {
1✔
51
        this.endpoint = endpoint;
1✔
52
    }
1✔
53

54
    public RESTClient authentication(Consumer<HttpURLConnection>  authenticationHeaderSupplier) {
55
        this.authenticationHandler = (conn, data) -> authenticationHeaderSupplier.accept(conn);
1✔
56
        return this;
1✔
57
    }
58

59
    public RESTClient authentication(BiConsumer<HttpURLConnection, byte[]>  authenticationHeaderSupplier) {
60
        this.authenticationHandler = authenticationHeaderSupplier;
1✔
61
        return this;
1✔
62
    }
63

64
    public RESTClient errorHandler(Function<Map<String, ?>, String> errorHandler) {
65
        this.errorHandler = errorHandler;
1✔
66
        return this;
1✔
67
    }
68

69
    public Map<String, ?> get(String resource) throws IOException {
70
        return query("GET", resource, null, null);
1✔
71
    }
72

73
    public Map<String, ?> post(String resource, String body) throws IOException {
74
        return query("POST", resource, body, null);
1✔
75
    }
76

77
    public Map<String, ?> post(String resource, String body, Map<String, String> headers) throws IOException {
78
        return query("POST", resource, body, headers);
1✔
79
    }
80

81
    public Map<String, ?> post(String resource, Map<String, String> params) throws IOException {
82
        return post(resource, params, false);
1✔
83
    }
84

85
    public Map<String, ?> post(String resource, Map<String, ?> params, boolean multipart) throws IOException {
86
        Map<String, String> headers = new HashMap<>();
1✔
87
        StringBuilder body = new StringBuilder();
1✔
88

89
        if (multipart) {
1✔
90
            String boundary = "------------------------" + Long.toHexString(new Random().nextLong());
1✔
91
            headers.put("Content-Type", "multipart/form-data; boundary=" + boundary);
1✔
92

93
            for (String name : params.keySet()) {
1✔
94
                Object value = params.get(name);
1✔
95

96
                body.append("--" + boundary + "\r\n");
1✔
97
                if (value instanceof byte[]) {
1✔
98
                    body.append("Content-Type: application/octet-stream" + "\r\n");
1✔
99
                    body.append("Content-Disposition: form-data; name=\"" + name + '"' + "; filename=\"" + name + ".data\"\r\n");
1✔
100
                    body.append("\r\n");
1✔
101
                    body.append(new String((byte[]) value, StandardCharsets.UTF_8));
1✔
102
                } else {
103
                    body.append("Content-Disposition: form-data; name=\"" + name + '"' + "\r\n");
1✔
104
                    body.append("\r\n");
1✔
105
                    body.append(params.get(name));
1✔
106
                }
107
                body.append("\r\n");
1✔
108
            }
1✔
109

110
            body.append("--" + boundary + "--");
1✔
111

112
        } else {
1✔
113
            headers.put("Content-Type", "application/x-www-form-urlencoded");
1✔
114

115
            for (Map.Entry<String, ?> param : params.entrySet()) {
1✔
116
                if (body.length() > 0) {
1✔
117
                    body.append('&');
1✔
118
                }
119
                body.append(param.getKey()).append('=').append(URLEncoder.encode(param.getValue().toString(), "UTF-8"));
1✔
120
            }
1✔
121
        }
122

123
        return post(resource, body.toString(), headers);
1✔
124
    }
125

126
    private Map<String, ?> query(String method, String resource, String body, Map<String, String> headers) throws IOException {
127
        URL url = new URL(resource.startsWith("http") ? resource : endpoint + resource);
1✔
128
        log.finest(method + " " + url);
1✔
129
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
1✔
130
        conn.setRequestMethod(method);
1✔
131
        String userAgent = System.getProperty("http.agent");
1✔
132
        conn.setRequestProperty("User-Agent", "Jsign (https://ebourg.github.io/jsign/)" + (userAgent != null ? " " + userAgent : ""));
1✔
133
        if (headers != null) {
1✔
134
            for (Map.Entry<String, String> header : headers.entrySet()) {
1✔
135
                conn.setRequestProperty(header.getKey(), header.getValue());
1✔
136
            }
1✔
137
        }
138

139
        byte[] data = body != null ? body.getBytes(StandardCharsets.UTF_8) : null;
1✔
140
        if (authenticationHandler != null) {
1✔
141
            authenticationHandler.accept(conn, data);
1✔
142
        }
143
        if (body != null) {
1✔
144
            if (!conn.getRequestProperties().containsKey("Content-Type")) {
1✔
145
                conn.setRequestProperty("Content-Type", "application/json; charset=utf-8");
1✔
146
            }
147
            conn.setRequestProperty("Content-Length", String.valueOf(data.length));
1✔
148
        }
149

150
        if (log.isLoggable(Level.FINEST)) {
1✔
UNCOV
151
            for (String requestHeader : conn.getRequestProperties().keySet()) {
×
UNCOV
152
                List<String> values = conn.getRequestProperties().get(requestHeader);
×
UNCOV
153
                log.finest(requestHeader + ": " + (values.size() == 1 ? values.get(0) : values));
×
UNCOV
154
            }
×
155
        }
156

157
        if (body != null) {
1✔
158
            log.finest("Content:\n" + body);
1✔
159
            conn.setDoOutput(true);
1✔
160
            conn.getOutputStream().write(data);
1✔
161
        }
162
        log.finest("");
1✔
163

164
        int responseCode = conn.getResponseCode();
1✔
165
        String contentType = conn.getHeaderField("Content-Type");
1✔
166
        log.finest("Response Code: " + responseCode);
1✔
167
        log.finest("Content-Type: " + contentType);
1✔
168

169
        if (responseCode < 400) {
1✔
170
            byte[] binaryResponse = IOUtils.toByteArray(conn.getInputStream());
1✔
171
            String response = new String(binaryResponse, StandardCharsets.UTF_8);
1✔
172
            log.finest("Content-Length: " + binaryResponse.length);
1✔
173
            log.finest("Content:\n" + response);
1✔
174
            log.finest("");
1✔
175

176
            Object value = JsonReader.jsonToJava(response);
1✔
177
            if (value instanceof Map) {
1✔
178
                return (Map) value;
1✔
179
            } else {
180
                Map<String, Object> map = new HashMap<>();
1✔
181
                map.put("result", value);
1✔
182
                return map;
1✔
183
            }
184
        } else {
185
            String error = conn.getErrorStream() != null ? IOUtils.toString(conn.getErrorStream(), StandardCharsets.UTF_8) : "";
1✔
186
            if (conn.getErrorStream() != null) {
1✔
187
                log.finest("Error:\n" + error);
1✔
188
            }
189
            if (contentType != null && (contentType.startsWith("application/json") || contentType.startsWith("application/x-amz-json-1.1"))) {
1✔
190
                throw new IOException(errorHandler != null ? errorHandler.apply(JsonReader.jsonToMaps(error)) : error);
1✔
191
            } else {
192
                throw new IOException("HTTP Error " + responseCode + (conn.getResponseMessage() != null ? " - " + conn.getResponseMessage() : "") + " (" + url + ")");
1✔
193
            }
194
        }
195
    }
196
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc