merge upstream changes v3.0

Mon, 24 Apr 2023 21:01:41 +0200

author
Mike Becker <universe@uap-core.de>
date
Mon, 24 Apr 2023 21:01:41 +0200
changeset 67
5da2cb5aea6b
parent 66
1b12cf799fee (current diff)
parent 65
7dd4fd1e7071 (diff)
child 68
4d1ce98e09cc

merge upstream changes

.hgignore file | annotate | diff | comparison | revisions
Makefile file | annotate | diff | comparison | revisions
src/c2html.c file | annotate | diff | comparison | revisions
src/frontend.c file | annotate | diff | comparison | revisions
src/highlighter.c file | annotate | diff | comparison | revisions
src/highlighter.h file | annotate | diff | comparison | revisions
test/ctest.c file | annotate | diff | comparison | revisions
test/ctestfile.c file | annotate | diff | comparison | revisions
test/empty.c file | annotate | diff | comparison | revisions
test/golden-master/bigtest.html file | annotate | diff | comparison | revisions
test/golden-master/ctest.html file | annotate | diff | comparison | revisions
test/golden-master/empty.html file | annotate | diff | comparison | revisions
test/golden-master/javatest.html file | annotate | diff | comparison | revisions
test/golden-master/plain.html file | annotate | diff | comparison | revisions
--- a/.hgignore	Mon Apr 24 20:54:38 2023 +0200
+++ b/.hgignore	Mon Apr 24 21:01:41 2023 +0200
@@ -5,4 +5,5 @@
 \.conflict\~$
 ^build/.*$
 ^nbproject/.*$
-^.idea/.*$
+.idea/
+
--- a/Makefile	Mon Apr 24 20:54:38 2023 +0200
+++ b/Makefile	Mon Apr 24 21:01:41 2023 +0200
@@ -49,18 +49,19 @@
 	$(MKDIR) $@
 	
 test: all
-	./build/$(BIN) test/ctestfile.c -o build/ctest.html \
+	./build/$(BIN) test/ctest.c -o build/ctest.html \
 	-H test/header.html -F test/footer.html
-	./build/$(BIN) -j test/javatestfile.java -o build/javatest.html \
+	./build/$(BIN) -j test/javatest.java -o build/javatest.html \
 	-H test/jheader.html -F test/footer.html
-	./build/$(BIN) test/bigtestfile.c -o build/bigtest.html \
+	./build/$(BIN) test/bigtest.c -o build/bigtest.html \
 	-H test/header.html -F test/footer.html
-	./build/$(BIN) -p test/plain.csp -o build/plain.html \
+	./build/$(BIN) -p test/plain.txt -o build/plain.html \
 	-H test/header.html -F test/footer.html
 	diff build/ctest.html test/gs/ctest.html && \
 	diff build/javatest.html test/gs/javatest.html && \
 	diff build/bigtest.html test/gs/bigtest.html && \
 	diff build/plain.html test/gs/plain.html
