/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * 
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "nsLDAPURL.h"
#include "netCore.h"
#include "plstr.h"
#include "nsCOMPtr.h"
#include "nsNetCID.h"
#include "nsComponentManagerUtils.h"
#include "nsIStandardURL.h"
#include "nsMsgUtils.h"

// The two schemes we support, LDAP and LDAPS
//
NS_NAMED_LITERAL_CSTRING(LDAP_SCHEME, "ldap");
NS_NAMED_LITERAL_CSTRING(LDAP_SSL_SCHEME, "ldaps");

NS_IMPL_ISUPPORTS(nsLDAPURL, nsILDAPURL, nsIURI)

nsLDAPURL::nsLDAPURL()
    : mScope(SCOPE_BASE),
      mOptions(0)
{
}

nsLDAPURL::~nsLDAPURL()
{
}

nsresult
nsLDAPURL::Init(uint32_t aUrlType, int32_t aDefaultPort,
                const nsACString &aSpec, const char* aOriginCharset,
                nsIURI *aBaseURI)
{
  if (!mBaseURL)
  {
    mBaseURL = do_CreateInstance(NS_STANDARDURL_CONTRACTID);
    if (!mBaseURL)
      return NS_ERROR_OUT_OF_MEMORY;
  }

  nsresult rv;
  nsCOMPtr<nsIStandardURL> standardURL(do_QueryInterface(mBaseURL, &rv));
  NS_ENSURE_SUCCESS(rv, rv);

  rv = standardURL->Init(aUrlType, aDefaultPort, aSpec, aOriginCharset,
                         aBaseURI);
  NS_ENSURE_SUCCESS(rv, rv);

  // Now get the spec from the mBaseURL in case it was a relative one
  nsCString spec;
  rv = mBaseURL->GetSpec(spec);
  NS_ENSURE_SUCCESS(rv, rv);

  return SetSpec(spec);
}

void
nsLDAPURL::GetPathInternal(nsCString &aPath)
{
  aPath.Assign('/');

  if (!mDN.IsEmpty())
    aPath.Append(mDN);

  if (!mAttributes.IsEmpty())
    aPath.Append('?');

  // If mAttributes isn't empty, cut off the internally stored commas at start
  // and end, and append to the path.
  if (!mAttributes.IsEmpty())
    aPath.Append(Substring(mAttributes, 1, mAttributes.Length() - 2));

  if (mScope || !mFilter.IsEmpty())
  {
    aPath.Append((mAttributes.IsEmpty() ? "??" : "?"));
    if (mScope)
    {
      if (mScope == SCOPE_ONELEVEL)
        aPath.Append("one");
      else if (mScope == SCOPE_SUBTREE)
        aPath.Append("sub");
    }
    if (!mFilter.IsEmpty())
    {
      aPath.Append('?');
      aPath.Append(mFilter);
    }
  }
}

nsresult
nsLDAPURL::SetPathInternal(const nsCString &aPath)
{
  LDAPURLDesc *desc;

  // This is from the LDAP C-SDK, which currently doesn't
  // support everything from RFC 2255... :(
  //
  int err = ldap_url_parse(aPath.get(), &desc);
  switch (err) {
  case LDAP_SUCCESS: {
    // The base URL can pick up the host & port details and deal with them
    // better than we can
    mDN = desc->lud_dn;
    mScope = desc->lud_scope;
    mFilter = desc->lud_filter;
    mOptions = desc->lud_options;
    nsresult rv = SetAttributeArray(desc->lud_attrs);
    if (NS_FAILED(rv))
      return rv;

    ldap_free_urldesc(desc);
    return NS_OK;
  }

  case LDAP_URL_ERR_NOTLDAP:
  case LDAP_URL_ERR_NODN:
  case LDAP_URL_ERR_BADSCOPE:
    return NS_ERROR_MALFORMED_URI;

  case LDAP_URL_ERR_MEM:
    NS_ERROR("nsLDAPURL::SetSpec: out of memory ");
    return NS_ERROR_OUT_OF_MEMORY;

  case LDAP_URL_ERR_PARAM: 
    return NS_ERROR_INVALID_POINTER;
  }

  // This shouldn't happen...
  return NS_ERROR_UNEXPECTED;
}

// A string representation of the URI. Setting the spec 
// causes the new spec to be parsed, initializing the URI. Setting
// the spec (or any of the accessors) causes also any currently
// open streams on the URI's channel to be closed.

