/**
   * @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));
  }