+	@echo "Tests successful."
 	
 clean:
 	$(RM) $(RMFLAGS) build
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/bigtest.c	Mon Apr 24 21:01:41 2023 +0200
@@ -0,0 +1,808 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Mike Becker. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include "rules.h"
+#include "chess.h"
+#include <string.h>
+#include <stdlib.h>
+#include <sys/time.h>
+
+static GameState gamestate_copy_sim(GameState *gamestate) {
+    GameState simulation = *gamestate;
+    if (simulation.lastmove) {
+        MoveList *lastmovecopy = malloc(sizeof(MoveList));
+        *lastmovecopy = *(simulation.lastmove);
+        simulation.movelist = simulation.lastmove = lastmovecopy;
+    }
+
+    return simulation;
+}
+
+void gamestate_init(GameState *gamestate) {
+    memset(gamestate, 0, sizeof(GameState));
+    
+    Board initboard = {
+        {WROOK, WKNIGHT, WBISHOP, WQUEEN, WKING, WBISHOP, WKNIGHT, WROOK},
+        {WPAWN, WPAWN,   WPAWN,   WPAWN,  WPAWN, WPAWN,   WPAWN,   WPAWN},
+        {0,     0,       0,       0,      0,     0,       0,       0},
+        {0,     0,       0,       0,      0,     0,       0,       0},
+        {0,     0,       0,       0,      0,     0,       0,       0},
+        {0,     0,       0,       0,      0,     0,       0,       0},
+        {BPAWN, BPAWN,   BPAWN,   BPAWN,  BPAWN, BPAWN,   BPAWN,   BPAWN},
+        {BROOK, BKNIGHT, BBISHOP, BQUEEN, BKING, BBISHOP, BKNIGHT, BROOK}
+    };
+    memcpy(gamestate->board, initboard, sizeof(Board));
+}
+
+void gamestate_cleanup(GameState *gamestate) {
+    MoveList *elem;
+    elem = gamestate->movelist;
+    while (elem) {
+        MoveList *cur = elem;
+        elem = elem->next;
+        free(cur);
+    };
+}
+
+/* MUST be called IMMEDIATLY after applying a move to work correctly */
+static void format_move(GameState *gamestate, Move *move) {
+    char *string = move->string;
+    
+    /* at least 8 characters should be available, wipe them out */
+    memset(string, 0, 8);
+    
+    /* special formats for castling */
+    if ((move->piece&PIECE_MASK) == KING &&
+            abs(move->tofile-move->fromfile) == 2) {
+        if (move->tofile==fileidx('c')) {
+            memcpy(string, "O-O-O", 5);
+        } else {
+            memcpy(string, "O-O", 3);
+        }
+    }
+
+    /* start by notating the piece character */
+    string[0] = getpiecechr(move->piece);
+    int idx = string[0] ? 1 : 0;
+    
+    /* find out how many source information we do need */
+    uint8_t piece = move->piece & PIECE_MASK;
+    if (piece == PAWN) {
+        if (move->capture) {
+            string[idx++] = filechr(move->fromfile);
+        }
+    } else if (piece != KING) {
+        Move threats[16];
+        uint8_t threatcount;
+        get_real_threats(gamestate, move->torow, move->tofile,
+            move->piece&COLOR_MASK, threats, &threatcount);
+        if (threatcount > 1) {
+            int ambrows = 0, ambfiles = 0;
+            for (uint8_t i = 0 ; i < threatcount ; i++) {
+                if (threats[i].fromrow == move->fromrow) {
+                    ambrows++;
+                }
+                if (threats[i].fromfile == move->fromfile) {
+                    ambfiles++;
+                }
+            }
+            /* ambiguous row, name file */
+            if (ambrows > 1) {
+                string[idx++] = filechr(move->fromfile);
+            }
+            /* ambiguous file, name row */
+            if (ambfiles > 1) {
+                string[idx++] = filechr(move->fromrow);
+            }
+        }
+    }
+    
+    /* capturing? */
+    if (move->capture) {
+        string[idx++] = 'x';
+    }
+    
+    /* destination */
+    string[idx++] = filechr(move->tofile);
+    string[idx++] = rowchr(move->torow);
+    
+    /* promotion? */
+    if (move->promotion) {
+        string[idx++] = '=';
+        string[idx++] = getpiecechr(move->promotion);
+    }
+    
+    /* check? */
+    if (move->check) {
+        /* works only, if this function is called when applying the move */
+        string[idx++] = gamestate->checkmate?'#':'+';
+    }
+}
+
+static void addmove(GameState* gamestate, Move *move) {
+    MoveList *elem = malloc(sizeof(MoveList));
+    elem->next = NULL;
+    elem->move = *move;
+    
+    struct timeval curtimestamp;
+    gettimeofday(&curtimestamp, NULL);
+    elem->move.timestamp.tv_sec = curtimestamp.tv_sec;
+    elem->move.timestamp.tv_usec = curtimestamp.tv_usec;
+    
+    if (gamestate->lastmove) {
+        struct movetimeval *lasttstamp = &(gamestate->lastmove->move.timestamp);
+        uint64_t sec = curtimestamp.tv_sec - lasttstamp->tv_sec;
+        suseconds_t micros;
+        if (curtimestamp.tv_usec < lasttstamp->tv_usec) {
+            micros = 1e6L-(lasttstamp->tv_usec - curtimestamp.tv_usec);
+            sec--;
+        } else {
+            micros = curtimestamp.tv_usec - lasttstamp->tv_usec;
+        }
+        
+        elem->move.movetime.tv_sec = sec;
+        elem->move.movetime.tv_usec = micros;
+        
+        gamestate->lastmove->next = elem;
+        gamestate->lastmove = elem;
+    } else {
+        elem->move.movetime.tv_usec = 0;
+        elem->move.movetime.tv_sec = 0;
+        gamestate->movelist = gamestate->lastmove = elem;
+    }
+}
+
+char getpiecechr(uint8_t piece) {
+    switch (piece & PIECE_MASK) {
+    case ROOK: return 'R';
+    case KNIGHT: return 'N';
+    case BISHOP: return 'B';
+    case QUEEN: return 'Q';
+    case KING: return 'K';
+    default: return '\0';
+    }
+}
+
+uint8_t getpiece(char c) {
+    switch (c) {
+        case 'R': return ROOK;
+        case 'N': return KNIGHT;
+        case 'B': return BISHOP;
+        case 'Q': return QUEEN;
+        case 'K': return KING;
+        default: return 0;
+    }
+}
+
+static void apply_move_impl(GameState *gamestate, Move *move, _Bool simulate) {
+    uint8_t piece = move->piece & PIECE_MASK;
+    uint8_t color = move->piece & COLOR_MASK;
+    
+    /* en passant capture */
+    if (move->capture && piece == PAWN &&
+        mdst(gamestate->board, move) == 0) {
+        gamestate->board[move->fromrow][move->tofile] = 0;
+    }
+    
+    /* remove old en passant threats */
+    for (uint8_t file = 0 ; file < 8 ; file++) {
+        gamestate->board[3][file] &= ~ENPASSANT_THREAT;
+        gamestate->board[4][file] &= ~ENPASSANT_THREAT;
+    }
+    
+    /* add new en passant threat */
+    if (piece == PAWN && (
+        (move->fromrow == 1 && move->torow == 3) ||
+        (move->fromrow == 6 && move->torow == 4))) {
+        move->piece |= ENPASSANT_THREAT;
+    }
+    
+    /* move (and maybe capture or promote) */
+    msrc(gamestate->board, move) = 0;
+    if (move->promotion) {
+        mdst(gamestate->board, move) = move->promotion;
+    } else {
+        mdst(gamestate->board, move) = move->piece;
+    }
+    
+    /* castling */
+    if (piece == KING && move->fromfile == fileidx('e')) {
+        
+        if (move->tofile == fileidx('g')) {
+            gamestate->board[move->torow][fileidx('h')] = 0;
+            gamestate->board[move->torow][fileidx('f')] = color|ROOK;
+        } else if (move->tofile == fileidx('c')) {
+            gamestate->board[move->torow][fileidx('a')] = 0;
+            gamestate->board[move->torow][fileidx('d')] = color|ROOK;
+        }
+    }
+
+    if (!simulate) {
+        if (!move->string[0]) {
+            format_move(gamestate, move);
+        }
+    }
+    /* add move, even in simulation (checkmate test needs it) */
+    addmove(gamestate, move);
+}
+
+void apply_move(GameState *gamestate, Move *move) {
+    apply_move_impl(gamestate, move, 0);
+}
+
+static int validate_move_rules(GameState *gamestate, Move *move) {
+    /* validate indices (don't trust opponent) */
+    if (!chkidx(move)) {
+        return INVALID_POSITION;
+    }
+    
+    /* must move */
+    if (move->fromfile == move->tofile && move->fromrow == move->torow) {
+        return INVALID_MOVE_SYNTAX;
+    }
+    
+    /* does piece exist */
+    if ((msrc(gamestate->board, move)&(PIECE_MASK|COLOR_MASK))
+           != (move->piece&(PIECE_MASK|COLOR_MASK))) {
+        return INVALID_POSITION;
+    }
+    
+    /* can't capture own pieces */
+    if ((mdst(gamestate->board, move) & COLOR_MASK)
+            == (move->piece & COLOR_MASK)) {
+        return RULES_VIOLATED;
+    }
+    
+    /* must capture, if and only if destination is occupied */
+    if ((mdst(gamestate->board, move) == 0 && move->capture) ||
+            (mdst(gamestate->board, move) != 0 && !move->capture)) {
+        return INVALID_MOVE_SYNTAX;
+    }
+    
+    /* validate individual rules */
+    _Bool chkrules;
+    switch (move->piece & PIECE_MASK) {
+    case PAWN:
+        chkrules = pawn_chkrules(gamestate, move) &&
+            !pawn_isblocked(gamestate, move);
+        break;
+    case ROOK:
+        chkrules = rook_chkrules(move) &&
+            !rook_isblocked(gamestate, move);
+        break;
+    case KNIGHT:
+        chkrules = knight_chkrules(move); /* knight is never blocked */
+        break;
+    case BISHOP:
+        chkrules = bishop_chkrules(move) &&
+            !bishop_isblocked(gamestate, move);
+        break;
+    case QUEEN:
+        chkrules = queen_chkrules(move) &&
+            !queen_isblocked(gamestate, move);
+        break;
+    case KING:
+        chkrules = king_chkrules(gamestate, move) &&
+            !king_isblocked(gamestate, move);
+        break;
+    default:
+        return INVALID_MOVE_SYNTAX;
+    }
+    
+    return chkrules ? VALID_MOVE_SEMANTICS : RULES_VIOLATED;
+}
+
+int validate_move(GameState *gamestate, Move *move) {
+    
+    int result = validate_move_rules(gamestate, move);
+    
+    /* cancel processing to save resources */
+    if (result != VALID_MOVE_SEMANTICS) {
+        return result;
+    }
+    
+    /* find kings for check validation */
+    uint8_t piececolor = (move->piece & COLOR_MASK);
+    
+    uint8_t mykingfile = 0, mykingrow = 0, opkingfile = 0, opkingrow = 0;
+    for (uint8_t row = 0 ; row < 8 ; row++) {
+        for (uint8_t file = 0 ; file < 8 ; file++) {
+            if (gamestate->board[row][file] ==
+                    (piececolor == WHITE?WKING:BKING)) {
+                mykingfile = file;
+                mykingrow = row;
+            } else if (gamestate->board[row][file] ==
+                    (piececolor == WHITE?BKING:WKING)) {
+                opkingfile = file;
+                opkingrow = row;
+            }
+        }
+    }
+    
+    /* simulate move for check validation */
+    GameState simulation = gamestate_copy_sim(gamestate);
+    Move simmove = *move;
+    apply_move_impl(&simulation, &simmove, 1);
+    
+    /* don't move into or stay in check position */
+    if (is_covered(&simulation, mykingrow, mykingfile,
+        opponent_color(piececolor))) {
+        
+        gamestate_cleanup(&simulation);
+        if ((move->piece & PIECE_MASK) == KING) {
+            return KING_MOVES_INTO_CHECK;
+        } else {
+            /* last move is always not null in this case */
+            return gamestate->lastmove->move.check ?
+                KING_IN_CHECK : PIECE_PINNED;
+        }
+    }
+    
+    /* correct check and checkmate flags (move is still valid) */
+    Move threats[16];
+    uint8_t threatcount;
+    move->check = get_threats(&simulation, opkingrow, opkingfile,
+        piececolor, threats, &threatcount);
+    
+    if (move->check) {
+        /* determine possible escape fields */
+        _Bool canescape = 0;
+        for (int dr = -1 ; dr <= 1 && !canescape ; dr++) {
+            for (int df = -1 ; df <= 1 && !canescape ; df++) {
+                if (!(dr == 0 && df == 0)  &&
+                        isidx(opkingrow + dr) && isidx(opkingfile + df)) {
+                    
+                    /* escape field neither blocked nor covered */
+                    if ((simulation.board[opkingrow + dr][opkingfile + df]
+                            & COLOR_MASK) != opponent_color(piececolor)) {
+                        canescape |= !is_covered(&simulation,
+                            opkingrow + dr, opkingfile + df, piececolor);
+                    }
+                }
+            }
+        }
+        /* can't escape, can he capture? */
+        if (!canescape && threatcount == 1) {
+            canescape = is_attacked(&simulation, threats[0].fromrow,
+                threats[0].fromfile, opponent_color(piececolor));
+        }
+        
+        /* can't capture, can he block? */
+        if (!canescape && threatcount == 1) {
+            Move *threat = &(threats[0]);
+            uint8_t threatpiece = threat->piece & PIECE_MASK;
+            
+            /* knight, pawns and the king cannot be blocked */
+            if (threatpiece == BISHOP || threatpiece == ROOK
+                || threatpiece == QUEEN) {
+                if (threat->fromrow == threat->torow) {
+                    /* rook aspect (on row) */
+                    int d = threat->tofile > threat->fromfile ? 1 : -1;
+                    uint8_t file = threat->fromfile;
+                    while (!canescape && file != threat->tofile - d) {
+                        file += d;
+                        canescape |= is_protected(&simulation,
+                            threat->torow, file, opponent_color(piececolor));
+                    }
+                } else if (threat->fromfile == threat->tofile) {
+                    /* rook aspect (on file) */
+                    int d = threat->torow > threat->fromrow ? 1 : -1;
+                    uint8_t row = threat->fromrow;
+                    while (!canescape && row != threat->torow - d) {
+                        row += d;
+                        canescape |= is_protected(&simulation,
+                            row, threat->tofile, opponent_color(piececolor));
+                    }
+                } else {
+                    /* bishop aspect */
+                    int dr = threat->torow > threat->fromrow ? 1 : -1;
+                    int df = threat->tofile > threat->fromfile ? 1 : -1;
+
+                    uint8_t row = threat->fromrow;
+                    uint8_t file = threat->fromfile;
+                    while (!canescape && file != threat->tofile - df
+                        && row != threat->torow - dr) {
+                        row += dr;
+                        file += df;
+                        canescape |= is_protected(&simulation, row, file,
+                            opponent_color(piececolor));
+                    }
+                }
+            }
+        }
+            
+        if (!canescape) {
+            gamestate->checkmate = 1;
+        }
+    }
+    
+    gamestate_cleanup(&simulation);
+    
+    return VALID_MOVE_SEMANTICS;
+}
+
+_Bool get_threats(GameState *gamestate, uint8_t row, uint8_t file,
+        uint8_t color, Move *threats, uint8_t *threatcount) {
+    Move candidates[32];
+    int candidatecount = 0;
+    for (uint8_t r = 0 ; r < 8 ; r++) {
+        for (uint8_t f = 0 ; f < 8 ; f++) {
+            if ((gamestate->board[r][f] & COLOR_MASK) == color) {
+                // non-capturing move
+                memset(&(candidates[candidatecount]), 0, sizeof(Move));
+                candidates[candidatecount].piece = gamestate->board[r][f];
+                candidates[candidatecount].fromrow = r;
+                candidates[candidatecount].fromfile = f;
+                candidates[candidatecount].torow = row;
+                candidates[candidatecount].tofile = file;
+                candidatecount++;
+
+                // capturing move
+                memcpy(&(candidates[candidatecount]),
+                    &(candidates[candidatecount-1]), sizeof(Move));
+                candidates[candidatecount].capture = 1;
+                candidatecount++;
+            }
+        }
+    }
+
+    if (threatcount) {
+        *threatcount = 0;
+    }
+    
+    
+    _Bool result = 0;
+    
+    for (int i = 0 ; i < candidatecount ; i++) {
+        if (validate_move_rules(gamestate, &(candidates[i]))
+                == VALID_MOVE_SEMANTICS) {
+            result = 1;
+            if (threats && threatcount) {
+                threats[(*threatcount)++] = candidates[i];
+            }
+        }
+    }
+    
+    return result;
+}
+
+_Bool is_pinned(GameState *gamestate, Move *move) {
+    uint8_t color = move->piece & COLOR_MASK;
+
+    uint8_t kingfile = 0, kingrow = 0;
+    for (uint8_t row = 0 ; row < 8 ; row++) {
+        for (uint8_t file = 0 ; file < 8 ; file++) {
+            if (gamestate->board[row][file] == (color|KING)) {
+                kingfile = file;
+                kingrow = row;
+            }
+        }
+    }
+
+    GameState simulation = gamestate_copy_sim(gamestate);
+    Move simmove = *move;
+    apply_move(&simulation, &simmove);
+    _Bool covered = is_covered(&simulation,
+        kingrow, kingfile, opponent_color(color));
+    gamestate_cleanup(&simulation);
+    
+    return covered;
+}
+
+_Bool get_real_threats(GameState *gamestate, uint8_t row, uint8_t file,
+        uint8_t color, Move *threats, uint8_t *threatcount) {
+    
+    if (threatcount) {
+        *threatcount = 0;
+    }
+
+    Move candidates[16];
+    uint8_t candidatecount;
+    if (get_threats(gamestate, row, file, color, candidates, &candidatecount)) {
+        
+        _Bool result = 0;
+        uint8_t kingfile = 0, kingrow = 0;
+        for (uint8_t row = 0 ; row < 8 ; row++) {
+            for (uint8_t file = 0 ; file < 8 ; file++) {
+                if (gamestate->board[row][file] == (color|KING)) {
+                    kingfile = file;
+                    kingrow = row;
+                }
+            }
+        }
+
+        for (uint8_t i = 0 ; i < candidatecount ; i++) {
+            GameState simulation = gamestate_copy_sim(gamestate);
+            Move simmove = candidates[i];
+            apply_move(&simulation, &simmove);
+            if (!is_covered(&simulation, kingrow, kingfile,
+                    opponent_color(color))) {
+                result = 1;
+                if (threats && threatcount) {
+                    threats[(*threatcount)++] = candidates[i];
+                }
+            }
+        }
+        
+        return result;
+    } else {
+        return 0;
+    }
+}
+
+static int getlocation(GameState *gamestate, Move *move) {   
+
+    uint8_t color = move->piece & COLOR_MASK;
+    _Bool incheck = gamestate->lastmove?gamestate->lastmove->move.check:0;
+    
+    Move threats[16], *threat = NULL;
+    uint8_t threatcount;
+    
+    if (get_threats(gamestate, move->torow, move->tofile, color,
+            threats, &threatcount)) {
+        
+        int reason = INVALID_POSITION;
+        
+        // find threats for the specified position
+        for (uint8_t i = 0 ; i < threatcount ; i++) {
+            if ((threats[i].piece & (PIECE_MASK | COLOR_MASK))
+                    == move->piece &&
+                    (move->fromrow == POS_UNSPECIFIED ||
+                    move->fromrow == threats[i].fromrow) &&
+                    (move->fromfile == POS_UNSPECIFIED ||
+                    move->fromfile == threats[i].fromfile)) {
+
+                if (threat) {
+                    return AMBIGUOUS_MOVE;
+                } else {
+                    // found threat is no real threat
+                    if (is_pinned(gamestate, &(threats[i]))) {
+                        reason = incheck?KING_IN_CHECK:PIECE_PINNED;
+                    } else {
+                        threat = &(threats[i]);
+                    }
+                }
+            }
+        }
+        
+        // can't threaten specified position
+        if (!threat) {
+            return reason;
+        }
+
+        memcpy(move, threat, sizeof(Move));
+        return VALID_MOVE_SYNTAX;
+    } else {
+        return INVALID_POSITION;
+    }
+}
+
+int eval_move(GameState *gamestate, char *mstr, Move *move, uint8_t color) {
+    memset(move, 0, sizeof(Move));
+    move->fromfile = POS_UNSPECIFIED;
+    move->fromrow = POS_UNSPECIFIED;
+
+    size_t len = strlen(mstr);
+    if (len < 1 || len > 6) {
+        return INVALID_MOVE_SYNTAX;
+    }
+    
+    /* evaluate check/checkmate flags */
+    if (mstr[len-1] == '+') {
+        len--; mstr[len] = '\0';
+        move->check = 1;
+    } else if (mstr[len-1] == '#') {
+        len--; mstr[len] = '\0';
+        /* ignore - validation should set game state */
+    }
+    
+    /* evaluate promotion */
+    if (len > 3 && mstr[len-2] == '=') {
+        move->promotion = getpiece(mstr[len-1]);
+        if (!move->promotion) {
+            return INVALID_MOVE_SYNTAX;
+        } else {
+            move->promotion |= color;
+            len -= 2;
+            mstr[len] = 0;
+        }
+    }
+    
+    if (len == 2) {
+        /* pawn move (e.g. "e4") */
+        move->piece = PAWN;
+        move->tofile = fileidx(mstr[0]);
+        move->torow = rowidx(mstr[1]);
+    } else if (len == 3) {
+        if (strcmp(mstr, "O-O") == 0) {
+            /* king side castling */
+            move->piece = KING;
+            move->fromfile = fileidx('e');
+            move->tofile = fileidx('g');
+            move->fromrow = move->torow = color == WHITE ? 0 : 7;
+        } else {
+            /* move (e.g. "Nf3") */
+            move->piece = getpiece(mstr[0]);
+            move->tofile = fileidx(mstr[1]);
+            move->torow = rowidx(mstr[2]);
+        }
+    } else if (len == 4) {
+        move->piece = getpiece(mstr[0]);
+        if (!move->piece) {
+            move->piece = PAWN;
+            move->fromfile = fileidx(mstr[0]);
+        }
+        if (mstr[1] == 'x') {
+            /* capture (e.g. "Nxf3", "dxe5") */
+            move->capture = 1;
+        } else {
+            /* move (e.g. "Ndf3", "N2c3", "e2e4") */
+            if (isfile(mstr[1])) {
+                move->fromfile = fileidx(mstr[1]);
+                if (move->piece == PAWN) {
+                    move->piece = 0;
+                }
+            } else {
+                move->fromrow = rowidx(mstr[1]);
+            }
+        }
+        move->tofile = fileidx(mstr[2]);
+        move->torow = rowidx(mstr[3]);
+    } else if (len == 5) {
+        if (strcmp(mstr, "O-O-O") == 0) {
+            /* queen side castling "O-O-O" */
+            move->piece = KING;
+            move->fromfile = fileidx('e');
+            move->tofile = fileidx('c');
+            move->fromrow = move->torow = color == WHITE ? 0 : 7;
+        } else {
+            move->piece = getpiece(mstr[0]);
+            if (mstr[2] == 'x') {
+                move->capture = 1;
+                if (move->piece) {
+                    /* capture (e.g. "Ndxf3") */
+                    move->fromfile = fileidx(mstr[1]);
+                } else {
+                    /* long notation capture (e.g. "e5xf6") */
+                    move->piece = PAWN;
+                    move->fromfile = fileidx(mstr[0]);
+                    move->fromrow = rowidx(mstr[1]);
+                }
+            } else {
+                /* long notation move (e.g. "Nc5a4") */
+                move->fromfile = fileidx(mstr[1]);
+                move->fromrow = rowidx(mstr[2]);
+            }
+            move->tofile = fileidx(mstr[3]);
+            move->torow = rowidx(mstr[4]);
+        }
+    } else if (len == 6) {
+        /* long notation capture (e.g. "Nc5xf3") */
+        if (mstr[3] == 'x') {
+            move->capture = 1;
+            move->piece = getpiece(mstr[0]);
+            move->fromfile = fileidx(mstr[1]);
+            move->fromrow = rowidx(mstr[2]);
+            move->tofile = fileidx(mstr[4]);
+            move->torow = rowidx(mstr[5]);
+        }
+    }
+
+    
+    if (move->piece) {
+        if (move->piece == PAWN
+            && move->torow == (color==WHITE?7:0)
+            && !move->promotion) {
+            return NEED_PROMOTION;
+        }
+        
+        move->piece |= color;
+        if (move->fromfile == POS_UNSPECIFIED
+            || move->fromrow == POS_UNSPECIFIED) {
+            return getlocation(gamestate, move);
+        } else {
+            return chkidx(move) ? VALID_MOVE_SYNTAX : INVALID_POSITION;
+        }
+    } else {
+        return INVALID_MOVE_SYNTAX;
+    }
+}
+
+_Bool is_protected(GameState *gamestate, uint8_t row, uint8_t file,
+        uint8_t color) {
+    
+    Move threats[16];
+    uint8_t threatcount;
+    if (get_real_threats(gamestate, row, file, color, threats, &threatcount)) {
+        for (int i = 0 ; i < threatcount ; i++) {
+            if (threats[i].piece != (color|KING)) {
+                return 1;
+            }
+        }
+        return 0;
+    } else {
+        return 0;
+    }
+}
+
+uint16_t remaining_movetime(GameInfo *gameinfo, GameState *gamestate,
+        uint8_t color) {
+    if (!gameinfo->timecontrol) {
+        return 0;
+    }
+    
+    if (gamestate->movelist) {
+        uint16_t time = gameinfo->time;
+        suseconds_t micros = 0;
+        
+        MoveList *movelist = color == WHITE ?
+            gamestate->movelist : gamestate->movelist->next;
+        
+        while (movelist) {
+            time += gameinfo->addtime;
+            
+            struct movetimeval *movetime = &(movelist->move.movetime);
+            if (movetime->tv_sec >= time) {
+                return 0;
+            }
+            
+            time -= movetime->tv_sec;
+            micros += movetime->tv_usec;
+            
+            movelist = movelist->next ? movelist->next->next : NULL;
+        }
+        
+        time_t sec;
+        movelist = gamestate->lastmove;
+        if ((movelist->move.piece & COLOR_MASK) != color) {
+            struct movetimeval *lastmovetstamp = &(movelist->move.timestamp);
+            struct timeval currenttstamp;
+            gettimeofday(&currenttstamp, NULL);
+            micros += currenttstamp.tv_usec - lastmovetstamp->tv_usec;
+            sec = currenttstamp.tv_sec - lastmovetstamp->tv_sec;
+            if (sec >= time) {
+                return 0;
+            }
+            
+            time -= sec;
+        }
+        
+        sec = micros / 1e6L;
+        
+        if (sec >= time) {
+            return 0;
+        }
+
+        time -= sec;
+        
+        return time;
+    } else {
+        return gameinfo->time;
+    }
+}
--- a/test/bigtestfile.c	Mon Apr 24 20:54:38 2023 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,808 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2014 Mike Becker. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- *   1. Redistributions of source code must retain the above copyright
- *      notice, this list of conditions and the following disclaimer.
- *
- *   2. Redistributions in binary form must reproduce the above copyright
- *      notice, this list of conditions and the following disclaimer in the
- *      documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *
- */
-
-#include "rules.h"
-#include "chess.h"
-#include <string.h>
-#include <stdlib.h>
-#include <sys/time.h>
-
-static GameState gamestate_copy_sim(GameState *gamestate) {
-    GameState simulation = *gamestate;
-    if (simulation.lastmove) {
-        MoveList *lastmovecopy = malloc(sizeof(MoveList));
-        *lastmovecopy = *(simulation.lastmove);
-        simulation.movelist = simulation.lastmove = lastmovecopy;
-    }
-
-    return simulation;
-}
-
-void gamestate_init(GameState *gamestate) {
-    memset(gamestate, 0, sizeof(GameState));
-    
-    Board initboard = {
-        {WROOK, WKNIGHT, WBISHOP, WQUEEN, WKING, WBISHOP, WKNIGHT, WROOK},
-        {WPAWN, WPAWN,   WPAWN,   WPAWN,  WPAWN, WPAWN,   WPAWN,   WPAWN},
-        {0,     0,       0,       0,      0,     0,       0,       0},
-        {0,     0,       0,       0,      0,     0,       0,       0},
-        {0,     0,       0,       0,      0,     0,       0,       0},
-        {0,     0,       0,       0,      0,     0,       0,       0},
-        {BPAWN, BPAWN,   BPAWN,   BPAWN,  BPAWN, BPAWN,   BPAWN,   BPAWN},
-        {BROOK, BKNIGHT, BBISHOP, BQUEEN, BKING, BBISHOP, BKNIGHT, BROOK}
-    };
-    memcpy(gamestate->board, initboard, sizeof(Board));
-}
-
-void gamestate_cleanup(GameState *gamestate) {
-    MoveList *elem;
-    elem = gamestate->movelist;
-    while (elem) {
-        MoveList *cur = elem;
-        elem = elem->next;
-        free(cur);
-    };
-}
-
-/* MUST be called IMMEDIATLY after applying a move to work correctly */
-static void format_move(GameState *gamestate, Move *move) {
-    char *string = move->string;
-    
-    /* at least 8 characters should be available, wipe them out */
-    memset(string, 0, 8);
-    
-    /* special formats for castling */
-    if ((move->piece&PIECE_MASK) == KING &&
-            abs(move->tofile-move->fromfile) == 2) {
-        if (move->tofile==fileidx('c')) {
-            memcpy(string, "O-O-O", 5);
-        } else {
-            memcpy(string, "O-O", 3);
-        }
-    }
-
-    /* start by notating the piece character */
-    string[0] = getpiecechr(move->piece);
-    int idx = string[0] ? 1 : 0;
-    
-    /* find out how many source information we do need */
-    uint8_t piece = move->piece & PIECE_MASK;
-    if (piece == PAWN) {
-        if (move->capture) {
-            string[idx++] = filechr(move->fromfile);
-        }
-    } else if (piece != KING) {
-        Move threats[16];
-        uint8_t threatcount;
-        get_real_threats(gamestate, move->torow, move->tofile,
-            move->piece&COLOR_MASK, threats, &threatcount);
-        if (threatcount > 1) {
-            int ambrows = 0, ambfiles = 0;
-            for (uint8_t i = 0 ; i < threatcount ; i++) {
-                if (threats[i].fromrow == move->fromrow) {
-                    ambrows++;
-                }
-                if (threats[i].fromfile == move->fromfile) {
-                    ambfiles++;
-                }
-            }
-            /* ambiguous row, name file */
-            if (ambrows > 1) {
-                string[idx++] = filechr(move->fromfile);
-            }
-            /* ambiguous file, name row */
-            if (ambfiles > 1) {
-                string[idx++] = filechr(move->fromrow);
-            }
-        }
-    }
-    
-    /* capturing? */
-    if (move->capture) {
-        string[idx++] = 'x';
-    }
-    
-    /* destination */
-    string[idx++] = filechr(move->tofile);
-    string[idx++] = rowchr(move->torow);
-    
-    /* promotion? */
-    if (move->promotion) {
-        string[idx++] = '=';
-        string[idx++] = getpiecechr(move->promotion);
-    }
-    
-    /* check? */
-    if (move->check) {
-        /* works only, if this function is called when applying the move */
-        string[idx++] = gamestate->checkmate?'#':'+';
-    }
-}
-
-static void addmove(GameState* gamestate, Move *move) {
-    MoveList *elem = malloc(sizeof(MoveList));
-    elem->next = NULL;
-    elem->move = *move;
-    
-    struct timeval curtimestamp;
-    gettimeofday(&curtimestamp, NULL);
-    elem->move.timestamp.tv_sec = curtimestamp.tv_sec;
-    elem->move.timestamp.tv_usec = curtimestamp.tv_usec;
-    
-    if (gamestate->lastmove) {
-        struct movetimeval *lasttstamp = &(gamestate->lastmove->move.timestamp);
-        uint64_t sec = curtimestamp.tv_sec - lasttstamp->tv_sec;
-        suseconds_t micros;
-        if (curtimestamp.tv_usec < lasttstamp->tv_usec) {
-            micros = 1e6L-(lasttstamp->tv_usec - curtimestamp.tv_usec);
-            sec--;
-        } else {
-            micros = curtimestamp.tv_usec - lasttstamp->tv_usec;
-        }
-        
-        elem->move.movetime.tv_sec = sec;
-        elem->move.movetime.tv_usec = micros;
-        
-        gamestate->lastmove->next = elem;
-        gamestate->lastmove = elem;
-    } else {
-        elem->move.movetime.tv_usec = 0;
-        elem->move.movetime.tv_sec = 0;
-        gamestate->movelist = gamestate->lastmove = elem;
-    }
-}
-
-char getpiecechr(uint8_t piece) {
-    switch (piece & PIECE_MASK) {
-    case ROOK: return 'R';
-    case KNIGHT: return 'N';
-    case BISHOP: return 'B';
-    case QUEEN: return 'Q';
-    case KING: return 'K';
-    default: return '\0';
-    }
-}
-
-uint8_t getpiece(char c) {
-    switch (c) {
-        case 'R': return ROOK;
-        case 'N': return KNIGHT;
-        case 'B': return BISHOP;
-        case 'Q': return QUEEN;
-        case 'K': return KING;
-        default: return 0;
-    }
-}
-
-static void apply_move_impl(GameState *gamestate, Move *move, _Bool simulate) {
-    uint8_t piece = move->piece & PIECE_MASK;
-    uint8_t color = move->piece & COLOR_MASK;
-    
-    /* en passant capture */
-    if (move->capture && piece == PAWN &&
-        mdst(gamestate->board, move) == 0) {
-        gamestate->board[move->fromrow][move->tofile] = 0;
-    }
-    
-    /* remove old en passant threats */
-    for (uint8_t file = 0 ; file < 8 ; file++) {
-        gamestate->board[3][file] &= ~ENPASSANT_THREAT;
-        gamestate->board[4][file] &= ~ENPASSANT_THREAT;
-    }
-    
-    /* add new en passant threat */
-    if (piece == PAWN && (
-        (move->fromrow == 1 && move->torow == 3) ||
-        (move->fromrow == 6 && move->torow == 4))) {
-        move->piece |= ENPASSANT_THREAT;
-    }
-    
-    /* move (and maybe capture or promote) */
-    msrc(gamestate->board, move) = 0;
-    if (move->promotion) {
-        mdst(gamestate->board, move) = move->promotion;
-    } else {
-        mdst(gamestate->board, move) = move->piece;
-    }
-    
-    /* castling */
-    if (piece == KING && move->fromfile == fileidx('e')) {
-        
-        if (move->tofile == fileidx('g')) {
-            gamestate->board[move->torow][fileidx('h')] = 0;
-            gamestate->board[move->torow][fileidx('f')] = color|ROOK;
-        } else if (move->tofile == fileidx('c')) {
-            gamestate->board[move->torow][fileidx('a')] = 0;
-            gamestate->board[move->torow][fileidx('d')] = color|ROOK;
-        }
-    }
-
-    if (!simulate) {
-        if (!move->string[0]) {
-            format_move(gamestate, move);
-        }
-    }
-    /* add move, even in simulation (checkmate test needs it) */
-    addmove(gamestate, move);
-}
-
-void apply_move(GameState *gamestate, Move *move) {
-    apply_move_impl(gamestate, move, 0);
-}
-
-static int validate_move_rules(GameState *gamestate, Move *move) {
-    /* validate indices (don't trust opponent) */
-    if (!chkidx(move)) {
-        return INVALID_POSITION;
-    }
-    
-    /* must move */
-    if (move->fromfile == move->tofile && move->fromrow == move->torow) {
-        return INVALID_MOVE_SYNTAX;
-    }
-    
-    /* does piece exist */
-    if ((msrc(gamestate->board, move)&(PIECE_MASK|COLOR_MASK))
-           != (move->piece&(PIECE_MASK|COLOR_MASK))) {
-        return INVALID_POSITION;
-    }
-    
-    /* can't capture own pieces */
-    if ((mdst(gamestate->board, move) & COLOR_MASK)
-            == (move->piece & COLOR_MASK)) {
-        return RULES_VIOLATED;
-    }
-    
-    /* must capture, if and only if destination is occupied */
-    if ((mdst(gamestate->board, move) == 0 && move->capture) ||
-            (mdst(gamestate->board, move) != 0 && !move->capture)) {
-        return INVALID_MOVE_SYNTAX;
-    }
-    
-    /* validate individual rules */
-    _Bool chkrules;
-    switch (move->piece & PIECE_MASK) {
-    case PAWN:
-        chkrules = pawn_chkrules(gamestate, move) &&
-            !pawn_isblocked(gamestate, move);
-        break;
-    case ROOK:
-        chkrules = rook_chkrules(move) &&
-            !rook_isblocked(gamestate, move);
-        break;
-    case KNIGHT:
-        chkrules = knight_chkrules(move); /* knight is never blocked */
-        break;
-    case BISHOP:
-        chkrules = bishop_chkrules(move) &&
-            !bishop_isblocked(gamestate, move);
-        break;
-    case QUEEN:
-        chkrules = queen_chkrules(move) &&
-            !queen_isblocked(gamestate, move);
-        break;
-    case KING:
-        chkrules = king_chkrules(gamestate, move) &&
-            !king_isblocked(gamestate, move);
-        break;
-    default:
-        return INVALID_MOVE_SYNTAX;
-    }
-    
-    return chkrules ? VALID_MOVE_SEMANTICS : RULES_VIOLATED;
-}
-
-int validate_move(GameState *gamestate, Move *move) {
-    
-    int result = validate_move_rules(gamestate, move);
-    
-    /* cancel processing to save resources */
-    if (result != VALID_MOVE_SEMANTICS) {
-        return result;
-    }
-    
-    /* find kings for check validation */
-    uint8_t piececolor = (move->piece & COLOR_MASK);
-    
-    uint8_t mykingfile = 0, mykingrow = 0, opkingfile = 0, opkingrow = 0;
-    for (uint8_t row = 0 ; row < 8 ; row++) {
-        for (uint8_t file = 0 ; file < 8 ; file++) {
-            if (gamestate->board[row][file] ==
-                    (piececolor == WHITE?WKING:BKING)) {
-                mykingfile = file;
-                mykingrow = row;
-            } else if (gamestate->board[row][file] ==
-                    (piececolor == WHITE?BKING:WKING)) {
-                opkingfile = file;
-                opkingrow = row;
-            }
-        }
-    }
-    
-    /* simulate move for check validation */
-    GameState simulation = gamestate_copy_sim(gamestate);
-    Move simmove = *move;
-    apply_move_impl(&simulation, &simmove, 1);
-    
-    /* don't move into or stay in check position */
-    if (is_covered(&simulation, mykingrow, mykingfile,
-        opponent_color(piececolor))) {
-        
-        gamestate_cleanup(&simulation);
-        if ((move->piece & PIECE_MASK) == KING) {
-            return KING_MOVES_INTO_CHECK;
-        } else {
-            /* last move is always not null in this case */
-            return gamestate->lastmove->move.check ?
-                KING_IN_CHECK : PIECE_PINNED;
-        }
-    }
-    
-    /* correct check and checkmate flags (move is still valid) */
-    Move threats[16];
-    uint8_t threatcount;
-    move->check = get_threats(&simulation, opkingrow, opkingfile,
-        piececolor, threats, &threatcount);
-    
-    if (move->check) {
-        /* determine possible escape fields */
-        _Bool canescape = 0;
-        for (int dr = -1 ; dr <= 1 && !canescape ; dr++) {
-            for (int df = -1 ; df <= 1 && !canescape ; df++) {
-                if (!(dr == 0 && df == 0)  &&
-                        isidx(opkingrow + dr) && isidx(opkingfile + df)) {
-                    
-                    /* escape field neither blocked nor covered */
-                    if ((simulation.board[opkingrow + dr][opkingfile + df]
-                            & COLOR_MASK) != opponent_color(piececolor)) {
-                        canescape |= !is_covered(&simulation,
-                            opkingrow + dr, opkingfile + df, piececolor);
-                    }
-                }
-            }
-        }
-        /* can't escape, can he capture? */
-        if (!canescape && threatcount == 1) {
-            canescape = is_attacked(&simulation, threats[0].fromrow,
-                threats[0].fromfile, opponent_color(piececolor));
-        }
-        
-        /* can't capture, can he block? */
-        if (!canescape && threatcount == 1) {
-            Move *threat = &(threats[0]);
-            uint8_t threatpiece = threat->piece & PIECE_MASK;
-            
-            /* knight, pawns and the king cannot be blocked */
-            if (threatpiece == BISHOP || threatpiece == ROOK
-                || threatpiece == QUEEN) {
-                if (threat->fromrow == threat->torow) {
-                    /* rook aspect (on row) */
-                    int d = threat->tofile > threat->fromfile ? 1 : -1;
-                    uint8_t file = threat->fromfile;
-                    while (!canescape && file != threat->tofile - d) {
-                        file += d;
-                        canescape |= is_protected(&simulation,
-                            threat->torow, file, opponent_color(piececolor));
-                    }
-                } else if (threat->fromfile == threat->tofile) {
-                    /* rook aspect (on file) */
-                    int d = threat->torow > threat->fromrow ? 1 : -1;
-                    uint8_t row = threat->fromrow;
-                    while (!canescape && row != threat->torow - d) {
-                        row += d;
-                        canescape |= is_protected(&simulation,
-                            row, threat->tofile, opponent_color(piececolor));
-                    }
-                } else {
-                    /* bishop aspect */
-                    int dr = threat->torow > threat->fromrow ? 1 : -1;
-                    int df = threat->tofile > threat->fromfile ? 1 : -1;
-
-                    uint8_t row = threat->fromrow;
-                    uint8_t file = threat->fromfile;
-                    while (!canescape && file != threat->tofile - df
-                        && row != threat->torow - dr) {
-                        row += dr;
-                        file += df;
-                        canescape |= is_protected(&simulation, row, file,
-                            opponent_color(piececolor));
-                    }
-                }
-            }
-        }
-            
-        if (!canescape) {
-            gamestate->checkmate = 1;
-        }
-    }
-    
-    gamestate_cleanup(&simulation);
-    
-    return VALID_MOVE_SEMANTICS;
-}
-
-_Bool get_threats(GameState *gamestate, uint8_t row, uint8_t file,
-        uint8_t color, Move *threats, uint8_t *threatcount) {
-    Move candidates[32];
-    int candidatecount = 0;
-    for (uint8_t r = 0 ; r < 8 ; r++) {
-        for (uint8_t f = 0 ; f < 8 ; f++) {
-            if ((gamestate->board[r][f] & COLOR_MASK) == color) {
-                // non-capturing move
-                memset(&(candidates[candidatecount]), 0, sizeof(Move));
-                candidates[candidatecount].piece = gamestate->board[r][f];
-                candidates[candidatecount].fromrow = r;
-                candidates[candidatecount].fromfile = f;
-                candidates[candidatecount].torow = row;
-                candidates[candidatecount].tofile = file;
-                candidatecount++;
-
-                // capturing move
-                memcpy(&(candidates[candidatecount]),
-                    &(candidates[candidatecount-1]), sizeof(Move));
-                candidates[candidatecount].capture = 1;
-                candidatecount++;
-            }
-        }
-    }
-
-    if (threatcount) {
-        *threatcount = 0;
-    }
-    
-    
-    _Bool result = 0;
-    
-    for (int i = 0 ; i < candidatecount ; i++) {
-        if (validate_move_rules(gamestate, &(candidates[i]))
-                == VALID_MOVE_SEMANTICS) {
-            result = 1;
-            if (threats && threatcount) {
-                threats[(*threatcount)++] = candidates[i];
-            }
-        }
-    }
-    
-    return result;
-}
-
-_Bool is_pinned(GameState *gamestate, Move *move) {
-    uint8_t color = move->piece & COLOR_MASK;
-
-    uint8_t kingfile = 0, kingrow = 0;
-    for (uint8_t row = 0 ; row < 8 ; row++) {
-        for (uint8_t file = 0 ; file < 8 ; file++) {
-            if (gamestate->board[row][file] == (color|KING)) {
-                kingfile = file;
-                kingrow = row;
-            }
-        }
-    }
-
-    GameState simulation = gamestate_copy_sim(gamestate);
-    Move simmove = *move;
-    apply_move(&simulation, &simmove);
-    _Bool covered = is_covered(&simulation,
-        kingrow, kingfile, opponent_color(color));
-    gamestate_cleanup(&simulation);
-    
-    return covered;
-}
-
-_Bool get_real_threats(GameState *gamestate, uint8_t row, uint8_t file,
-        uint8_t color, Move *threats, uint8_t *threatcount) {
-    
-    if (threatcount) {
-        *threatcount = 0;
-    }
-
-    Move candidates[16];
-    uint8_t candidatecount;
-    if (get_threats(gamestate, row, file, color, candidates, &candidatecount)) {
-        
-        _Bool result = 0;
-        uint8_t kingfile = 0, kingrow = 0;
-        for (uint8_t row = 0 ; row < 8 ; row++) {
-            for (uint8_t file = 0 ; file < 8 ; file++) {
-                if (gamestate->board[row][file] == (color|KING)) {
-                    kingfile = file;
-                    kingrow = row;
-                }
-            }
-        }
-
-        for (uint8_t i = 0 ; i < candidatecount ; i++) {
-            GameState simulation = gamestate_copy_sim(gamestate);
-            Move simmove = candidates[i];
-            apply_move(&simulation, &simmove);
-            if (!is_covered(&simulation, kingrow, kingfile,
-                    opponent_color(color))) {
-                result = 1;
-                if (threats && threatcount) {
-                    threats[(*threatcount)++] = candidates[i];
-                }
-            }
-        }
-        
-        return result;
-    } else {
-        return 0;
-    }
-}
-
-static int getlocation(GameState *gamestate, Move *move) {   
-
-    uint8_t color = move->piece & COLOR_MASK;
-    _Bool incheck = gamestate->lastmove?gamestate->lastmove->move.check:0;
-    
-    Move threats[16], *threat = NULL;
-    uint8_t threatcount;
-    
-    if (get_threats(gamestate, move->torow, move->tofile, color,
-            threats, &threatcount)) {
-        
-        int reason = INVALID_POSITION;
-        
-        // find threats for the specified position
-        for (uint8_t i = 0 ; i < threatcount ; i++) {
-            if ((threats[i].piece & (PIECE_MASK | COLOR_MASK))
-                    == move->piece &&
-                    (move->fromrow == POS_UNSPECIFIED ||
-                    move->fromrow == threats[i].fromrow) &&
-                    (move->fromfile == POS_UNSPECIFIED ||
-                    move->fromfile == threats[i].fromfile)) {
-
-                if (threat) {
-                    return AMBIGUOUS_MOVE;
-                } else {
-                    // found threat is no real threat
-                    if (is_pinned(gamestate, &(threats[i]))) {
-                        reason = incheck?KING_IN_CHECK:PIECE_PINNED;
-                    } else {
-                        threat = &(threats[i]);
-                    }
-                }
-            }
-        }
-        
-        // can't threaten specified position
-        if (!threat) {
-            return reason;
-        }
-
-        memcpy(move, threat, sizeof(Move));
-        return VALID_MOVE_SYNTAX;
-    } else {
-        return INVALID_POSITION;
-    }
-}
-
-int eval_move(GameState *gamestate, char *mstr, Move *move, uint8_t color) {
-    memset(move, 0, sizeof(Move));
-    move->fromfile = POS_UNSPECIFIED;
-    move->fromrow = POS_UNSPECIFIED;
-
-    size_t len = strlen(mstr);
-    if (len < 1 || len > 6) {
-        return INVALID_MOVE_SYNTAX;
-    }
-    
-    /* evaluate check/checkmate flags */
-    if (mstr[len-1] == '+') {
-        len--; mstr[len] = '\0';
-        move->check = 1;
-    } else if (mstr[len-1] == '#') {
-        len--; mstr[len] = '\0';
-        /* ignore - validation should set game state */
-    }
-    
-    /* evaluate promotion */
-    if (len > 3 && mstr[len-2] == '=') {
-        move->promotion = getpiece(mstr[len-1]);
-        if (!move->promotion) {
-            return INVALID_MOVE_SYNTAX;
-        } else {
-            move->promotion |= color;
-            len -= 2;
-            mstr[len] = 0;
-        }
-    }
-    
-    if (len == 2) {
-        /* pawn move (e.g. "e4") */
-        move->piece = PAWN;
-        move->tofile = fileidx(mstr[0]);
-        move->torow = rowidx(mstr[1]);
-    } else if (len == 3) {
-        if (strcmp(mstr, "O-O") == 0) {
-            /* king side castling */
-            move->piece = KING;
-            move->fromfile = fileidx('e');
-            move->tofile = fileidx('g');
-            move->fromrow = move->torow = color == WHITE ? 0 : 7;
-        } else {
-            /* move (e.g. "Nf3") */
-            move->piece = getpiece(mstr[0]);
-            move->tofile = fileidx(mstr[1]);
-            move->torow = rowidx(mstr[2]);
-        }
-    } else if (len == 4) {
-        move->piece = getpiece(mstr[0]);
-        if (!move->piece) {
-            move->piece = PAWN;
-            move->fromfile = fileidx(mstr[0]);
-        }
-        if (mstr[1] == 'x') {
-            /* capture (e.g. "Nxf3", "dxe5") */
-            move->capture = 1;
-        } else {
-            /* move (e.g. "Ndf3", "N2c3", "e2e4") */
-            if (isfile(mstr[1])) {
-                move->fromfile = fileidx(mstr[1]);
-                if (move->piece == PAWN) {
-                    move->piece = 0;
-                }
-            } else {
-                move->fromrow = rowidx(mstr[1]);
-            }
-        }
-        move->tofile = fileidx(mstr[2]);
-        move->torow = rowidx(mstr[3]);
-    } else if (len == 5) {
-        if (strcmp(mstr, "O-O-O") == 0) {
-            /* queen side castling "O-O-O" */
-            move->piece = KING;
-            move->fromfile = fileidx('e');
-            move->tofile = fileidx('c');
-            move->fromrow = move->torow = color == WHITE ? 0 : 7;
-        } else {
-            move->piece = getpiece(mstr[0]);
-            if (mstr[2] == 'x') {
-                move->capture = 1;
-                if (move->piece) {
-                    /* capture (e.g. "Ndxf3") */
-                    move->fromfile = fileidx(mstr[1]);
-                } else {
-                    /* long notation capture (e.g. "e5xf6") */
-                    move->piece = PAWN;
-                    move->fromfile = fileidx(mstr[0]);
-                    move->fromrow = rowidx(mstr[1]);
-                }
-            } else {
-                /* long notation move (e.g. "Nc5a4") */
-                move->fromfile = fileidx(mstr[1]);
-                move->fromrow = rowidx(mstr[2]);
-            }
-            move->tofile = fileidx(mstr[3]);
-            move->torow = rowidx(mstr[4]);
-        }
-    } else if (len == 6) {
-        /* long notation capture (e.g. "Nc5xf3") */
-        if (mstr[3] == 'x') {
-            move->capture = 1;
-            move->piece = getpiece(mstr[0]);
-            move->fromfile = fileidx(mstr[1]);
-            move->fromrow = rowidx(mstr[2]);
-            move->tofile = fileidx(mstr[4]);
-            move->torow = rowidx(mstr[5]);
-        }
-    }
-
-    
-    if (move->piece) {
-        if (move->piece == PAWN
-            && move->torow == (color==WHITE?7:0)
-            && !move->promotion) {
-            return NEED_PROMOTION;
-        }
-        
-        move->piece |= color;
-        if (move->fromfile == POS_UNSPECIFIED
-            || move->fromrow == POS_UNSPECIFIED) {
-            return getlocation(gamestate, move);
-        } else {
-            return chkidx(move) ? VALID_MOVE_SYNTAX : INVALID_POSITION;
-        }
-    } else {
-        return INVALID_MOVE_SYNTAX;
-    }
-}
-
-_Bool is_protected(GameState *gamestate, uint8_t row, uint8_t file,
-        uint8_t color) {
-    
-    Move threats[16];
-    uint8_t threatcount;
-    if (get_real_threats(gamestate, row, file, color, threats, &threatcount)) {
-        for (int i = 0 ; i < threatcount ; i++) {
-            if (threats[i].piece != (color|KING)) {
-                return 1;
-            }
-        }
-        return 0;
-    } else {
-        return 0;
-    }
-}
-
-uint16_t remaining_movetime(GameInfo *gameinfo, GameState *gamestate,
-        uint8_t color) {
-    if (!gameinfo->timecontrol) {
-        return 0;
-    }
-    
-    if (gamestate->movelist) {
-        uint16_t time = gameinfo->time;
-        suseconds_t micros = 0;
-        
-        MoveList *movelist = color == WHITE ?
-            gamestate->movelist : gamestate->movelist->next;
-        
-        while (movelist) {
-            time += gameinfo->addtime;
-            
-            struct movetimeval *movetime = &(movelist->move.movetime);
-            if (movetime->tv_sec >= time) {
-                return 0;
-            }
-            
-            time -= movetime->tv_sec;
-            micros += movetime->tv_usec;
-            
-            movelist = movelist->next ? movelist->next->next : NULL;
-        }
-        
-        time_t sec;
-        movelist = gamestate->lastmove;
-        if ((movelist->move.piece & COLOR_MASK) != color) {
-            struct movetimeval *lastmovetstamp = &(movelist->move.timestamp);
-            struct timeval currenttstamp;
-            gettimeofday(&currenttstamp, NULL);
-            micros += currenttstamp.tv_usec - lastmovetstamp->tv_usec;
-            sec = currenttstamp.tv_sec - lastmovetstamp->tv_sec;
-            if (sec >= time) {
-                return 0;
-            }
-            
-            time -= sec;
-        }
-        
-        sec = micros / 1e6L;
-        
-        if (sec >= time) {
-            return 0;
-        }
-
-        time -= sec;
-        
-        return time;
-    } else {
-        return gameinfo->time;
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/ctest.c	Mon Apr 24 21:01:41 2023 +0200
@@ -0,0 +1,391 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2015 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <time.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ucx/string.h>
+#include <ucx/buffer.h>
+#include <ucx/utils.h>
+#include <libxml/tree.h>
+#include <curl/curl.h>
+
+#include <openssl/sha.h>
+#include <openssl/hmac.h>
+#include <openssl/evp.h>
+#include <openssl/bio.h>
+#include <openssl/buffer.h>
+#include <openssl/rand.h>
+
+#include "utils.h"
+#include "crypto.h"
+#include "webdav.h"
+
+#define MACRO1337 1337L
+
+/* -------------------- This is a testing file. -------------------------- */
+/*
+time_t util_parse_creationdate(char *str) {
+    // example: 2012-11-29T21:35:35Z
+    if(!str) {
+        return 0;
+    }
+    // TODO
+    return 0;
+}
+*/
+time_t util_parse_lastmodified(char *str) {
+    // example: Thu, 29 Nov 2012 21:35:35 GMT
+    if(!str) {
+        return 0;
+    } else {
+        return curl_getdate(str, NULL);
+    }
+}
+
+int util_getboolean(char *v) {
+    if(v[0] == 'T' || v[0] == 't') {
+        return 1;
+    }
+    return 0;
+}
+
+int util_strtoint(char *str, int64_t *value) {
+    char *end;
+    int64_t val = strtoll(str, &end, 0);
+    if(strlen(end) == 0) {
+        *value = val;
+        return 1;
+    } else {
+        return 0;
+    }
+}
+
+char* util_url_path(char *url) { 
+    char *path = NULL;
+    size_t len = strlen(url);
+    int slashcount = 0;
+    int slmax;
+    if(len > 7 && !strncasecmp(url, "http://", 7)) {
+        slmax = 3;
+    } else if(len > 8 && !strncasecmp(url, "https://", 8)) {
+        slmax = 3;
+    } else {
+        slmax = 1;
+    }
+    char c;
+    for(int i=0;i<len;i++) {
+        c = url[i];
+        if(c == '/') {
+            slashcount++;
+            if(slashcount == slmax) {
+                path = url + i;
+                break;
+            }
+        }
+    } 
+    return path;
+}
+
+char* util_url_decode(DavSession *sn, char *url) {
+    char *unesc = curl_easy_unescape(sn->handle, url, strlen(url), NULL);
+    char *ret = strdup(unesc);
+    curl_free(unesc);
+    return ret;
+}
+
+char* util_resource_name(char *url) {
+    int si = 0;
+    int osi = 0;
+    int i = 0;
+    int p = 0;
+    char c;
+    while((c = url[i]) != 0) {
+        if(c == '/') {
+            osi = si;
+            si = i;
+            p = 1;
+        }
+        i++;
+    }
+    
+    char *name = url + si + p;
+    if(name[0] == 0) {
+        name = url + osi + p;
+        if(name[0] == 0) {
+            return url;
+        }
+    }
+    
+    return name;
+}
+
+int util_mkdir(char *path, mode_t mode) {
+#ifdef _WIN32
+    return mkdir(path);
+#else
+    return mkdir(path, mode);
+#endif
+}
+
+char* util_concat_path(char *url_base, char *p) {
+    sstr_t base = sstr(url_base);
+    sstr_t path;
+    if(p) {
+        path = sstr(p);
+    } else {
+        path = sstrn("", 0);
+    }
+    
+    int add_separator = 0;
+    if(base.ptr[base.length-1] == '/') {
+        if(path.ptr[0] == '/') {
+            base.length--;
+        }
+    } else {
+        if(path.length == 0 || path.ptr[0] != '/') {
+            add_separator = 1;
+        }
+    }
+    
+    sstr_t url;
+    if(add_separator) {
+        url = sstrcat(3, base, sstr("/"), path);
+    } else {
+        url = sstrcat(2, base, path);
+    }
+    
+    return url.ptr;
+}
+
+void util_set_url(DavSession *sn, char *href) {
+    sstr_t base = sstr(sn->base_url);
+    sstr_t href_str = sstr(href);
+    
+    char *base_path = util_url_path(sn->base_url);
+    base.length -= strlen(base_path);
+    
+    sstr_t url = sstrcat(2, base, href_str);
+    
+    curl_easy_setopt(sn->handle, CURLOPT_URL, url.ptr);
+    free(url.ptr);
+}
+
+char* util_path_to_url(DavSession *sn, char *path) {
+    char *space = malloc(256);
+    UcxBuffer *url = ucx_buffer_new(space, 256, CX_BUFFER_AUTO_EXTEND);
+    
+    // add base url
+    ucx_buffer_write(sn->base_url, 1, strlen(sn->base_url), url);
+    // remove trailing slash
+    ucx_buffer_seek(url, -1, SEEK_CUR);
+    
+    sstr_t p = sstr(path);
+    ssize_t ntk = 0;
+    sstr_t *tks = sstrsplit(p, S("/"), &ntk);
+    
+    for(int i=0;i<ntk;i++) {
+        sstr_t node = tks[i];
+        if(node.length > 0) {
+            char *esc = curl_easy_escape(sn->handle, node.ptr, node.length);
+            ucx_buffer_putc(url, '/');
+            ucx_buffer_write(esc, 1, strlen(esc), url);
+            curl_free(esc);
+        }
+        free(node.ptr);
+    }
+    free(tks);
+    if(path[p.length-1] == '/') {
+        ucx_buffer_putc(url, '/');
+    }
+    ucx_buffer_putc(url, 0);
+    
+    space = url->space;
+    ucx_buffer_free(url);
+    
+    return space;
+}
+
+char* util_parent_path(char *path) {
+    char *name = util_resource_name(path);
+    size_t namelen = strlen(name);
+    size_t pathlen = strlen(path);
+    size_t parentlen = pathlen - namelen;
+    char *parent = malloc(parentlen + 1);
+    memcpy(parent, path, parentlen);
+    parent[parentlen] = '\0';
+    return parent;
+}
+
+
+char* util_xml_get_text(xmlNode *elm) {
+    xmlNode *node = elm->children;
+    while(node) {
+        if(node->type == XML_TEXT_NODE) {
+            return (char*)node->content;
+        }
+        node = node->next;
+    }
+    return NULL;
+}
+
+
+char* util_base64decode(char *in) {
+    int len = 0;
+    return util_base64decode_len(in, &len);
+}
+
+char* util_base64decode_len(char* in, int *outlen) {
+    size_t len = strlen(in);
+    char *out = calloc(1, len);
+    
+    BIO* b = BIO_new_mem_buf(in, len);
+    BIO *d = BIO_new(BIO_f_base64());
+    BIO_set_flags(d, BIO_FLAGS_BASE64_NO_NL);
+    b = BIO_push(d, b);
+
+    *outlen = BIO_read(b, out, len);
+    BIO_free_all(b);
+    
+    return out;
+}
+
+char* util_base64encode(char *in, size_t len) { 
+    BIO *b;
+    BIO *e;
+    BUF_MEM *mem;
+
+    e = BIO_new(BIO_f_base64());
+    b = BIO_new(BIO_s_mem());
+    
+    e = BIO_push(e, b);
+    BIO_write(e, in, len);
+    BIO_flush(e);
+    
+    BIO_get_mem_ptr(e, &mem);
+    char *out = malloc(mem->length);
+    memcpy(out, mem->data, mem->length -1);
+    out[mem->length - 1] = '\0';
+
+    BIO_free_all(e);
+
+    return out;
+}
+
+char* util_encrypt_str(DavSession *sn, char *str, char *key) {
+    DavKey *k = dav_context_get_key(sn->context, key);
+    if(!k) {
+        // TODO: session error
+        return NULL;
+    }
+    
+    char *enc_str = aes_encrypt(str, k);
+    char *ret_str = dav_session_strdup(sn, enc_str);
+    free(enc_str);
+    return ret_str;
+}
+
+/* commented out for testing reasons */
+/*
+char* util_decrypt_str(DavSession *sn, char *str, char *key) {
+    DavKey *k = dav_context_get_key(sn->context, key);
+    if(!k) {
+        // TODO: session error
+        return NULL;
+    }
+    
+    char *dec_str = aes_decrypt(str, k);
+    char *ret_str = dav_session_strdup(sn, dec_str);
+    free(dec_str);
+    return ret_str;
+}
+*/
+char* util_random_str() {
+    unsigned char *str = malloc(25);
+    str[24] = '\0';
+    
+    sstr_t t = S(
+            "01234567890"
+            "abcdefghijklmnopqrstuvwxyz"
+            "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
+    const unsigned char *table = (const unsigned char*)t.ptr;
+    
+    RAND_pseudo_bytes(str, 24);
+    for(int i=0;i<24;i++) {
+        int c = str[i] % t.length;
+        str[i] = table[c];
+    }
+    
+    return (char*)str;
+}
+
+/*
+ * gets a substring from 0 to the appearance of the token
+ * tokens are separated by space
+ * sets sub to the substring and returns the remaining string
+ */
+sstr_t util_getsubstr_until_token(sstr_t str, sstr_t token, sstr_t *sub) {  
+    int i;
+    int token_start = -1;
+    int token_end = -1;
+    for(i=0;i<=str.length;i++) {
+        int c;
+        if(i == str.length) {
+            c = ' ';
+        } else {
+            c = str.ptr[i];
+        }
+        if(c < 33) {
+            if(token_start != -1) {
+                token_end = i;
+                size_t len = token_end - token_start;
+                sstr_t tk = sstrsubsl(str, token_start, len);
+                //printf("token: {%.*s}\n", token.length, token.ptr);
+                if(!sstrcmp(tk, token)) {
+                    *sub = sstrtrim(sstrsubsl(str, 0, token_start));
+                    break;
+                }
+                token_start = -1;
+                token_end = -1;
+            }
+        } else {
+            if(token_start == -1) {
+                token_start = i;
+            }
+        }
+    }
+    
+    if(i < str.length) {
+        return sstrtrim(sstrsubs(str, i));
+    } else {
+        str.ptr = NULL;
+        str.length = 0;
+        return str;
+    }
+}
--- a/test/ctestfile.c	Mon Apr 24 20:54:38 2023 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,391 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2015 Olaf Wintermann. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- *   1. Redistributions of source code must retain the above copyright
- *      notice, this list of conditions and the following disclaimer.
- *
- *   2. Redistributions in binary form must reproduce the above copyright
- *      notice, this list of conditions and the following disclaimer in the
- *      documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- */
-
-#include <time.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <ucx/string.h>
-#include <ucx/buffer.h>
-#include <ucx/utils.h>
-#include <libxml/tree.h>
-#include <curl/curl.h>
-
-#include <openssl/sha.h>
-#include <openssl/hmac.h>
-#include <openssl/evp.h>
-#include <openssl/bio.h>
-#include <openssl/buffer.h>
-#include <openssl/rand.h>
-
-#include "utils.h"
-#include "crypto.h"
-#include "webdav.h"
-
-#define MACRO1337 1337L
-
-/* -------------------- This is a testing file. -------------------------- */
-/*
-time_t util_parse_creationdate(char *str) {
-    // example: 2012-11-29T21:35:35Z
-    if(!str) {
-        return 0;
-    }
-    // TODO
-    return 0;
-}
-*/
-time_t util_parse_lastmodified(char *str) {
-    // example: Thu, 29 Nov 2012 21:35:35 GMT
-    if(!str) {
-        return 0;
-    } else {
-        return curl_getdate(str, NULL);
-    }
-}
-
-int util_getboolean(char *v) {
-    if(v[0] == 'T' || v[0] == 't') {
-        return 1;
-    }
-    return 0;
-}
-
-int util_strtoint(char *str, int64_t *value) {
-    char *end;
-    int64_t val = strtoll(str, &end, 0);
-    if(strlen(end) == 0) {
-        *value = val;
-        return 1;
-    } else {
-        return 0;
-    }
-}
-
-char* util_url_path(char *url) { 
-    char *path = NULL;
-    size_t len = strlen(url);
-    int slashcount = 0;
-    int slmax;
-    if(len > 7 && !strncasecmp(url, "http://", 7)) {
-        slmax = 3;
-    } else if(len > 8 && !strncasecmp(url, "https://", 8)) {
-        slmax = 3;
-    } else {
-        slmax = 1;
-    }
-    char c;
-    for(int i=0;i<len;i++) {
-        c = url[i];
-        if(c == '/') {
-            slashcount++;
-            if(slashcount == slmax) {
-                path = url + i;
-                break;
-            }
-        }
-    } 
-    return path;
-}
-
-char* util_url_decode(DavSession *sn, char *url) {
-    char *unesc = curl_easy_unescape(sn->handle, url, strlen(url), NULL);
-    char *ret = strdup(unesc);
-    curl_free(unesc);
-    return ret;
-}
-
-char* util_resource_name(char *url) {
-    int si = 0;
-    int osi = 0;
-    int i = 0;
-    int p = 0;
-    char c;
-    while((c = url[i]) != 0) {
-        if(c == '/') {
-            osi = si;
-            si = i;
-            p = 1;
-        }
-        i++;
-    }
-    
-    char *name = url + si + p;
-    if(name[0] == 0) {
-        name = url + osi + p;
-        if(name[0] == 0) {
-            return url;
-        }
-    }
-    
-    return name;
-}
-
-int util_mkdir(char *path, mode_t mode) {
-#ifdef _WIN32
-    return mkdir(path);
-#else
-    return mkdir(path, mode);
-#endif
-}
-
-char* util_concat_path(char *url_base, char *p) {
-    sstr_t base = sstr(url_base);
-    sstr_t path;
-    if(p) {
-        path = sstr(p);
-    } else {
-        path = sstrn("", 0);
-    }
-    
-    int add_separator = 0;
-    if(base.ptr[base.length-1] == '/') {
-        if(path.ptr[0] == '/') {
-            base.length--;
-        }
-    } else {
-        if(path.length == 0 || path.ptr[0] != '/') {
-            add_separator = 1;
-        }
-    }
-    
-    sstr_t url;
-    if(add_separator) {
-        url = sstrcat(3, base, sstr("/"), path);
-    } else {
-        url = sstrcat(2, base, path);
-    }
-    
-    return url.ptr;
-}
-
-void util_set_url(DavSession *sn, char *href) {
-    sstr_t base = sstr(sn->base_url);
-    sstr_t href_str = sstr(href);
-    
-    char *base_path = util_url_path(sn->base_url);
-    base.length -= strlen(base_path);
-    
-    sstr_t url = sstrcat(2, base, href_str);
-    
-    curl_easy_setopt(sn->handle, CURLOPT_URL, url.ptr);
-    free(url.ptr);
-}
-
-char* util_path_to_url(DavSession *sn, char *path) {
-    char *space = malloc(256);
-    UcxBuffer *url = ucx_buffer_new(space, 256, CX_BUFFER_AUTO_EXTEND);
-    
-    // add base url
-    ucx_buffer_write(sn->base_url, 1, strlen(sn->base_url), url);
-    // remove trailing slash
-    ucx_buffer_seek(url, -1, SEEK_CUR);
-    
-    sstr_t p = sstr(path);
-    ssize_t ntk = 0;
-    sstr_t *tks = sstrsplit(p, S("/"), &ntk);
-    
-    for(int i=0;i<ntk;i++) {
-        sstr_t node = tks[i];
-        if(node.length > 0) {
-            char *esc = curl_easy_escape(sn->handle, node.ptr, node.length);
-            ucx_buffer_putc(url, '/');
-            ucx_buffer_write(esc, 1, strlen(esc), url);
-            curl_free(esc);
-        }
-        free(node.ptr);
-    }
-    free(tks);
-    if(path[p.length-1] == '/') {
-        ucx_buffer_putc(url, '/');
-    }
-    ucx_buffer_putc(url, 0);
-    
-    space = url->space;
-    ucx_buffer_free(url);
-    
-    return space;
-}
-
-char* util_parent_path(char *path) {
-    char *name = util_resource_name(path);
-    size_t namelen = strlen(name);
-    size_t pathlen = strlen(path);
-    size_t parentlen = pathlen - namelen;
-    char *parent = malloc(parentlen + 1);
-    memcpy(parent, path, parentlen);
-    parent[parentlen] = '\0';
-    return parent;
-}
-
-
-char* util_xml_get_text(xmlNode *elm) {
-    xmlNode *node = elm->children;
-    while(node) {
-        if(node->type == XML_TEXT_NODE) {
-            return (char*)node->content;
-        }
-        node = node->next;
-    }
-    return NULL;
-}
-
-
-char* util_base64decode(char *in) {
-    int len = 0;
-    return util_base64decode_len(in, &len);
-}
-
-char* util_base64decode_len(char* in, int *outlen) {
-    size_t len = strlen(in);
-    char *out = calloc(1, len);
-    
-    BIO* b = BIO_new_mem_buf(in, len);
-    BIO *d = BIO_new(BIO_f_base64());
-    BIO_set_flags(d, BIO_FLAGS_BASE64_NO_NL);
-    b = BIO_push(d, b);
-
-    *outlen = BIO_read(b, out, len);
-    BIO_free_all(b);
-    
-    return out;
-}
-
-char* util_base64encode(char *in, size_t len) { 
-    BIO *b;
-    BIO *e;
-    BUF_MEM *mem;
-
-    e = BIO_new(BIO_f_base64());
-    b = BIO_new(BIO_s_mem());
-    
-    e = BIO_push(e, b);
-    BIO_write(e, in, len);
-    BIO_flush(e);
-    
-    BIO_get_mem_ptr(e, &mem);
-    char *out = malloc(mem->length);
-    memcpy(out, mem->data, mem->length -1);
-    out[mem->length - 1] = '\0';
-
-    BIO_free_all(e);
-
-    return out;
-}
-
-char* util_encrypt_str(DavSession *sn, char *str, char *key) {
-    DavKey *k = dav_context_get_key(sn->context, key);
-    if(!k) {
-        // TODO: session error
-        return NULL;
-    }
-    
-    char *enc_str = aes_encrypt(str, k);
-    char *ret_str = dav_session_strdup(sn, enc_str);
-    free(enc_str);
-    return ret_str;
-}
-
-/* commented out for testing reasons */
-/*
-char* util_decrypt_str(DavSession *sn, char *str, char *key) {
-    DavKey *k = dav_context_get_key(sn->context, key);
-    if(!k) {
-        // TODO: session error
-        return NULL;
-    }
-    
-    char *dec_str = aes_decrypt(str, k);
-    char *ret_str = dav_session_strdup(sn, dec_str);
-    free(dec_str);
-    return ret_str;
-}
-*/
-char* util_random_str() {
-    unsigned char *str = malloc(25);
-    str[24] = '\0';
-    
-    sstr_t t = S(
-            "01234567890"
-            "abcdefghijklmnopqrstuvwxyz"
-            "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
-    const unsigned char *table = (const unsigned char*)t.ptr;
-    
-    RAND_pseudo_bytes(str, 24);
-    for(int i=0;i<24;i++) {
-        int c = str[i] % t.length;
-        str[i] = table[c];
-    }
-    
-    return (char*)str;
-}
-
-/*
- * gets a substring from 0 to the appearance of the token
- * tokens are separated by space
- * sets sub to the substring and returns the remaining string
- */
-sstr_t util_getsubstr_until_token(sstr_t str, sstr_t token, sstr_t *sub) {  
-    int i;
-    int token_start = -1;
-    int token_end = -1;
-    for(i=0;i<=str.length;i++) {
-        int c;
-        if(i == str.length) {
-            c = ' ';
-        } else {
-            c = str.ptr[i];
-        }
-        if(c < 33) {
-            if(token_start != -1) {
-                token_end = i;
-                size_t len = token_end - token_start;
-                sstr_t tk = sstrsubsl(str, token_start, len);
-                //printf("token: {%.*s}\n", token.length, token.ptr);
-                if(!sstrcmp(tk, token)) {
-                    *sub = sstrtrim(sstrsubsl(str, 0, token_start));
-                    break;
-                }
-                token_start = -1;
-                token_end = -1;
-            }
-        } else {
-            if(token_start == -1) {
-                token_start = i;
-            }
-        }
-    }
-    
-    if(i < str.length) {
-        return sstrtrim(sstrsubs(str, i));
-    } else {
-        str.ptr = NULL;
-        str.length = 0;
-        return str;
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/javatest.java	Mon Apr 24 21:01:41 2023 +0200
@@ -0,0 +1,176 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Mike Becker. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+package de.uapcore.sigred.doc.base;
+
+import de.uapcore.sigred.doc.Resources;
+import de.uapcore.sigrapi.impl.Digraph;
+import de.uapcore.sigrapi.impl.Graph;
+import de.uapcore.sigrapi.IGraph;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+import org.apache.xerces.impl.Constants;
+import org.dom4j.Document;
+import org.dom4j.DocumentException;
+import org.dom4j.DocumentHelper;
+import org.dom4j.Element;
+import org.dom4j.Namespace;
+import org.dom4j.QName;
+import org.dom4j.io.OutputFormat;
+import org.dom4j.io.SAXReader;
+import org.dom4j.io.XMLWriter;
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+
+public abstract class AbstractGraphDocument<T extends IGraph>
+        extends FileBackedDocument {
+    
+    protected static final Namespace NAMESPACE = Namespace.get("sigred",
+        "http://develop.uap-core.de/sigred/");
+    
+    private static final
+        QName TAG_GRAPHDOC = QName.get("graph-document", NAMESPACE);
+    private static final
+        QName TAG_GRAPH = QName.get("graph", NAMESPACE);
+    private static final
+        QName TAG_DIGRAPH = QName.get("digraph", NAMESPACE);
+    private static final
+        QName TAG_METADATA = QName.get("metadata", NAMESPACE);
+    
+    protected final T graph;
+    
+    private final GraphDocumentMetadata metadata;
+    
+    public AbstractGraphDocument(Class<T> graphType) {
+        T g;
+        try {
+            g = graphType.newInstance();
+        } catch (ReflectiveOperationException e) {
+            assert false;
+            g = null; // for the compiler
+        }
+        graph = g;
+        metadata = new GraphDocumentMetadata();
+    }
+
+    public T getGraph() {
+        return graph;
+    }
+    
+    public GraphDocumentMetadata getMetadata() {
+        return metadata;
+    }
+
+    protected abstract void writeGraph(Element rootNode) throws IOException;
+    protected abstract void readGraph(Element rootNode) throws IOException;
+
+    @Override
+    public void writeTo(OutputStream out) throws IOException {
+        Document doc = DocumentHelper.createDocument();
+
+        Element rootNode = doc.addElement(TAG_GRAPHDOC);
+
+        Element metadataNode = rootNode.addElement(TAG_METADATA);
+
+        metadata.write(metadataNode);
+
+        if (graph instanceof Graph) {
+            writeGraph(rootNode.addElement(TAG_GRAPH));
+        } else if (graph instanceof Digraph) {
+            writeGraph(rootNode.addElement(TAG_DIGRAPH));
+        } else {
+            throw new IOException("unsupported graph type");
+        }
+
+        XMLWriter writer = new XMLWriter(out, OutputFormat.createPrettyPrint());
+        writer.write(doc);
+        writer.flush();
+    }
+
+    @Override
+    public void readFrom(InputStream in) throws IOException {
+        try {
+            SAXReader reader = new SAXReader(true);
+            reader.setStripWhitespaceText(true);
+            
+            reader.setFeature(Constants.XERCES_FEATURE_PREFIX+
+                Constants.SCHEMA_VALIDATION_FEATURE, true);
+            reader.setProperty(Constants.XERCES_PROPERTY_PREFIX +
+                Constants.SCHEMA_LOCATION, String.format("%s %s",
+                    NAMESPACE.getURI(), Resources.class.getResource(
+                        "graph-document.xsd").toExternalForm()));
+            
+            final AtomicBoolean passed = new AtomicBoolean(true);
+            final AtomicReference<SAXParseException> xmlerror = new AtomicReference<>();
+            // TODO: we should do more detailed error handling here
+            reader.setErrorHandler(new ErrorHandler() {
+                @Override
+                public void warning(SAXParseException exception) throws SAXException {
+                }
+
+                @Override
+                public void error(SAXParseException exception) throws SAXException {
+                    xmlerror.set(exception);
+                    passed.set(false);
+                }
+
+                @Override
+                public void fatalError(SAXParseException exception) throws SAXException {
+                    xmlerror.set(exception);
+                    passed.set(false);
+                }
+                
+            });
+            Document doc = reader.read(in);
+            if (!passed.get()) {
+                // TODO: provide details (maybe via separate error object?)
+                throw xmlerror.get();
+            }
+            
+            doc.normalize();
+            
+            Element root = doc.getRootElement();
+            metadata.read(root.element(TAG_METADATA));
+            
+            if (graph instanceof Graph) {
+                readGraph(root.element(TAG_GRAPH));
+            } else if (graph instanceof Digraph) {
+                readGraph(root.element(TAG_DIGRAPH));
+            } else {
+                throw new IOException("unsupported graph type");
+            }
+        } catch (DocumentException | SAXException ex) {
+            throw new IOException(ex);
+        }
+    }
+}
--- a/test/javatestfile.java	Mon Apr 24 20:54:38 2023 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,176 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2014 Mike Becker. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- *   1. Redistributions of source code must retain the above copyright
- *      notice, this list of conditions and the following disclaimer.
- *
- *   2. Redistributions in binary form must reproduce the above copyright
- *      notice, this list of conditions and the following disclaimer in the
- *      documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *
- */
-
-package de.uapcore.sigred.doc.base;
-
-import de.uapcore.sigred.doc.Resources;
-import de.uapcore.sigrapi.impl.Digraph;
-import de.uapcore.sigrapi.impl.Graph;
-import de.uapcore.sigrapi.IGraph;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicReference;
-import org.apache.xerces.impl.Constants;
-import org.dom4j.Document;
-import org.dom4j.DocumentException;
-import org.dom4j.DocumentHelper;
-import org.dom4j.Element;
-import org.dom4j.Namespace;
-import org.dom4j.QName;
-import org.dom4j.io.OutputFormat;
-import org.dom4j.io.SAXReader;
-import org.dom4j.io.XMLWriter;
-import org.xml.sax.ErrorHandler;
-import org.xml.sax.SAXException;
-import org.xml.sax.SAXParseException;
-
-public abstract class AbstractGraphDocument<T extends IGraph>
-        extends FileBackedDocument {
-    
-    protected static final Namespace NAMESPACE = Namespace.get("sigred",
-        "http://develop.uap-core.de/sigred/");
-    
-    private static final
-        QName TAG_GRAPHDOC = QName.get("graph-document", NAMESPACE);
-    private static final
-        QName TAG_GRAPH = QName.get("graph", NAMESPACE);
-    private static final
-        QName TAG_DIGRAPH = QName.get("digraph", NAMESPACE);
-    private static final
-        QName TAG_METADATA = QName.get("metadata", NAMESPACE);
-    
-    protected final T graph;
-    
-    private final GraphDocumentMetadata metadata;
-    
-    public AbstractGraphDocument(Class<T> graphType) {
-        T g;
-        try {
-            g = graphType.newInstance();
-        } catch (ReflectiveOperationException e) {
-            assert false;
-            g = null; // for the compiler
-        }
-        graph = g;
-        metadata = new GraphDocumentMetadata();
-    }
-
-    public T getGraph() {
-        return graph;
-    }
-    
-    public GraphDocumentMetadata getMetadata() {
-        return metadata;
-    }
-
-    protected abstract void writeGraph(Element rootNode) throws IOException;
-    protected abstract void readGraph(Element rootNode) throws IOException;
-
-    @Override
-    public void writeTo(OutputStream out) throws IOException {
-        Document doc = DocumentHelper.createDocument();
-
-        Element rootNode = doc.addElement(TAG_GRAPHDOC);
-
-        Element metadataNode = rootNode.addElement(TAG_METADATA);
-
-        metadata.write(metadataNode);
-
-        if (graph instanceof Graph) {
-            writeGraph(rootNode.addElement(TAG_GRAPH));
-        } else if (graph instanceof Digraph) {
-            writeGraph(rootNode.addElement(TAG_DIGRAPH));
-        } else {
-            throw new IOException("unsupported graph type");
-        }
-
-        XMLWriter writer = new XMLWriter(out, OutputFormat.createPrettyPrint());
-        writer.write(doc);
-        writer.flush();
-    }
-
-    @Override
-    public void readFrom(InputStream in) throws IOException {
-        try {
-            SAXReader reader = new SAXReader(true);
-            reader.setStripWhitespaceText(true);
-            
-            reader.setFeature(Constants.XERCES_FEATURE_PREFIX+
-                Constants.SCHEMA_VALIDATION_FEATURE, true);
-            reader.setProperty(Constants.XERCES_PROPERTY_PREFIX +
-                Constants.SCHEMA_LOCATION, String.format("%s %s",
-                    NAMESPACE.getURI(), Resources.class.getResource(
-                        "graph-document.xsd").toExternalForm()));
-            
-            final AtomicBoolean passed = new AtomicBoolean(true);
-            final AtomicReference<SAXParseException> xmlerror = new AtomicReference<>();
-            // TODO: we should do more detailed error handling here
-            reader.setErrorHandler(new ErrorHandler() {
-                @Override
-                public void warning(SAXParseException exception) throws SAXException {
-                }
-
-                @Override
-                public void error(SAXParseException exception) throws SAXException {
-                    xmlerror.set(exception);
-                    passed.set(false);
-                }
-
-                @Override
-                public void fatalError(SAXParseException exception) throws SAXException {
-                    xmlerror.set(exception);
-                    passed.set(false);
-                }
-                
-            });
-            Document doc = reader.read(in);
-            if (!passed.get()) {
-                // TODO: provide details (maybe via separate error object?)
-                throw xmlerror.get();
-            }
-            
-            doc.normalize();
-            
-            Element root = doc.getRootElement();
-            metadata.read(root.element(TAG_METADATA));
-            
-            if (graph instanceof Graph) {
-                readGraph(root.element(TAG_GRAPH));
-            } else if (graph instanceof Digraph) {
-                readGraph(root.element(TAG_DIGRAPH));
-            } else {
-                throw new IOException("unsupported graph type");
-            }
-        } catch (DocumentException | SAXException ex) {
-            throw new IOException(ex);
-        }
-    }
-}
--- a/test/plain.csp	Mon Apr 24 20:54:38 2023 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,6 +0,0 @@
-</body>
-</html>
-<!c
-pblock_free(q);
-!>
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/plain.txt	Mon Apr 24 21:01:41 2023 +0200
@@ -0,0 +1,6 @@
+</body>
+</html>
+<!c
+pblock_free(q);
+!>
+

mercurial