NS_IMETHODIMP 
nsLDAPURL::GetSpec(nsACString &_retval)
{
  if (!mBaseURL)
    return NS_ERROR_NOT_INITIALIZED;

  return mBaseURL->GetSpec(_retval);
}

NS_IMETHODIMP 
nsLDAPURL::SetSpec(const nsACString &aSpec)
{
  if (!mBaseURL)
    return NS_ERROR_NOT_INITIALIZED;

  // Cache the original spec in case we don't like what we've been passed and
  // need to reset ourselves.
  nsCString originalSpec;
  nsresult rv = mBaseURL->GetSpec(originalSpec);
  NS_ENSURE_SUCCESS(rv, rv);

  rv = mBaseURL->SetSpec(aSpec);
  NS_ENSURE_SUCCESS(rv, rv);

  rv = SetPathInternal(PromiseFlatCString(aSpec));
  if (NS_FAILED(rv))
    mBaseURL->SetSpec(originalSpec);

  return rv;
}

NS_IMETHODIMP nsLDAPURL::GetPrePath(nsACString &_retval)
{
  if (!mBaseURL)
    return NS_ERROR_NOT_INITIALIZED;

  return mBaseURL->GetPrePath(_retval);
}

NS_IMETHODIMP nsLDAPURL::GetScheme(nsACString &_retval)
{
  if (!mBaseURL)
    return NS_ERROR_NOT_INITIALIZED;

  return mBaseURL->GetScheme(_retval);
}

NS_IMETHODIMP nsLDAPURL::SetScheme(const nsACString &aScheme)
{
  if (!mBaseURL)
    return NS_ERROR_NOT_INITIALIZED;

  if (aScheme.Equals(LDAP_SCHEME, nsCaseInsensitiveCStringComparator()))
    mOptions &= !OPT_SECURE;
  else if (aScheme.Equals(LDAP_SSL_SCHEME,
                          nsCaseInsensitiveCStringComparator()))
    mOptions |= OPT_SECURE;
  else
    return NS_ERROR_MALFORMED_URI;

  return mBaseURL->SetScheme(aScheme);
}

NS_IMETHODIMP 
nsLDAPURL::GetUserPass(nsACString &_retval)
{
  _retval.Truncate();
  return NS_OK;
}

NS_IMETHODIMP
nsLDAPURL::SetUserPass(const nsACString &aUserPass)
{
  return NS_OK;
}

NS_IMETHODIMP
nsLDAPURL::GetUsername(nsACString &_retval)
{
  _retval.Truncate();
  return NS_OK;
}

NS_IMETHODIMP
nsLDAPURL::SetUsername(const nsACString &aUsername)
{
  return NS_OK;
}

NS_IMETHODIMP 
nsLDAPURL::GetPassword(nsACString &_retval)
{
  _retval.Truncate();
  return NS_OK;
}

NS_IMETHODIMP 
nsLDAPURL::SetPassword(const nsACString &aPassword)
{
  return NS_OK;
}

NS_IMETHODIMP 
nsLDAPURL::GetHostPort(nsACString &_retval)
{
  if (!mBaseURL)
    return NS_ERROR_NOT_INITIALIZED;

  return mBaseURL->GetHostPort(_retval);
}

NS_IMETHODIMP 
nsLDAPURL::SetHostPort(const nsACString &aHostPort)
{
  if (!mBaseURL)
    return NS_ERROR_NOT_INITIALIZED;

  return mBaseURL->SetHostPort(aHostPort);
}

NS_IMETHODIMP
nsLDAPURL::SetHostAndPort(const nsACString &aHostPort)
{
  if (!mBaseURL)
    return NS_ERROR_NOT_INITIALIZED;

  return mBaseURL->SetHostAndPort(aHostPort);
}

NS_IMETHODIMP 
nsLDAPURL::GetHost(nsACString &_retval)
{
  if (!mBaseURL)
    return NS_ERROR_NOT_INITIALIZED;

  return mBaseURL->GetHost(_retval);
}

NS_IMETHODIMP 
nsLDAPURL::SetHost(const nsACString &aHost)
{
  if (!mBaseURL)
    return NS_ERROR_NOT_INITIALIZED;

  return mBaseURL->SetHost(aHost);
}

NS_IMETHODIMP 
nsLDAPURL::GetPort(int32_t *_retval)
{
  if (!mBaseURL)
    return NS_ERROR_NOT_INITIALIZED;

  return mBaseURL->GetPort(_retval);
}

