1 |
/* |
/* |
2 |
* $Id$ |
* Request.java |
3 |
* Copyright (C) 2004 The Free Software Foundation |
* Copyright (C) 2004 The Free Software Foundation |
4 |
* |
* |
5 |
* This file is part of GNU inetlib, a library. |
* This file is part of GNU inetlib, a library. |
126 |
* @param method the HTTP method |
* @param method the HTTP method |
127 |
* @param path the resource path including query part |
* @param path the resource path including query part |
128 |
*/ |
*/ |
129 |
protected Request (HTTPConnection connection, String method, |
protected Request(HTTPConnection connection, String method, |
130 |
String path) |
String path) |
131 |
{ |
{ |
132 |
this.connection = connection; |
this.connection = connection; |
133 |
this.method = method; |
this.method = method; |
134 |
this.path = path; |
this.path = path; |
135 |
requestHeaders = new Headers (); |
requestHeaders = new Headers(); |
136 |
responseHeaderHandlers = new HashMap (); |
responseHeaderHandlers = new HashMap(); |
137 |
requestBodyNegotiationThreshold = 4096; |
requestBodyNegotiationThreshold = 4096; |
138 |
} |
} |
139 |
|
|
141 |
* Returns the connection associated with this request. |
* Returns the connection associated with this request. |
142 |
* @see #connection |
* @see #connection |
143 |
*/ |
*/ |
144 |
public HTTPConnection getConnection () |
public HTTPConnection getConnection() |
145 |
{ |
{ |
146 |
return connection; |
return connection; |
147 |
} |
} |
150 |
* Returns the HTTP method to invoke. |
* Returns the HTTP method to invoke. |
151 |
* @see #method |
* @see #method |
152 |
*/ |
*/ |
153 |
public String getMethod () |
public String getMethod() |
154 |
{ |
{ |
155 |
return method; |
return method; |
156 |
} |
} |
159 |
* Returns the resource path. |
* Returns the resource path. |
160 |
* @see #path |
* @see #path |
161 |
*/ |
*/ |
162 |
public String getPath () |
public String getPath() |
163 |
{ |
{ |
164 |
return path; |
return path; |
165 |
} |
} |
168 |
* Returns the full request-URI represented by this request, as specified |
* Returns the full request-URI represented by this request, as specified |
169 |
* by HTTP/1.1. |
* by HTTP/1.1. |
170 |
*/ |
*/ |
171 |
public String getRequestURI () |
public String getRequestURI() |
172 |
{ |
{ |
173 |
return connection.getURI () + path; |
return connection.getURI() + path; |
174 |
} |
} |
175 |
|
|
176 |
/** |
/** |
177 |
* Returns the headers in this request. |
* Returns the headers in this request. |
178 |
*/ |
*/ |
179 |
public Headers getHeaders () |
public Headers getHeaders() |
180 |
{ |
{ |
181 |
return requestHeaders; |
return requestHeaders; |
182 |
} |
} |
185 |
* Returns the value of the specified header in this request. |
* Returns the value of the specified header in this request. |
186 |
* @param name the header name |
* @param name the header name |
187 |
*/ |
*/ |
188 |
public String getHeader (String name) |
public String getHeader(String name) |
189 |
{ |
{ |
190 |
return requestHeaders.getValue (name); |
return requestHeaders.getValue(name); |
191 |
} |
} |
192 |
|
|
193 |
/** |
/** |
194 |
* Returns the value of the specified header in this request as an integer. |
* Returns the value of the specified header in this request as an integer. |
195 |
* @param name the header name |
* @param name the header name |
196 |
*/ |
*/ |
197 |
public int getIntHeader (String name) |
public int getIntHeader(String name) |
198 |
{ |
{ |
199 |
return requestHeaders.getIntValue (name); |
return requestHeaders.getIntValue(name); |
200 |
} |
} |
201 |
|
|
202 |
/** |
/** |
203 |
* Returns the value of the specified header in this request as a date. |
* Returns the value of the specified header in this request as a date. |
204 |
* @param name the header name |
* @param name the header name |
205 |
*/ |
*/ |
206 |
public Date getDateHeader (String name) |
public Date getDateHeader(String name) |
207 |
{ |
{ |
208 |
return requestHeaders.getDateValue (name); |
return requestHeaders.getDateValue(name); |
209 |
} |
} |
210 |
|
|
211 |
/** |
/** |
213 |
* @param name the header name |
* @param name the header name |
214 |
* @param value the header value |
* @param value the header value |
215 |
*/ |
*/ |
216 |
public void setHeader (String name, String value) |
public void setHeader(String name, String value) |
217 |
{ |
{ |
218 |
requestHeaders.put (name, value); |
requestHeaders.put(name, value); |
219 |
} |
} |
220 |
|
|
221 |
/** |
/** |
222 |
* Convenience method to set the entire request body. |
* Convenience method to set the entire request body. |
223 |
* @param requestBody the request body content |
* @param requestBody the request body content |
224 |
*/ |
*/ |
225 |
public void setRequestBody (byte[] requestBody) |
public void setRequestBody(byte[] requestBody) |
226 |
{ |
{ |
227 |
setRequestBodyWriter (new ByteArrayRequestBodyWriter (requestBody)); |
setRequestBodyWriter(new ByteArrayRequestBodyWriter(requestBody)); |
228 |
} |
} |
229 |
|
|
230 |
/** |
/** |
231 |
* Sets the request body provider. |
* Sets the request body provider. |
232 |
* @param requestBodyWriter the handler used to obtain the request body |
* @param requestBodyWriter the handler used to obtain the request body |
233 |
*/ |
*/ |
234 |
public void setRequestBodyWriter (RequestBodyWriter requestBodyWriter) |
public void setRequestBodyWriter(RequestBodyWriter requestBodyWriter) |
235 |
{ |
{ |
236 |
this.requestBodyWriter = requestBodyWriter; |
this.requestBodyWriter = requestBodyWriter; |
237 |
} |
} |
241 |
* @param responseBodyReader the handler to receive notifications of |
* @param responseBodyReader the handler to receive notifications of |
242 |
* response body content |
* response body content |
243 |
*/ |
*/ |
244 |
public void setResponseBodyReader (ResponseBodyReader responseBodyReader) |
public void setResponseBodyReader(ResponseBodyReader responseBodyReader) |
245 |
{ |
{ |
246 |
this.responseBodyReader = responseBodyReader; |
this.responseBodyReader = responseBodyReader; |
247 |
} |
} |
251 |
* @param name the header name |
* @param name the header name |
252 |
* @param handler the handler to receive the value for the header |
* @param handler the handler to receive the value for the header |
253 |
*/ |
*/ |
254 |
public void setResponseHeaderHandler (String name, |
public void setResponseHeaderHandler(String name, |
255 |
ResponseHeaderHandler handler) |
ResponseHeaderHandler handler) |
256 |
{ |
{ |
257 |
responseHeaderHandlers.put (name, handler); |
responseHeaderHandlers.put(name, handler); |
258 |
} |
} |
259 |
|
|
260 |
/** |
/** |
262 |
* automatically. |
* automatically. |
263 |
* @param authenticator the authenticator |
* @param authenticator the authenticator |
264 |
*/ |
*/ |
265 |
public void setAuthenticator (Authenticator authenticator) |
public void setAuthenticator(Authenticator authenticator) |
266 |
{ |
{ |
267 |
this.authenticator = authenticator; |
this.authenticator = authenticator; |
268 |
} |
} |
270 |
/** |
/** |
271 |
* Sets the request body negotiation threshold. |
* Sets the request body negotiation threshold. |
272 |
* If this is set, it determines the maximum size that the request body |
* If this is set, it determines the maximum size that the request body |
273 |
* may be before body negotiation occurs (via the |
* may be before body negotiation occurs(via the |
274 |
* <code>100-continue</code> expectation). This ensures that a large |
* <code>100-continue</code> expectation). This ensures that a large |
275 |
* request body is not sent when the server wouldn't have accepted it |
* request body is not sent when the server wouldn't have accepted it |
276 |
* anyway. |
* anyway. |
277 |
* @param threshold the body negotiation threshold, or <=0 to disable |
* @param threshold the body negotiation threshold, or <=0 to disable |
278 |
* request body negotation entirely |
* request body negotation entirely |
279 |
*/ |
*/ |
280 |
public void setRequestBodyNegotiationThreshold (int threshold) |
public void setRequestBodyNegotiationThreshold(int threshold) |
281 |
{ |
{ |
282 |
requestBodyNegotiationThreshold = threshold; |
requestBodyNegotiationThreshold = threshold; |
283 |
} |
} |
289 |
* @exception IOException if an I/O error occurred |
* @exception IOException if an I/O error occurred |
290 |
* @return an HTTP response object representing the result of the operation |
* @return an HTTP response object representing the result of the operation |
291 |
*/ |
*/ |
292 |
public Response dispatch () |
public Response dispatch() |
293 |
throws IOException |
throws IOException |
294 |
{ |
{ |
295 |
if (dispatched) |
if (dispatched) |
296 |
{ |
{ |
297 |
throw new ProtocolException ("request already dispatched"); |
throw new ProtocolException("request already dispatched"); |
298 |
} |
} |
299 |
final String CRLF = "\r\n"; |
final String CRLF = "\r\n"; |
300 |
final String HEADER_SEP = ": "; |
final String HEADER_SEP = ": "; |
301 |
final String US_ASCII = "US-ASCII"; |
final String US_ASCII = "US-ASCII"; |
302 |
final String version = connection.getVersion (); |
final String version = connection.getVersion(); |
303 |
Response response; |
Response response; |
304 |
int contentLength = -1; |
int contentLength = -1; |
305 |
boolean retry = false; |
boolean retry = false; |
307 |
boolean expectingContinue = false; |
boolean expectingContinue = false; |
308 |
if (requestBodyWriter != null) |
if (requestBodyWriter != null) |
309 |
{ |
{ |
310 |
contentLength = requestBodyWriter.getContentLength (); |
contentLength = requestBodyWriter.getContentLength(); |
311 |
if (contentLength > requestBodyNegotiationThreshold) |
if (contentLength > requestBodyNegotiationThreshold) |
312 |
{ |
{ |
313 |
expectingContinue = true; |
expectingContinue = true; |
314 |
setHeader ("Expect", "100-continue"); |
setHeader("Expect", "100-continue"); |
315 |
} |
} |
316 |
else |
else |
317 |
{ |
{ |
318 |
setHeader ("Content-Length", Integer.toString (contentLength)); |
setHeader("Content-Length", Integer.toString(contentLength)); |
319 |
} |
} |
320 |
} |
} |
321 |
|
|
326 |
{ |
{ |
327 |
retry = false; |
retry = false; |
328 |
// Send request |
// Send request |
329 |
connection.fireRequestEvent (RequestEvent.REQUEST_SENDING, this); |
connection.fireRequestEvent(RequestEvent.REQUEST_SENDING, this); |
330 |
|
|
331 |
// Get socket output and input streams |
// Get socket output and input streams |
332 |
OutputStream out = connection.getOutputStream (); |
OutputStream out = connection.getOutputStream(); |
333 |
LineInputStream in = |
LineInputStream in = |
334 |
new LineInputStream (connection.getInputStream ()); |
new LineInputStream(connection.getInputStream()); |
335 |
// Request line |
// Request line |
336 |
String requestUri = path; |
String requestUri = path; |
337 |
if (connection.isUsingProxy () && |
if (connection.isUsingProxy() && |
338 |
!"*".equals (requestUri) && |
!"*".equals(requestUri) && |
339 |
!"CONNECT".equals (method)) |
!"CONNECT".equals(method)) |
340 |
{ |
{ |
341 |
requestUri = getRequestURI (); |
requestUri = getRequestURI(); |
342 |
} |
} |
343 |
String line = method + ' ' + requestUri + ' ' + version + CRLF; |
String line = method + ' ' + requestUri + ' ' + version + CRLF; |
344 |
out.write (line.getBytes (US_ASCII)); |
out.write(line.getBytes(US_ASCII)); |
345 |
// Request headers |
// Request headers |
346 |
for (Iterator i = requestHeaders.keySet ().iterator (); |
for (Iterator i = requestHeaders.keySet().iterator(); |
347 |
i.hasNext (); ) |
i.hasNext(); ) |
348 |
{ |
{ |
349 |
String name = (String) i.next (); |
String name =(String) i.next(); |
350 |
String value = (String) requestHeaders.get (name); |
String value =(String) requestHeaders.get(name); |
351 |
line = name + HEADER_SEP + value + CRLF; |
line = name + HEADER_SEP + value + CRLF; |
352 |
out.write (line.getBytes (US_ASCII)); |
out.write(line.getBytes(US_ASCII)); |
353 |
} |
} |
354 |
out.write (CRLF.getBytes (US_ASCII)); |
out.write(CRLF.getBytes(US_ASCII)); |
355 |
// Request body |
// Request body |
356 |
if (requestBodyWriter != null && !expectingContinue) |
if (requestBodyWriter != null && !expectingContinue) |
357 |
{ |
{ |
359 |
int len; |
int len; |
360 |
int count = 0; |
int count = 0; |
361 |
|
|
362 |
requestBodyWriter.reset (); |
requestBodyWriter.reset(); |
363 |
do |
do |
364 |
{ |
{ |
365 |
len = requestBodyWriter.write (buffer); |
len = requestBodyWriter.write(buffer); |
366 |
if (len > 0) |
if (len > 0) |
367 |
{ |
{ |
368 |
out.write (buffer, 0, len); |
out.write(buffer, 0, len); |
369 |
} |
} |
370 |
count += len; |
count += len; |
371 |
} |
} |
372 |
while (len > -1 && count < contentLength); |
while (len > -1 && count < contentLength); |
373 |
out.write (CRLF.getBytes (US_ASCII)); |
out.write(CRLF.getBytes(US_ASCII)); |
374 |
} |
} |
375 |
out.flush (); |
out.flush(); |
376 |
// Sent event |
// Sent event |
377 |
connection.fireRequestEvent (RequestEvent.REQUEST_SENT, this); |
connection.fireRequestEvent(RequestEvent.REQUEST_SENT, this); |
378 |
// Get response |
// Get response |
379 |
response = readResponse (in); |
response = readResponse(in); |
380 |
int sc = response.getCode (); |
int sc = response.getCode(); |
381 |
if (sc == 401 && authenticator != null) |
if (sc == 401 && authenticator != null) |
382 |
{ |
{ |
383 |
if (authenticate (response, attempts++)) |
if (authenticate(response, attempts++)) |
384 |
{ |
{ |
385 |
retry = true; |
retry = true; |
386 |
} |
} |
387 |
} |
} |
388 |
else if (sc == 100 && expectingContinue) |
else if (sc == 100 && expectingContinue) |
389 |
{ |
{ |
390 |
requestHeaders.remove ("Expect"); |
requestHeaders.remove("Expect"); |
391 |
setHeader ("Content-Length", Integer.toString (contentLength)); |
setHeader("Content-Length", Integer.toString(contentLength)); |
392 |
expectingContinue = false; |
expectingContinue = false; |
393 |
retry = true; |
retry = true; |
394 |
} |
} |
397 |
} |
} |
398 |
catch (IOException e) |
catch (IOException e) |
399 |
{ |
{ |
400 |
connection.close (); |
connection.close(); |
401 |
throw e; |
throw e; |
402 |
} |
} |
403 |
return response; |
return response; |
404 |
} |
} |
405 |
|
|
406 |
Response readResponse (LineInputStream in) |
Response readResponse(LineInputStream in) |
407 |
throws IOException |
throws IOException |
408 |
{ |
{ |
409 |
String line; |
String line; |
410 |
int len; |
int len; |
411 |
|
|
412 |
// Read response status line |
// Read response status line |
413 |
line = in.readLine (); |
line = in.readLine(); |
414 |
if (line == null) |
if (line == null) |
415 |
{ |
{ |
416 |
throw new ProtocolException ("Peer closed connection"); |
throw new ProtocolException("Peer closed connection"); |
417 |
} |
} |
418 |
if (!line.startsWith ("HTTP/")) |
if (!line.startsWith("HTTP/")) |
419 |
{ |
{ |
420 |
throw new ProtocolException (line); |
throw new ProtocolException(line); |
421 |
} |
} |
422 |
len = line.length (); |
len = line.length(); |
423 |
int start = 5, end = 6; |
int start = 5, end = 6; |
424 |
while (line.charAt (end) != '.') |
while (line.charAt(end) != '.') |
425 |
{ |
{ |
426 |
end++; |
end++; |
427 |
} |
} |
428 |
int majorVersion = Integer.parseInt (line.substring (start, end)); |
int majorVersion = Integer.parseInt(line.substring(start, end)); |
429 |
start = end + 1; |
start = end + 1; |
430 |
end = start + 1; |
end = start + 1; |
431 |
while (line.charAt (end) != ' ') |
while (line.charAt(end) != ' ') |
432 |
{ |
{ |
433 |
end++; |
end++; |
434 |
} |
} |
435 |
int minorVersion = Integer.parseInt (line.substring (start, end)); |
int minorVersion = Integer.parseInt(line.substring(start, end)); |
436 |
start = end + 1; |
start = end + 1; |
437 |
end = start + 3; |
end = start + 3; |
438 |
int code = Integer.parseInt (line.substring (start, end)); |
int code = Integer.parseInt(line.substring(start, end)); |
439 |
String message = line.substring (end + 1, len - 1); |
String message = line.substring(end + 1, len - 1); |
440 |
// Read response headers |
// Read response headers |
441 |
Headers responseHeaders = new Headers (); |
Headers responseHeaders = new Headers(); |
442 |
responseHeaders.parse (in); |
responseHeaders.parse(in); |
443 |
notifyHeaderHandlers (responseHeaders); |
notifyHeaderHandlers(responseHeaders); |
444 |
// Construct response |
// Construct response |
445 |
int codeClass = code / 100; |
int codeClass = code / 100; |
446 |
Response ret = new Response (majorVersion, minorVersion, code, |
Response ret = new Response(majorVersion, minorVersion, code, |
447 |
codeClass, message, responseHeaders); |
codeClass, message, responseHeaders); |
448 |
switch (code) |
switch (code) |
449 |
{ |
{ |
450 |
case 204: |
case 204: |
455 |
boolean notify = (responseBodyReader != null); |
boolean notify = (responseBodyReader != null); |
456 |
if (notify) |
if (notify) |
457 |
{ |
{ |
458 |
if (!responseBodyReader.accept (this, ret)) |
if (!responseBodyReader.accept(this, ret)) |
459 |
{ |
{ |
460 |
notify = false; |
notify = false; |
461 |
} |
} |
462 |
} |
} |
463 |
readResponseBody (ret, in, notify); |
readResponseBody(ret, in, notify); |
464 |
} |
} |
465 |
return ret; |
return ret; |
466 |
} |
} |
467 |
|
|
468 |
void notifyHeaderHandlers (Headers headers) |
void notifyHeaderHandlers(Headers headers) |
469 |
{ |
{ |
470 |
for (Iterator i = headers.entrySet ().iterator (); i.hasNext (); ) |
for (Iterator i = headers.entrySet().iterator(); i.hasNext(); ) |
471 |
{ |
{ |
472 |
Map.Entry entry = (Map.Entry) i.next (); |
Map.Entry entry = (Map.Entry) i.next(); |
473 |
String name = (String) entry.getKey (); |
String name =(String) entry.getKey(); |
474 |
// Handle Set-Cookie |
// Handle Set-Cookie |
475 |
if ("Set-Cookie".equalsIgnoreCase (name)) |
if ("Set-Cookie".equalsIgnoreCase(name)) |
476 |
{ |
{ |
477 |
String value = (String) entry.getValue (); |
String value = (String) entry.getValue(); |
478 |
handleSetCookie (value); |
handleSetCookie(value); |
479 |
} |
} |
480 |
ResponseHeaderHandler handler = |
ResponseHeaderHandler handler = |
481 |
(ResponseHeaderHandler) responseHeaderHandlers.get (name); |
(ResponseHeaderHandler) responseHeaderHandlers.get(name); |
482 |
if (handler != null) |
if (handler != null) |
483 |
{ |
{ |
484 |
String value = (String) entry.getValue (); |
String value = (String) entry.getValue(); |
485 |
handler.setValue (value); |
handler.setValue(value); |
486 |
} |
} |
487 |
} |
} |
488 |
} |
} |
489 |
|
|
490 |
void readResponseBody (Response response, InputStream in, |
void readResponseBody(Response response, InputStream in, |
491 |
boolean notify) |
boolean notify) |
492 |
throws IOException |
throws IOException |
493 |
{ |
{ |
494 |
byte[] buffer = new byte[4096]; |
byte[] buffer = new byte[4096]; |
495 |
int contentLength = -1; |
int contentLength = -1; |
496 |
Headers trailer = null; |
Headers trailer = null; |
497 |
|
|
498 |
String transferCoding = response.getHeader ("Transfer-Encoding"); |
String transferCoding = response.getHeader("Transfer-Encoding"); |
499 |
if ("chunked".equalsIgnoreCase (transferCoding)) |
if ("chunked".equalsIgnoreCase(transferCoding)) |
500 |
{ |
{ |
501 |
trailer = new Headers (); |
trailer = new Headers(); |
502 |
in = new ChunkedInputStream (in, trailer); |
in = new ChunkedInputStream(in, trailer); |
503 |
} |
} |
504 |
else |
else |
505 |
{ |
{ |
506 |
contentLength = response.getIntHeader ("Content-Length"); |
contentLength = response.getIntHeader("Content-Length"); |
507 |
} |
} |
508 |
String contentCoding = response.getHeader ("Content-Encoding"); |
String contentCoding = response.getHeader("Content-Encoding"); |
509 |
if (contentCoding != null && !"identity".equals (contentCoding)) |
if (contentCoding != null && !"identity".equals(contentCoding)) |
510 |
{ |
{ |
511 |
if ("gzip".equals (contentCoding)) |
if ("gzip".equals(contentCoding)) |
512 |
{ |
{ |
513 |
in = new GZIPInputStream (in); |
in = new GZIPInputStream(in); |
514 |
} |
} |
515 |
else if ("deflate".equals (contentCoding)) |
else if ("deflate".equals(contentCoding)) |
516 |
{ |
{ |
517 |
in = new InflaterInputStream (in); |
in = new InflaterInputStream(in); |
518 |
} |
} |
519 |
else |
else |
520 |
{ |
{ |
521 |
throw new ProtocolException ("Unsupported Content-Encoding: " + |
throw new ProtocolException("Unsupported Content-Encoding: " + |
522 |
contentCoding); |
contentCoding); |
523 |
} |
} |
524 |
} |
} |
525 |
|
|
526 |
// Persistent connections are the default in HTTP/1.1 |
// Persistent connections are the default in HTTP/1.1 |
527 |
boolean doClose = "close".equalsIgnoreCase (getHeader ("Connection")) || |
boolean doClose = "close".equalsIgnoreCase(getHeader("Connection")) || |
528 |
"close".equalsIgnoreCase (response.getHeader ("Connection")) || |
"close".equalsIgnoreCase(response.getHeader("Connection")) || |
529 |
(connection.majorVersion == 1 && connection.minorVersion == 0) || |
(connection.majorVersion == 1 && connection.minorVersion == 0) || |
530 |
(response.majorVersion == 1 && response.minorVersion == 0); |
(response.majorVersion == 1 && response.minorVersion == 0); |
531 |
|
|
534 |
len = (len > buffer.length) ? buffer.length : len; |
len = (len > buffer.length) ? buffer.length : len; |
535 |
while (len > -1) |
while (len > -1) |
536 |
{ |
{ |
537 |
len = in.read (buffer, 0, len); |
len = in.read(buffer, 0, len); |
538 |
if (len < 0) |
if (len < 0) |
539 |
{ |
{ |
540 |
// EOF |
// EOF |
541 |
connection.closeConnection (); |
connection.closeConnection(); |
542 |
break; |
break; |
543 |
} |
} |
544 |
if (notify) |
if (notify) |
545 |
{ |
{ |
546 |
responseBodyReader.read (buffer, 0, len); |
responseBodyReader.read(buffer, 0, len); |
547 |
} |
} |
548 |
if (count > -1) |
if (count > -1) |
549 |
{ |
{ |
552 |
{ |
{ |
553 |
if (doClose) |
if (doClose) |
554 |
{ |
{ |
555 |
connection.closeConnection (); |
connection.closeConnection(); |
556 |
} |
} |
557 |
break; |
break; |
558 |
} |
} |
560 |
} |
} |
561 |
if (notify) |
if (notify) |
562 |
{ |
{ |
563 |
responseBodyReader.close (); |
responseBodyReader.close(); |
564 |
} |
} |
565 |
if (trailer != null) |
if (trailer != null) |
566 |
{ |
{ |
567 |
response.getHeaders ().putAll (trailer); |
response.getHeaders().putAll(trailer); |
568 |
notifyHeaderHandlers (trailer); |
notifyHeaderHandlers(trailer); |
569 |
} |
} |
570 |
} |
} |
571 |
|
|
572 |
boolean authenticate (Response response, int attempts) |
boolean authenticate(Response response, int attempts) |
573 |
throws IOException |
throws IOException |
574 |
{ |
{ |
575 |
String challenge = response.getHeader ("WWW-Authenticate"); |
String challenge = response.getHeader("WWW-Authenticate"); |
576 |
if (challenge == null) |
if (challenge == null) |
577 |
{ |
{ |
578 |
challenge = response.getHeader ("Proxy-Authenticate"); |
challenge = response.getHeader("Proxy-Authenticate"); |
579 |
} |
} |
580 |
int si = challenge.indexOf (' '); |
int si = challenge.indexOf(' '); |
581 |
String scheme = (si == -1) ? challenge : challenge.substring (0, si); |
String scheme = (si == -1) ? challenge : challenge.substring(0, si); |
582 |
if ("Basic".equalsIgnoreCase (scheme)) |
if ("Basic".equalsIgnoreCase(scheme)) |
583 |
{ |
{ |
584 |
Properties params = parseAuthParams (challenge.substring (si + 1)); |
Properties params = parseAuthParams(challenge.substring(si + 1)); |
585 |
String realm = params.getProperty ("realm"); |
String realm = params.getProperty("realm"); |
586 |
Credentials creds = authenticator.getCredentials (realm, attempts); |
Credentials creds = authenticator.getCredentials(realm, attempts); |
587 |
String userPass = creds.getUsername () + ':' + creds.getPassword (); |
String userPass = creds.getUsername() + ':' + creds.getPassword(); |
588 |
byte[] b_userPass = userPass.getBytes ("US-ASCII"); |
byte[] b_userPass = userPass.getBytes("US-ASCII"); |
589 |
byte[] b_encoded = BASE64.encode (b_userPass); |
byte[] b_encoded = BASE64.encode(b_userPass); |
590 |
String authorization = |
String authorization = |
591 |
scheme + " " + new String (b_encoded, "US-ASCII"); |
scheme + " " + new String(b_encoded, "US-ASCII"); |
592 |
setHeader ("Authorization", authorization); |
setHeader("Authorization", authorization); |
593 |
return true; |
return true; |
594 |
} |
} |
595 |
else if ("Digest".equalsIgnoreCase (scheme)) |
else if ("Digest".equalsIgnoreCase(scheme)) |
596 |
{ |
{ |
597 |
Properties params = parseAuthParams (challenge.substring (si + 1)); |
Properties params = parseAuthParams(challenge.substring(si + 1)); |
598 |
String realm = params.getProperty ("realm"); |
String realm = params.getProperty("realm"); |
599 |
String nonce = params.getProperty ("nonce"); |
String nonce = params.getProperty("nonce"); |
600 |
String qop = params.getProperty ("qop"); |
String qop = params.getProperty("qop"); |
601 |
String algorithm = params.getProperty ("algorithm"); |
String algorithm = params.getProperty("algorithm"); |
602 |
String digestUri = getRequestURI (); |
String digestUri = getRequestURI(); |
603 |
Credentials creds = authenticator.getCredentials (realm, attempts); |
Credentials creds = authenticator.getCredentials(realm, attempts); |
604 |
String username = creds.getUsername (); |
String username = creds.getUsername(); |
605 |
String password = creds.getPassword (); |
String password = creds.getPassword(); |
606 |
connection.incrementNonce (nonce); |
connection.incrementNonce(nonce); |
607 |
try |
try |
608 |
{ |
{ |
609 |
MessageDigest md5 = MessageDigest.getInstance ("MD5"); |
MessageDigest md5 = MessageDigest.getInstance("MD5"); |
610 |
final byte[] COLON = { 0x3a }; |
final byte[] COLON = { 0x3a }; |
611 |
|
|
612 |
// Calculate H(A1) |
// Calculate H(A1) |
613 |
md5.reset (); |
md5.reset(); |
614 |
md5.update (username.getBytes ("US-ASCII")); |
md5.update(username.getBytes("US-ASCII")); |
615 |
md5.update (COLON); |
md5.update(COLON); |
616 |
md5.update (realm.getBytes ("US-ASCII")); |
md5.update(realm.getBytes("US-ASCII")); |
617 |
md5.update (COLON); |
md5.update(COLON); |
618 |
md5.update (password.getBytes ("US-ASCII")); |
md5.update(password.getBytes("US-ASCII")); |
619 |
byte[] ha1 = md5.digest (); |
byte[] ha1 = md5.digest(); |
620 |
if ("md5-sess".equals (algorithm)) |
if ("md5-sess".equals(algorithm)) |
621 |
{ |
{ |
622 |
byte[] cnonce = generateNonce (); |
byte[] cnonce = generateNonce(); |
623 |
md5.reset (); |
md5.reset(); |
624 |
md5.update (ha1); |
md5.update(ha1); |
625 |
md5.update (COLON); |
md5.update(COLON); |
626 |
md5.update (nonce.getBytes ("US-ASCII")); |
md5.update(nonce.getBytes("US-ASCII")); |
627 |
md5.update (COLON); |
md5.update(COLON); |
628 |
md5.update (cnonce); |
md5.update(cnonce); |
629 |
ha1 = md5.digest(); |
ha1 = md5.digest(); |
630 |
} |
} |
631 |
String ha1Hex = toHexString (ha1); |
String ha1Hex = toHexString(ha1); |
632 |
|
|
633 |
// Calculate H(A2) |
// Calculate H(A2) |
634 |
md5.reset (); |
md5.reset(); |
635 |
md5.update (method.getBytes ("US-ASCII")); |
md5.update(method.getBytes("US-ASCII")); |
636 |
md5.update (COLON); |
md5.update(COLON); |
637 |
md5.update (digestUri.getBytes ("US-ASCII")); |
md5.update(digestUri.getBytes("US-ASCII")); |
638 |
if ("auth-int".equals (qop)) |
if ("auth-int".equals(qop)) |
639 |
{ |
{ |
640 |
byte[] hEntity = null; // TODO hash of entity body |
byte[] hEntity = null; // TODO hash of entity body |
641 |
md5.update (COLON); |
md5.update(COLON); |
642 |
md5.update (hEntity); |
md5.update(hEntity); |
643 |
} |
} |
644 |
byte[] ha2 = md5.digest (); |
byte[] ha2 = md5.digest(); |
645 |
String ha2Hex = toHexString (ha2); |
String ha2Hex = toHexString(ha2); |
646 |
|
|
647 |
// Calculate response |
// Calculate response |
648 |
md5.reset (); |
md5.reset(); |
649 |
md5.update (ha1Hex.getBytes ("US-ASCII")); |
md5.update(ha1Hex.getBytes("US-ASCII")); |
650 |
md5.update (COLON); |
md5.update(COLON); |
651 |
md5.update (nonce.getBytes ("US-ASCII")); |
md5.update(nonce.getBytes("US-ASCII")); |
652 |
if ("auth".equals (qop) || "auth-int".equals (qop)) |
if ("auth".equals(qop) || "auth-int".equals(qop)) |
653 |
{ |
{ |
654 |
String nc = getNonceCount (nonce); |
String nc = getNonceCount(nonce); |
655 |
byte[] cnonce = generateNonce (); |
byte[] cnonce = generateNonce(); |
656 |
md5.update (COLON); |
md5.update(COLON); |
657 |
md5.update (nc.getBytes ("US-ASCII")); |
md5.update(nc.getBytes("US-ASCII")); |
658 |
md5.update (COLON); |
md5.update(COLON); |
659 |
md5.update (cnonce); |
md5.update(cnonce); |
660 |
md5.update (COLON); |
md5.update(COLON); |
661 |
md5.update (qop.getBytes ("US-ASCII")); |
md5.update(qop.getBytes("US-ASCII")); |
662 |
} |
} |
663 |
md5.update (COLON); |
md5.update(COLON); |
664 |
md5.update (ha2Hex.getBytes ("US-ASCII")); |
md5.update(ha2Hex.getBytes("US-ASCII")); |
665 |
String digestResponse = toHexString (md5.digest ()); |
String digestResponse = toHexString(md5.digest()); |
666 |
|
|
667 |
String authorization = scheme + |
String authorization = scheme + |
668 |
" username=\"" + username + "\"" + |
" username=\"" + username + "\"" + |
670 |
" nonce=\"" + nonce + "\"" + |
" nonce=\"" + nonce + "\"" + |
671 |
" uri=\"" + digestUri + "\"" + |
" uri=\"" + digestUri + "\"" + |
672 |
" response=\"" + digestResponse + "\""; |
" response=\"" + digestResponse + "\""; |
673 |
setHeader ("Authorization", authorization); |
setHeader("Authorization", authorization); |
674 |
return true; |
return true; |
675 |
} |
} |
676 |
catch (NoSuchAlgorithmException e) |
catch (NoSuchAlgorithmException e) |
682 |
return false; |
return false; |
683 |
} |
} |
684 |
|
|
685 |
Properties parseAuthParams (String text) |
Properties parseAuthParams(String text) |
686 |
{ |
{ |
687 |
int len = text.length (); |
int len = text.length(); |
688 |
String key = null; |
String key = null; |
689 |
StringBuffer buf = new StringBuffer (); |
StringBuffer buf = new StringBuffer(); |
690 |
Properties ret = new Properties (); |
Properties ret = new Properties(); |
691 |
boolean inQuote = false; |
boolean inQuote = false; |
692 |
for (int i = 0; i < len; i++) |
for (int i = 0; i < len; i++) |
693 |
{ |
{ |
694 |
char c = text.charAt (i); |
char c = text.charAt(i); |
695 |
if (c == '"') |
if (c == '"') |
696 |
{ |
{ |
697 |
inQuote = !inQuote; |
inQuote = !inQuote; |
698 |
} |
} |
699 |
else if (c == '=' && key == null) |
else if (c == '=' && key == null) |
700 |
{ |
{ |
701 |
key = buf.toString ().trim (); |
key = buf.toString().trim(); |
702 |
buf.setLength (0); |
buf.setLength(0); |
703 |
} |
} |
704 |
else if (c == ' ' && !inQuote) |
else if (c == ' ' && !inQuote) |
705 |
{ |
{ |
706 |
String value = unquote (buf.toString ().trim ()); |
String value = unquote(buf.toString().trim()); |
707 |
ret.put (key, value); |
ret.put(key, value); |
708 |
key = null; |
key = null; |
709 |
buf.setLength (0); |
buf.setLength(0); |
710 |
} |
} |
711 |
else if (c != ',' || (i < (len - 1) && text.charAt (i + 1) != ' ')) |
else if (c != ',' || (i <(len - 1) && text.charAt(i + 1) != ' ')) |
712 |
{ |
{ |
713 |
buf.append (c); |
buf.append(c); |
714 |
} |
} |
715 |
} |
} |
716 |
if (key != null) |
if (key != null) |
717 |
{ |
{ |
718 |
String value = unquote (buf.toString ().trim ()); |
String value = unquote(buf.toString().trim()); |
719 |
ret.put (key, value); |
ret.put(key, value); |
720 |
} |
} |
721 |
return ret; |
return ret; |
722 |
} |
} |
723 |
|
|
724 |
String unquote (String text) |
String unquote(String text) |
725 |
{ |
{ |
726 |
int len = text.length (); |
int len = text.length(); |
727 |
if (len > 0 && text.charAt (0) == '"' && text.charAt (len - 1) == '"') |
if (len > 0 && text.charAt(0) == '"' && text.charAt(len - 1) == '"') |
728 |
{ |
{ |
729 |
return text.substring (1, len - 1); |
return text.substring(1, len - 1); |
730 |
} |
} |
731 |
return text; |
return text; |
732 |
} |
} |
735 |
* Returns the number of times the specified nonce value has been seen. |
* Returns the number of times the specified nonce value has been seen. |
736 |
* This always returns an 8-byte 0-padded hexadecimal string. |
* This always returns an 8-byte 0-padded hexadecimal string. |
737 |
*/ |
*/ |
738 |
String getNonceCount (String nonce) |
String getNonceCount(String nonce) |
739 |
{ |
{ |
740 |
int nc = connection.getNonceCount (nonce); |
int nc = connection.getNonceCount(nonce); |
741 |
String hex = Integer.toHexString (nc); |
String hex = Integer.toHexString(nc); |
742 |
StringBuffer buf = new StringBuffer (); |
StringBuffer buf = new StringBuffer(); |
743 |
for (int i = 8 - hex.length (); i > 0; i--) |
for (int i = 8 - hex.length(); i > 0; i--) |
744 |
{ |
{ |
745 |
buf.append ('0'); |
buf.append('0'); |
746 |
} |
} |
747 |
buf.append (hex); |
buf.append(hex); |
748 |
return buf.toString (); |
return buf.toString(); |
749 |
} |
} |
750 |
|
|
751 |
/** |
/** |
756 |
/** |
/** |
757 |
* Generates a new client nonce value. |
* Generates a new client nonce value. |
758 |
*/ |
*/ |
759 |
byte[] generateNonce () |
byte[] generateNonce() |
760 |
throws IOException, NoSuchAlgorithmException |
throws IOException, NoSuchAlgorithmException |
761 |
{ |
{ |
762 |
if (nonce == null) |
if (nonce == null) |
763 |
{ |
{ |
764 |
long time = System.currentTimeMillis (); |
long time = System.currentTimeMillis(); |
765 |
MessageDigest md5 = MessageDigest.getInstance ("MD5"); |
MessageDigest md5 = MessageDigest.getInstance("MD5"); |
766 |
md5.update (Long.toString (time).getBytes ("US-ASCII")); |
md5.update(Long.toString(time).getBytes("US-ASCII")); |
767 |
nonce = md5.digest (); |
nonce = md5.digest(); |
768 |
} |
} |
769 |
return nonce; |
return nonce; |
770 |
} |
} |
771 |
|
|
772 |
String toHexString (byte[] bytes) |
String toHexString(byte[] bytes) |
773 |
{ |
{ |
774 |
char[] ret = new char[bytes.length * 2]; |
char[] ret = new char[bytes.length * 2]; |
775 |
for (int i = 0, j = 0; i < bytes.length; i++) |
for (int i = 0, j = 0; i < bytes.length; i++) |
776 |
{ |
{ |
777 |
int c = (int) bytes[i]; |
int c =(int) bytes[i]; |
778 |
if (c < 0) |
if (c < 0) |
779 |
{ |
{ |
780 |
c += 0x100; |
c += 0x100; |
781 |
} |
} |
782 |
ret[j++] = Character.forDigit (c / 0x10, 0x10); |
ret[j++] = Character.forDigit(c / 0x10, 0x10); |
783 |
ret[j++] = Character.forDigit (c % 0x10, 0x10); |
ret[j++] = Character.forDigit(c % 0x10, 0x10); |
784 |
} |
} |
785 |
return new String (ret); |
return new String(ret); |
786 |
} |
} |
787 |
|
|
788 |
/** |
/** |
789 |
* Parse the specified cookie list and notify the cookie manager. |
* Parse the specified cookie list and notify the cookie manager. |
790 |
*/ |
*/ |
791 |
void handleSetCookie (String text) |
void handleSetCookie(String text) |
792 |
{ |
{ |
793 |
CookieManager cookieManager = connection.getCookieManager (); |
CookieManager cookieManager = connection.getCookieManager(); |
794 |
if (cookieManager == null) |
if (cookieManager == null) |
795 |
{ |
{ |
796 |
return; |
return; |
798 |
String name = null; |
String name = null; |
799 |
String value = null; |
String value = null; |
800 |
String comment = null; |
String comment = null; |
801 |
String domain = connection.getHostName (); |
String domain = connection.getHostName(); |
802 |
String path = this.path; |
String path = this.path; |
803 |
int lsi = path.lastIndexOf ('/'); |
int lsi = path.lastIndexOf('/'); |
804 |
if (lsi != -1) |
if (lsi != -1) |
805 |
{ |
{ |
806 |
path = path.substring (0, lsi); |
path = path.substring(0, lsi); |
807 |
} |
} |
808 |
boolean secure = false; |
boolean secure = false; |
809 |
Date expires = null; |
Date expires = null; |
810 |
|
|
811 |
int len = text.length (); |
int len = text.length(); |
812 |
String attr = null; |
String attr = null; |
813 |
StringBuffer buf = new StringBuffer (); |
StringBuffer buf = new StringBuffer(); |
814 |
boolean inQuote = false; |
boolean inQuote = false; |
815 |
for (int i = 0; i <= len; i++) |
for (int i = 0; i <= len; i++) |
816 |
{ |
{ |
817 |
char c = (i == len) ? '\u0000' : text.charAt (i); |
char c =(i == len) ? '\u0000' : text.charAt(i); |
818 |
if (c == '"') |
if (c == '"') |
819 |
{ |
{ |
820 |
inQuote = !inQuote; |
inQuote = !inQuote; |
823 |
{ |
{ |
824 |
if (c == '=' && attr == null) |
if (c == '=' && attr == null) |
825 |
{ |
{ |
826 |
attr = buf.toString ().trim (); |
attr = buf.toString().trim(); |
827 |
buf.setLength (0); |
buf.setLength(0); |
828 |
} |
} |
829 |
else if (c == ';' || i == len || c == ',') |
else if (c == ';' || i == len || c == ',') |
830 |
{ |
{ |
831 |
String val = unquote (buf.toString ().trim ()); |
String val = unquote(buf.toString().trim()); |
832 |
if (name == null) |
if (name == null) |
833 |
{ |
{ |
834 |
name = attr; |
name = attr; |
835 |
value = val; |
value = val; |
836 |
} |
} |
837 |
else if ("Comment".equalsIgnoreCase (attr)) |
else if ("Comment".equalsIgnoreCase(attr)) |
838 |
{ |
{ |
839 |
comment = val; |
comment = val; |
840 |
} |
} |
841 |
else if ("Domain".equalsIgnoreCase (attr)) |
else if ("Domain".equalsIgnoreCase(attr)) |
842 |
{ |
{ |
843 |
domain = val; |
domain = val; |
844 |
} |
} |
845 |
else if ("Path".equalsIgnoreCase (attr)) |
else if ("Path".equalsIgnoreCase(attr)) |
846 |
{ |
{ |
847 |
path = val; |
path = val; |
848 |
} |
} |
849 |
else if ("Secure".equalsIgnoreCase (val)) |
else if ("Secure".equalsIgnoreCase(val)) |
850 |
{ |
{ |
851 |
secure = true; |
secure = true; |
852 |
} |
} |
853 |
else if ("Max-Age".equalsIgnoreCase (attr)) |
else if ("Max-Age".equalsIgnoreCase(attr)) |
854 |
{ |
{ |
855 |
int delta = Integer.parseInt (val); |
int delta = Integer.parseInt(val); |
856 |
Calendar cal = Calendar.getInstance (); |
Calendar cal = Calendar.getInstance(); |
857 |
cal.setTimeInMillis (System.currentTimeMillis ()); |
cal.setTimeInMillis(System.currentTimeMillis()); |
858 |
cal.add (Calendar.SECOND, delta); |
cal.add(Calendar.SECOND, delta); |
859 |
expires = cal.getTime (); |
expires = cal.getTime(); |
860 |
} |
} |
861 |
else if ("Expires".equalsIgnoreCase (attr)) |
else if ("Expires".equalsIgnoreCase(attr)) |
862 |
{ |
{ |
863 |
DateFormat dateFormat = new HTTPDateFormat (); |
DateFormat dateFormat = new HTTPDateFormat(); |
864 |
try |
try |
865 |
{ |
{ |
866 |
expires = dateFormat.parse (val); |
expires = dateFormat.parse(val); |
867 |
} |
} |
868 |
catch (ParseException e) |
catch (ParseException e) |
869 |
{ |
{ |
870 |
// if this isn't a valid date, it may be that |
// if this isn't a valid date, it may be that |
871 |
// the value was returned unquoted; in that case, we |
// the value was returned unquoted; in that case, we |
872 |
// want to continue buffering the value |
// want to continue buffering the value |
873 |
buf.append (c); |
buf.append(c); |
874 |
continue; |
continue; |
875 |
} |
} |
876 |
} |
} |
877 |
attr = null; |
attr = null; |
878 |
buf.setLength (0); |
buf.setLength(0); |
879 |
// case EOL |
// case EOL |
880 |
if (i == len || c == ',') |
if (i == len || c == ',') |
881 |
{ |
{ |
882 |
Cookie cookie = new Cookie (name, value, comment, domain, |
Cookie cookie = new Cookie(name, value, comment, domain, |
883 |
path, secure, expires); |
path, secure, expires); |
884 |
cookieManager.setCookie (cookie); |
cookieManager.setCookie(cookie); |
885 |
} |
} |
886 |
if (c == ',') |
if (c == ',') |
887 |
{ |
{ |
889 |
name = null; |
name = null; |
890 |
value = null; |
value = null; |
891 |
comment = null; |
comment = null; |
892 |
domain = connection.getHostName (); |
domain = connection.getHostName(); |
893 |
path = this.path; |
path = this.path; |
894 |
if (lsi != -1) |
if (lsi != -1) |
895 |
{ |
{ |
896 |
path = path.substring (0, lsi); |
path = path.substring(0, lsi); |
897 |
} |
} |
898 |
secure = false; |
secure = false; |
899 |
expires = null; |
expires = null; |
901 |
} |
} |
902 |
else |
else |
903 |
{ |
{ |
904 |
buf.append (c); |
buf.append(c); |
905 |
} |
} |
906 |
} |
} |
907 |
else |
else |
908 |
{ |
{ |
909 |
buf.append (c); |
buf.append(c); |
910 |
} |
} |
911 |
} |
} |
912 |
} |
} |
913 |
|
|
914 |
} |
} |
915 |
|
|