/**********************************************************************
 * Project:  CPL - Common Portability Library
 * Purpose:  Microsoft Azure Storage Blob routines
 * Author:   Even Rouault <even.rouault at spatialys.com>
 *
 **********************************************************************
 * Copyright (c) 2017, Even Rouault <even.rouault at spatialys.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINAzureBlob IN THE SOFTWARE.
 ****************************************************************************/

#include "cpl_azure.h"
#include "cpl_vsi_error.h"
#include "cpl_sha256.h"
#include "cpl_time.h"
#include "cpl_http.h"
#include "cpl_multiproc.h"

#include <mutex>

//! @cond Doxygen_Suppress

CPL_CVSID("$Id: cpl_azure.cpp 6888f976933c444a7a12d7d65bfbeeaaa696d7b5 2021-01-27 22:43:42 +0100 Even Rouault $")

#ifdef HAVE_CURL

/************************************************************************/
/*                            GetSignature()                            */
/************************************************************************/

static CPLString GetSignature(const CPLString& osStringToSign,
                              const CPLString& osStorageKeyB64 )
{

/* -------------------------------------------------------------------- */
/*      Compute signature.                                              */
/* -------------------------------------------------------------------- */

    CPLString osStorageKeyUnbase64(osStorageKeyB64);
    int nB64Length =
        CPLBase64DecodeInPlace(reinterpret_cast<GByte*>(&osStorageKeyUnbase64[0]));
    osStorageKeyUnbase64.resize(nB64Length);
#ifdef DEBUG_VERBOSE
    CPLDebug("AZURE", "signing key size: %d", nB64Length);
#endif

    GByte abySignature[CPL_SHA256_HASH_SIZE] = {};
    CPL_HMAC_SHA256( osStorageKeyUnbase64, nB64Length,
                     osStringToSign, osStringToSign.size(),
                     abySignature);

    char* pszB64Signature = CPLBase64Encode(CPL_SHA256_HASH_SIZE, abySignature);
    CPLString osSignature(pszB64Signature);
    CPLFree(pszB64Signature);
    return osSignature;
}

/************************************************************************/
/*                          GetAzureBlobHeaders()                       */
/************************************************************************/

