commit b654228675cdd273d1cf1bc3d68fc213d1ac1cff
Author: Massimo Melina
Date: Sat May 2 19:04:16 2020 +0200
first commit
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..4f2fd81
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,13 @@
+tmp/
+__history/
+__recovery/
+win32/
+*.vfs
+*.dcu
+*.exe
+*.map
+hfs.ini
+*.tmp
+*.bak
+*.*-
+*.corrupted
\ No newline at end of file
diff --git a/WindowsXP.manifest b/WindowsXP.manifest
new file mode 100644
index 0000000..84283b0
--- /dev/null
+++ b/WindowsXP.manifest
@@ -0,0 +1,21 @@
+
+
+
+Windows Shell
+
+
+
+
+
+
diff --git a/alias.txt b/alias.txt
new file mode 100644
index 0000000..14e597b
--- /dev/null
+++ b/alias.txt
@@ -0,0 +1,11 @@
+var length=length|var=$1
+cache=trim|{.set|#cache.tmp|{.from table|$1|$2.}.} {.if not|{.^#cache.tmp.}|{:{.set|#cache.tmp|{.dequote|$3.}.}{.set table|$1|$2={.^#cache.tmp.}.}:}.} {.^#cache.tmp.} {.set|#cache.tmp.}
+is substring=pos|$1|$2
+set append=set|$1|$2|mode=append
+123 if 2=if|$2|$1$2$3
+between=if|{.$1 < $3.}|{:{.and|{.$1 <= $2.}|{.$2 <= $3.}:}|{:{.and|{.$3 <= $2.}|{.$2 <= $1.}:}
+between!=if|{.$1 < $3.}|{:{.and|{.$1 < $2.}|{.$2 < $3.}:}|{:{.and|{.$3 < $2.}|{.$2 < $1.}:}
+file changed=if| {.{.filetime|$1.} > {.^#file changed.$1.}.}|{: {.set|#file changed.$1|{.filetime|$1.}.} {.if|$2|{:{.load|$1|var=$2.}:}.} 1:}
+play system event=play
+redirect=add header|Location: $1
+chop={.cut|{.calc|{.pos|$2|var=$1.}+{.length|$2.}.}||var=$1|remainder=#chop.tmp.}{.^#chop.tmp.}
\ No newline at end of file
diff --git a/classesLib.pas b/classesLib.pas
new file mode 100644
index 0000000..05f060a
--- /dev/null
+++ b/classesLib.pas
@@ -0,0 +1,1213 @@
+{
+Copyright (C) 2002-2012 Massimo Melina (www.rejetto.com)
+
+This file is part of HFS ~ HTTP File Server.
+
+ HFS is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ HFS is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with HFS; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+}
+{$INCLUDE defs.inc }
+unit classesLib;
+
+interface
+
+uses
+ iniFiles, types, hslib, strUtils, sysUtils, classes, math;
+
+type
+ TfastStringAppend = class
+ protected
+ buff: string;
+ n: integer;
+ public
+ function length():integer;
+ function reset():string;
+ function get():string;
+ function append(s:string):integer;
+ end;
+
+ PcachedIcon = ^TcachedIcon;
+ TcachedIcon = record
+ data: ansistring;
+ idx: integer;
+ time: Tdatetime;
+ end;
+
+ TiconsCache = class
+ n: integer;
+ icons: array of TcachedIcon;
+ function get(data:ansistring):PcachedIcon;
+ procedure put(data:ansistring; idx:integer; time:Tdatetime);
+ procedure clear();
+ procedure purge(olderThan:Tdatetime);
+ function idxOf(data:shortstring):integer;
+ end;
+
+ TusersInVFS = class
+ protected
+ users: TstringDynArray;
+ pwds: array of TstringDynArray;
+ public
+ procedure reset();
+ procedure track(usr, pwd:string); overload;
+ procedure drop(usr, pwd:string); overload;
+ function match(usr, pwd:string):boolean; overload;
+ function empty():boolean;
+ end;
+
+ TarchiveStream = class(Tstream)
+ protected
+ pos, cachedTotal: int64;
+ cur: integer;
+
+ procedure invalidate();
+ procedure calculate(); virtual; abstract;
+ function getTotal():int64;
+ public
+ flist: array of record
+ src, // full path of the file on the disk
+ dst: ansistring; // full path of the file in the archive
+ firstByte, // offset of the file inside the archive
+ mtime,
+ size: int64;
+ data: Tobject; // extra data
+ end;
+ onDestroy: TNotifyEvent;
+
+ constructor create;
+ destructor Destroy; override;
+ function addFile(src:ansistring; dst:ansistring=''; data:Tobject=NIL):boolean; virtual;
+ function count():integer;
+ procedure reset(); virtual;
+ property totalSize:int64 read getTotal;
+ property current:integer read cur;
+ end; // TarchiveStream
+
+ TtarStreamWhere = (TW_HEADER, TW_FILE, TW_PAD);
+
+ TtarStream = class(TarchiveStream)
+ protected
+ fs: TFileStream;
+ block: TStringStream;
+ lastSeekFake: int64;
+ where: TtarStreamWhere;
+ function fsInit():boolean;
+ procedure headerInit(); // fill block with header
+ procedure padInit(full:boolean=FALSE); // fill block with pad
+ function headerLengthForFilename(fn:ansistring):integer;
+ procedure calculate(); override;
+ public
+ fileNamesOEM: boolean;
+ constructor create;
+ destructor Destroy; override;
+ function Read(var Buffer; Count: Longint): Longint; override;
+ function Write(const Buffer; Count: Longint): Longint; override;
+ function Seek(const Offset: Int64; Origin: TSeekOrigin=soBeginning): Int64; override;
+
+ procedure reset(); override;
+ end; // TtarStream
+
+ Thasher = class(TstringList)
+ procedure loadFrom(path:string);
+ function getHashFor(fn:string):string;
+ end;
+
+ TstringToIntHash = class(ThashedStringList)
+ constructor create;
+ function getInt(s:string):integer;
+ function getIntByIdx(idx:integer):integer;
+ function incInt(s:string):integer;
+ procedure setInt(s:string; int:integer);
+ end;
+
+ PtplSection = ^TtplSection;
+ TtplSection = record
+ name, txt: string;
+ nolog, nourl, cache: boolean;
+ ts: Tdatetime;
+ end;
+
+ Ttpl = class
+ protected
+ src: string;
+ lastExt, // cache for getTxtByExt()
+ last: record section:string; idx:integer; end; // cache for getIdx()
+ fileExts: TStringDynArray;
+ strTable: THashedStringList;
+ fUTF8: boolean;
+ fOver: Ttpl;
+ function getIdx(section:string):integer;
+ function getTxt(section:string):string;
+ function newSection(section:string):PtplSection;
+ procedure fromString(txt:string);
+ procedure setOver(v:Ttpl);
+ procedure updateUTF8();
+ public
+ onChange: TNotifyEvent;
+ sections: array of TtplSection;
+ constructor create(txt:string=''; over:Ttpl=NIL);
+ destructor Destroy; override;
+ property txt[section:string]:string read getTxt; default;
+ property fullText:string read src write fromString;
+ property utf8:boolean read fUTF8;
+ property over:Ttpl read fOver write setOver;
+ function sectionExist(section:string):boolean;
+ function getTxtByExt(fileExt:string):string;
+ function getSection(section:string):PtplSection;
+ function getSections():TStringDynArray;
+ procedure appendString(txt:string);
+ function getStrByID(id:string):string;
+ function me():Ttpl;
+ end; // Ttpl
+
+ TcachedTplObj = class
+ ts: Tdatetime;
+ tpl: Ttpl;
+ end;
+
+ TcachedTpls = class(THashedStringList)
+ public
+ function getTplFor(fn:string):Ttpl;
+ destructor Destroy; override;
+ end; // TcachedTpls
+
+ TperIp = class // for every different address, we have an object of this class. These objects are never freed until hfs is closed.
+ public
+ limiter: TspeedLimiter;
+ customizedLimiter: boolean;
+ constructor create();
+ destructor Destroy; override;
+ end;
+
+ Ttlv = class
+ protected
+ cur, bound: integer;
+ whole, lastValue: ansistring;
+ stack: array of integer;
+ stackTop: integer;
+ public
+ procedure parse(data:ansistring);
+ function pop(var value:ansistring):integer;
+ function down():boolean;
+ function up():boolean;
+ function getTotal():integer;
+ function getCursor():integer;
+ function getPerc():real;
+ function isOver():boolean;
+ function getTheRest():ansistring;
+ end;
+
+implementation
+
+uses
+ utilLib, main, windows, dateUtils, forms;
+
+constructor TperIp.create();
+begin
+limiter:=TspeedLimiter.create();
+srv.limiters.add(limiter);
+end;
+
+destructor TperIp.Destroy;
+begin
+srv.limiters.remove(limiter);
+limiter.free;
+end;
+
+//////////// TcachedTpls
+
+destructor TcachedTpls.Destroy;
+var
+ i: integer;
+begin
+for i:=0 to count-1 do
+ objects[i].free;
+end;
+
+function TcachedTpls.getTplFor(fn:string):Ttpl;
+var
+ i: integer;
+ o: TcachedTplObj;
+ s: string;
+begin
+fn:=trim(lowercase(fn));
+i:=indexOf(fn);
+if i >= 0 then
+ o:=objects[i] as TcachedTplObj
+else
+ begin
+ o:=TcachedTplObj.create();
+ if addObject(fn, o) > 100 then
+ delete(0);
+ end;
+result:=o.tpl;
+if getMtime(fn) = o.ts then exit;
+o.ts:=getMtime(fn);
+s:=loadTextFile(fn);
+if o.tpl = NIL then
+ begin
+ result:=Ttpl.create();
+ o.tpl:=result;
+ end;
+o.tpl.fromString(s);
+end; // getTplFor
+
+//////////// TusersInVFS
+
+function TusersInVFS.empty():boolean;
+begin result:= users = NIL end;
+
+procedure TusersInVFS.reset();
+begin
+users:=NIL;
+pwds:=NIL;
+end; // reset
+
+procedure TusersInVFS.track(usr, pwd: string);
+var
+ i: integer;
+begin
+if usr = '' then exit;
+i:=idxOf(usr, users);
+if i < 0 then i:=addString(usr, users);
+if i >= length(pwds) then setLength(pwds, i+1);
+addString(pwd, pwds[i]);
+end; // track
+
+procedure TusersInVFS.drop(usr, pwd: string);
+var
+ i, j: integer;
+begin
+i:=idxOf(usr, users);
+if i < 0 then exit;
+j:=AnsiIndexStr(pwd, pwds[i]);
+if j < 0 then exit;
+removeString(pwds[i], j);
+if assigned(pwds[i]) then exit;
+// this username does not exist with any password
+removeString(users, i);
+while i+1 < length(pwds) do
+ begin
+ pwds[i]:=pwds[i+1];
+ inc(i);
+ end;
+setLength(pwds, i);
+end; // drop
+
+function TusersInVFS.match(usr, pwd:string):boolean;
+var
+ i: integer;
+begin
+result:=FALSE;
+i:=idxOf(usr, users);
+if i < 0 then exit;
+result:= 0 <= AnsiIndexStr(pwd, pwds[i]);
+end; // match
+
+//////////// TiconsCache
+
+function TiconsCache.idxOf(data:shortstring):integer;
+var
+ b, e, c: integer;
+begin
+result:=0;
+if n = 0 then exit;
+// binary search
+b:=0;
+e:=n-1;
+ repeat
+ result:=(b+e) div 2;
+ c:=compareStr(data, icons[result].data);
+ if c = 0 then exit;
+ if c < 0 then e:=result-1;
+ if c > 0 then b:=result+1;
+ until b > e;
+result:=b;
+end; // idxOf
+
+function TiconsCache.get(data:ansistring):PcachedIcon;
+var
+ i: integer;
+begin
+result:=NIL;
+i:=idxOf(data);
+if (i >= 0) and (i < n) and (icons[i].data = data) then
+ result:=@icons[i];
+end; // get
+
+procedure TiconsCache.put(data:ansistring; idx:integer; time:Tdatetime);
+var
+ i, w: integer;
+begin
+if length(icons) <= n then setlength(icons, n+50);
+w:=idxOf(data);
+for i:=n downto w+1 do icons[i]:=icons[i-1]; // shift
+icons[w].data:=data;
+icons[w].idx:=idx;
+icons[w].time:=time;
+inc(n);
+end; // put
+
+procedure TiconsCache.clear();
+begin
+icons:=NIL;
+n:=0;
+end; // clear
+
+procedure TiconsCache.purge(olderThan:Tdatetime);
+var
+ i, m: integer;
+begin
+exit;
+m:=0;
+for i:=0 to n-1 do
+ if icons[i].time < olderThan then dec(n) // this does not shorten the loop
+ else
+ begin
+ if m < i then icons[m]:=icons[i];
+ inc(m);
+ end;
+end; // purge
+
+//////////// TfastStringAppend
+
+function TfastStringAppend.length():integer;
+begin result:=n end;
+
+function TfastStringAppend.get():string;
+begin
+setlength(buff, n);
+result:=buff;
+end; // get
+
+function TfastStringAppend.reset():string;
+begin
+result:=get();
+buff:='';
+n:=0;
+end; // reset
+
+function TfastStringAppend.append(s:string):integer;
+var
+ ls, lb: integer;
+begin
+ls:=system.length(s);
+lb:=system.length(buff);
+if n+ls > lb then setlength(buff, lb+ls+20000);
+moveChars(s[1], buff[n+1], ls);
+inc(n, ls);
+result:=n;
+end; // append
+
+//////////// TarchiveStream
+
+function TarchiveStream.getTotal():int64;
+begin
+if cachedTotal < 0 then calculate();
+result:=cachedTotal;
+end; // getTotal
+
+function TarchiveStream.addFile(src:ansistring; dst:ansistring=''; data:Tobject=NIL):boolean;
+
+ function getMtime(fh:Thandle):int64;
+ var
+ ctime, atime, mtime: Tfiletime;
+ st: TSystemTime;
+ begin
+ getFileTime(fh, @ctime, @atime, @mtime);
+ fileTimeToSystemTime(mtime, st);
+ result:=dateTimeToUnix(SystemTimeToDateTime(st));
+ end; // getMtime
+
+var
+ i, fh: integer;
+begin
+result:=FALSE;
+fh:=fileopen(src, fmOpenRead+fmShareDenyNone);
+if fh = -1 then exit;
+result:=TRUE;
+if dst = '' then
+ dst:=extractFileName(src);
+i:=length(flist);
+setLength(flist, i+1);
+flist[i].src:=src;
+flist[i].dst:=dst;
+flist[i].data:=data;
+flist[i].size:=sizeOfFile(fh);
+flist[i].mtime:=getMtime(fh);
+flist[i].firstByte:=-1;
+fileClose(fh);
+invalidate();
+end; // addFile
+
+procedure TarchiveStream.invalidate();
+begin cachedTotal:=-1 end;
+
+constructor TarchiveStream.create;
+begin
+inherited;
+reset();
+end; // create
+
+destructor TarchiveStream.destroy;
+begin
+if assigned(onDestroy) then onDestroy(self);
+inherited;
+end; // destroy
+
+procedure TarchiveStream.reset();
+begin
+flist:=NIL;
+cur:=0;
+pos:=0;
+invalidate();
+end; // reset
+
+function TarchiveStream.count():integer;
+begin result:=length(flist) end;
+
+//////////// TtarStream
+
+constructor TtarStream.create;
+begin
+block:=TStringStream.create('');
+lastSeekFake:=-1;
+where:=TW_HEADER;
+fileNamesOEM:=FALSE;
+inherited;
+end; // create
+
+destructor TtarStream.destroy;
+begin
+freeAndNIL(fs);
+inherited;
+end; // destroy
+
+procedure TtarStream.reset();
+begin
+inherited;
+block.size:=0;
+end; // reset
+
+function TtarStream.fsInit():boolean;
+begin
+if assigned(fs) and (fs.FileName = flist[cur].src) then
+ begin
+ result:=TRUE;
+ exit;
+ end;
+result:=FALSE;
+try
+ freeAndNIL(fs);
+ fs:=TfileStream.Create(flist[cur].src, fmOpenRead+fmShareDenyWrite);
+ result:=TRUE;
+except
+ fs:=NIL;
+ end;
+end; // fsInit
+
+procedure TtarStream.headerInit();
+
+ function num(i:int64; fieldLength:integer):ansistring;
+ const
+ CHARS : array [0..7] of ansichar = '01234567';
+ var
+ d: integer;
+ begin
+ d:=fieldLength-1;
+ result:=ansistring(dupeString('0', d))+#0;
+ while d > 0 do
+ begin
+ result[d]:=CHARS[i and 7];
+ dec(d);
+ i:=i shr 3;
+ if i = 0 then break;
+ end;
+ end; // num
+
+ function str(s:ansistring; fieldLength:integer; fill:ansistring=#0):ansistring;
+ begin
+ setLength(s, min(length(s), fieldLength-1));
+ result:=s+ansistring( dupeString(fill, fieldLength-length(s)) );
+ end; // str
+
+ function sum(s:ansistring):integer;
+ var
+ i: integer;
+ begin
+ result:=0;
+ for i:=1 to length(s) do
+ inc(result, ord(s[i]));
+ end; // sum
+
+ procedure applyChecksum(var s:ansistring);
+ var
+ chk: ansistring;
+ begin
+ chk:=num(sum(s), 7)+' ';
+ chk[7]:=#0;
+ move(chk[1], s[100+24+12+12+1], length(chk));
+ end; // applyChecksum
+
+const
+ FAKE_CHECKSUM = ' ';
+ USTAR = 'ustar'#0'00';
+ PERM = '0100777'#0'0000000'#0'0000000'#0; // file mode, uid, gid
+var
+ fn, s, pre: ansistring;
+begin
+fn:=ansistring(replaceStr(flist[cur].dst,'\','/'));
+if fileNamesOEM then
+ CharToOem(pWideChar(string(fn)), pAnsiChar(fn));
+pre:='';
+if length(fn) >= 100 then
+ begin
+ pre:=str('././@LongLink', 100)+PERM
+ +num(length(fn)+1, 12)+num(flist[cur].mtime, 12)
+ +FAKE_CHECKSUM+'L';
+ pre:=str(pre, 256)+str(#0+USTAR,256);
+ applyChecksum(pre);
+ pre:=pre+str(fn, 512);
+ end;
+s:=str(fn, 100)+PERM
+ +num(flist[cur].size, 12) // file size
+ +num(flist[cur].mtime, 12) // mtime
+ +FAKE_CHECKSUM
+ +'0'+str('', 100) // link properties
+ +USTAR;
+applyChecksum(s);
+s:=str(s, 512); // pad
+block.Size:=0;
+block.WriteString(pre+s);
+block.seek(0, soBeginning);
+end; // headerInit
+
+function TtarStream.write(const Buffer; Count: Longint): Longint;
+begin raise EWriteError.Create('write unsupproted') end;
+
+function gap512(i:int64):word; inline;
+begin
+result:=i and 511;
+if result > 0 then result:=512-result;
+end; // gap512
+
+procedure TtarStream.padInit(full:boolean=FALSE);
+begin
+block.Size:=0;
+block.WriteString(dupeString(#0, if_(full,512,gap512(pos)) ));
+block.Seek(0, soBeginning);
+end; // padInit
+
+function TtarStream.headerLengthForFilename(fn:ansistring):integer;
+begin
+result:=length(fn);
+result:=512*if_(result<100, 1, 3+result div 512);
+end; // headerLengthForFilename
+
+procedure TtarStream.calculate();
+var
+ pos: int64;
+ i: integer;
+begin
+pos:=0;
+for i:=0 to length(flist)-1 do
+ with flist[i] do
+ begin
+ firstByte:=pos;
+ inc(pos, size+headerLengthForFilename(dst));
+ inc(pos, gap512(pos));
+ end;
+inc(pos, 512); // last empty block
+cachedTotal:=pos;
+end; // calculate
+
+function TtarStream.seek(const Offset: Int64; Origin: TSeekOrigin): Int64;
+
+ function left():int64;
+ begin result:=offset-pos end;
+
+ procedure fineSeek(s:Tstream);
+ begin inc(pos, s.seek(left(), soBeginning)) end;
+
+ function skipMoreThan(size:int64):boolean;
+ begin
+ result:=left() > size;
+ if result then inc(pos, size);
+ end;
+
+var
+ bak: int64;
+ prevCur: integer;
+begin
+{ The lastSeekFake trick is a way to fastly manage a sequence of
+ seek(0,soCurrent); seek(0,soEnd); seek(0,soBeginning);
+ such sequence called very often, while it is used to just read
+ the size of the stream, no real seeking requirement.
+}
+bak:=lastSeekFake;
+lastSeekFake:=-1;
+if (origin = soCurrent) and (offset <> 0) then
+ seek(pos+offset, soBeginning);
+if origin = soEnd then
+ if offset < 0 then
+ seek(totalSize+offset, soBeginning)
+ else
+ begin
+ lastSeekFake:=pos;
+ pos:=totalsize;
+ end;
+result:=pos;
+if origin <> soBeginning then exit;
+if bak >= 0 then
+ begin
+ pos:=bak;
+ exit;
+ end;
+
+// here starts the normal seeking algo
+
+prevCur:=cur;
+cur:=0; // flist index
+pos:=0; // current position in the file
+block.size:=0;
+while (left() > 0) and (cur < length(flist)) do
+ begin
+ // are we seeking inside this header?
+ if not skipMoreThan(headerLengthForFilename(flist[cur].dst)) then
+ begin
+ if (prevCur <> cur) or (where <> TW_HEADER) or eos(block) then
+ headerInit();
+ fineSeek(block);
+ where:=TW_HEADER;
+ break;
+ end;
+ // are we seeking inside this file?
+ if not skipMoreThan(flist[cur].size) then
+ begin
+ if not fsInit() then
+ raise Exception.Create('TtarStream.seek: cannot open '+flist[cur].src);
+ fineSeek(fs);
+ where:=TW_FILE;
+ break;
+ end;
+ // are we seeking inside this pad?
+ if not skipMoreThan(gap512(pos)) then
+ begin
+ padInit();
+ fineSeek(block);
+ where:=TW_PAD;
+ break;
+ end;
+ inc(cur);
+ end;//while
+if left() > 0 then
+ begin
+ padInit(TRUE);
+ fineSeek(block);
+ end;
+result:=pos;
+end; // seek
+
+function TtarStream.read(var Buffer; Count: Longint): Longint;
+var
+ p: Pbyte;
+
+ procedure goForth(d: int64);
+ begin
+ dec(count, d);
+ inc(pos, d);
+ inc(p, d);
+ end; // goForth
+
+ procedure goRead(s:Tstream);
+ begin goForth( s.read(p^, count) ) end;
+
+var
+ i, posBak: int64;
+begin
+posBak:=pos;
+p:=@buffer;
+while (count > 0) and (cur < length(flist)) do
+ case where of
+ TW_HEADER:
+ begin
+ if block.size = 0 then
+ headerInit();
+ goRead(block);
+ if not eos(block) then continue;
+ where:=TW_FILE;
+ freeAndNIL(fs); // in case the same files appear twice in a row, we must be sure to reinitialize the reader stream
+ block.size:=0;
+ end;
+ TW_FILE:
+ begin
+ fsInit();
+ if assigned(fs) then
+ goRead(fs);
+ { We reserved a fixed space for this file in the archive, but the file
+ may not exist anymore, or its size may be shorter than expected,
+ so we can't rely on eos(fs) to know if we are done in this section.
+ Lets calculate how far we are from the theoretical end of the file,
+ and decide after it.
+ }
+ i:=headerLengthForFilename(flist[cur].dst);
+ i:=flist[cur].firstByte+i+flist[cur].size-pos;
+ if count >= i then
+ where:=TW_PAD;
+ // In case the file is shorter, we pad the rest with NUL bytes
+ i:=min(count, max(0,i));
+ fillChar(p^,i,0);
+ goForth(i);
+ end;
+ TW_PAD:
+ begin
+ if block.size = 0 then padInit();
+ goRead(block);
+ if not eos(block) then continue;
+ where:=TW_HEADER;
+ block.size:=0;
+ inc(cur);
+ end;
+ end;//case
+
+// last empty block
+if count > 0 then
+ begin
+ padInit(TRUE);
+ goRead(block);
+ end;
+result:=pos-posBak;
+end; // read
+
+//////////// Thasher
+
+procedure Thasher.loadFrom(path:string);
+var
+ sr: TsearchRec;
+ s, l, h: string;
+begin
+if path='' then exit;
+path:=includeTrailingPathDelimiter(lowercase(path));
+if findFirst(path+'*.md5', faAnyFile-faDirectory, sr) <> 0 then exit;
+ repeat
+ s:=loadTextfile(path+sr.name);
+ while s > '' do
+ begin
+ l:=chopline(s);
+ h:=trim(chop('*',l));
+ if h = '' then break;
+ if l = '' then
+ // assume it is referring to the filename without the extention
+ l:=copy(sr.name, 1, length(sr.name)-4);
+ add(path+lowercase(l)+'='+h);
+ end;
+ until findnext(sr) <> 0;
+sysutils.findClose(sr);
+end; // loadFrom
+
+function Thasher.getHashFor(fn:string):string;
+begin
+try result:=values[lowercase(fn)]
+except result:='' end
+end;
+
+//////////// TstringToIntHash
+
+constructor TstringToIntHash.create;
+begin
+inherited create;
+sorted:=TRUE;
+duplicates:=dupIgnore;
+end; // create
+
+function TstringToIntHash.getIntByIdx(idx:integer):integer;
+begin if idx < 0 then result:=0 else result:=integer(objects[idx]) end;
+
+function TstringToIntHash.getInt(s:string):integer;
+begin result:=getIntByIdx(indexOf(s)) end;
+
+procedure TstringToIntHash.setInt(s:string; int:integer);
+begin
+beginUpdate();
+objects[add(s)]:=Tobject(int);
+endUpdate();
+end; // setInt
+
+function TstringToIntHash.incInt(s:string):integer;
+var
+ i: integer;
+begin
+beginUpdate();
+i:=add(s);
+result:=integer(objects[i]);
+inc(result);
+objects[i]:=Tobject(result);
+endUpdate();
+end; // autoupdatedFiles_getCounter
+
+//////////// Ttpl
+
+constructor Ttpl.create(txt:string=''; over:Ttpl=NIL);
+begin
+fullText:=txt;
+self.over:=over;
+end;
+
+destructor Ttpl.destroy;
+begin
+freeAndNIL(strTable);
+inherited;
+end; // destroy
+
+function Ttpl.getStrByID(id:string):string;
+begin
+if strTable = NIL then
+ begin
+ strTable:=THashedStringList.create;
+ strTable.text:=txt['special:strings'];
+ end;
+result:=strTable.values[id];
+if (result = '') and assigned(over) then
+ result:=over.getStrByID(id)
+end; // getStrByID
+
+function Ttpl.getIdx(section:string):integer;
+begin
+if section <> last.section then
+ begin
+ last.section:=section;
+ for result:=0 to length(sections)-1 do
+ if sameText(sections[result].name, section) then
+ begin
+ last.idx:=result;
+ exit;
+ end;
+ last.idx:=-1;
+ end;
+result:=last.idx
+end; // getIdx
+
+function Ttpl.newSection(section:string):PtplSection;
+var
+ i: integer;
+begin
+// add
+i:=length(sections);
+setLength(sections, i+1);
+result:=@sections[i];
+result.name:=section;
+// getIdx just filled 'last' with not-found, so we must update
+last.section:=section;
+last.idx:=i;
+// manage file.EXT sections
+if not ansiStartsText('file.', section) then exit;
+i:=length(fileExts);
+setLength(fileExts, i+2);
+delete(section, 1, 4);
+fileExts[i]:=section;
+fileExts[i+1]:=str_(last.idx);
+lastExt.section:=section;
+lastExt.idx:=last.idx;
+end; // newSection
+
+function Ttpl.sectionExist(section:string):boolean;
+begin
+result:=getIdx(section)>=0;
+if not result and assigned(over) then
+ result:=over.sectionExist(section);
+end;
+
+function Ttpl.getSection(section:string):PtplSection;
+var
+ i: integer;
+begin
+result:=NIL;
+i:=getIdx(section);
+if i >= 0 then result:=@sections[i];
+if assigned(over) and ((result = NIL) or (trim(result.txt) = '')) then
+ result:=over.getSection(section);
+end; // getSection
+
+function Ttpl.getTxt(section:string):string;
+var
+ i: integer;
+begin
+i:=getIdx(section);
+if i >= 0 then
+ result:=sections[i].txt
+else if assigned(over) then
+ result:=over[section]
+else
+ result:=''
+end; // getTxt
+
+function Ttpl.getTxtByExt(fileExt:string):string;
+var
+ i: integer;
+begin
+result:='';
+if (lastExt.section > '') and (fileExt = lastExt.section) then
+ begin
+ if lastExt.idx >= 0 then result:=sections[lastExt.idx].txt;
+ exit;
+ end;
+i:=idxOf(fileExt, fileExts);
+if (i < 0) and assigned(over) then
+ begin
+ result:=over.getTxtByExt(fileExt);
+ if result > '' then exit;
+ end;
+lastExt.section:=fileExt;
+lastExt.idx:=i;
+if i < 0 then exit;
+i:=int_(fileExts[i+1]);
+lastExt.idx:=i;
+result:=sections[i].txt;
+end; // getTxtByExt
+
+procedure Ttpl.fromString(txt:string);
+begin
+src:='';
+sections:=NIL;
+fileExts:=NIL;
+last.section:=#255'null'; // '' is a valid (and often used) section name. This is a better null value.
+freeAndNIL(strTable); // mod by mars
+
+appendString(txt);
+end; // fromString
+
+procedure Ttpl.appendString(txt:string);
+var
+ ptxt, bos: Pchar;
+ cur_section, next_section: string;
+
+ function pred(p:pchar):pchar; inline;
+ begin
+ result:=p;
+ if p <> NIL then
+ dec(result);
+ end;
+
+ function succ(p:pchar):pchar; inline;
+ begin
+ result:=p;
+ if p <> NIL then
+ inc(result);
+ end;
+
+ procedure findNextSection();
+ begin
+ // find start
+ bos:=ptxt;
+ repeat
+ if bos^ <> '[' then bos:=ansiStrPos(bos, #10'[');
+ if bos = NIL then exit;
+ if bos^ = #10 then inc(bos);
+ if getSectionAt(bos, next_section) then
+ exit;
+ inc(bos);
+ until false;
+ end; // findNextSection
+
+ procedure saveInSection();
+ var
+ ss: TStringDynArray;
+ s: string;
+ i, si: integer;
+ base: TtplSection;
+ till: pchar;
+ append: boolean;
+ sect, from: PtplSection;
+ begin
+ till:=pred(bos);
+ if till = NIL then till:=pred(strEnd(ptxt));
+ if till^ = #10 then dec(till);
+ if till^ = #13 then dec(till);
+
+ base.txt:=getStr(ptxt, till);
+ // there may be flags after |
+ s:=cur_section;
+ cur_section:=chop('|', s);
+ base.nolog:=ansiPos('no log', s) > 0;
+ base.nourl:=ansiPos('private', s) > 0;
+ base.cache:=ansiPos('cache', s) > 0;
+ base.ts:=now();
+
+ s:=cur_section;
+ append:=ansiStartsStr('+', s);
+ if append then
+ delete(s,1,1);
+
+ // there may be several section names separated by =
+ ss:=split('=', s);
+ // handle the main section specific case
+ if ss = NIL then addString('', ss);
+ // assign to every name the same txt
+ for i:=0 to length(ss)-1 do
+ begin
+ s:=trim(ss[i]);
+ si:=getIdx(s);
+ from:=NIL;
+ if si < 0 then // not found
+ begin
+ if append then
+ from:=getSection(s);
+ sect:=newSection(s);
+ end
+ else
+ begin
+ sect:=@sections[si];
+ if append then
+ from:=sect;
+ end;
+ if from<>NIL then
+ begin // inherit from it
+ sect.txt:=from.txt+base.txt;
+ sect.nolog:=from.nolog or base.nolog;
+ sect.nourl:=from.nourl or base.nourl;
+ continue;
+ end;
+ sect^:=base;
+ sect.name:=s; // restore this lost attribute
+ end;
+ end; // saveInSection
+
+const
+ BOM = #$EF#$BB#$BF;
+var
+ first: boolean;
+begin
+// this is used by some unicode files. at the moment we just ignore it.
+if ansiStartsStr(BOM, txt) then
+ delete(txt, 1, length(BOM));
+
+if txt = '' then exit;
+src:=src+txt;
+cur_section:='';
+ptxt:=@txt[1];
+first:=TRUE;
+ repeat
+ findNextSection();
+ if not first or (trim(getStr(ptxt, pred(bos))) > '') then
+ saveInSection();
+ if bos = NIL then break;
+ cur_section:=next_section;
+ inc(bos, length(cur_section)); // get faster to the end of line
+ ptxt:=succ(ansiStrPos(bos, #10)); // get to the end of line (and then beyond)
+ first:=FALSE;
+ until ptxt = NIL;
+updateUTF8();
+if assigned(onChange) then
+ onChange(self);
+end; // appendString
+
+procedure Ttpl.setOver(v:Ttpl);
+begin
+fOver:=v;
+updateUTF8();
+end; // setOver
+
+procedure Ttpl.updateUTF8();
+begin fUTF8:=assigned(over) and over.utf8 or utf8test(fullText) end;
+
+function Ttpl.getSections():TStringDynArray;
+var
+ i: integer;
+begin
+i:=length(sections);
+setLength(result, i);
+for i:=0 to i-1 do
+ result[i]:=sections[i].name;
+end;
+
+function Ttpl.me():Ttpl;
+begin result:=self end;
+
+
+
+procedure Ttlv.parse(data:ansistring);
+begin
+whole:=data;
+cur:=1;
+bound:=length(data);
+stackTop:=0;
+end; // parse
+
+function Ttlv.pop(var value:ansistring):integer;
+var
+ n: integer;
+begin
+result:=-1;
+if isOver() then exit; // finished
+result:=integer((@whole[cur])^);
+n:=Pinteger(@whole[cur+4])^;
+value:=copy(whole, cur+8, n);
+lastValue:=value;
+inc(cur, 8+n);
+end; // pop
+
+function Ttlv.down():boolean;
+begin
+// do we have anything to recur on?
+if (cur = 1) then
+ begin
+ result:=false;
+ exit;
+ end;
+// push into the stack
+if (stackTop = length(stack)) then // space over
+ setLength(stack, stackTop+10); // make space
+stack[stackTop]:=cur;
+inc(stackTop);
+stack[stackTop]:=bound;
+inc(stackTop);
+
+bound:=cur;
+dec(cur, length(lastValue));
+result:=true;
+end; // down
+
+function Ttlv.up():boolean;
+begin
+if stackTop = 0 then
+ begin
+ result:=false;
+ exit;
+ end;
+dec(stackTop);
+bound:=stack[stackTop];
+dec(stackTop);
+cur:=stack[stackTop];
+result:=true;
+end; // up
+
+function Ttlv.getTotal():integer;
+begin result:=length(whole) end;
+
+function Ttlv.getCursor():integer;
+begin result:=cur end;
+
+function Ttlv.getPerc():real;
+begin
+if length(whole) = 0 then result:=0
+else result:=cur/length(whole)
+end; // getPerc
+
+function Ttlv.isOver():boolean;
+begin result:=(cur+8 > bound) end;
+
+function Ttlv.getTheRest():ansistring;
+begin result:=copy(whole, cur, bound-cur+1) end;
+
+end.
diff --git a/copyright.txt b/copyright.txt
new file mode 100644
index 0000000..7055b61
--- /dev/null
+++ b/copyright.txt
@@ -0,0 +1,6 @@
+HFS version %s, Copyright (C) 2002-2020 Massimo Melina (www.rejetto.com)
+HFS comes with ABSOLUTELY NO WARRANTY; for details click Menu -> Web links -> License
+This is FREE software, and you are welcome to redistribute it
+under certain conditions.
+
+Build #%s
diff --git a/country.cache b/country.cache
new file mode 100644
index 0000000..c570154
--- /dev/null
+++ b/country.cache
@@ -0,0 +1 @@
+127.0.0.1=(Private Address) (XX)
\ No newline at end of file
diff --git a/data.RES b/data.RES
new file mode 100644
index 0000000..f882cec
Binary files /dev/null and b/data.RES differ
diff --git a/data.rc b/data.rc
new file mode 100644
index 0000000..f3ce7e6
--- /dev/null
+++ b/data.rc
@@ -0,0 +1,12 @@
+1 24 "WindowsXP.manifest"
+defaultTpl TEXT default.tpl
+copyright TEXT copyright.txt
+dmBrowserTpl TEXT dmBrowser.tpl
+invertban TEXT invertban.txt
+filelistTpl TEXT filelist.tpl
+uploadDisabled TEXT upload_disabled.txt
+uploadHowTo TEXT upload_how.txt
+alias TEXT alias.txt
+shell GIF shell.gif
+IPservices TEXT ipservices.txt
+jquery TEXT jquery.min.js
diff --git a/default.tpl b/default.tpl
new file mode 100644
index 0000000..d4610e1
--- /dev/null
+++ b/default.tpl
@@ -0,0 +1,1155 @@
+Welcome! This is the default template for HFS 2.4
+template revision TR3.
+
+Here below you'll find some options affecting the template.
+Consider 1 is used for "yes", and 0 is used for "no".
+
+DO NOT EDIT this template just to change options. It's a very bad way to do it, and you'll pay for it!
+Correct way: in Virtual file system, right click on home/root, properties, diff template,
+put this text [+special:strings]
+and following all the options you want to change, using the same syntax you see here.
+
+[+special:strings]
+
+option.newfolder=1
+option.move=1
+option.comment=1
+option.rename=1
+COMMENT with these you can disable some features of the template. Please note this is not about user permissions, this is global!
+
+[common-head]
+
+
+
+
+
+
+
+
+[]
+{.$common-head.}
+ {.!HFS.} %folder%
+
+
+
+
+
+
+
+
WARNING: this template is only to be used with HFS 2.3 (and macros enabled)
+ {.$menu panel.}
+ {.$folder panel.}
+ {.$list panel.}
+
+
+
+
+
+[list panel]
+{.if not| %number% |{:
+ {.!{.if|{.length|{.?search.}.}|No results|No files.}.}
+:}|{:
+
+:}.}
+
+
+
+[menu panel]
+
+
+
+
+[title-bar]
+ {.!title.}
+
+
+
+
+[folder panel]
+
+ {.breadcrumbs|{:
{.if|{.length|%bread-name%.}|/ %bread-name%|
.}:} .}
+
+{.if|%number%|
+
+ %number-folders% {.!folders.}, %number-files% {.!files.}, {.add bytes|%total-size%.}
+
+.}
+{.123 if 2| .}
+
+[upload panel]
+
+
+ {.!Uploaded.}: 0 - {.!Failed.}: 0 - {.!Queued.}: 0
+
+
+
+
+
+
+[search panel]
+
+
+
+
+
+
+[+special:strings]
+title=HTTP File Server
+max s dl msg=There is a limit on the number of simultaneous downloads on this server.
This limit has been reached. Retry later.
+retry later=Please, retry later.
+item folder=in folder
+no files=No files in this folder
+no results=No items match your search query
+confirm=Are you sure?
+
+[icons.css|no log]
+@font-face { font-family: 'fontello';
+ src: url('data:application/x-font-woff;base64,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') format('woff');
+}
+.fa { font-family: "fontello"; font-style: normal; font-weight: normal; }
+.fa-asterisk::before { content:"\e800" }
+.fa-check-circled::before { content:"\e801" }
+.fa-user::before { content:"\e802" }
+.fa-clock-o::before { content:"\e803" }
+.fa-download::before { content:"\e804" }
+.fa-ban::before { content:"\e805" }
+.fa-edit::before { content:"\e806" }
+.fa-check-square::before { content:"\e807" }
+.fa-folder::before { content:"\e808" }
+.fa-globe::before { content:"\e809" }
+.fa-home::before { content:"\e80a" }
+.fa-key::before { content:"\e80b" }
+.fa-lock::before { content:"\e80c" }
+.fa-refresh::before { content:"\e80d" }
+.fa-retweet::before { content:"\e80e" }
+.fa-search::before { content:"\e80f" }
+.fa-star::before { content:"\e810" }
+.fa-cancel-circled::before { content:"\e811"; }
+.fa-truck::before { content:"\e812" }
+.fa-upload::before { content:"\e813" }
+.fa-bars::before { content:"\f0c9" }
+.fa-coffee::before { content:"\f0f4" }
+.fa-quote-left::before { content:"\f10d" }
+.fa-file-archive-o::before { content:"\f1c6" }
+.fa-trash::before { content:"\f1f8" }
+.fa-user-circle::before { content:"\f2bd" }
+.fa-lightbulb:before { content: '\f0eb' }
+.fa-sort:before { content: '\f0dc' }
+.fa-sort-alt-up:before { content: '\f160' }
+.fa-sort-alt-down:before { content: '\f161' }
+
+[style.css|no log|cache]
+/*! normalize.css v8.0.0 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}template{display:none}[hidden]{display:none}
+
+{.$icons.css.}
+
+.pure-button {
+ background-color: #cde; padding: .5em 1em; color: #444; color: rgba(0,0,0,.8); border: 1px solid #999; border: transparent; text-decoration: none; box-sizing: border-box;
+ border-radius: 2px; display: inline-block; zoom: 1; white-space: nowrap; vertical-align: middle; text-align: center; cursor: pointer; user-select: none;
+}
+body { font-family:tahoma, verdana, arial, helvetica, sans; transition:background-color 1s ease; }
+a { text-decoration:none; color:#26c; border:1px solid transparent; padding:0 0.1em; }
+#folder-path { float:left; margin-bottom: 0.2em; }
+#folder-path a { padding: .5em; }
+#folder-path a:first-child { padding:.28em } #folder-path i.fa { font-size:135% }
+button i.fa { font-size:110% }
+.item { margin-bottom:.3em; padding:.3em .8em; border-top:1px solid #ddd; }
+.item:hover { background:#f8f8f8; }
+.item-props { float:right; font-size:90%; margin-left:12px; color:#777; margin-top:.2em; }
+.item-link { float:left; word-break:break-word; /* fix long names without spaces on mobile */ }
+.item img { vertical-align: text-bottom; margin:0 0.2em; }
+.item .fa-lock { margin-right: 0.2em; }
+.item .clearer { clear:both }
+.comment { color:#666; padding:.1em .5em .2em; background-color: #f5f5f5; border-radius: 1em; margin-top: 0.1em; }
+.comment>i { margin-right:0.5em; }
+.item-size { margin-left:.3em }
+.selector { float:left; width: 1.2em; height:1.2em; margin-right: .5em;}
+.item-menu { padding:0.1em 0.3em; border-radius:0.6em; border: 1px outset; position: relative; top: -0.1em;}
+.dialog-content .buttons { margin-top:1.5em }
+.dialog-content .buttons button { margin:.5em auto; min-width: 9em; display:block; }
+.dialog-content.error { background: #fcc; }
+.dialog-content.error h2 { text-align:center }
+.dialog-content.error button { background-color: #f77; color: white; }
+#wrapper { max-width:60em; margin:auto; } /* not too wide or it will be harder to follow rows */
+#serverinfo { font-size:80%; text-align:center; margin: 1.5em 0 0.5em; }
+#selection-panel { text-align:center; }
+#selection-panel label { margin-right:0.8em }
+#selection-panel button { vertical-align:baseline; }
+#selection-panel .buttons { white-space:nowrap }
+
+.item-menu { display:none }
+.can-comment .item-menu,
+.can-rename .item-menu,
+.can-delete .item-menu { display:inline-block; display:initial; }
+
+#folder-stats { font-size:90%; padding:.1em .3em; margin:.5em; float:right; }
+#files,#nothing { clear:both }
+#nothing { padding:1em }
+
+.dialog-overlay { background:rgba(0,0,0,.75); position:fixed; top:0; left:0; width:100%; height:100%; z-index:100; }
+.dialog-content { position: absolute; top: 50%; left: 50%;
+ transform: translate(-50%, -50%);
+ -webkit-transform: translate(-50%, -50%);
+ -moz-transform: translate(-50%, -50%);
+ -ms-transform: translate(-50%, -50%);
+ -o-transform: translate(-50%, -50%);
+ background:#fff; border-radius: 1em; padding: 1em; text-align:center; min-width: 10em;
+}
+.ask input { border:1px solid rgba(0,0,0,0.5); padding: .2em; margin-top: .5em; }
+.ask .close { float: right; font-size: 1.2em; color: red; position: relative; top: -0.4em; right: -0.3em; }
+
+#additional-panels input { color: #555; padding: .1em 0.3em; border-radius: 0.4em; }
+
+.additional-panel { position:relative; max-height: calc(100vh - 5em); text-align:left; margin: 0.5em 1em; padding: 0.5em 1em; border-radius: 1em; background-color:#555; border: 2px solid #aaa; color:#fff; line-height: 1.5em; display:inline-block; }
+.additional-panel .close { position: absolute; right: -0.8em; top: -0.2em; color: #aaa; font-size: 130%; }
+
+body.dark-theme { background:#222; color:#aaa; }
+body.dark-theme #title-bar { color:#bbb }
+body.dark-theme a { color:#79b }
+body.dark-theme a.pure-button { color:#444 }
+body.dark-theme .item:hover { background:#111; }
+body.dark-theme .pure-button { background:#89a; }
+body.dark-theme .item .comment { background-color:#444; color:#888; }
+body.dark-theme #foldercomment { background-color:#333; color:#999; }
+body.dark-theme .dialog-overlay { background:rgba(100,100,100,.5) }
+body.dark-theme .dialog-content { background:#222; color:#888; }
+body.dark-theme input,
+body.dark-theme textarea,
+body.dark-theme select,
+body.dark-theme #additional-panels input
+{ background: #111; color: #aaa; }
+
+#msgs { display:none; }
+#msgs li:first-child { font-weight:bold; }
+
+#menu-panel { position:fixed; top:0; left:0; width: 100%; background:#555; text-align:center;
+position: -webkit-sticky; position: -moz-sticky; position: -ms-sticky; position: -o-sticky; position: sticky; margin-bottom:0.3em;
+z-index:1; /* without this .item-menu will be over*/ }
+#menu-panel button span { margin-left:.8em }
+#user-panel button { padding:0.3em 0.6em; font-size:smaller; margin-left:1em; }
+#user-panel span { position: relative; top: 0.1em; }
+#menu-bar { padding:0.2em 0 }
+
+@media (min-width: 50em) {
+#toggleTs { display: none }
+}
+@media (max-width: 50em) {
+#menu-panel button { padding: .4em .6em; }
+.additional-panel button span,
+#menu-bar button span { display:none } /* icons only */
+#menu-bar i { font-size:120%; } /* bigger icons */
+#menu-bar button { width: 3em; max-width:10.7vw; padding: .4em 0; }
+.hideTs .item-ts { display:none }
+}
+
+#upload-panel { font-size: 88%;}
+#upload-progress { margin-top:.5em; display:none; }
+#upload-progress progress { width:10em; position:relative; top:.1em; }
+#progress-text { position: absolute; color: #000; font-size: 80%; margin-left:.5em; z-index:1; }
+#upload-results a { color:#b0c2d4; }
+#upload-results>* { display:block; word-break: break-all; }
+#upload-results>span { margin-left:.15em; } /* better alignment */
+#upload-results { max-height: calc(100vh - 11em); overflow: auto;}
+#upload-panel>button { margin: auto; display: block; margin-top:.8em;} /* center it*/
+
+
+[file=folder=link|private]
+
+
+
+ {.cut||-3|%item-modified%.}
+[+file]
+ %item-size%B
+[+file=folder=link]
+ {.if|{.get|is new.}|.}
+[+file=folder]
+
+[+file=folder=link]
+
+
+[+file=folder=link]
+ {.if| {.length|{.?search.}.} |{:{.123 if 2|
{.!item folder.} |{.breadcrumbs|{:
%bread-name%/:}|from={.count substring|/|%folder%.}/breadcrumbs.}|
.}:} .}
+ {.123 if 2|.}
+
+
+[error-page]
+
+
+
+
+
+
+
+
+%content%
+
+
+
+
+
+[not found]
+{.!Not found.}
+{.!go to root.}
+
+[overload]
+{.!Server Too Busy.}
+{.!The server is too busy to handle your request at this time. Retry later.}
+
+[max contemp downloads]
+{.!Download limit.}
+{.!max s dl msg.}
+
({.disconnection reason.})
+
+[unauthorized]
+{.!Unauthorized.}
+{.!Either your user name and password do not match, or you are not permitted to access this resource..}
+
+[deny]
+{.!Forbidden.}
+{.or|%reason%|{.!This resource is not accessible..}.}
+
+[ban]
+{.!You are banned.}
+%reason%
+
+[upload]
+
+[upload-file]
+
+[upload-results]
+[{.cut|1|-1|%uploaded-files%.}
+]
+
+[upload-success]
+{
+"url":"%item-url%",
+"name":"%item-name%",
+"size":"%item-size%",
+"speed":"%smart-speed%"
+},
+{.if| {.length|%user%.} |{:
+ {.set item|{.force ansi|%folder%%item-name%.}|comment={.!uploaded by.} %user%.}
+:}.}
+
+[upload-failed]
+{ "err":"{.!%reason%.}", "name":"%item-name%" },
+
+[progress|no log]
+
+
+
+[progress-nofiles]
+{.!No file exchange in progress..}
+
+[progress-upload-file]
+{.if not|{.{.?only.} = down.}|{:
+ {.!Uploading.} %total% @ %speed-kb% KB/s
+
%filename%
+
{.!Time left.} %time-left%"
+
%perc%%
+:}.}
+
+[progress-download-file]
+{.if not|{.{.?only.} = up.}|{:
+ {.!Downloading.} %total% @ %speed-kb% KB/s
+
%filename%
+
{.!Time left.} %time-left%"
+
%perc%%
+:}.}
+
+[ajax.mkdir|no log]
+{.check session.}
+{.set|x|{.postvar|name.}.}
+{.break|if={.pos|\|var=x.}{.pos|/|var=x.}|result=forbidden.}
+{.break|if={.not|{.can mkdir.}.}|result=not authorized.}
+{.set|x|{.force ansi|%folder%{.^x.}.}.}
+{.break|if={.exists|{.^x.}.}|result=exists.}
+{.break|if={.not|{.length|{.mkdir|{.^x.}.}.}.}|result=failed.}
+{.add to log|{.!User.} %user% {.!created folder.} "{.^x.}".}
+{.pipe|ok.}
+
+[ajax.rename|no log]
+{.check session.}
+{.break|if={.not|{.can rename.}.}|result=forbidden.}
+{.break|if={.is file protected|{.postvar|from.}.}|result=forbidden.}
+{.break|if={.is file protected|{.postvar|to.}.}|result=forbidden.}
+{.set|x|{.force ansi|%folder%{.postvar|from.}.}.}
+{.set|yn|{.force ansi|{.postvar|to.}.}.}
+{.set|y|{.force ansi|%folder%.}{.^yn.}.}
+{.break|if={.not|{.exists|{.^x.}.}.}|result=not found.}
+{.break|if={.exists|{.^y.}.}|result=exists.}
+{.set|comment| {.get item|{.^x.}|comment.} .}
+{.set item|{.^x.}|comment=.}
+{.break|if={.not|{.length|{.rename|{.^x.}|{.^yn.}.}.}.}|result=failed.}
+{.set item|{.^x.}|resource={.filepath|{.get item|{.^x.}|resource.}.}{.^yn.}.}
+{.set item|{.^x.}|name={.^yn.}.}
+{.set item|{.^y.}|comment={.^comment.}.}
+{.add to log|{.if|%user%|{.!User.} %user%|{.!Anonymous.}.} {.!renamed.} "{.^x.}" {.!to.} "{.^yn.}".}
+{.pipe|ok.}
+
+[ajax.move|no log]
+{.check session.}
+{.set|dst|{.force ansi|{.postvar|dst.}.}.}
+{.break|if={.not|{.and|{.can move.}|{.get|can delete.}|{.get|can upload|path={.^dst.}.}/and.}.} |result=forbidden.}
+{.set|log|{.!Moving items to.} {.^dst.}.}
+{.for each|fn|{.replace|:|{.no pipe||.}|{.force ansi|{.postvar|files.}.}.}|{:
+ {.break|if={.is file protected|var=fn.}|result=forbidden.}
+ {.set|x|{.force ansi|%folder%.}{.^fn.}.}
+ {.set|y|{.^dst.}/{.^fn.}.}
+ {.if not |{.exists|{.^x.}.}|{.^x.}: {.!not found.}|{:
+ {.if|{.exists|{.^y.}.}|{.^y.}: {.!already exists.}|{:
+ {.set|comment| {.get item|{.^x.}|comment.} .}
+ {.set item|{.^x.}|comment=.} {.comment| this must be done before moving, or it will fail.}
+ {.if|{.length|{.move|{.^x.}|{.^y.}.}.} |{:
+ {.move|{.^x.}.md5|{.^y.}.md5.}
+ {.set|log|{.chr|13.}{.^fn.}|mode=append.}
+ {.set item|{.^y.}|comment={.^comment.}.}
+ :} | {:
+ {.set|log|{.chr|13.}{.^fn.} (failed)|mode=append.}
+ {.maybe utf8|{.^fn.}.}: {.!not moved.}
+ :}/if.}
+ :}/if.}
+ :}.}
+ ;
+:}.}
+{.add to log|{.^log.}.}
+
+[ajax.comment|no log]
+{.check session.}
+{.break|if={.not|{.can comment.}.} |result=forbidden.}
+{.for each|fn|{.replace|:|{.no pipe||.}|{.postvar|files.}.}|{:
+ {.break|if={.is file protected|var=fn.}|result=forbidden.}
+ {.set item|{.force ansi|%folder%{.^fn.}.}|comment={.force ansi|{.postvar|text.}.}.}
+:}.}
+{.pipe|ok.}
+
+[ajax.changepwd|no log]
+{.check session.}
+{.break|if={.not|{.can change pwd.}.} |result=forbidden.}
+{.if|{.length|{.set account||password={.force ansi|{.postvar|new.}.}.}/length.}|ok|failed.}
+
+[special:alias]
+check session=if|{.{.cookie|HFS_SID_.} != {.postvar|token.}.}|{:{.cookie|HFS_SID_|value=|expires=-1.} {.break|result=bad session.}:}
+can mkdir=and|{.get|can upload.}|{.!option.newfolder.}
+can comment=and|{.get|can upload.}|{.!option.comment.}
+can rename=and|{.get|can delete.}|{.!option.rename.}
+can delete=get|can delete
+can change pwd=member of|can change password
+can move=or|1|1
+escape attr=replace|"|"|$1
+commentNL=if|{.pos|
|$1.}
+add bytes=switch|{.cut|-1||$1.}|,|0,1,2,3,4,5,6,7,8,9|$1 {.!Bytes.}|K,M,G,T|$1B
+
+[special:import]
+{.new account|can change password|enabled=1|is group=1|notes=accounts members of this group will be allowed to change their password.}
+
+[lib.js|no log|cache]
+//