NS_IMETHODIMP 
nsLDAPURL::SetPort(int32_t aPort)
{
  if (!mBaseURL)
    return NS_ERROR_NOT_INITIALIZED;

  return mBaseURL->SetPort(aPort);
}

NS_IMETHODIMP nsLDAPURL::GetPath(nsACString &_retval)
{
  if (!mBaseURL)
    return NS_ERROR_NOT_INITIALIZED;

  return mBaseURL->GetPath(_retval);
}

NS_IMETHODIMP nsLDAPURL::SetPath(const nsACString &aPath)
{
  if (!mBaseURL)
    return NS_ERROR_NOT_INITIALIZED;

  nsresult rv = SetPathInternal(PromiseFlatCString(aPath));
  NS_ENSURE_SUCCESS(rv, rv);

  return mBaseURL->SetPath(aPath);
}

NS_IMETHODIMP nsLDAPURL::GetAsciiSpec(nsACString &_retval)
{
  if (!mBaseURL)
    return NS_ERROR_NOT_INITIALIZED;

  // XXX handle extra items?
  return mBaseURL->GetAsciiSpec(_retval);
}

NS_IMETHODIMP nsLDAPURL::GetAsciiHost(nsACString &_retval)
{
  if (!mBaseURL)
    return NS_ERROR_NOT_INITIALIZED;

  return mBaseURL->GetAsciiHost(_retval);
}

NS_IMETHODIMP
nsLDAPURL::GetAsciiHostPort(nsACString &_retval)
{
  if (!mBaseURL)
    return NS_ERROR_NOT_INITIALIZED;

  return mBaseURL->GetAsciiHostPort(_retval);
}

NS_IMETHODIMP nsLDAPURL::GetOriginCharset(nsACString &result)
{
  if (!mBaseURL)
    return NS_ERROR_NOT_INITIALIZED;

  return mBaseURL->GetOriginCharset(result);
}

// boolean equals (in nsIURI other)
// (based on nsSimpleURI::Equals)
NS_IMETHODIMP nsLDAPURL::Equals(nsIURI *other, bool *_retval)
{
  *_retval = false;
  if (other)
  {
    nsresult rv;
    nsCOMPtr<nsILDAPURL> otherURL(do_QueryInterface(other, &rv));
    if (NS_SUCCEEDED(rv))
    {
      nsAutoCString thisSpec, otherSpec;
      uint32_t otherOptions;

      rv = GetSpec(thisSpec);
      NS_ENSURE_SUCCESS(rv, rv);

      rv = otherURL->GetSpec(otherSpec);
      NS_ENSURE_SUCCESS(rv, rv);

      rv = otherURL->GetOptions(&otherOptions);
      NS_ENSURE_SUCCESS(rv, rv);

      if (thisSpec == otherSpec && mOptions == otherOptions)
        *_retval = true;
    }
  }
  return NS_OK;
}

// boolean schemeIs(in const char * scheme);
//
NS_IMETHODIMP nsLDAPURL::SchemeIs(const char *aScheme, bool *aEquals)
{
  if (!mBaseURL)
    return NS_ERROR_NOT_INITIALIZED;

  return mBaseURL->SchemeIs(aScheme, aEquals);
}

// nsIURI clone ();
//
nsresult
nsLDAPURL::CloneInternal(RefHandlingEnum aRefHandlingMode,
                         const nsACString& newRef, nsIURI** aResult)
{
  NS_ENSURE_ARG_POINTER(aResult);

  nsLDAPURL *clone = new nsLDAPURL();

  if (!clone)
    return NS_ERROR_OUT_OF_MEMORY;

  clone->mDN = mDN;
  clone->mScope = mScope;
  clone->mFilter = mFilter;
  clone->mOptions = mOptions;
  clone->mAttributes = mAttributes;

  nsresult rv;
  if (aRefHandlingMode == eHonorRef) {
    rv = mBaseURL->Clone(getter_AddRefs(clone->mBaseURL));
  } else if (aRefHandlingMode == eReplaceRef) {
    rv = mBaseURL->CloneWithNewRef(newRef, getter_AddRefs(clone->mBaseURL));
  } else {
    rv = mBaseURL->CloneIgnoringRef(getter_AddRefs(clone->mBaseURL));
  }
  NS_ENSURE_SUCCESS(rv, rv);

  NS_ADDREF(*aResult = clone);
  return NS_OK;
}

NS_IMETHODIMP
nsLDAPURL::Clone(nsIURI** result)
{
  return CloneInternal(eHonorRef, EmptyCString(), result);
}