static
struct curl_slist* GetAzureBlobHeaders( const CPLString& osVerb,
                                        const struct curl_slist* psExistingHeaders,
                                        const CPLString& osResource,
                                        const std::map<CPLString, CPLString>& oMapQueryParameters,
                                        const CPLString& osStorageAccount,
                                        const CPLString& osStorageKeyB64 )
{
    /* See https://docs.microsoft.com/en-us/rest/api/storageservices/authentication-for-the-azure-storage-services */

    CPLString osDate = CPLGetConfigOption("CPL_AZURE_TIMESTAMP", "");
    if( osDate.empty() )
    {
        osDate = IVSIS3LikeHandleHelper::GetRFC822DateTime();
    }
    if( osStorageKeyB64.empty() )
    {
        struct curl_slist *headers=nullptr;
        headers = curl_slist_append(
            headers, CPLSPrintf("x-ms-date: %s", osDate.c_str()));
        return headers;
    }

    CPLString osMsVersion("2019-12-12");
    std::map<CPLString, CPLString> oSortedMapMSHeaders;
    oSortedMapMSHeaders["x-ms-version"] = osMsVersion;
    oSortedMapMSHeaders["x-ms-date"] = osDate;
    CPLString osCanonicalizedHeaders(
        IVSIS3LikeHandleHelper::BuildCanonicalizedHeaders(
                            oSortedMapMSHeaders,
                            psExistingHeaders,
                            "x-ms-"));

    CPLString osCanonicalizedResource;
    osCanonicalizedResource += "/" + osStorageAccount;
    osCanonicalizedResource += osResource;

    // We assume query parameters are in lower case and they are not repeated
    std::map<CPLString, CPLString>::const_iterator
        oIter = oMapQueryParameters.begin();
    for( ; oIter != oMapQueryParameters.end(); ++oIter )
    {
        osCanonicalizedResource += "\n";
        osCanonicalizedResource += oIter->first;
        osCanonicalizedResource += ":";
        osCanonicalizedResource += oIter->second;
    }

    CPLString osStringToSign;
    osStringToSign += osVerb + "\n";
    osStringToSign += CPLAWSGetHeaderVal(psExistingHeaders, "Content-Encoding") + "\n";
    osStringToSign += CPLAWSGetHeaderVal(psExistingHeaders, "Content-Language") + "\n";
    CPLString osContentLength(CPLAWSGetHeaderVal(psExistingHeaders, "Content-Length"));
    if( osContentLength == "0" )
        osContentLength.clear(); // since x-ms-version 2015-02-21
    osStringToSign += osContentLength + "\n";
    osStringToSign += CPLAWSGetHeaderVal(psExistingHeaders, "Content-MD5") + "\n";
    osStringToSign += CPLAWSGetHeaderVal(psExistingHeaders, "Content-Type") + "\n";
    osStringToSign += CPLAWSGetHeaderVal(psExistingHeaders, "Date") + "\n";
    osStringToSign += CPLAWSGetHeaderVal(psExistingHeaders, "If-Modified-Since") + "\n";
    osStringToSign += CPLAWSGetHeaderVal(psExistingHeaders, "If-Match") + "\n";
    osStringToSign += CPLAWSGetHeaderVal(psExistingHeaders, "If-None-Match") + "\n";
    osStringToSign += CPLAWSGetHeaderVal(psExistingHeaders, "If-Unmodified-Since") + "\n";
    osStringToSign += CPLAWSGetHeaderVal(psExistingHeaders, "Range") + "\n";
    osStringToSign += osCanonicalizedHeaders;
    osStringToSign += osCanonicalizedResource;

#ifdef DEBUG_VERBOSE
    CPLDebug("AZURE", "osStringToSign = %s", osStringToSign.c_str());
#endif

/* -------------------------------------------------------------------- */
/*      Compute signature.                                              */
/* -------------------------------------------------------------------- */

    CPLString osAuthorization("SharedKey " + osStorageAccount + ":" +
                              GetSignature(osStringToSign, osStorageKeyB64));

    struct curl_slist *headers=nullptr;
    headers = curl_slist_append(
        headers, CPLSPrintf("x-ms-date: %s", osDate.c_str()));
    headers = curl_slist_append(
        headers, CPLSPrintf("x-ms-version: %s", osMsVersion.c_str()));
    headers = curl_slist_append(
        headers, CPLSPrintf("Authorization: %s", osAuthorization.c_str()));
    return headers;
}

/************************************************************************/
/*                     VSIAzureBlobHandleHelper()                       */
/************************************************************************/
VSIAzureBlobHandleHelper::VSIAzureBlobHandleHelper(
                                            const CPLString& osEndpoint,
                                            const CPLString& osBucket,
                                            const CPLString& osObjectKey,
                                            const CPLString& osStorageAccount,
                                            const CPLString& osStorageKey,
                                            const CPLString& osSAS,
                                            bool bUseHTTPS,
                                            bool bFromManagedIdentities ) :
    m_osURL(BuildURL(osEndpoint, osStorageAccount,
            osBucket, osObjectKey, osSAS, bUseHTTPS)),
    m_osEndpoint(osEndpoint),
    m_osBucket(osBucket),
    m_osObjectKey(osObjectKey),
    m_osStorageAccount(osStorageAccount),
    m_osStorageKey(osStorageKey),
    m_osSAS(osSAS),
    m_bUseHTTPS(bUseHTTPS),
    m_bFromManagedIdendities(bFromManagedIdentities)
{
}

/************************************************************************/
/*                     ~VSIAzureBlobHandleHelper()                      */
/************************************************************************/

VSIAzureBlobHandleHelper::~VSIAzureBlobHandleHelper()
{
}


/************************************************************************/
/*                       AzureCSGetParameter()                          */
/************************************************************************/

static
CPLString AzureCSGetParameter(const CPLString& osStr, const char* pszKey,
                              bool bErrorIfMissing)
{
    CPLString osKey(pszKey + CPLString("="));
    size_t nPos = osStr.find(osKey);
    if( nPos == std::string::npos )
    {
        const char* pszMsg = CPLSPrintf(
            "%s missing in AZURE_STORAGE_CONNECTION_STRING", pszKey);
        if( bErrorIfMissing )
        {
            CPLDebug("AZURE", "%s", pszMsg);
            VSIError(VSIE_AWSInvalidCredentials, "%s", pszMsg);
        }
        return CPLString();
    }
    size_t nPos2 = osStr.find(";", nPos);
    return osStr.substr(
        nPos + osKey.size(),
        nPos2 == std::string::npos ? nPos2 : nPos2 - nPos - osKey.size());
}

