| /* Request.java -- | |
| Copyright (C) 2004, 2005, 2006, 2007 Free Software Foundation, Inc. | |
| This file is part of GNU Classpath. | |
| GNU Classpath is free software; you can redistribute it and/or modify | |
| it under the terms of the GNU General Public License as published by | |
| the Free Software Foundation; either version 2, or (at your option) | |
| any later version. | |
| GNU Classpath is distributed in the hope that it will be useful, but | |
| WITHOUT ANY WARRANTY; without even the implied warranty of | |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| General Public License for more details. | |
| You should have received a copy of the GNU General Public License | |
| along with GNU Classpath; see the file COPYING. If not, write to the | |
| Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | |
| 02110-1301 USA. | |
| Linking this library statically or dynamically with other modules is | |
| making a combined work based on this library. Thus, the terms and | |
| conditions of the GNU General Public License cover the whole | |
| combination. | |
| As a special exception, the copyright holders of this library give you | |
| permission to link this library with independent modules to produce an | |
| executable, regardless of the license terms of these independent | |
| modules, and to copy and distribute the resulting executable under | |
| terms of your choice, provided that you also meet, for each linked | |
| independent module, the terms and conditions of the license of that | |
| module. An independent module is a module which is not derived from | |
| or based on this library. If you modify this library, you may extend | |
| this exception to your version of the library, but you are not | |
| obligated to do so. If you do not wish to do so, delete this | |
| exception statement from your version. */ | |
| package gnu.java.net.protocol.http; | |
| import gnu.java.lang.CPStringBuilder; | |
| import gnu.java.net.LineInputStream; | |
| import gnu.java.util.Base64; | |
| import java.io.IOException; | |
| import java.io.InputStream; | |
| import java.io.OutputStream; | |
| import java.net.ProtocolException; | |
| import java.security.MessageDigest; | |
| import java.security.NoSuchAlgorithmException; | |
| import java.text.DateFormat; | |
| import java.text.ParseException; | |
| import java.util.Calendar; | |
| import java.util.Date; | |
| import java.util.HashMap; | |
| import java.util.Map; | |
| import java.util.Properties; | |
| import java.util.zip.GZIPInputStream; | |
| import java.util.zip.InflaterInputStream; | |
| /** | |
| * A single HTTP request. | |
| * | |
| * @author Chris Burdess (dog@gnu.org) | |
| */ | |
| public class Request | |
| { | |
| /** | |
| * The connection context in which this request is invoked. | |
| */ | |
| protected final HTTPConnection connection; | |
| /** | |
| * The HTTP method to invoke. | |
| */ | |
| protected final String method; | |
| /** | |
| * The path identifying the resource. | |
| * This string must conform to the abs_path definition given in RFC2396, | |
| * with an optional "?query" part, and must be URI-escaped by the caller. | |
| */ | |
| protected final String path; | |
| /** | |
| * The headers in this request. | |
| */ | |
| protected final Headers requestHeaders; | |
| /** | |
| * The request body provider. | |
| */ | |
| protected RequestBodyWriter requestBodyWriter; | |
| /** | |
| * Map of response header handlers. | |
| */ | |
| protected Map<String, ResponseHeaderHandler> responseHeaderHandlers; | |
| /** | |
| * The authenticator. | |
| */ | |
| protected Authenticator authenticator; | |
| /** | |
| * Whether this request has been dispatched yet. | |
| */ | |
| private boolean dispatched; | |
| /** | |
| * Constructor for a new request. | |
| * @param connection the connection context | |
| * @param method the HTTP method | |
| * @param path the resource path including query part | |
| */ | |
| protected Request(HTTPConnection connection, String method, | |
| String path) | |
| { | |
| this.connection = connection; | |
| this.method = method; | |
| this.path = path; | |
| requestHeaders = new Headers(); | |
| responseHeaderHandlers = new HashMap<String, ResponseHeaderHandler>(); | |
| } | |
| /** | |
| * Returns the connection associated with this request. | |
| * @see #connection | |
| */ | |
| public HTTPConnection getConnection() | |
| { | |
| return connection; | |
| } | |
| /** | |
| * Returns the HTTP method to invoke. | |
| * @see #method | |
| */ | |
| public String getMethod() | |
| { | |
| return method; | |
| } | |
| /** | |
| * Returns the resource path. | |
| * @see #path | |
| */ | |
| public String getPath() | |
| { | |
| return path; | |
| } | |
| /** | |
| * Returns the full request-URI represented by this request, as specified | |
| * by HTTP/1.1. | |
| */ | |
| public String getRequestURI() | |
| { | |
| return connection.getURI() + path; | |
| } | |
| /** | |
| * Returns the headers in this request. | |
| */ | |
| public Headers getHeaders() | |
| { | |
| return requestHeaders; | |
| } | |
| /** | |
| * Returns the value of the specified header in this request. | |
| * @param name the header name | |
| */ | |
| public String getHeader(String name) | |
| { | |
| return requestHeaders.getValue(name); | |
| } | |
| /** | |
| * Returns the value of the specified header in this request as an integer. | |
| * @param name the header name | |
| */ | |
| public int getIntHeader(String name) | |
| { | |
| return requestHeaders.getIntValue(name); | |
| } | |
| /** | |
| * Returns the value of the specified header in this request as a date. | |
| * @param name the header name | |
| */ | |
| public Date getDateHeader(String name) | |
| { | |
| return requestHeaders.getDateValue(name); | |
| } | |
| /** | |
| * Sets the specified header in this request. | |
| * @param name the header name | |
| * @param value the header value | |
| */ | |
| public void setHeader(String name, String value) | |
| { | |
| requestHeaders.put(name, value); | |
| } | |
| /** | |
| * Convenience method to set the entire request body. | |
| * @param requestBody the request body content | |
| */ | |
| public void setRequestBody(byte[] requestBody) | |
| { | |
| setRequestBodyWriter(new ByteArrayRequestBodyWriter(requestBody)); | |
| } | |
| /** | |
| * Sets the request body provider. | |
| * @param requestBodyWriter the handler used to obtain the request body | |
| */ | |
| public void setRequestBodyWriter(RequestBodyWriter requestBodyWriter) | |
| { | |
| this.requestBodyWriter = requestBodyWriter; | |
| } | |
| /** | |
| * Sets a callback handler to be invoked for the specified header name. | |
| * @param name the header name | |
| * @param handler the handler to receive the value for the header | |
| */ | |
| public void setResponseHeaderHandler(String name, | |
| ResponseHeaderHandler handler) | |
| { | |
| responseHeaderHandlers.put(name, handler); | |
| } | |
| /** | |
| * Sets an authenticator that can be used to handle authentication | |
| * automatically. | |
| * @param authenticator the authenticator | |
| */ | |
| public void setAuthenticator(Authenticator authenticator) | |
| { | |
| this.authenticator = authenticator; | |
| } | |
| /** | |
| * Dispatches this request. | |
| * A request can only be dispatched once; calling this method a second | |
| * time results in a protocol exception. | |
| * @exception IOException if an I/O error occurred | |
| * @return an HTTP response object representing the result of the operation | |
| */ | |
| public Response dispatch() | |
| throws IOException | |
| { | |
| if (dispatched) | |
| { | |
| throw new ProtocolException("request already dispatched"); | |
| } | |
| final String CRLF = "\r\n"; | |
| final String HEADER_SEP = ": "; | |
| final String US_ASCII = "US-ASCII"; | |
| final String version = connection.getVersion(); | |
| Response response; | |
| int contentLength = -1; | |
| boolean retry = false; | |
| int attempts = 0; | |
| boolean expectingContinue = false; | |
| if (requestBodyWriter != null) | |
| { | |
| contentLength = requestBodyWriter.getContentLength(); | |
| String expect = getHeader("Expect"); | |
| if (expect != null && expect.equals("100-continue")) | |
| { | |
| expectingContinue = true; | |
| } | |
| else | |
| { | |
| setHeader("Content-Length", Integer.toString(contentLength)); | |
| } | |
| } | |
| try | |
| { | |
| // Loop while authentication fails or continue | |
| do | |
| { | |
| retry = false; | |
| // Get socket output and input streams | |
| OutputStream out = connection.getOutputStream(); | |
| // Request line | |
| String requestUri = path; | |
| if (connection.isUsingProxy() && | |
| !"*".equals(requestUri) && | |
| !"CONNECT".equals(method)) | |
| { | |
| requestUri = getRequestURI(); | |
| } | |
| String line = method + ' ' + requestUri + ' ' + version + CRLF; | |
| out.write(line.getBytes(US_ASCII)); | |
| // Request headers | |
| for (Headers.HeaderElement elt : requestHeaders) | |
| { | |
| line = elt.name + HEADER_SEP + elt.value + CRLF; | |
| out.write(line.getBytes(US_ASCII)); | |
| } | |
| out.write(CRLF.getBytes(US_ASCII)); | |
| // Request body | |
| if (requestBodyWriter != null && !expectingContinue) | |
| { | |
| byte[] buffer = new byte[4096]; | |
| int len; | |
| int count = 0; | |
| requestBodyWriter.reset(); | |
| do | |
| { | |
| len = requestBodyWriter.write(buffer); | |
| if (len > 0) | |
| { | |
| out.write(buffer, 0, len); | |
| } | |
| count += len; | |
| } | |
| while (len > -1 && count < contentLength); | |
| } | |
| out.flush(); | |
| // Get response | |
| while(true) | |
| { | |
| response = readResponse(connection.getInputStream()); | |
| int sc = response.getCode(); | |
| if (sc == 401 && authenticator != null) | |
| { | |
| if (authenticate(response, attempts++)) | |
| { | |
| retry = true; | |
| } | |
| } | |
| else if (sc == 100) | |
| { | |
| if (expectingContinue) | |
| { | |
| requestHeaders.remove("Expect"); | |
| setHeader("Content-Length", | |
| Integer.toString(contentLength)); | |
| expectingContinue = false; | |
| retry = true; | |
| } | |
| else | |
| { | |
| // A conforming server can send an unsoliceted | |
| // Continue response but *should* not (RFC 2616 | |
| // sec 8.2.3). Ignore the bogus Continue | |
| // response and get the real response that | |
| // should follow | |
| continue; | |
| } | |
| } | |
| break; | |
| } | |
| } | |
| while (retry); | |
| } | |
| catch (IOException e) | |
| { | |
| connection.close(); | |
| throw e; | |
| } | |
| return response; | |
| } | |
| Response readResponse(InputStream in) | |
| throws IOException | |
| { | |
| String line; | |
| int len; | |
| // Read response status line | |
| LineInputStream lis = new LineInputStream(in); | |
| line = lis.readLine(); | |
| if (line == null) | |
| { | |
| throw new ProtocolException("Peer closed connection"); | |
| } | |
| if (!line.startsWith("HTTP/")) | |
| { | |
| throw new ProtocolException(line); | |
| } | |
| len = line.length(); | |
| int start = 5, end = 6; | |
| while (line.charAt(end) != '.') | |
| { | |
| end++; | |
| } | |
| int majorVersion = Integer.parseInt(line.substring(start, end)); | |
| start = end + 1; | |
| end = start + 1; | |
| while (line.charAt(end) != ' ') | |
| { | |
| end++; | |
| } | |
| int minorVersion = Integer.parseInt(line.substring(start, end)); | |
| start = end + 1; | |
| end = start + 3; | |
| int code = Integer.parseInt(line.substring(start, end)); | |
| String message = line.substring(end + 1, len - 1); | |
| // Read response headers | |
| Headers responseHeaders = new Headers(); | |
| responseHeaders.parse(lis); | |
| notifyHeaderHandlers(responseHeaders); | |
| InputStream body = null; | |
| switch (code) | |
| { | |
| case 100: | |
| break; | |
| case 204: | |
| case 205: | |
| case 304: | |
| body = createResponseBodyStream(responseHeaders, majorVersion, | |
| minorVersion, in, false); | |
| break; | |
| default: | |
| body = createResponseBodyStream(responseHeaders, majorVersion, | |
| minorVersion, in, true); | |
| } | |
| // Construct response | |
| Response ret = new Response(majorVersion, minorVersion, code, | |
| message, responseHeaders, body); | |
| return ret; | |
| } | |
| void notifyHeaderHandlers(Headers headers) | |
| { | |
| for (Headers.HeaderElement entry : headers) | |
| { | |
| // Handle Set-Cookie | |
| if ("Set-Cookie".equalsIgnoreCase(entry.name)) | |
| handleSetCookie(entry.value); | |
| ResponseHeaderHandler handler = | |
| (ResponseHeaderHandler) responseHeaderHandlers.get(entry.name); | |
| if (handler != null) | |
| handler.setValue(entry.value); | |
| } | |
| } | |
| private InputStream createResponseBodyStream(Headers responseHeaders, | |
| int majorVersion, | |
| int minorVersion, | |
| InputStream in, | |
| boolean mayHaveBody) | |
| throws IOException | |
| { | |
| long contentLength = -1; | |
| // Persistent connections are the default in HTTP/1.1 | |
| boolean doClose = "close".equalsIgnoreCase(getHeader("Connection")) || | |
| "close".equalsIgnoreCase(responseHeaders.getValue("Connection")) || | |
| (connection.majorVersion == 1 && connection.minorVersion == 0) || | |
| (majorVersion == 1 && minorVersion == 0); | |
| String transferCoding = responseHeaders.getValue("Transfer-Encoding"); | |
| if ("HEAD".equals(method) || !mayHaveBody) | |
| { | |
| // Special case no body. | |
| in = new LimitedLengthInputStream(in, 0, true, connection, doClose); | |
| } | |
| else if ("chunked".equalsIgnoreCase(transferCoding)) | |
| { | |
| in = new LimitedLengthInputStream(in, -1, false, connection, doClose); | |
| in = new ChunkedInputStream(in, responseHeaders); | |
| } | |
| else | |
| { | |
| contentLength = responseHeaders.getLongValue("Content-Length"); | |
| if (contentLength < 0) | |
| doClose = true; // No Content-Length, must close. | |
| in = new LimitedLengthInputStream(in, contentLength, | |
| contentLength >= 0, | |
| connection, doClose); | |
| } | |
| String contentCoding = responseHeaders.getValue("Content-Encoding"); | |
| if (contentCoding != null && !"identity".equals(contentCoding)) | |
| { | |
| if ("gzip".equals(contentCoding)) | |
| { | |
| in = new GZIPInputStream(in); | |
| } | |
| else if ("deflate".equals(contentCoding)) | |
| { | |
| in = new InflaterInputStream(in); | |
| } | |
| else | |
| { | |
| throw new ProtocolException("Unsupported Content-Encoding: " + | |
| contentCoding); | |
| } | |
| // Remove the Content-Encoding header because the content is | |
| // no longer compressed. | |
| responseHeaders.remove("Content-Encoding"); | |
| } | |
| return in; | |
| } | |
| boolean authenticate(Response response, int attempts) | |
| throws IOException | |
| { | |
| String challenge = response.getHeader("WWW-Authenticate"); | |
| if (challenge == null) | |
| { | |
| challenge = response.getHeader("Proxy-Authenticate"); | |
| } | |
| int si = challenge.indexOf(' '); | |
| String scheme = (si == -1) ? challenge : challenge.substring(0, si); | |
| if ("Basic".equalsIgnoreCase(scheme)) | |
| { | |
| Properties params = parseAuthParams(challenge.substring(si + 1)); | |
| String realm = params.getProperty("realm"); | |
| Credentials creds = authenticator.getCredentials(realm, attempts); | |
| String userPass = creds.getUsername() + ':' + creds.getPassword(); | |
| byte[] b_userPass = userPass.getBytes("US-ASCII"); | |
| byte[] b_encoded = Base64.encode(b_userPass).getBytes("US-ASCII"); | |
| String authorization = | |
| scheme + " " + new String(b_encoded, "US-ASCII"); | |
| setHeader("Authorization", authorization); | |
| return true; | |
| } | |
| else if ("Digest".equalsIgnoreCase(scheme)) | |
| { | |
| Properties params = parseAuthParams(challenge.substring(si + 1)); | |
| String realm = params.getProperty("realm"); | |
| String nonce = params.getProperty("nonce"); | |
| String qop = params.getProperty("qop"); | |
| String algorithm = params.getProperty("algorithm"); | |
| String digestUri = getRequestURI(); | |
| Credentials creds = authenticator.getCredentials(realm, attempts); | |
| String username = creds.getUsername(); | |
| String password = creds.getPassword(); | |
| connection.incrementNonce(nonce); | |
| try | |
| { | |
| MessageDigest md5 = MessageDigest.getInstance("MD5"); | |
| final byte[] COLON = { 0x3a }; | |
| // Calculate H(A1) | |
| md5.reset(); | |
| md5.update(username.getBytes("US-ASCII")); | |
| md5.update(COLON); | |
| md5.update(realm.getBytes("US-ASCII")); | |
| md5.update(COLON); | |
| md5.update(password.getBytes("US-ASCII")); | |
| byte[] ha1 = md5.digest(); | |
| if ("md5-sess".equals(algorithm)) | |
| { | |
| byte[] cnonce = generateNonce(); | |
| md5.reset(); | |
| md5.update(ha1); | |
| md5.update(COLON); | |
| md5.update(nonce.getBytes("US-ASCII")); | |
| md5.update(COLON); | |
| md5.update(cnonce); | |
| ha1 = md5.digest(); | |
| } | |
| String ha1Hex = toHexString(ha1); | |
| // Calculate H(A2) | |
| md5.reset(); | |
| md5.update(method.getBytes("US-ASCII")); | |
| md5.update(COLON); | |
| md5.update(digestUri.getBytes("US-ASCII")); | |
| if ("auth-int".equals(qop)) | |
| { | |
| byte[] hEntity = null; // TODO hash of entity body | |
| md5.update(COLON); | |
| md5.update(hEntity); | |
| } | |
| byte[] ha2 = md5.digest(); | |
| String ha2Hex = toHexString(ha2); | |
| // Calculate response | |
| md5.reset(); | |
| md5.update(ha1Hex.getBytes("US-ASCII")); | |
| md5.update(COLON); | |
| md5.update(nonce.getBytes("US-ASCII")); | |
| if ("auth".equals(qop) || "auth-int".equals(qop)) | |
| { | |
| String nc = getNonceCount(nonce); | |
| byte[] cnonce = generateNonce(); | |
| md5.update(COLON); | |
| md5.update(nc.getBytes("US-ASCII")); | |
| md5.update(COLON); | |
| md5.update(cnonce); | |
| md5.update(COLON); | |
| md5.update(qop.getBytes("US-ASCII")); | |
| } | |
| md5.update(COLON); | |
| md5.update(ha2Hex.getBytes("US-ASCII")); | |
| String digestResponse = toHexString(md5.digest()); | |
| String authorization = scheme + | |
| " username=\"" + username + "\"" + | |
| " realm=\"" + realm + "\"" + | |
| " nonce=\"" + nonce + "\"" + | |
| " uri=\"" + digestUri + "\"" + | |
| " response=\"" + digestResponse + "\""; | |
| setHeader("Authorization", authorization); | |
| return true; | |
| } | |
| catch (NoSuchAlgorithmException e) | |
| { | |
| return false; | |
| } | |
| } | |
| // Scheme not recognised | |
| return false; | |
| } | |
| Properties parseAuthParams(String text) | |
| { | |
| int len = text.length(); | |
| String key = null; | |
| CPStringBuilder buf = new CPStringBuilder(); | |
| Properties ret = new Properties(); | |
| boolean inQuote = false; | |
| for (int i = 0; i < len; i++) | |
| { | |
| char c = text.charAt(i); | |
| if (c == '"') | |
| { | |
| inQuote = !inQuote; | |
| } | |
| else if (c == '=' && key == null) | |
| { | |
| key = buf.toString().trim(); | |
| buf.setLength(0); | |
| } | |
| else if (c == ' ' && !inQuote) | |
| { | |
| String value = unquote(buf.toString().trim()); | |
| ret.put(key, value); | |
| key = null; | |
| buf.setLength(0); | |
| } | |
| else if (c != ',' || (i <(len - 1) && text.charAt(i + 1) != ' ')) | |
| { | |
| buf.append(c); | |
| } | |
| } | |
| if (key != null) | |
| { | |
| String value = unquote(buf.toString().trim()); | |
| ret.put(key, value); | |
| } | |
| return ret; | |
| } | |
| String unquote(String text) | |
| { | |
| int len = text.length(); | |
| if (len > 0 && text.charAt(0) == '"' && text.charAt(len - 1) == '"') | |
| { | |
| return text.substring(1, len - 1); | |
| } | |
| return text; | |
| } | |
| /** | |
| * Returns the number of times the specified nonce value has been seen. | |
| * This always returns an 8-byte 0-padded hexadecimal string. | |
| */ | |
| String getNonceCount(String nonce) | |
| { | |
| int nc = connection.getNonceCount(nonce); | |
| String hex = Integer.toHexString(nc); | |
| CPStringBuilder buf = new CPStringBuilder(); | |
| for (int i = 8 - hex.length(); i > 0; i--) | |
| { | |
| buf.append('0'); | |
| } | |
| buf.append(hex); | |
| return buf.toString(); | |
| } | |
| /** | |
| * Client nonce value. | |
| */ | |
| byte[] nonce; | |
| /** | |
| * Generates a new client nonce value. | |
| */ | |
| byte[] generateNonce() | |
| throws IOException, NoSuchAlgorithmException | |
| { | |
| if (nonce == null) | |
| { | |
| long time = System.currentTimeMillis(); | |
| MessageDigest md5 = MessageDigest.getInstance("MD5"); | |
| md5.update(Long.toString(time).getBytes("US-ASCII")); | |
| nonce = md5.digest(); | |
| } | |
| return nonce; | |
| } | |
| String toHexString(byte[] bytes) | |
| { | |
| char[] ret = new char[bytes.length * 2]; | |
| for (int i = 0, j = 0; i < bytes.length; i++) | |
| { | |
| int c =(int) bytes[i]; | |
| if (c < 0) | |
| { | |
| c += 0x100; | |
| } | |
| ret[j++] = Character.forDigit(c / 0x10, 0x10); | |
| ret[j++] = Character.forDigit(c % 0x10, 0x10); | |
| } | |
| return new String(ret); | |
| } | |
| /** | |
| * Parse the specified cookie list and notify the cookie manager. | |
| */ | |
| void handleSetCookie(String text) | |
| { | |
| CookieManager cookieManager = connection.getCookieManager(); | |
| if (cookieManager == null) | |
| { | |
| return; | |
| } | |
| String name = null; | |
| String value = null; | |
| String comment = null; | |
| String domain = connection.getHostName(); | |
| String path = this.path; | |
| int lsi = path.lastIndexOf('/'); | |
| if (lsi != -1) | |
| { | |
| path = path.substring(0, lsi); | |
| } | |
| boolean secure = false; | |
| Date expires = null; | |
| int len = text.length(); | |
| String attr = null; | |
| CPStringBuilder buf = new CPStringBuilder(); | |
| boolean inQuote = false; | |
| for (int i = 0; i <= len; i++) | |
| { | |
| char c =(i == len) ? '\u0000' : text.charAt(i); | |
| if (c == '"') | |
| { | |
| inQuote = !inQuote; | |
| } | |
| else if (!inQuote) | |
| { | |
| if (c == '=' && attr == null) | |
| { | |
| attr = buf.toString().trim(); | |
| buf.setLength(0); | |
| } | |
| else if (c == ';' || i == len || c == ',') | |
| { | |
| String val = unquote(buf.toString().trim()); | |
| if (name == null) | |
| { | |
| name = attr; | |
| value = val; | |
| } | |
| else if ("Comment".equalsIgnoreCase(attr)) | |
| { | |
| comment = val; | |
| } | |
| else if ("Domain".equalsIgnoreCase(attr)) | |
| { | |
| domain = val; | |
| } | |
| else if ("Path".equalsIgnoreCase(attr)) | |
| { | |
| path = val; | |
| } | |
| else if ("Secure".equalsIgnoreCase(val)) | |
| { | |
| secure = true; | |
| } | |
| else if ("Max-Age".equalsIgnoreCase(attr)) | |
| { | |
| int delta = Integer.parseInt(val); | |
| Calendar cal = Calendar.getInstance(); | |
| cal.setTimeInMillis(System.currentTimeMillis()); | |
| cal.add(Calendar.SECOND, delta); | |
| expires = cal.getTime(); | |
| } | |
| else if ("Expires".equalsIgnoreCase(attr)) | |
| { | |
| DateFormat dateFormat = new HTTPDateFormat(); | |
| try | |
| { | |
| expires = dateFormat.parse(val); | |
| } | |
| catch (ParseException e) | |
| { | |
| // if this isn't a valid date, it may be that | |
| // the value was returned unquoted; in that case, we | |
| // want to continue buffering the value | |
| buf.append(c); | |
| continue; | |
| } | |
| } | |
| attr = null; | |
| buf.setLength(0); | |
| // case EOL | |
| if (i == len || c == ',') | |
| { | |
| Cookie cookie = new Cookie(name, value, comment, domain, | |
| path, secure, expires); | |
| cookieManager.setCookie(cookie); | |
| } | |
| if (c == ',') | |
| { | |
| // Reset cookie fields | |
| name = null; | |
| value = null; | |
| comment = null; | |
| domain = connection.getHostName(); | |
| path = this.path; | |
| if (lsi != -1) | |
| { | |
| path = path.substring(0, lsi); | |
| } | |
| secure = false; | |
| expires = null; | |
| } | |
| } | |
| else | |
| { | |
| buf.append(c); | |
| } | |
| } | |
| else | |
| { | |
| buf.append(c); | |
| } | |
| } | |
| } | |
| } |