NS_IMETHODIMP
nsLDAPURL::CloneIgnoringRef(nsIURI** result)
{
  return CloneInternal(eIgnoreRef, EmptyCString(), result);
}

NS_IMETHODIMP
nsLDAPURL::CloneWithNewRef(const nsACString& newRef, nsIURI** result)
{
  return CloneInternal(eReplaceRef, newRef, result);
}

// string resolve (in string relativePath);
//
NS_IMETHODIMP nsLDAPURL::Resolve(const nsACString &relativePath,
                                 nsACString &_retval)
{
  return NS_ERROR_NOT_IMPLEMENTED;
}

// The following attributes come from nsILDAPURL

// attribute AUTF8String dn;
//
NS_IMETHODIMP nsLDAPURL::GetDn(nsACString& _retval)
{
    _retval.Assign(mDN);
    return NS_OK;
}
NS_IMETHODIMP nsLDAPURL::SetDn(const nsACString& aDn)
{
  if (!mBaseURL)
    return NS_ERROR_NOT_INITIALIZED;

  mDN.Assign(aDn);

  // Now get the current path
  nsCString newPath;
  GetPathInternal(newPath);

  // and update the base url
  return mBaseURL->SetPath(newPath);
}

NS_IMETHODIMP nsLDAPURL::GetAttributes(nsACString &aAttributes)
{
  if (mAttributes.IsEmpty())
  {
    aAttributes.Truncate();
    return NS_OK;
  }

  NS_ASSERTION(mAttributes[0] == ',' &&
               mAttributes[mAttributes.Length() - 1] == ',',
               "mAttributes does not begin and end with a comma");

  // We store the string internally with comma before and after, so strip
  // them off here.
  aAttributes = Substring(mAttributes, 1, mAttributes.Length() - 2);
  return NS_OK;
}

NS_IMETHODIMP nsLDAPURL::SetAttributes(const nsACString &aAttributes)
{
  if (!mBaseURL)
    return NS_ERROR_NOT_INITIALIZED;

  if (aAttributes.IsEmpty())
    mAttributes.Truncate();
  else
  {
    // We need to make sure we start off the string with a comma.
    if (aAttributes[0] != ',')
      mAttributes = ',';

    mAttributes.Append(aAttributes);

    // Also end with a comma if appropriate.
    if (mAttributes[mAttributes.Length() - 1] != ',')
      mAttributes.Append(',');
  }

  // Now get the current path
  nsCString newPath;
  GetPathInternal(newPath);

  // and update the base url
  return mBaseURL->SetPath(newPath);
}

nsresult nsLDAPURL::SetAttributeArray(char** aAttributes)
{
  mAttributes.Truncate();

  while (aAttributes && *aAttributes)
  {
    // Always start with a comma as that's what we store internally.
    mAttributes.Append(',');
    mAttributes.Append(*aAttributes);
    ++aAttributes;
  }

  // Add a comma on the end if we have something.
  if (!mAttributes.IsEmpty())
    mAttributes.Append(',');

  return NS_OK;
}

NS_IMETHODIMP nsLDAPURL::AddAttribute(const nsACString &aAttribute)
{
  if (!mBaseURL)
    return NS_ERROR_NOT_INITIALIZED;

  if (mAttributes.IsEmpty())
  {
    mAttributes = ',';
    mAttributes.Append(aAttribute);
    mAttributes.Append(',');
  }
  else
  {
    // Wrap the attribute in commas, so that we can do an exact match.
    nsAutoCString findAttribute(",");
    findAttribute.Append(aAttribute);
    findAttribute.Append(',');

    // Check to see if the attribute is already stored. If it is, then also
    // check to see if it is the last attribute in the string, or if the next
    // character is a comma, this means we won't match substrings.
    int32_t pos = mAttributes.Find(findAttribute, CaseInsensitiveCompare);
    if (pos != -1)
      return NS_OK;

    mAttributes.Append(Substring(findAttribute, 1));
  }

  // Now get the current path
  nsCString newPath;
  GetPathInternal(newPath);

  // and update the base url
  return mBaseURL->SetPath(newPath);
}