/************************************************************************/
/*                          ParseSimpleJson()                           */
/*                                                                      */
/*      Return a string list of name/value pairs extracted from a       */
/*      JSON doc.  The EC2 IAM web service returns simple JSON          */
/*      responses.  The parsing as done currently is very fragile       */
/*      and depends on JSON documents being in a very very simple       */
/*      form.                                                           */
/************************************************************************/

static CPLStringList ParseSimpleJson(const char *pszJson)

{
/* -------------------------------------------------------------------- */
/*      We are expecting simple documents like the following with no    */
/*      hierarchy or complex structure.                                 */
/* -------------------------------------------------------------------- */
/*
    {
    "Code" : "Success",
    "LastUpdated" : "2017-07-03T16:20:17Z",
    "Type" : "AWS-HMAC",
    "AccessKeyId" : "bla",
    "SecretAccessKey" : "bla",
    "Token" : "bla",
    "Expiration" : "2017-07-03T22:42:58Z"
    }
*/

    CPLStringList oWords(
        CSLTokenizeString2(pszJson, " \n\t,:{}", CSLT_HONOURSTRINGS ));
    CPLStringList oNameValue;

    for( int i=0; i < oWords.size(); i += 2 )
    {
        oNameValue.SetNameValue(oWords[i], oWords[i+1]);
    }

    return oNameValue;
}

/************************************************************************/
/*                GetConfigurationFromManagedIdentities()               */
/************************************************************************/

std::mutex gMutex;
static CPLString gosAccessToken;
static GIntBig gnGlobalExpiration = 0;


bool VSIAzureBlobHandleHelper::GetConfigurationFromManagedIdentities(
                                                    CPLString& osAccessToken)
{
    std::lock_guard<std::mutex> guard(gMutex);
    time_t nCurTime;
    time(&nCurTime);
    // Try to reuse credentials if they are still valid, but
    // keep one minute of margin...
    if( !gosAccessToken.empty() && nCurTime < gnGlobalExpiration - 60 )
    {
        osAccessToken = gosAccessToken;
        return true;
    }

    // coverity[tainted_data]
    const CPLString osRootURL(
        CPLGetConfigOption("CPL_AZURE_VM_API_ROOT_URL", "http://169.254.169.254"));
    if( osRootURL == "disabled" )
        return false;

    // Fetch credentials
    CPLStringList oResponse;
    const char* const apszOptions[] = { "HEADERS=Metadata: true", nullptr };
    CPLHTTPResult* psResult = CPLHTTPFetch(
        (osRootURL + "/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https%3A%2F%2Fstorage.azure.com%2F").c_str(),
        apszOptions );
    if( psResult )
    {
        if( psResult->nStatus == 0 && psResult->pabyData != nullptr )
        {
            const CPLString osJSon =
                    reinterpret_cast<char*>(psResult->pabyData);
            oResponse = ParseSimpleJson(osJSon);
            if( oResponse.FetchNameValue("error") )
            {
                CPLDebug("AZURE", "Cannot retrieve managed identities credentials: %s",
                         osJSon.c_str());
            }
        }
        CPLHTTPDestroyResult(psResult);
    }
    osAccessToken = oResponse.FetchNameValueDef("access_token", "");
    const GIntBig nExpiresOn = CPLAtoGIntBig(oResponse.FetchNameValueDef("expires_on", ""));
    if( !osAccessToken.empty() && nExpiresOn > 0 )
    {
        gosAccessToken = osAccessToken;
        gnGlobalExpiration = nExpiresOn;
        CPLDebug("AZURE", "Storing credentials until " CPL_FRMT_GIB,
                 gnGlobalExpiration);
    }

    return !osAccessToken.empty();
}

/************************************************************************/
/*                             ClearCache()                             */
/************************************************************************/

void VSIAzureBlobHandleHelper::ClearCache()
{
    std::lock_guard<std::mutex> guard(gMutex);
    gosAccessToken.clear();
    gnGlobalExpiration = 0;
}

/************************************************************************/
/*                        GetConfiguration()                            */
/************************************************************************/

