// Filename: httpDigestAuthorization.cxx
// Created by:  drose (25Oct02)
//
////////////////////////////////////////////////////////////////////
//
// PANDA 3D SOFTWARE
// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
//
// All use of this software is subject to the terms of the Panda 3d
// Software license.  You should have received a copy of this license
// along with this source code; you will also find a current copy of
// the license at http://etc.cmu.edu/panda3d/docs/license/ .
//
// To contact the maintainers of this program write to
// panda3d-general@lists.sourceforge.net .
//
////////////////////////////////////////////////////////////////////

#include "httpDigestAuthorization.h"

#ifdef HAVE_OPENSSL

#include "httpChannel.h"
#include "openssl/ssl.h"
#include "openssl/md5.h"
#include <time.h>

const string HTTPDigestAuthorization::_mechanism = "digest";

////////////////////////////////////////////////////////////////////
//     Function: HTTPDigestAuthorization::Constructor
//       Access: Protected
//  Description: 
////////////////////////////////////////////////////////////////////
HTTPDigestAuthorization::
HTTPDigestAuthorization(const HTTPAuthorization::Tokens &tokens, 
                        const URLSpec &url, bool is_proxy) : 
  HTTPAuthorization(tokens, url, is_proxy)
{
  Tokens::const_iterator ti;
  ti = tokens.find("nonce");
  if (ti != tokens.end()) {
    _nonce = (*ti).second;
  }
  _nonce_count = 0;

  ti = tokens.find("opaque");
  if (ti != tokens.end()) {
    _opaque = (*ti).second;
  }

  _algorithm = A_md5;
  ti = tokens.find("algorithm");
  if (ti != tokens.end()) {
    string algo_str = HTTPChannel::downcase((*ti).second);
    if (algo_str == "md5") {
      _algorithm = A_md5;
    } else if (algo_str == "md5-sess") {
      _algorithm = A_md5_sess;
    } else {
      _algorithm = A_unknown;
    }
  }

  _qop = 0;
  ti = tokens.find("qop");
  if (ti != tokens.end()) {
    string qop_str = HTTPChannel::downcase((*ti).second);
    // A comma-delimited list of tokens.

    size_t p = 0;
    while (p < qop_str.length()) {
      while (p < qop_str.length() && isspace(qop_str[p])) {
        ++p;
      }
      size_t q = p;
      size_t last_char = p;
      while (q < qop_str.length() && qop_str[q] != ',') {
        if (!isspace(qop_str[q])) {
          qop_str[q] = tolower(qop_str[q]);
          last_char = q;
        }
        ++q;
      }

      if (last_char > p) {
        _qop |= match_qop_token(qop_str.substr(p, last_char - p + 1));
      }
      p = q + 1;
    }
  }

  // Compute an arbitrary client nonce.
  ostringstream strm;
  strm << time(NULL) << ":" << clock() << ":" 
       << url.get_url() << ":Panda";

  _cnonce = calc_md5(strm.str());
}

////////////////////////////////////////////////////////////////////
//     Function: HTTPDigestAuthorization::Destructor
//       Access: Public, Virtual
//  Description: 
////////////////////////////////////////////////////////////////////
HTTPDigestAuthorization::
~HTTPDigestAuthorization() {
}

////////////////////////////////////////////////////////////////////
//     Function: HTTPDigestAuthorization::is_valid
//       Access: Public, Virtual
//  Description: Returns true if the authorization challenge was
//               correctly parsed and is usable, or false if there was
//               some unsupported algorithm or some such requested by
//               the server, rendering the challenge unmeetable.
////////////////////////////////////////////////////////////////////
bool HTTPDigestAuthorization::
is_valid() {
  return (_algorithm != A_unknown);
}

////////////////////////////////////////////////////////////////////
//     Function: HTTPDigestAuthorization::get_mechanism
//       Access: Public, Virtual
//  Description: Returns the type of authorization mechanism,
//               represented as a string, e.g. "digest".
////////////////////////////////////////////////////////////////////
const string &HTTPDigestAuthorization::
get_mechanism() const {
  return _mechanism;
}

////////////////////////////////////////////////////////////////////
//     Function: HTTPDigestAuthorization::generate
//       Access: Public, Virtual
//  Description: Generates a suitable authorization string to send
//               to the server, based on the data stored within this
//               object, for retrieving the indicated URL with the
//               given username:password.
////////////////////////////////////////////////////////////////////
string HTTPDigestAuthorization::
generate(HTTPEnum::Method method, const string &request_path,
         const string &username, const string &body) {
  _nonce_count++;

  size_t colon = username.find(':');
  string username_only = username.substr(0, colon);
  string password_only = username.substr(colon + 1);

  string digest = calc_request_digest(username_only, password_only,
                                      method, request_path, body);

  ostringstream strm;
  strm << "Digest username=\"" << username.substr(0, colon) << "\""
       << ", realm=\"" << get_realm() << "\""
       << ", nonce=\"" << _nonce << "\""
       << ", uri=" << request_path
       << ", response=\"" << digest << "\""
       << ", algorithm=" << _algorithm;

  if (!_opaque.empty()) {
    strm << ", opaque=\"" << _opaque << "\"";
  }

  if (_chosen_qop != Q_unused) {
    strm << ", qop=" << _chosen_qop
         << ", cnonce=\"" << _cnonce << "\""
         << ", nc=" << get_hex_nonce_count();
  }

  return strm.str();
}

////////////////////////////////////////////////////////////////////
//     Function: HTTPDigestAuthorization::match_qop_token
//       Access: Private, Static
//  Description: Returns the bitfield corresponding to the indicated
//               qop token string, or 0 if the token string is
//               unrecognized.
////////////////////////////////////////////////////////////////////
int HTTPDigestAuthorization::
match_qop_token(const string &token) {
  if (token == "auth") {
    return Q_auth;
  } else if (token == "auth-int") {
    return Q_auth_int;
  }
  return 0;
}

