/** * @return The HTTP-authentication object or null if there is no supported scheme in the header. * If there are several valid schemes present, we pick the strongest one. If there are several * schemes of the same strength, we pick the one that comes first. */ private HttpAuthHeader parseAuthHeader(String header) { if (header != null) { int posMax = 256; int posLen = 0; int[] pos = new int[posMax]; int headerLen = header.length(); if (headerLen > 0) { // first, we find all unquoted instances of 'Basic' and 'Digest' boolean quoted = false; for (int i = 0; i < headerLen && posLen < posMax; ++i) { if (header.charAt(i) == '\"') { quoted = !quoted; } else { if (!quoted) { if (header.startsWith(HttpAuthHeader.BASIC_TOKEN, i)) { pos[posLen++] = i; continue; } if (header.startsWith(HttpAuthHeader.DIGEST_TOKEN, i)) { pos[posLen++] = i; continue; } } } } } if (posLen > 0) { // consider all digest schemes first (if any) for (int i = 0; i < posLen; i++) { if (header.startsWith(HttpAuthHeader.DIGEST_TOKEN, pos[i])) { String sub = header.substring(pos[i], (i + 1 < posLen ? pos[i + 1] : headerLen)); HttpAuthHeader rval = new HttpAuthHeader(sub); if (rval.isSupportedScheme()) { // take the first match return rval; } } } // ...then consider all basic schemes (if any) for (int i = 0; i < posLen; i++) { if (header.startsWith(HttpAuthHeader.BASIC_TOKEN, pos[i])) { String sub = header.substring(pos[i], (i + 1 < posLen ? pos[i + 1] : headerLen)); HttpAuthHeader rval = new HttpAuthHeader(sub); if (rval.isSupportedScheme()) { // take the first match return rval; } } } } } return null; }
/** @return HTTP authentication realm or null if none. */ String realm() { if (mAuthHeader == null) { return null; } else { return mAuthHeader.getRealm(); } }
/** @return True iff this loader is in the proxy-authenticate state. */ boolean proxyAuthenticate() { if (mAuthHeader != null) { return mAuthHeader.isProxy(); } return false; }
/** Uses user-supplied credentials to restar a request. */ void handleAuthResponse(String username, String password) { if (Config.LOGV) { Log.v( LOGTAG, "LoadListener.handleAuthResponse: url: " + mUrl + " username: "******" password: " + password); } // create and queue an authentication-response if (username != null && password != null) { if (mAuthHeader != null && mRequestHandle != null) { mAuthHeader.setUsername(username); mAuthHeader.setPassword(password); int scheme = mAuthHeader.getScheme(); if (scheme == HttpAuthHeader.BASIC) { // create a basic response boolean isProxy = mAuthHeader.isProxy(); mRequestHandle.setupBasicAuthResponse(isProxy, username, password); } else { if (scheme == HttpAuthHeader.DIGEST) { // create a digest response boolean isProxy = mAuthHeader.isProxy(); String realm = mAuthHeader.getRealm(); String nonce = mAuthHeader.getNonce(); String qop = mAuthHeader.getQop(); String algorithm = mAuthHeader.getAlgorithm(); String opaque = mAuthHeader.getOpaque(); mRequestHandle.setupDigestAuthResponse( isProxy, username, password, realm, nonce, qop, algorithm, opaque); } } } } }
/** Returns true iff an HTTP authentication problem has occured (credentials invalid). */ boolean authCredentialsInvalid() { // if it is digest and the nonce is stale, we just // resubmit with a new nonce return (mAuthFailed && !(mAuthHeader.isDigest() && mAuthHeader.getStale())); }
/** * Event handler's endData call. Send a message to the handler notifying them that the data has * finished. IMPORTANT: as this is called from network thread, can't call native directly */ public void endData() { if (Config.LOGV) { Log.v(LOGTAG, "LoadListener.endData(): url: " + url()); } if (mCancelled) return; switch (mStatusCode) { case HTTP_MOVED_PERMANENTLY: // 301 - permanent redirect mPermanent = true; case HTTP_FOUND: case HTTP_SEE_OTHER: case HTTP_TEMPORARY_REDIRECT: if (mMethod == null && mRequestHandle == null) { Log.e(LOGTAG, "LoadListener.endData(): method is null!"); Log.e(LOGTAG, "LoadListener.endData(): url = " + url()); } // 301, 302, 303, and 307 - redirect if (mStatusCode == HTTP_TEMPORARY_REDIRECT) { if (mRequestHandle != null && mRequestHandle.getMethod().equals("POST")) { sendMessageInternal(obtainMessage(MSG_LOCATION_CHANGED_REQUEST)); } else if (mMethod != null && mMethod.equals("POST")) { sendMessageInternal(obtainMessage(MSG_LOCATION_CHANGED_REQUEST)); } else { sendMessageInternal(obtainMessage(MSG_LOCATION_CHANGED)); } } else { sendMessageInternal(obtainMessage(MSG_LOCATION_CHANGED)); } return; case HTTP_AUTH: case HTTP_PROXY_AUTH: // According to rfc2616, the response for HTTP_AUTH must include // WWW-Authenticate header field and the response for // HTTP_PROXY_AUTH must include Proxy-Authenticate header field. if (mAuthHeader != null && (Network.getInstance(mContext).isValidProxySet() || !mAuthHeader.isProxy())) { Network.getInstance(mContext).handleAuthRequest(this); return; } break; // use default case HTTP_NOT_MODIFIED: // Server could send back NOT_MODIFIED even if we didn't // ask for it, so make sure we have a valid CacheLoader // before calling it. if (mCacheLoader != null) { detachRequestHandle(); mCacheLoader.load(); if (Config.LOGV) { Log.v(LOGTAG, "LoadListener cache load url=" + url()); } return; } break; // use default case HTTP_NOT_FOUND: // Not an error, the server can send back content. default: break; } sendMessageInternal(obtainMessage(MSG_CONTENT_FINISHED)); detachRequestHandle(); }
/** * Parse the headers sent from the server. * * @param headers gives up the HeaderGroup IMPORTANT: as this is called from network thread, can't * call native directly */ public void headers(Headers headers) { if (Config.LOGV) Log.v(LOGTAG, "LoadListener.headers"); if (mCancelled) return; mHeaders = headers; mMimeType = ""; mEncoding = ""; ArrayList<String> cookies = headers.getSetCookie(); for (int i = 0; i < cookies.size(); ++i) { CookieManager.getInstance().setCookie(mUri, cookies.get(i)); } long contentLength = headers.getContentLength(); if (contentLength != Headers.NO_CONTENT_LENGTH) { mContentLength = contentLength; } else { mContentLength = 0; } String contentType = headers.getContentType(); if (contentType != null) { parseContentTypeHeader(contentType); // If we have one of "generic" MIME types, try to deduce // the right MIME type from the file extension (if any): if (mMimeType.equalsIgnoreCase("text/plain") || mMimeType.equalsIgnoreCase("application/octet-stream")) { String newMimeType = guessMimeTypeFromExtension(); if (newMimeType != null) { mMimeType = newMimeType; } } else if (mMimeType.equalsIgnoreCase("text/vnd.wap.wml")) { // As we don't support wml, render it as plain text mMimeType = "text/plain"; } else { // XXX: Until the servers send us either correct xhtml or // text/html, treat application/xhtml+xml as text/html. // It seems that xhtml+xml and vnd.wap.xhtml+xml mime // subtypes are used interchangeably. So treat them the same. if (mMimeType.equalsIgnoreCase("application/xhtml+xml") || mMimeType.equals("application/vnd.wap.xhtml+xml")) { mMimeType = "text/html"; } } } else { /* Often when servers respond with 304 Not Modified or a Redirect, then they don't specify a MIMEType. When this occurs, the function below is called. In the case of 304 Not Modified, the cached headers are used rather than the headers that are returned from the server. */ guessMimeType(); } // is it an authentication request? boolean mustAuthenticate = (mStatusCode == HTTP_AUTH || mStatusCode == HTTP_PROXY_AUTH); // is it a proxy authentication request? boolean isProxyAuthRequest = (mStatusCode == HTTP_PROXY_AUTH); // is this authentication request due to a failed attempt to // authenticate ealier? mAuthFailed = false; // if we tried to authenticate ourselves last time if (mAuthHeader != null) { // we failed, if we must to authenticate again now and // we have a proxy-ness match mAuthFailed = (mustAuthenticate && isProxyAuthRequest == mAuthHeader.isProxy()); // if we did NOT fail and last authentication request was a // proxy-authentication request if (!mAuthFailed && mAuthHeader.isProxy()) { Network network = Network.getInstance(mContext); // if we have a valid proxy set if (network.isValidProxySet()) { /* The proxy credentials can be read in the WebCore thread */ synchronized (network) { // save authentication credentials for pre-emptive proxy // authentication network.setProxyUsername(mAuthHeader.getUsername()); network.setProxyPassword(mAuthHeader.getPassword()); } } } } // it is only here that we can reset the last mAuthHeader object // (if existed) and start a new one!!! mAuthHeader = null; if (mustAuthenticate) { if (mStatusCode == HTTP_AUTH) { mAuthHeader = parseAuthHeader(headers.getWwwAuthenticate()); } else { mAuthHeader = parseAuthHeader(headers.getProxyAuthenticate()); // if successfully parsed the header if (mAuthHeader != null) { // mark the auth-header object as a proxy mAuthHeader.setProxy(); } } } // Only create a cache file if the server has responded positively. if ((mStatusCode == HTTP_OK || mStatusCode == HTTP_FOUND || mStatusCode == HTTP_MOVED_PERMANENTLY || mStatusCode == HTTP_TEMPORARY_REDIRECT) && mNativeLoader != 0) { // Content arriving from a StreamLoader (eg File, Cache or Data) // will not be cached as they have the header: // cache-control: no-store mCacheResult = CacheManager.createCacheFile(mUrl, mStatusCode, headers, mMimeType, false); if (mCacheResult != null) { mCacheResult.encoding = mEncoding; } } sendMessageInternal(obtainMessage(MSG_CONTENT_HEADERS)); }