diff --git a/source/app.d b/source/app.d index 6886497..a2e56bf 100644 --- a/source/app.d +++ b/source/app.d @@ -41,19 +41,43 @@ void main() { } void handleVisitorLog(ref HttpRequestContext ctx) { - infoF!"Got visitor log from %s"(ctx.request.remoteAddress); JSONValue logBody = ctx.request.readBodyAsJson(); string name = logBody.object["name"].str; + if (name.length > 32) { + ctx.response.setStatus(HttpStatus.BAD_REQUEST); + ctx.response.writeBodyString("Name is too long."); + return; + } string message = logBody.object["message"].str; - string remoteAddress = "UNKNOWN"; - if (ctx.request.remoteAddress !is null) { - remoteAddress = ctx.request.remoteAddress.toString(); + if (message.length > 255) { + ctx.response.setStatus(HttpStatus.BAD_REQUEST); + ctx.response.writeBodyString("Message is too long."); + return; + } + if (!ctx.request.hasHeader("X-Forwarded-For")) { + ctx.response.setStatus(HttpStatus.FORBIDDEN); + ctx.response.writeBodyString("Missing remote IP"); + return; + } + string remoteAddress = ctx.request.getHeader("X-Forwarded-For"); + LogEntry[] recentLogsByThisAddress = getRecentLogEntriesByRemoteAddress(remoteAddress); + SysTime now = Clock.currTime(); + if (recentLogsByThisAddress.length > 0 && now - recentLogsByThisAddress[0].createdAt < minutes(1)) { + ctx.response.setStatus(HttpStatus.TOO_MANY_REQUESTS); + return; } insertLogEntry(remoteAddress, name, message); } void handleLogbookRequest(ref HttpRequestContext ctx) { - LogEntry[] entries = getRecentLogEntries(); + uint limit = ctx.request.getParamAs!uint("limit", 5); + if (limit > 100) { + ctx.response.setStatus(HttpStatus.BAD_REQUEST); + ctx.response.writeBodyString("Limit is too large."); + return; + } + uint offset = ctx.request.getParamAs!uint("offset", 0); + LogEntry[] entries = getRecentLogEntries(limit, offset); JSONValue entriesJson = JSONValue(JSONValue[].init); foreach (LogEntry entry; entries) { entriesJson.array ~= entry.toJson(); @@ -79,6 +103,19 @@ struct LogEntry { obj["message"] = JSONValue(message); return obj; } + + static LogEntry fromDbRow(ref Row row) { + LogEntry entry; + entry.id = row.peek!ulong(0); + string createdAtStr = row.peek!string(1); + string isoCreatedAt = createdAtStr[0 .. 4] ~ createdAtStr[5 .. 7] ~ createdAtStr[8 .. 10] ~ 'T' ~ + createdAtStr[11 .. 13] ~ createdAtStr[14 .. 16] ~ createdAtStr[17 .. $]; + entry.createdAt = SysTime.fromISOString(isoCreatedAt); + entry.remoteAddress = row.peek!string(2); + entry.name = row.peek!string(3); + entry.message = row.peek!string(4); + return entry; + } } void initDb() { @@ -111,21 +148,32 @@ SQL"); infoF!"Added log entry for %s @ %s"(name, remoteAddress); } -LogEntry[] getRecentLogEntries() { +LogEntry[] findAllByQuery(string query) { + import std.array : Appender, appender; Database db = Database("logbook.sqlite"); - ResultRange results = db.execute("SELECT * FROM log_entry ORDER BY created_at DESC LIMIT 5"); - LogEntry[] entries; + ResultRange results = db.execute(query); + Appender!(LogEntry[]) app = appender!(LogEntry[])(); foreach (Row row; results) { - LogEntry entry; - entry.id = row.peek!ulong(0); - string createdAtStr = row.peek!string(1); - string isoCreatedAt = createdAtStr[0 .. 4] ~ createdAtStr[5 .. 7] ~ createdAtStr[8 .. 10] ~ 'T' ~ - createdAtStr[11 .. 13] ~ createdAtStr[14 .. 16] ~ createdAtStr[17 .. $]; - entry.createdAt = SysTime.fromISOString(isoCreatedAt); - entry.remoteAddress = row.peek!string(2); - entry.name = row.peek!string(3); - entry.message = row.peek!string(4); - entries ~= entry; + app ~= LogEntry.fromDbRow(row); } - return entries; + return app.data(); +} + +LogEntry[] getRecentLogEntries(uint limit, uint offset) { + import std.format; + string query = format!"SELECT * FROM log_entry ORDER BY created_at DESC LIMIT %d OFFSET %d"(limit, offset); + return findAllByQuery(query); +} + +LogEntry[] getRecentLogEntriesByRemoteAddress(string remoteAddress) { + import std.array : Appender, appender; + Database db = Database("logbook.sqlite"); + Statement stmt = db.prepare("SELECT * FROM log_entry WHERE remote_address = :addr ORDER BY created_at DESC LIMIT 10"); + stmt.bind(0, remoteAddress); + ResultRange results = stmt.execute(); + Appender!(LogEntry[]) app = appender!(LogEntry[])(); + foreach (Row row; results) { + app ~= LogEntry.fromDbRow(row); + } + return app.data(); }