bool VSIAzureBlobHandleHelper::GetConfiguration(CSLConstList papszOptions,
                                                Service eService,
                                                bool& bUseHTTPS,
                                                CPLString& osEndpoint,
                                                CPLString& osStorageAccount,
                                                CPLString& osStorageKey,
                                                CPLString& osSAS,
                                                bool& bFromManagedIdentities)
{
    bFromManagedIdentities = false;

    const CPLString osServicePrefix ( eService == Service::BLOB ? "blob" : "dfs" );
    bUseHTTPS = CPLTestBool(CPLGetConfigOption("CPL_AZURE_USE_HTTPS", "YES"));
    osEndpoint =
        CPLGetConfigOption("CPL_AZURE_ENDPOINT",
                                (osServicePrefix + ".core.windows.net").c_str());

    const CPLString osStorageConnectionString(
        CSLFetchNameValueDef(papszOptions, "AZURE_STORAGE_CONNECTION_STRING",
        CPLGetConfigOption("AZURE_STORAGE_CONNECTION_STRING", "")));
    if( !osStorageConnectionString.empty() )
    {
        osStorageAccount = AzureCSGetParameter(osStorageConnectionString,
                                               "AccountName", true);
        osStorageKey = AzureCSGetParameter(osStorageConnectionString,
                                               "AccountKey", true);
        if( osStorageAccount.empty() || osStorageKey.empty() )
            return false;

        CPLString osProtocol(AzureCSGetParameter(
            osStorageConnectionString, "DefaultEndpointsProtocol", false));
        bUseHTTPS = (osProtocol != "http");

        CPLString osBlobEndpoint = AzureCSGetParameter(
            osStorageConnectionString, "BlobEndpoint", false);
        if( !osBlobEndpoint.empty() )
        {
            osEndpoint = osBlobEndpoint;
        }
        else
        {
            CPLString osEndpointSuffix(AzureCSGetParameter(
                osStorageConnectionString, "EndpointSuffix", false));
            if( STARTS_WITH(osEndpointSuffix, "127.0.0.1") )
                osEndpoint = osEndpointSuffix;
            else if( !osEndpointSuffix.empty() )
                osEndpoint = osServicePrefix + "." + osEndpointSuffix;
        }

        return true;
    }
    else
    {
        osStorageAccount = CSLFetchNameValueDef(papszOptions,
            "AZURE_STORAGE_ACCOUNT",
            CPLGetConfigOption("AZURE_STORAGE_ACCOUNT", ""));
        if( !osStorageAccount.empty() )
        {
            osStorageKey = CSLFetchNameValueDef(papszOptions,
                "AZURE_STORAGE_ACCESS_KEY",
                CPLGetConfigOption("AZURE_STORAGE_ACCESS_KEY", ""));
            if( osStorageKey.empty() )
            {
                osSAS = CPLGetConfigOption("AZURE_SAS", "");
                if( osSAS.empty() )
                {
                    if( CPLTestBool(CPLGetConfigOption("AZURE_NO_SIGN_REQUEST", "NO")) )
                    {
                        return true;
                    }

                    CPLString osAccessToken;
                    if( GetConfigurationFromManagedIdentities(osAccessToken) )
                    {
                        bFromManagedIdentities = true;
                        return true;
                    }

                    const char* pszMsg =
                        "AZURE_STORAGE_ACCESS_KEY or AZURE_SAS or AZURE_NO_SIGN_REQUEST configuration option "
                        "not defined";
                    CPLDebug("AZURE", "%s", pszMsg);
                    VSIError(VSIE_AWSInvalidCredentials, "%s", pszMsg);
                    return false;
                }
            }
            return true;
        }
    }

    const char* pszMsg = "Missing AZURE_STORAGE_ACCOUNT+"
                         "(AZURE_STORAGE_ACCESS_KEY or AZURE_SAS or AZURE_NO_SIGN_REQUEST) or "
                         "AZURE_STORAGE_CONNECTION_STRING "
                         "configuration options";
    CPLDebug("AZURE", "%s", pszMsg);
    VSIError(VSIE_AWSInvalidCredentials, "%s", pszMsg);
    return false;
}


/************************************************************************/
/*                          BuildFromURI()                              */
/************************************************************************/