NS_IMETHODIMP nsLDAPURL::RemoveAttribute(const nsACString &aAttribute)
{
  if (!mBaseURL)
    return NS_ERROR_NOT_INITIALIZED;

  if (mAttributes.IsEmpty())
    return NS_OK;

  nsAutoCString findAttribute(",");
  findAttribute.Append(aAttribute);
  findAttribute.Append(',');

  if (mAttributes.Equals(findAttribute, nsCaseInsensitiveCStringComparator()))
    mAttributes.Truncate();
  else
  {
    int32_t pos = mAttributes.Find(findAttribute, CaseInsensitiveCompare);
    if (pos == -1)
      return NS_OK;

    mAttributes.Cut(pos, findAttribute.Length() - 1);
  }

  // Now get the current path
  nsCString newPath;
  GetPathInternal(newPath);

  // and update the base url
  return mBaseURL->SetPath(newPath);
}

NS_IMETHODIMP nsLDAPURL::HasAttribute(const nsACString &aAttribute,
                                      bool *_retval)
{
  NS_ENSURE_ARG_POINTER(_retval);

  nsAutoCString findAttribute(",");
  findAttribute.Append(aAttribute);
  findAttribute.Append(',');

  *_retval = mAttributes.Find(findAttribute, CaseInsensitiveCompare) != -1;
  return NS_OK;
}

NS_IMETHODIMP nsLDAPURL::GetScope(int32_t *_retval)
{
  NS_ENSURE_ARG_POINTER(_retval);
  *_retval = mScope;
  return NS_OK;
}

NS_IMETHODIMP nsLDAPURL::SetScope(int32_t aScope)
{
  if (!mBaseURL)
    return NS_ERROR_NOT_INITIALIZED;

  // Only allow scopes supported by the C-SDK
  if ((aScope != SCOPE_BASE) && (aScope != SCOPE_ONELEVEL) &&
      (aScope != SCOPE_SUBTREE))
    return NS_ERROR_MALFORMED_URI;

  mScope = aScope;

  // Now get the current path
  nsCString newPath;
  GetPathInternal(newPath);

  // and update the base url
  return mBaseURL->SetPath(newPath);
}

NS_IMETHODIMP nsLDAPURL::GetFilter(nsACString& _retval)
{
    _retval.Assign(mFilter);
    return NS_OK;
}
NS_IMETHODIMP nsLDAPURL::SetFilter(const nsACString& aFilter)
{
  if (!mBaseURL)
    return NS_ERROR_NOT_INITIALIZED;

  mFilter.Assign(aFilter);

  if (mFilter.IsEmpty())
    mFilter.AssignLiteral("(objectclass=*)");

  // Now get the current path
  nsCString newPath;
  GetPathInternal(newPath);

  // and update the base url
  return mBaseURL->SetPath(newPath);
}

NS_IMETHODIMP nsLDAPURL::GetOptions(uint32_t *_retval)
{
  NS_ENSURE_ARG_POINTER(_retval);
  *_retval = mOptions;
  return NS_OK;
}

NS_IMETHODIMP nsLDAPURL::SetOptions(uint32_t aOptions)
{
  // Secure is the only option supported at the moment
  if ((mOptions & OPT_SECURE) == (aOptions & OPT_SECURE))
    return NS_OK;

  mOptions = aOptions;

  if ((aOptions & OPT_SECURE) == OPT_SECURE)
    return SetScheme(LDAP_SSL_SCHEME);

  return SetScheme(LDAP_SCHEME);
}

NS_IMETHODIMP nsLDAPURL::SetRef(const nsACString &aRef)
{
  return mBaseURL->SetRef(aRef);
}

NS_IMETHODIMP
nsLDAPURL::GetRef(nsACString &result)
{
  return mBaseURL->GetRef(result);
}

NS_IMETHODIMP nsLDAPURL::EqualsExceptRef(nsIURI *other, bool *result)
{
  return mBaseURL->EqualsExceptRef(other, result);
}

NS_IMETHODIMP
nsLDAPURL::GetSpecIgnoringRef(nsACString &result)
{
  return mBaseURL->GetSpecIgnoringRef(result);
}

NS_IMETHODIMP
nsLDAPURL::GetHasRef(bool *result)
{
  return mBaseURL->GetHasRef(result);
}


NS_IMETHODIMP
nsLDAPURL::GetFilePath(nsACString &aFilePath)
{
  return mBaseURL->GetFilePath(aFilePath);
}

NS_IMETHODIMP
nsLDAPURL::SetFilePath(const nsACString &aFilePath)
{
  return mBaseURL->SetFilePath(aFilePath);
}

NS_IMETHODIMP
nsLDAPURL::GetQuery(nsACString &aQuery)
{
  return mBaseURL->GetQuery(aQuery);
}

NS_IMETHODIMP
nsLDAPURL::SetQuery(const nsACString &aQuery)
{
  return mBaseURL->SetQuery(aQuery);
}