////////////////////////////////////////////////////////////////////
//     Function: HTTPDigestAuthorization::calc_request_digest
//       Access: Private
//  Description: Calculates the appropriate digest response, according
//               to RFC 2617.
////////////////////////////////////////////////////////////////////
string HTTPDigestAuthorization::
calc_request_digest(const string &username, const string &password,
                    HTTPEnum::Method method, const string &request_path, 
                    const string &body) {
  _chosen_qop = Q_unused;
  string h_a1 = calc_h(get_a1(username, password));
  string h_a2 = calc_h(get_a2(method, request_path, body));

  ostringstream strm;

  if (_qop == 0) {
    _chosen_qop = Q_unused;
    strm << _nonce << ":" << h_a2;

  } else {
    strm << _nonce << ":" << get_hex_nonce_count() << ":"
         << _cnonce << ":" << _chosen_qop << ":"
         << h_a2;
  }

  return calc_kd(h_a1, strm.str());
}

////////////////////////////////////////////////////////////////////
//     Function: HTTPDigestAuthorization::calc_h
//       Access: Private
//  Description: Applies the specified checksum algorithm to the data,
//               according to RFC 2617.
////////////////////////////////////////////////////////////////////
string HTTPDigestAuthorization::
calc_h(const string &data) const {
  switch (_algorithm) {
  case A_unknown:
  case A_md5:
  case A_md5_sess:
    return calc_md5(data);
  }

  return string();
}

////////////////////////////////////////////////////////////////////
//     Function: HTTPDigestAuthorization::calc_kd
//       Access: Private
//  Description: Applies the specified digest algorithm to the
//               indicated data with the indicated secret, according
//               to RFC 2617.
////////////////////////////////////////////////////////////////////
string HTTPDigestAuthorization::
calc_kd(const string &secret, const string &data) const {
  switch (_algorithm) {
  case A_unknown:
  case A_md5:
  case A_md5_sess:
    return calc_h(secret + ":" + data);
  }

  return string();
}

////////////////////////////////////////////////////////////////////
//     Function: HTTPDigestAuthorization::get_a1
//       Access: Private
//  Description: Returns the A1 value, as defined by RFC 2617.
////////////////////////////////////////////////////////////////////
string HTTPDigestAuthorization::
get_a1(const string &username, const string &password) {
  switch (_algorithm) {
  case A_unknown:
  case A_md5:
    return username + ":" + get_realm() + ":" + password;

  case A_md5_sess:
    if (_a1.empty()) {
      _a1 = calc_h(username + ":" + get_realm() + ":" + password) +
        ":" + _nonce + ":" + _cnonce;
    }
    return _a1;
  }

  return string();
}

////////////////////////////////////////////////////////////////////
//     Function: HTTPDigestAuthorization::get_a2
//       Access: Private
//  Description: Returns the A2 value, as defined by RFC 2617.
////////////////////////////////////////////////////////////////////
string HTTPDigestAuthorization::
get_a2(HTTPEnum::Method method, const string &request_path,
       const string &body) {
  ostringstream strm;

  if ((_qop & Q_auth_int) != 0 && !body.empty()) {
    _chosen_qop = Q_auth_int;
    strm << method << ":" << request_path << ":" << calc_h(body);

  } else {
    _chosen_qop = Q_auth;
    strm << method << ":" << request_path;
  }

  return strm.str();
}

////////////////////////////////////////////////////////////////////
//     Function: HTTPDigestAuthorization::get_hex_nonce_count
//       Access: Private
//  Description: Returns the current nonce count (the number of times
//               we have used the server's nonce value, including this
//               time) as an eight-digit hexadecimal value.
////////////////////////////////////////////////////////////////////
string HTTPDigestAuthorization::
get_hex_nonce_count() const {
  ostringstream strm;
  strm << hex << setfill('0') << setw(8) << _nonce_count;
  return strm.str();
}

////////////////////////////////////////////////////////////////////
//     Function: HTTPDigestAuthorization::calc_md5
//       Access: Private, Static
//  Description: Computes the MD5 of the indicated source string and
//               returns it as a hexadecimal string of 32 ASCII
//               characters.
////////////////////////////////////////////////////////////////////
string HTTPDigestAuthorization::
calc_md5(const string &source) {
  unsigned char binary[MD5_DIGEST_LENGTH];

  MD5((const unsigned char *)source.data(), source.length(), binary);

  string result;
  result.reserve(MD5_DIGEST_LENGTH * 2);

  for (int i = 0; i < MD5_DIGEST_LENGTH; i++) {
    result += hexdigit((binary[i] >> 4) & 0xf);
    result += hexdigit(binary[i] & 0xf);
  }

  return result;
}

ostream &
operator << (ostream &out, HTTPDigestAuthorization::Algorithm algorithm) {
  switch (algorithm) {
  case HTTPDigestAuthorization::A_md5:
    out << "MD5";
    break;
  case HTTPDigestAuthorization::A_md5_sess:
    out << "MD5-sess";
    break;
  case HTTPDigestAuthorization::A_unknown:
    out << "unknown";
    break;
  }

  return out;
}

ostream &
operator << (ostream &out, HTTPDigestAuthorization::Qop qop) {
  switch (qop) {
  case HTTPDigestAuthorization::Q_auth:
    out << "auth";
    break;
  case HTTPDigestAuthorization::Q_auth_int:
    out << "auth-int";
    break;
  case HTTPDigestAuthorization::Q_unused:
    out << "unused";
    break;
  }

  return out;
}

#endif  // HAVE_OPENSSL
