FTPDirectoryDocument.cpp [plain text]
#include "config.h"
#include "FTPDirectoryDocument.h"
#if ENABLE(FTPDIR)
#include "HTMLAnchorElement.h"
#include "HTMLBodyElement.h"
#include "HTMLDocumentParser.h"
#include "HTMLTableCellElement.h"
#include "HTMLTableElement.h"
#include "LocalizedStrings.h"
#include "Logging.h"
#include "FTPDirectoryParser.h"
#include "Settings.h"
#include "SharedBuffer.h"
#include "Text.h"
#include <wtf/GregorianDateTime.h>
#include <wtf/IsoMallocInlines.h>
#include <wtf/StdLibExtras.h>
#include <wtf/text/StringConcatenateNumbers.h>
#include <wtf/unicode/CharacterNames.h>
namespace WebCore {
WTF_MAKE_ISO_ALLOCATED_IMPL(FTPDirectoryDocument);
using namespace HTMLNames;
class FTPDirectoryDocumentParser final : public HTMLDocumentParser {
public:
static Ref<FTPDirectoryDocumentParser> create(HTMLDocument& document)
{
return adoptRef(*new FTPDirectoryDocumentParser(document));
}
private:
void append(RefPtr<StringImpl>&&) override;
void finish() override;
bool isWaitingForScripts() const override { return false; }
void checkBuffer(int len = 10)
{
if ((m_dest - m_buffer) > m_size - len) {
int newSize = std::max(m_size * 2, m_size + len);
int oldOffset = m_dest - m_buffer;
m_buffer = static_cast<UChar*>(fastRealloc(m_buffer, newSize * sizeof(UChar)));
m_dest = m_buffer + oldOffset;
m_size = newSize;
}
}
FTPDirectoryDocumentParser(HTMLDocument&);
bool loadDocumentTemplate();
void createBasicDocument();
void parseAndAppendOneLine(const String&);
void appendEntry(const String& name, const String& size, const String& date, bool isDirectory);
Ref<Element> createTDForFilename(const String&);
RefPtr<HTMLTableElement> m_tableElement;
bool m_skipLF { false };
int m_size { 254 };
UChar* m_buffer;
UChar* m_dest;
String m_carryOver;
ListState m_listState;
};
FTPDirectoryDocumentParser::FTPDirectoryDocumentParser(HTMLDocument& document)
: HTMLDocumentParser(document)
, m_buffer(static_cast<UChar*>(fastMalloc(sizeof(UChar) * m_size)))
, m_dest(m_buffer)
{
}
void FTPDirectoryDocumentParser::appendEntry(const String& filename, const String& size, const String& date, bool isDirectory)
{
auto& document = *this->document();
auto rowElement = m_tableElement->insertRow(-1).releaseReturnValue();
rowElement->setAttributeWithoutSynchronization(HTMLNames::classAttr, AtomString("ftpDirectoryEntryRow", AtomString::ConstructFromLiteral));
auto typeElement = HTMLTableCellElement::create(tdTag, document);
typeElement->appendChild(Text::create(document, String(&noBreakSpace, 1)));
if (isDirectory)
typeElement->setAttributeWithoutSynchronization(HTMLNames::classAttr, AtomString("ftpDirectoryIcon ftpDirectoryTypeDirectory", AtomString::ConstructFromLiteral));
else
typeElement->setAttributeWithoutSynchronization(HTMLNames::classAttr, AtomString("ftpDirectoryIcon ftpDirectoryTypeFile", AtomString::ConstructFromLiteral));
rowElement->appendChild(typeElement);
auto nameElement = createTDForFilename(filename);
nameElement->setAttributeWithoutSynchronization(HTMLNames::classAttr, AtomString("ftpDirectoryFileName", AtomString::ConstructFromLiteral));
rowElement->appendChild(nameElement);
auto dateElement = HTMLTableCellElement::create(tdTag, document);
dateElement->appendChild(Text::create(document, date));
dateElement->setAttributeWithoutSynchronization(HTMLNames::classAttr, AtomString("ftpDirectoryFileDate", AtomString::ConstructFromLiteral));
rowElement->appendChild(dateElement);
auto sizeElement = HTMLTableCellElement::create(tdTag, document);
sizeElement->appendChild(Text::create(document, size));
sizeElement->setAttributeWithoutSynchronization(HTMLNames::classAttr, AtomString("ftpDirectoryFileSize", AtomString::ConstructFromLiteral));
rowElement->appendChild(sizeElement);
}
Ref<Element> FTPDirectoryDocumentParser::createTDForFilename(const String& filename)
{
auto& document = *this->document();
String fullURL = document.baseURL().string();
if (fullURL.endsWith('/'))
fullURL = fullURL + filename;
else
fullURL = fullURL + '/' + filename;
auto anchorElement = HTMLAnchorElement::create(document);
anchorElement->setAttributeWithoutSynchronization(HTMLNames::hrefAttr, fullURL);
anchorElement->appendChild(Text::create(document, filename));
auto tdElement = HTMLTableCellElement::create(tdTag, document);
tdElement->appendChild(anchorElement);
return WTFMove(tdElement);
}
static String processFilesizeString(const String& size, bool isDirectory)
{
if (isDirectory)
return "--"_s;
bool valid;
int64_t bytes = size.toUInt64(&valid);
if (!valid)
return unknownFileSizeText();
if (bytes < 1000000)
return makeString(FormattedNumber::fixedWidth(bytes / 1000., 2), " KB");
if (bytes < 1000000000)
return makeString(FormattedNumber::fixedWidth(bytes / 1000000., 2), " MB");
return makeString(FormattedNumber::fixedWidth(bytes / 1000000000., 2), " GB");
}
static bool wasLastDayOfMonth(int year, int month, int day)
{
static const int lastDays[] = { 31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
if (month < 0 || month > 11)
return false;
if (month == 2) {
if (year % 4 == 0 && (year % 100 || year % 400 == 0)) {
if (day == 29)
return true;
return false;
}
if (day == 28)
return true;
return false;
}
return lastDays[month] == day;
}
static String processFileDateString(const FTPTime& fileTime)
{
String timeOfDay;
if (!(fileTime.tm_hour == 0 && fileTime.tm_min == 0 && fileTime.tm_sec == 0)) {
int hour = fileTime.tm_hour;
ASSERT(hour >= 0 && hour < 24);
if (hour < 12) {
if (hour == 0)
hour = 12;
timeOfDay = makeString(", ", hour, ':', pad('0', 2, fileTime.tm_min), " AM");
} else {
hour = hour - 12;
if (hour == 0)
hour = 12;
timeOfDay = makeString(", ", hour, ':', pad('0', 2, fileTime.tm_min), " PM");
}
}
GregorianDateTime now;
now.setToCurrentLocalTime();
if (fileTime.tm_year == now.year()) {
if (fileTime.tm_mon == now.month()) {
if (fileTime.tm_mday == now.monthDay())
return "Today" + timeOfDay;
if (fileTime.tm_mday == now.monthDay() - 1)
return "Yesterday" + timeOfDay;
}
if (now.monthDay() == 1 && (now.month() == fileTime.tm_mon + 1 || (now.month() == 0 && fileTime.tm_mon == 11)) &&
wasLastDayOfMonth(fileTime.tm_year, fileTime.tm_mon, fileTime.tm_mday))
return "Yesterday" + timeOfDay;
}
if (fileTime.tm_year == now.year() - 1 && fileTime.tm_mon == 12 && fileTime.tm_mday == 31 && now.month() == 1 && now.monthDay() == 1)
return "Yesterday" + timeOfDay;
static const char* months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "???" };
int month = fileTime.tm_mon;
if (month < 0 || month > 11)
month = 12;
String dateString;
if (fileTime.tm_year > -1)
dateString = makeString(months[month], ' ', fileTime.tm_mday, ", ", fileTime.tm_year);
else
dateString = makeString(months[month], ' ', fileTime.tm_mday, ", ", now.year());
return dateString + timeOfDay;
}
void FTPDirectoryDocumentParser::parseAndAppendOneLine(const String& inputLine)
{
ListResult result;
CString latin1Input = inputLine.latin1();
FTPEntryType typeResult = parseOneFTPLine(latin1Input.data(), m_listState, result);
if (typeResult == FTPMiscEntry || typeResult == FTPJunkEntry)
return;
String filename(result.filename, result.filenameLength);
if (result.type == FTPDirectoryEntry) {
filename.append('/');
if (filename == "./")
return;
}
LOG(FTP, "Appending entry - %s, %s", filename.ascii().data(), result.fileSize.ascii().data());
appendEntry(filename, processFilesizeString(result.fileSize, result.type == FTPDirectoryEntry), processFileDateString(result.modifiedTime), result.type == FTPDirectoryEntry);
}
static inline RefPtr<SharedBuffer> createTemplateDocumentData(const Settings& settings)
{
auto buffer = SharedBuffer::createWithContentsOfFile(settings.ftpDirectoryTemplatePath());
if (buffer)
LOG(FTP, "Loaded FTPDirectoryTemplate of length %zu\n", buffer->size());
return buffer;
}
bool FTPDirectoryDocumentParser::loadDocumentTemplate()
{
static SharedBuffer* templateDocumentData = createTemplateDocumentData(document()->settings()).leakRef();
if (!templateDocumentData) {
LOG_ERROR("Could not load templateData");
return false;
}
HTMLDocumentParser::insert(String(templateDocumentData->data(), templateDocumentData->size()));
auto& document = *this->document();
auto foundElement = makeRefPtr(document.getElementById(String("ftpDirectoryTable"_s)));
if (!foundElement)
LOG_ERROR("Unable to find element by id \"ftpDirectoryTable\" in the template document.");
else if (!is<HTMLTableElement>(foundElement))
LOG_ERROR("Element of id \"ftpDirectoryTable\" is not a table element");
else {
m_tableElement = downcast<HTMLTableElement>(foundElement.get());
return true;
}
m_tableElement = HTMLTableElement::create(document);
m_tableElement->setAttributeWithoutSynchronization(HTMLNames::idAttr, AtomString("ftpDirectoryTable", AtomString::ConstructFromLiteral));
if (auto body = makeRefPtr(document.bodyOrFrameset()))
body->appendChild(*m_tableElement);
else
document.appendChild(*m_tableElement);
return true;
}
void FTPDirectoryDocumentParser::createBasicDocument()
{
LOG(FTP, "Creating a basic FTP document structure as no template was loaded");
auto& document = *this->document();
auto bodyElement = HTMLBodyElement::create(document);
document.appendChild(bodyElement);
m_tableElement = HTMLTableElement::create(document);
m_tableElement->setAttributeWithoutSynchronization(HTMLNames::idAttr, AtomString("ftpDirectoryTable", AtomString::ConstructFromLiteral));
m_tableElement->setAttribute(HTMLNames::styleAttr, AtomString("width:100%", AtomString::ConstructFromLiteral));
bodyElement->appendChild(*m_tableElement);
document.processViewport("width=device-width", ViewportArguments::ViewportMeta);
}
void FTPDirectoryDocumentParser::append(RefPtr<StringImpl>&& inputSource)
{
if (!m_tableElement) {
if (!loadDocumentTemplate())
createBasicDocument();
ASSERT(m_tableElement);
}
bool foundNewLine = false;
m_dest = m_buffer;
SegmentedString string { String { WTFMove(inputSource) } };
while (!string.isEmpty()) {
UChar c = string.currentCharacter();
if (c == '\r') {
*m_dest++ = '\n';
foundNewLine = true;
m_skipLF = true;
} else if (c == '\n') {
if (!m_skipLF)
*m_dest++ = c;
else
m_skipLF = false;
} else {
*m_dest++ = c;
m_skipLF = false;
}
string.advance();
checkBuffer();
}
if (!foundNewLine) {
m_dest = m_buffer;
return;
}
UChar* start = m_buffer;
UChar* cursor = start;
while (cursor < m_dest) {
if (*cursor == '\n') {
m_carryOver.append(String(start, cursor - start));
LOG(FTP, "%s", m_carryOver.ascii().data());
parseAndAppendOneLine(m_carryOver);
m_carryOver = String();
start = ++cursor;
} else
cursor++;
}
if (cursor - start > 1)
m_carryOver.append(String(start, cursor - start - 1));
}
void FTPDirectoryDocumentParser::finish()
{
if (!m_carryOver.isEmpty()) {
parseAndAppendOneLine(m_carryOver);
m_carryOver = String();
}
m_tableElement = nullptr;
fastFree(m_buffer);
HTMLDocumentParser::finish();
}
FTPDirectoryDocument::FTPDirectoryDocument(Frame* frame, const URL& url)
: HTMLDocument(frame, url)
{
#if !LOG_DISABLED
LogFTP.state = WTFLogChannelState::On;
#endif
}
Ref<DocumentParser> FTPDirectoryDocument::createParser()
{
return FTPDirectoryDocumentParser::create(*this);
}
}
#endif // ENABLE(FTPDIR)