Merge branch 'master' of github.com:adamdruppe/arsd

This commit is contained in:
Adam D. Ruppe 2019-05-11 09:43:00 -04:00
commit bfc8afad87
9 changed files with 370 additions and 33 deletions

21
apng.d
View File

@ -57,6 +57,7 @@ class ApngFrame {
auto height = frameControlChunk.height;
auto bytesPerLine = bytesPerLineOfPng(parent.header.depth, parent.header.type, width);
bytesPerLine--; // removing filter byte from this calculation since we handle separtely
int idataIdx;
ubyte[] idata;
@ -79,11 +80,7 @@ class ApngFrame {
this.data = idata;
}
// then need to uncompress it
// and unfilter it...
// and then convert it to the right format.
MemoryImage frameData;
//MemoryImage frameData;
}
class ApngAnimation {
@ -109,16 +106,16 @@ enum APNG_BLEND_OP : byte {
OVER = 1
}
void readApng(in ubyte[] data) {
ApngAnimation readApng(in ubyte[] data) {
auto png = readPng(data);
auto header = PngHeader.fromChunk(png.chunks[0]);
Color[] palette;
if(header.type == 3) {
palette = fetchPalette(png);
}
auto obj = new ApngAnimation();
if(header.type == 3) {
obj.palette = fetchPalette(png);
}
bool seenIdat = false;
bool seenFctl = false;
@ -227,11 +224,13 @@ void readApng(in ubyte[] data) {
expectedSequenceNumber++;
// and the rest of it is a datastream...
obj.frames[frameNumber - 1].compressedDatastream ~= chunk.payload;
obj.frames[frameNumber - 1].compressedDatastream ~= chunk.payload[offset .. $];
break;
default:
// ignore
}
}
return obj;
}

91
argon2.d Normal file
View File

@ -0,0 +1,91 @@
/++
My minimal interface to https://github.com/p-h-c/phc-winner-argon2
You must compile and install the C library separately.
+/
module arsd.argon2;
// it is conceivably useful to hash the password with a secret key before passing to this function,
// but I'm not going to do that automatically here just to keep this thin and simple.
import core.stdc.stdint;
pragma(lib, "argon2");
extern(C)
int argon2id_hash_encoded(
const uint32_t t_cost,
const uint32_t m_cost,
const uint32_t parallelism,
const void *pwd, const size_t pwdlen,
const void *salt, const size_t saltlen,
const size_t hashlen, char *encoded,
const size_t encodedlen);
extern(C)
int argon2id_verify(const char *encoded, const void *pwd,
const size_t pwdlen);
enum ARGON2_OK = 0;
/// Parameters to the argon2 function. Bigger numbers make it harder to
/// crack, but also take more resources for legitimate users too
/// (e.g. making logins and signups slower and more memory-intensive). Some
/// examples are provided. HighSecurity is about 3/4 second on my computer,
/// MediumSecurity about 1/3 second, LowSecurity about 1/10 second.
struct SecurityParameters {
uint cpuCost;
uint memoryCost; /// in KiB fyi
uint parallelism;
}
/// ditto
enum HighSecurity = SecurityParameters(8, 512_000, 8);
/// ditto
enum MediumSecurity = SecurityParameters(4, 256_000, 4);
/// ditto
enum LowSecurity = SecurityParameters(2, 128_000, 4);
/// Check's a user's provided password against the saved password, and returns true if they matched. Neither string can be empty.
bool verify(string savedPassword, string providedPassword) {
return argon2id_verify((savedPassword[$-1] == 0 ? savedPassword : (savedPassword ~ '\0')).ptr, providedPassword.ptr, providedPassword.length) == ARGON2_OK;
}
/// encode a password for secure storage. verify later with [verify]
string encode(string password, SecurityParameters params = MediumSecurity) {
char[256] buffer;
enum HASHLEN = 80;
import core.stdc.string;
ubyte[32] salt = void;
version(linux) {{
import core.sys.posix.unistd;
import core.sys.posix.fcntl;
int fd = open("/dev/urandom", O_RDONLY);
auto ret = read(fd, salt.ptr, salt.length);
assert(ret == salt.length);
close(fd);
}} else {
import std.random;
foreach(ref s; salt)
s = cast(ubyte) uniform(0, 256);
}
auto ret = argon2id_hash_encoded(
params.cpuCost,
params.memoryCost,
params.parallelism,
password.ptr, password.length,
salt.ptr, salt.length,
HASHLEN, // desired size of hash. I think this is fine being arbitrary
buffer.ptr,
buffer.length
);
if(ret != ARGON2_OK)
throw new Exception("wtf");
return buffer[0 .. strlen(buffer.ptr) + 1].idup;
}

193
cgi.d
View File

@ -1109,7 +1109,7 @@ class Cgi {
///
void writeToFile(string filenameToSaveTo) {
void writeToFile(string filenameToSaveTo) const {
import std.file;
if(contentInMemory)
std.file.write(filenameToSaveTo, content);
@ -5003,6 +5003,143 @@ private struct SerializationBuffer {
will have to have dump and restore too, so i can restart without losing stuff.
*/
/++
A convenience object for talking to the [BasicDataServer] from a higher level.
See: [getSessionObject]
You pass it a `Data` struct describing the data you want saved in the session.
Then, this class will generate getter and setter properties that allow access
to that data.
Note that each load and store will be done as-accessed; it doesn't front-load
mutable data nor does it batch updates out of fear of read-modify-write race
conditions. (In fact, right now it does this for everything, but in the future,
I might batch load `immutable` members of the Data struct.)
At some point in the future, I might also let it do different backends, like
a client-side cookie store too, but idk.
+/
class Session(Data) {
private Cgi cgi;
private string sessionId;
/// You probably don't want to call this directly, see [getSessionObject] instead.
this(Cgi cgi) {
this.cgi = cgi;
if(auto ptr = "sessionId" in cgi.cookies)
sessionId = (*ptr).length ? *ptr : null;
}
/++
Starts a new session. Note that a session is also
implicitly started as soon as you write data to it,
so if you need to alter these parameters from their
defaults, be sure to explicitly call this BEFORE doing
any writes to session data.
Params:
idleLifetime = How long, in seconds, the session
should remain in memory when not being read from
or written to. The default is one day.
NOT IMPLEMENTED
useExtendedLifetimeCookie = The session ID is always
stored in a HTTP cookie, and by default, that cookie
is discarded when the user closes their browser.
But if you set this to true, it will use a non-perishable
cookie for the given idleLifetime.
NOT IMPLEMENTED
+/
void start(int idleLifetime = 2600 * 24, bool useExtendedLifetimeCookie = false) {
assert(sessionId is null);
// FIXME: what if there is a session ID cookie, but no corresponding session on the server?
import std.random, std.conv;
sessionId = to!string(uniform(1, long.max));
BasicDataServer.connection.createSession(sessionId, idleLifetime);
setCookie();
}
private void setCookie() {
cgi.setCookie(
"sessionId", sessionId,
0 /* expiration */,
null /* path */,
null /* domain */,
true /* http only */,
cgi.https /* if the session is started on https, keep it there, otherwise, be flexible */);
}
/++
Regenerates the session ID and updates the associated
cookie.
This is also your chance to change immutable data
(not yet implemented).
+/
void regenerateId() {
if(sessionId is null) {
start();
return;
}
import std.random, std.conv;
auto oldSessionId = sessionId;
sessionId = to!string(uniform(1, long.max));
BasicDataServer.connection.renameSession(oldSessionId, sessionId);
setCookie();
}
/++
Terminates this session, deleting all saved data.
+/
void terminate() {
BasicDataServer.connection.destroySession(sessionId);
sessionId = null;
setCookie();
}
/++
Plain-old-data members of your `Data` struct are wrapped here via
the opDispatch property getters and setters.
If the member is a non-string array, it returns a magical array proxy
object which allows for atomic appends and replaces via overloaded operators.
You can slice this to get a range representing a $(B const) view of the array.
This is to protect you against read-modify-write race conditions.
+/
@property typeof(__traits(getMember, Data, name)) opDispatch(string name)() {
if(sessionId is null)
return typeof(return).init;
auto v = BasicDataServer.connection.getSessionData(sessionId, name);
if(v.length == 0)
return typeof(return).init;
import std.conv;
return to!(typeof(return))(v);
}
/// ditto
@property void opDispatch(string name)(typeof(__traits(getMember, Data, name)) value) {
if(sessionId is null)
start();
import std.conv;
BasicDataServer.connection.setSessionData(sessionId, name, to!string(value));
}
}
/++
Gets a session object associated with the `cgi` request.
+/
Session!Data getSessionObject(Data)(Cgi cgi) {
return new Session!Data(cgi);
}
/++
+/
@ -5047,10 +5184,20 @@ final class BasicDataServerImplementation : BasicDataServer, EventIoServer {
sessions.remove(oldSessionId);
}
void setSessionData(string sessionId, string dataKey, string dataValue) {
if(sessionId !in sessions)
createSession(sessionId, 3600); // FIXME?
sessions[sessionId].values[dataKey.idup] = dataValue.idup;
}
string getSessionData(string sessionId, string dataKey) {
return sessions[sessionId].values[dataKey];
if(auto session = sessionId in sessions) {
if(auto data = dataKey in (*session).values)
return *data;
else
return null; // no such data
} else {
return null; // no session
}
}
@ -5757,10 +5904,17 @@ ssize_t read_fd(int fd, void *ptr, size_t nbytes, int *recvfd) {
switch to choose if you want to override.
*/
struct DispatcherDefinition(alias dispatchHandler) {// if(is(typeof(dispatchHandler("str", Cgi.init) == bool))) { // bool delegate(string urlPrefix, Cgi cgi) dispatchHandler;
struct DispatcherDefinition(alias dispatchHandler) {// if(is(typeof(dispatchHandler("str", Cgi.init, void) == bool))) { // bool delegate(string urlPrefix, Cgi cgi) dispatchHandler;
alias handler = dispatchHandler;
string urlPrefix;
bool rejectFurther;
DispatcherDetails details;
}
// tbh I am really unhappy with this part
struct DispatcherDetails {
string filename;
string contentType;
}
private string urlify(string name) {
@ -6428,7 +6582,7 @@ auto serveApi(T)(string urlPrefix) {
import arsd.dom;
import arsd.jsvar;
static bool handler(string urlPrefix, Cgi cgi) {
static bool handler(string urlPrefix, Cgi cgi, DispatcherDetails details) {
auto obj = new T();
obj.initialize(cgi);
@ -6780,7 +6934,7 @@ class CollectionOf(Obj, Helper = void) : RestObject!(Helper) {
+/
auto serveRestObject(T)(string urlPrefix) {
assert(urlPrefix[$ - 1] != '/', "Do NOT use a trailing slash on REST objects.");
static bool handler(string urlPrefix, Cgi cgi) {
static bool handler(string urlPrefix, Cgi cgi, DispatcherDetails details) {
string url = cgi.pathInfo[urlPrefix.length .. $];
if(url.length && url[$ - 1] == '/') {
@ -7071,15 +7225,26 @@ auto serveStaticFile(string urlPrefix, string filename = null, string contentTyp
if(filename is null)
filename = urlPrefix[1 .. $];
if(contentType is null) {
if(filename.endsWith(".png"))
contentType = "image/png";
if(filename.endsWith(".jpg"))
contentType = "image/jpeg";
if(filename.endsWith(".html"))
contentType = "text/html";
if(filename.endsWith(".css"))
contentType = "text/css";
if(filename.endsWith(".js"))
contentType = "application/javascript";
}
static bool handler(string urlPrefix, Cgi cgi) {
//cgi.setResponseContentType(contentType);
//cgi.write(std.file.read(filename), true);
cgi.write(std.file.read(urlPrefix[1 .. $]), true);
static bool handler(string urlPrefix, Cgi cgi, DispatcherDetails details) {
if(details.contentType.indexOf("image/") == 0)
cgi.setCache(true);
cgi.setResponseContentType(details.contentType);
cgi.write(std.file.read(details.filename), true);
return true;
}
return DispatcherDefinition!handler(urlPrefix, true);
return DispatcherDefinition!handler(urlPrefix, true, DispatcherDetails(filename, contentType));
}
auto serveRedirect(string urlPrefix, string redirectTo) {
@ -7108,15 +7273,15 @@ auto serveStaticData(string urlPrefix, const(void)[] data, string contentType) {
+/
bool dispatcher(definitions...)(Cgi cgi) {
// I can prolly make this more efficient later but meh.
foreach(definition; definitions) {
static foreach(definition; definitions) {
if(definition.rejectFurther) {
if(cgi.pathInfo == definition.urlPrefix) {
auto ret = definition.handler(definition.urlPrefix, cgi);
auto ret = definition.handler(definition.urlPrefix, cgi, definition.details);
if(ret)
return true;
}
} else if(cgi.pathInfo.startsWith(definition.urlPrefix)) {
auto ret = definition.handler(definition.urlPrefix, cgi);
auto ret = definition.handler(definition.urlPrefix, cgi, definition.details);
if(ret)
return true;
}

2
dom.d
View File

@ -819,7 +819,7 @@ class Document : FileResource {
}
if(strict)
enforce(data[pos] == '>');//, format("got %s when expecting >\nContext:\n%s", data[pos], data[pos - 100 .. pos + 100]));
enforce(data[pos] == '>', format("got %s when expecting > (possible missing attribute name)\nContext:\n%s", data[pos], data[pos - 100 .. pos + 100]));
else {
// if we got here, it's probably because a slash was in an
// unquoted attribute - don't trust the selfClosed value

10
jsvar.d
View File

@ -654,6 +654,10 @@ struct var {
static if(is(typeof(__traits(getMember, t, member)) == function)) {
// skipping these because the delegate we get isn't going to work anyway; the object may be dead and certainly won't be updated
//this[member] = &__traits(getMember, proxyObject, member);
//but for simple toString, I'll allow it. or maybe not it doesn't work right.
//static if(member == "toString" && is(typeof(&__traits(getMember, t, member)) == string delegate()))
//this[member] = &__traits(getMember, t, member);
} else
this[member] = __traits(getMember, t, member);
}
@ -1241,6 +1245,12 @@ struct var {
return v;
}
@property static var emptyObject(var prototype) {
if(prototype._type == Type.Object)
return var.emptyObject(prototype._payload._object);
return var.emptyObject();
}
@property PrototypeObject prototypeObject() {
var v = prototype();
if(v._type == Type.Object)

3
png.d
View File

@ -231,7 +231,7 @@ void convertPngData(ubyte type, ubyte depth, const(ubyte)[] data, int width, uby
break;
default: assert(0);
}
assert(data.length == 0, "not all consumed, wtf ");// ~ to!string(h));
assert(data.length == 0, "not all consumed, wtf " ~ to!string(data));
}
/*
@ -1482,6 +1482,7 @@ struct LazyPngFile(LazyPngChunksProvider)
}
// FIXME: doesn't handle interlacing... I think
// note it returns the length including the filter byte!!
@nogc @safe pure nothrow
int bytesPerLineOfPng(ubyte depth, ubyte type, uint width) {
immutable bitsPerChannel = depth;

View File

@ -81,6 +81,8 @@ class PostgreSql : Database {
auto res = PQexec(conn, toStringz(sql));
int ress = PQresultStatus(res);
// https://www.postgresql.org/docs/current/libpq-exec.html
// FIXME: PQresultErrorField can get a lot more info in a more structured way
if(ress != PGRES_TUPLES_OK
&& ress != PGRES_COMMAND_OK)
throw new DatabaseException(error());

View File

@ -198,6 +198,17 @@ import core.thread;
final class AudioPcmOutThread : Thread {
///
this() {
version(linux) {
// this thread has no business intercepting signals from the main thread,
// so gonna block a couple of them
import core.sys.posix.signal;
sigset_t sigset;
auto err = sigfillset(&sigset);
assert(!err);
err = sigprocmask(SIG_BLOCK, &sigset, null);
assert(!err);
}
super(&run);
}

View File

@ -848,8 +848,6 @@ version(Windows) {
// http://wiki.dlang.org/Simpledisplay.d
// FIXME: SIGINT handler is necessary to clean up shared memory handles upon ctrl+c
// see : http://www.sbin.org/doc/Xlib/chapt_09.html section on Keyboard Preferences re: scroll lock led
// Cool stuff: I want right alt and scroll lock to do different stuff for personal use. maybe even right ctrl
@ -1351,6 +1349,14 @@ class SimpleWindow : CapableOfHandlingNativeEvent, CapableOfBeingDrawnUpon {
is the drawable space inside; it excludes the title bar, etc.)
Windows based on images will not be resizable and do not use OpenGL.
It will draw the image in upon creation, but this will be overwritten
upon any draws, including the initial window visible event.
You probably do not want to use this and it may be removed from
the library eventually, or I might change it to be a "permanent"
background image; one that is automatically drawn on it before any
other drawing event. idk.
+/
this(Image image, string title = null) {
this(image.width, image.height, title);
@ -2345,6 +2351,7 @@ private:
version(X11) {
__gshared int customEventFD = -1;
__gshared int customSignalFD = -1;
} else version(Windows) {
__gshared HANDLE customEventH = null;
}
@ -2713,6 +2720,12 @@ struct EventLoop {
impl.notExited = false;
}
version(linux)
ref void delegate(int) signalHandler() {
assert(impl !is null);
return impl.signalHandler;
}
static EventLoopImpl* impl;
}
@ -2741,6 +2754,8 @@ struct EventLoopImpl {
static import unix = core.sys.posix.unistd;
static import err = core.stdc.errno;
import core.sys.linux.timerfd;
void delegate(int) signalHandler;
}
version(X11) {
@ -2843,6 +2858,29 @@ struct EventLoopImpl {
throw new Exception("can't create eventfd for custom event processing");
}
}
if (customSignalFD == -1) {
import core.sys.linux.sys.signalfd;
sigset_t sigset;
auto err = sigemptyset(&sigset);
assert(!err);
err = sigaddset(&sigset, SIGINT);
assert(!err);
err = sigaddset(&sigset, SIGHUP);
assert(!err);
err = sigprocmask(SIG_BLOCK, &sigset, null);
assert(!err);
customSignalFD = signalfd(-1, &sigset, SFD_NONBLOCK);
assert(customSignalFD != -1);
ep.epoll_event ev = void;
{ import core.stdc.string : memset; memset(&ev, 0, ev.sizeof); } // this makes valgrind happy
ev.events = ep.EPOLLIN;
ev.data.fd = customSignalFD;
ep.epoll_ctl(epollFd, ep.EPOLL_CTL_ADD, customSignalFD, &ev);
}
}
SimpleWindow.processAllCustomEvents(); // process events added before event FD creation
@ -2909,8 +2947,10 @@ struct EventLoopImpl {
dispose();
}
version(X11)
version(linux)
ref int customEventFD() { return SimpleWindow.customEventFD; }
version(linux)
ref int customSignalFD() { return SimpleWindow.customSignalFD; }
version(Windows)
ref auto customEventH() { return SimpleWindow.customEventH; }
@ -2957,7 +2997,22 @@ struct EventLoopImpl {
assert(fd != -1); // should never happen cuz the api doesn't do that but better to assert than assume.
auto flags = events[idx].events;
if(flags & ep.EPOLLIN) {
if(fd == display.fd) {
if (fd == customSignalFD) {
version(linux) {
import core.sys.linux.sys.signalfd;
import core.sys.posix.unistd : read;
signalfd_siginfo info;
read(customSignalFD, &info, info.sizeof);
auto sig = info.ssi_signo;
if(EventLoop.get.signalHandler !is null) {
EventLoop.get.signalHandler()(sig);
} else {
EventLoop.get.exit();
}
}
} else if(fd == display.fd) {
version(sdddd) { import std.stdio; writeln("X EVENT PENDING!"); }
this.mtLock();
scope(exit) this.mtUnlock();
@ -4628,8 +4683,8 @@ version(X11) {
setX11Selection!"SECONDARY"(window, text);
}
///
void setX11Selection(string atomName)(SimpleWindow window, string text) {
/// The `after` delegate is called after a client requests the UTF8_STRING thing. it is a mega-hack right now!
void setX11Selection(string atomName)(SimpleWindow window, string text, void delegate() after = null) {
assert(window !is null);
auto display = XDisplayConnection.get();
@ -4673,6 +4728,9 @@ version(X11) {
event.target,
8 /* bits */, 0 /* PropModeReplace */,
text.ptr, cast(int) text.length);
if(after)
after();
} else {
selectionEvent.property = None; // I don't know how to handle this type...
}