root/src/kits/network/libnetservices/HttpTime.cpp
/*
 * Copyright 2010-2016 Haiku Inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *              Christophe Huriaux, c.huriaux@gmail.com
 *              Adrien Destugues, pulkomandy@gmail.com
 */

#include <HttpTime.h>

#include <new>

#include <cstdio>
#include <locale.h>

using namespace BPrivate::Network;


// The formats used should be, in order of preference (according to RFC2616,
// section 3.3):
// RFC1123 / RFC822: "Sun, 06 Nov 1994 08:49:37 GMT"
// RFC1036 / RFC850: "Sunday, 06-Nov-94 08:49:37 GMT"
// asctime         : "Sun Nov  6 08:49:37 1994"
//
// RFC1123 is the preferred one because it has 4 digit years.
//
// But of course in real life, all possible mixes of the formats are used.
// Believe it or not, it's even possible to find some website that gets this
// right and use one of the 3 formats above.
// Often seen variants are:
// - RFC1036 but with 4 digit year,
// - Missing or different timezone indicator
// - Invalid weekday
static const char* kDateFormats[] = {
        // RFC1123
        "%a, %d %b %Y %H:%M:%S",     // without timezone
        "%a, %d %b %Y %H:%M:%S GMT", // canonical

        // RFC1036
        "%A, %d-%b-%y %H:%M:%S",     // without timezone
        "%A, %d-%b-%y %H:%M:%S GMT", // canonical

        // RFC1036 with 4 digit year
        "%a, %d-%b-%Y %H:%M:%S",     // without timezone
        "%a, %d-%b-%Y %H:%M:%S GMT", // with 4-digit year
        "%a, %d-%b-%Y %H:%M:%S UTC", // "UTC" timezone

        // asctime
        "%a %d %b %H:%M:%S %Y"
};


static locale_t posix = newlocale(LC_ALL_MASK, "POSIX", (locale_t)0);


BHttpTime::BHttpTime()
        :
        fDate(0),
        fDateFormat(B_HTTP_TIME_FORMAT_PREFERRED)
{
}


BHttpTime::BHttpTime(BDateTime date)
        :
        fDate(date),
        fDateFormat(B_HTTP_TIME_FORMAT_PREFERRED)
{
}


BHttpTime::BHttpTime(const BString& dateString)
        :
        fDateString(dateString),
        fDate(0),
        fDateFormat(B_HTTP_TIME_FORMAT_PREFERRED)
{
}


// #pragma mark Date modification


void
BHttpTime::SetString(const BString& string)
{
        fDateString = string;
}


void
BHttpTime::SetDate(BDateTime date)
{
        fDate = date;
}


// #pragma mark Date conversion


BDateTime
BHttpTime::Parse()
{
        struct tm expireTime;

        if (fDateString.Length() < 4)
                return 0;

        memset(&expireTime, 0, sizeof(struct tm));

        // Save the current locale, switch to POSIX for strptime to match strings
        // in English, switch back when we're done.
        locale_t current = uselocale(posix);

        fDateFormat = B_HTTP_TIME_FORMAT_PARSED;
        unsigned int i;
        for (i = 0; i < sizeof(kDateFormats) / sizeof(const char*);
                i++) {
                const char* result = strptime(fDateString.String(), kDateFormats[i],
                        &expireTime);

                // We need to parse the complete value for the "Expires" key.
                // Otherwise, we consider this to be a session cookie (or try another
                // one of the date formats).
                if (result == fDateString.String() + fDateString.Length()) {
                        fDateFormat = i;
                        break;
                }
        }

        uselocale(current);

        // Did we identify some valid format?
        if (fDateFormat == B_HTTP_TIME_FORMAT_PARSED)
                return 0;

        // Now convert the struct tm from strptime into a BDateTime.
        BTime time(expireTime.tm_hour, expireTime.tm_min, expireTime.tm_sec);
        BDate date(expireTime.tm_year + 1900, expireTime.tm_mon + 1,
                expireTime.tm_mday);
        BDateTime dateTime(date, time);
        return dateTime;
}


BString
BHttpTime::ToString(int8 format)
{
        BString expirationFinal;
        struct tm expirationTm;
        expirationTm.tm_sec = fDate.Time().Second();
        expirationTm.tm_min = fDate.Time().Minute();
        expirationTm.tm_hour = fDate.Time().Hour();
        expirationTm.tm_mday = fDate.Date().Day();
        expirationTm.tm_mon = fDate.Date().Month() - 1;
        expirationTm.tm_year = fDate.Date().Year() - 1900;
        // strftime starts weekday count at 0 for Sunday,
        // while DayOfWeek starts at 1 for Monday and thus uses 7 for Sunday
        expirationTm.tm_wday = fDate.Date().DayOfWeek() % 7;
        expirationTm.tm_yday = 0;
        expirationTm.tm_isdst = 0;

        if (format == B_HTTP_TIME_FORMAT_PARSED)
                format = fDateFormat;

        if (format != B_HTTP_TIME_FORMAT_PARSED) {
                static const uint16 kTimetToStringMaxLength = 128;
                char expirationString[kTimetToStringMaxLength + 1];
                size_t strLength;

                strLength = strftime(expirationString, kTimetToStringMaxLength,
                        kDateFormats[format], &expirationTm);

                expirationFinal.SetTo(expirationString, strLength);
        }
        return expirationFinal;
}