VSIAzureBlobHandleHelper* VSIAzureBlobHandleHelper::BuildFromURI( const char* pszURI,
                                                    const char* pszFSPrefix,
                                                    CSLConstList papszOptions )
{
    if( strcmp(pszFSPrefix, "/vsiaz/") != 0 &&
        strcmp(pszFSPrefix, "/vsiaz_streaming/") != 0 &&
        strcmp(pszFSPrefix, "/vsiadls/") != 0 )
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Unsupported FS prefix");
        return nullptr;
    }

    const auto eService = strcmp(pszFSPrefix, "/vsiaz/") == 0 ||
                          strcmp(pszFSPrefix, "/vsiaz_streaming/") == 0 ?
                                                Service::BLOB : Service::ADLS;

    bool bUseHTTPS = true;
    CPLString osStorageAccount;
    CPLString osStorageKey;
    CPLString osEndpoint;
    CPLString osSAS;
    bool bFromManagedIdentities = false;

    if( !GetConfiguration(papszOptions, eService,
                    bUseHTTPS, osEndpoint,
                    osStorageAccount, osStorageKey, osSAS,
                    bFromManagedIdentities) )
    {
        return nullptr;
    }

    // pszURI == bucket/object
    const CPLString osBucketObject( pszURI );
    CPLString osBucket(osBucketObject);
    CPLString osObjectKey;
    size_t nSlashPos = osBucketObject.find('/');
    if( nSlashPos != std::string::npos )
    {
        osBucket = osBucketObject.substr(0, nSlashPos);
        osObjectKey = osBucketObject.substr(nSlashPos+1);
    }

    return new VSIAzureBlobHandleHelper( osEndpoint,
                                  osBucket,
                                  osObjectKey,
                                  osStorageAccount,
                                  osStorageKey,
                                  osSAS,
                                  bUseHTTPS,
                                  bFromManagedIdentities );
}

/************************************************************************/
/*                            BuildURL()                                */
/************************************************************************/

CPLString VSIAzureBlobHandleHelper::BuildURL(const CPLString& osEndpoint,
                                             const CPLString& osStorageAccount,
                                             const CPLString& osBucket,
                                             const CPLString& osObjectKey,
                                             const CPLString& osSAS,
                                             bool bUseHTTPS)
{
    CPLString osURL = (bUseHTTPS) ? "https://" : "http://";
    if( STARTS_WITH(osEndpoint, "127.0.0.1") )
    {
        osURL += osEndpoint + "/azure/blob/" + osStorageAccount;
    }
    else
    {
        osURL += osStorageAccount + "." + osEndpoint;
    }
    osURL += "/";
    osURL += CPLAWSURLEncode(osBucket,false);
    if( !osObjectKey.empty() )
        osURL += "/" + CPLAWSURLEncode(osObjectKey,false);
    if( !osSAS.empty() )
        osURL += '?' + osSAS;
    return osURL;
}


/************************************************************************/
/*                           RebuildURL()                               */
/************************************************************************/

void VSIAzureBlobHandleHelper::RebuildURL()
{
    m_osURL = BuildURL(m_osEndpoint, m_osStorageAccount,
                       m_osBucket, m_osObjectKey, CPLString(), m_bUseHTTPS);
    m_osURL += GetQueryString(false);
    if( !m_osSAS.empty() )
        m_osURL += (m_oMapQueryParameters.empty() ? '?' : '&') + m_osSAS;
}

/************************************************************************/
/*                        GetSASQueryString()                           */
/************************************************************************/

std::string VSIAzureBlobHandleHelper::GetSASQueryString() const
{
    if( !m_osSAS.empty() )
        return '?' + m_osSAS;
    return std::string();
}

/************************************************************************/
/*                           GetCurlHeaders()                           */
/************************************************************************/

struct curl_slist *
VSIAzureBlobHandleHelper::GetCurlHeaders( const CPLString& osVerb,
                                          const struct curl_slist* psExistingHeaders,
                                          const void *,
                                          size_t ) const
{
    if( m_bFromManagedIdendities )
    {
        CPLString osAccessToken;
        if( !GetConfigurationFromManagedIdentities(osAccessToken) )
            return nullptr;

        struct curl_slist *headers=nullptr;
        headers = curl_slist_append(
            headers, CPLSPrintf("Authorization: Bearer %s", osAccessToken.c_str()));
        headers = curl_slist_append(headers, "x-ms-version: 2019-12-12");
        return headers;
    }

    CPLString osResource("/" + m_osBucket);
    if( !m_osObjectKey.empty() )
        osResource += "/" + CPLAWSURLEncode(m_osObjectKey,false);

    return GetAzureBlobHeaders( osVerb,
                                psExistingHeaders,
                                osResource,
                                m_oMapQueryParameters,
                                m_osStorageAccount,
                                m_osStorageKey );
}

