View Javadoc
1   /*
2     (C) Copyright IBM Corp. 2005, 2013
3   
4     THIS FILE IS PROVIDED UNDER THE TERMS OF THE ECLIPSE PUBLIC LICENSE
5     ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THIS FILE
6     CONSTITUTES RECIPIENTS ACCEPTANCE OF THE AGREEMENT.
7   
8     You can obtain a current copy of the Eclipse Public License from
9     http://www.opensource.org/licenses/eclipse-1.0.php
10  
11    @author : Roberto Pineiro, IBM, roberto.pineiro@us.ibm.com
12   * @author : Chung-hao Tan, IBM, chungtan@us.ibm.com
13   * 
14   * 
15   * Change History
16   * Flag       Date        Prog         Description
17   *------------------------------------------------------------------------------- 
18   * 1565892    2006-11-28  lupusalex    Make SBLIM client JSR48 compliant
19   * 2003590    2008-06-30  blaschke-oss Change licensing from CPL to EPL
20   * 2524131    2009-01-21  raman_arora  Upgrade client to JDK 1.5 (Phase 1)
21   * 2531371    2009-02-10  raman_arora  Upgrade client to JDK 1.5 (Phase 2)
22   * 3596303    2013-01-04  blaschke-oss windows http response WWW-Authenticate: Negotiate fails
23   */
24  
25  package org.metricshub.wbem.sblim.cimclient.internal.http;
26  
27  /*-
28   * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲
29   * WBEM Java Client
30   * ჻჻჻჻჻჻
31   * Copyright 2023 - 2025 MetricsHub
32   * ჻჻჻჻჻჻
33   * Licensed under the Apache License, Version 2.0 (the "License");
34   * you may not use this file except in compliance with the License.
35   * You may obtain a copy of the License at
36   *
37   *      http://www.apache.org/licenses/LICENSE-2.0
38   *
39   * Unless required by applicable law or agreed to in writing, software
40   * distributed under the License is distributed on an "AS IS" BASIS,
41   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
42   * See the License for the specific language governing permissions and
43   * limitations under the License.
44   * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱
45   */
46  
47  import java.util.Vector;
48  
49  /**
50   * Class Challenge holds a http authentication challenge
51   *
52   */
53  public class Challenge {
54  	private String iScheme;
55  
56  	private HttpHeader iParams;
57  
58  	private Challenge() {
59  		// hidden ctor
60  	}
61  
62  	/**
63  	 * Returns the parameters
64  	 *
65  	 * @return The parameters
66  	 */
67  	public HttpHeader getParams() {
68  		return this.iParams;
69  	}
70  
71  	/**
72  	 * Returns the scheme
73  	 *
74  	 * @return The scheme
75  	 */
76  	public String getScheme() {
77  		return this.iScheme;
78  	}
79  
80  	/**
81  	 * Returns the realm
82  	 *
83  	 * @return The realm
84  	 */
85  	public String getRealm() {
86  		return this.iParams.getField("realm");
87  	}
88  
89  	/**
90  	 * Parses the challenge as received from the host. RFC 2617 defines the
91  	 * following syntax for a challenge:
92  	 *
93  	 * <pre>
94  	 * challenge = auth-scheme 1*SP 1#auth-param
95  	 * auth-scheme = token
96  	 * auth-param = token &quot;=&quot; ( token | quoted-string )
97  	 * </pre>
98  	 *
99  	 * @param pLine
100 	 *            The challenge string
101 	 * @return The parsed challenge
102 	 * @throws HttpParseException
103 	 *             If the challenge string is ill-formed
104 	 */
105 	public static Challenge[] parseChallenge(String pLine) throws HttpParseException {
106 		if (pLine == null || pLine.length() == 0) throw new IllegalArgumentException("Invalid challenge, empty");
107 		pLine = pLine.trim();
108 		if (pLine.length() == 0) throw new IllegalArgumentException("Invalid challenge, empty");
109 
110 		Vector<Challenge> challenges = new Vector<Challenge>();
111 
112 		try {
113 			int start = 0, end = 0;
114 
115 			// Break up comma-separated list into tokens
116 			Vector<String> tokensBetweenCommas = new Vector<String>();
117 			while ((end = indexOfOutsideQuotedString(pLine, ',', start)) > 0) {
118 				tokensBetweenCommas.add(removeWhitespace(pLine.substring(start, end)));
119 				start = end + 1;
120 			}
121 			if (start < pLine.length()) tokensBetweenCommas.add(removeWhitespace(pLine.substring(start)));
122 
123 			// Break up tokens into auth-scheme and auth-param
124 			Vector<String> tokens = new Vector<String>();
125 			for (int i = 0; i < tokensBetweenCommas.size(); i++) {
126 				String token = tokensBetweenCommas.elementAt(i);
127 
128 				if (token.length() == 0) continue;
129 
130 				start = 0;
131 				end = indexOfOutsideQuotedString(token, ' ', start);
132 
133 				if (end == -1) {
134 					tokens.add(token);
135 				} else {
136 					tokens.add(token.substring(0, end));
137 					start = end + 1;
138 					end = indexOfOutsideQuotedString(token, ' ', start);
139 
140 					if (end == -1) {
141 						// RFC 2617 indicates this token should be of the
142 						// name=value format, but at least one old CIMOM (SVC
143 						// ICAT) does not follow this rule. The Client
144 						// effectively ignores this token, and so must continue
145 						// to do so.
146 						if (indexOfOutsideQuotedString(token, '=', start) == -1) {
147 							// throw new
148 							// HttpParseException("Invalid challenge, second token must be name=value in "
149 							// + token);
150 						} else {
151 							tokens.add(token.substring(start));
152 						}
153 					} else {
154 						throw new HttpParseException("Invalid challenge, too many tokens in " + token);
155 					}
156 				}
157 			}
158 
159 			Challenge challenge = new Challenge();
160 			challenge.iScheme = null;
161 			challenge.iParams = new HttpHeader();
162 
163 			for (int i = 0; i < tokens.size(); i++) {
164 				String token = tokens.elementAt(i);
165 
166 				int equalSign = indexOfOutsideQuotedString(token, '=', 0);
167 				if (equalSign == 0) {
168 					throw new HttpParseException("Invalid challenge, no token before equal sign in " + token);
169 				} else if (equalSign > 0) {
170 					// param
171 					if (challenge.iScheme == null) throw new HttpParseException("Invalid challenge, param without scheme");
172 					String name = token.substring(0, equalSign);
173 					String value = token.substring(equalSign + 1);
174 					if (value.length() == 0) {
175 						throw new HttpParseException("Invalid challenge, no token after equal sign in " + token);
176 					} else if (value.startsWith("\"") && value.endsWith("\"") && value.length() > 1) {
177 						challenge.iParams.addField(name, value.substring(1, value.length() - 1));
178 					} else {
179 						challenge.iParams.addField(name, value);
180 					}
181 				} else {
182 					// scheme
183 					if (challenge.iScheme != null) {
184 						// new scheme
185 						challenges.add(challenge);
186 
187 						challenge = new Challenge();
188 						challenge.iParams = new HttpHeader();
189 					}
190 					challenge.iScheme = new String(token);
191 				}
192 			}
193 
194 			if (challenge.iScheme != null) {
195 				challenges.add(challenge);
196 			}
197 		} catch (HttpParseException e) {
198 			throw e;
199 		} catch (Exception e) {
200 			throw new HttpParseException("Invalid challenge, " + e.getMessage());
201 		}
202 
203 		return challenges.toArray(new Challenge[challenges.size()]);
204 	}
205 
206 	/*
207 	 * Removes unnecessary whitespace from a challenge such that
208 	 * "  scheme   name  =  value  " becomes "scheme name=value"
209 	 */
210 	private static String removeWhitespace(String str) throws HttpParseException {
211 		char inBuf[] = str.trim().toCharArray();
212 		StringBuilder outStr = new StringBuilder();
213 		boolean inQuotes = false;
214 
215 		for (int inIdx = 0; inIdx < inBuf.length; inIdx++) {
216 			if (inQuotes || !Character.isSpaceChar(inBuf[inIdx])) {
217 				if (inBuf[inIdx] == '=' && outStr.length() == 0) throw new HttpParseException(
218 					"Invalid challenge, no token before equal sign in " + str
219 				);
220 
221 				outStr.append(inBuf[inIdx]);
222 			} else {
223 				// Whitespace not within quoted string
224 				int i = skipSpaces(inBuf, inIdx + 1);
225 				if (i >= inBuf.length) throw new HttpParseException("Invalid challenge, no token after space in " + str);
226 
227 				if (inBuf[i] == '=') {
228 					// auth-param, remove all whitespace up to next token
229 					i = skipSpaces(inBuf, i + 1);
230 					if (i >= inBuf.length) throw new HttpParseException("Invalid challenge, no token after equal sign in " + str);
231 					outStr.append('=');
232 				} else {
233 					// another token, combine all whitespace up to next token
234 					// into single space
235 					outStr.append(' ');
236 				}
237 				outStr.append(inBuf[i]);
238 				inIdx = i;
239 			}
240 			if (inBuf[inIdx] == '\"') inQuotes = !inQuotes;
241 		}
242 		if (inQuotes) throw new HttpParseException("Invalid challenge, quoted string not terminated in " + str);
243 		return outStr.toString();
244 	}
245 
246 	private static int skipSpaces(char[] buf, int pos) {
247 		while (pos < buf.length && Character.isSpaceChar(buf[pos])) pos++;
248 		return pos;
249 	}
250 
251 	private static int indexOfOutsideQuotedString(String str, int ch, int pos) throws HttpParseException {
252 		int len = str.length();
253 		boolean inQuotes = false;
254 
255 		while (pos < len) {
256 			if (str.charAt(pos) == '\"') {
257 				inQuotes = !inQuotes;
258 			} else if (str.charAt(pos) == ch) {
259 				if (!inQuotes) return pos;
260 			}
261 			pos++;
262 		}
263 		if (inQuotes) throw new HttpParseException("Invalid callenge, quoted string not terminated in " + str);
264 		return -1;
265 	}
266 }