/************************************************************************/
/*                           GetSignedURL()                             */
/************************************************************************/

CPLString VSIAzureBlobHandleHelper::GetSignedURL(CSLConstList papszOptions)
{
    if( m_osStorageKey.empty() )
        return m_osURL;

    CPLString osStartDate(CPLGetAWS_SIGN4_Timestamp());
    const char* pszStartDate = CSLFetchNameValue(papszOptions, "START_DATE");
    if( pszStartDate )
        osStartDate = pszStartDate;
    int nYear, nMonth, nDay, nHour = 0, nMin = 0, nSec = 0;
    if( sscanf(osStartDate, "%04d%02d%02dT%02d%02d%02dZ",
                &nYear, &nMonth, &nDay, &nHour, &nMin, &nSec) < 3 )
    {
        return CPLString();
    }
    osStartDate = CPLSPrintf("%04d-%02d-%02dT%02d:%02d:%02dZ",
                                nYear, nMonth, nDay, nHour, nMin, nSec);

    struct tm brokendowntime;
    brokendowntime.tm_year = nYear - 1900;
    brokendowntime.tm_mon = nMonth - 1;
    brokendowntime.tm_mday = nDay;
    brokendowntime.tm_hour = nHour;
    brokendowntime.tm_min = nMin;
    brokendowntime.tm_sec = nSec;
    GIntBig nStartDate = CPLYMDHMSToUnixTime(&brokendowntime);
    GIntBig nEndDate = nStartDate + atoi(
        CSLFetchNameValueDef(papszOptions, "EXPIRATION_DELAY", "3600"));
    CPLUnixTimeToYMDHMS(nEndDate, &brokendowntime);
    nYear = brokendowntime.tm_year + 1900;
    nMonth = brokendowntime.tm_mon + 1;
    nDay = brokendowntime.tm_mday;
    nHour = brokendowntime.tm_hour;
    nMin = brokendowntime.tm_min;
    nSec = brokendowntime.tm_sec;
    CPLString osEndDate = CPLSPrintf("%04d-%02d-%02dT%02d:%02d:%02dZ",
                                nYear, nMonth, nDay, nHour, nMin, nSec);

    CPLString osVerb(CSLFetchNameValueDef(papszOptions, "VERB", "GET"));
    CPLString osSignedPermissions(CSLFetchNameValueDef(papszOptions,
        "SIGNEDPERMISSIONS", 
        (EQUAL(osVerb, "GET") || EQUAL(osVerb, "HEAD")) ? "r" : "w"  ));

    CPLString osSignedIdentifier(CSLFetchNameValueDef(papszOptions,
                                                     "SIGNEDIDENTIFIER", ""));

    CPLString osStringToSign;
    osStringToSign += osSignedPermissions + "\n";
    osStringToSign += osStartDate + "\n";
    osStringToSign += osEndDate + "\n";
    osStringToSign += "/" + m_osStorageAccount + "/" + m_osBucket + "\n";
    osStringToSign += osSignedIdentifier + "\n";
    osStringToSign += "2012-02-12";

#ifdef DEBUG_VERBOSE
    CPLDebug("AZURE", "osStringToSign = %s", osStringToSign.c_str());
#endif

/* -------------------------------------------------------------------- */
/*      Compute signature.                                              */
/* -------------------------------------------------------------------- */
    CPLString osSignature(GetSignature(osStringToSign, m_osStorageKey));

    ResetQueryParameters();
    AddQueryParameter("sv", "2012-02-12");
    AddQueryParameter("st", osStartDate);
    AddQueryParameter("se", osEndDate);
    AddQueryParameter("sr", "c");
    AddQueryParameter("sp", osSignedPermissions);
    AddQueryParameter("sig", osSignature);
    if( !osSignedIdentifier.empty() )
        AddQueryParameter("si", osSignedIdentifier);
    return m_osURL;
}


#endif // HAVE_CURL

//! @endcond
