From 1802ad65725c89116ddd18f712a48a64a2ad356e Mon Sep 17 00:00:00 2001 From: Fae Date: Wed, 1 Jul 2020 00:17:23 -0700 Subject: [PATCH 01/20] Start using translations from Crowdin - Removed Google-Translated test file (German) in favor of the Crowdin files-to-be --- i18n/de/LC_MESSAGES/ci_snapshots.mo | Bin 4871 -> 0 bytes i18n/de/LC_MESSAGES/ci_snapshots.po | 207 ---------------------------- i18n/ru/LC_MESSAGES/ci_snapshots.mo | Bin 0 -> 5782 bytes i18n/ru/LC_MESSAGES/ci_snapshots.po | 182 ++++++++++++++++++++++++ 4 files changed, 182 insertions(+), 207 deletions(-) delete mode 100644 i18n/de/LC_MESSAGES/ci_snapshots.mo delete mode 100644 i18n/de/LC_MESSAGES/ci_snapshots.po create mode 100644 i18n/ru/LC_MESSAGES/ci_snapshots.mo create mode 100644 i18n/ru/LC_MESSAGES/ci_snapshots.po diff --git a/i18n/de/LC_MESSAGES/ci_snapshots.mo b/i18n/de/LC_MESSAGES/ci_snapshots.mo deleted file mode 100644 index 108e624593dd40fbef9b4f7ee2e77b9f0ee59dae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4871 zcmcgvO>87b6)s3%g98bKgEx6TlPi!yNDd;C;ZeKwj@@;0fSWAVh@%ei|49 z9|XQo&;JDYIlTWAD1pBPvVCs@7lHo({t-BfLALiUEc#jCNg%H~5Bvb|O#QwBWINV? z9|K;kF|X&p4g4U+zXR+5Uk1JbAN>aSDZIag#U22@24t804mbn+J@9*wdke_+r7(uq zu|U@MO(6U4hrq{xzXArpzXSgQydT0J2HpYZoCKZ(O5meFj?-6wtbYJxeJ=p-0)7`5 z0AB=t1~>*j4E!^Y?Kp9d5Dx+G0X_+Q0?6Yz@EmXl_$Kh@K$s%Fe{bc#9|5ry@e3fP z#IJz}wfF;&ClGjP;5fE>r4Ve<39H-YTGe*t+P z{|3T+lMCkii|1OnStozb7k~c+-0Uaj7VG7huz&cHxY*9FBT+c7w8($yA z&3s|5vR~L&95eB>a?WOE~Vwx<%ykgn}uW^`^`c~Dig_G zp?6e&7k)@}f49}GgdS4aaIu+V3y!#9G;OO2^V02WUEx<*>27rozWME}q^azs%E6bJ z75wIOMpmkVq&D)+(TyUO5#iOY&T94E3GPjl574d2vd3kfQ{v%9va~(Ppr(EY;RQ=EhJLP`UZ@Ev5j6ML+mO4DwJ$+O`KaXR=a>r|^C2pajl9=dVLSD_wwy2mR z*YiQ%Jom~p4H{=Q;kRN#WhC~~)d%~asFi+blQPOeWcZF&2z!2A(}EORV3y>~%9v>+ z!ABm|dnC48UZ?@dD;BKTPDmD291-vAZn*8;T7ai zqFnTqVV8y^E%d_C+C&su^*FN(I}bGJJRW(&Y>L%gnWBXP7i(T(G@6#43(%xXv(D z+AKNQw}--pU23$oqr_`jK6S{5n|5TDF0NHg@Wc6okLR*%b%7`0l@=ZfwRM8{A%GXyIr~+>J7%Pa2>A7OsjkFt?3QOHwV5uKmgDlki15y0!CVt zgr-B7434Nkpm>;T$sXwex;P|QJbrx;a$#z!ug@=yB{wuhRPb?Gd0@Y5*eDBmZGPo;ajoz~`a!cd9 z!xwV=I_JftH{nnGnuwS(%xXM<(%hk3EXFs-KSq->q$)h%+K5oDoH&o=I1MTaAgI1u zDZvK`O36rr0$J0jnkh@$Vx^H_h9Ti(omvO`7v^i_m0+uBUa&H9fw}8WvkDoNDBQ<& zF-%_k`+;^PwP=M06dJTkJ2*M3GuC?ddv_h6vUh`w(D@vxH@9nMaKKKQ8Fs>SHzRQr z`$U%`c9W?)jTv~7n+r}9=&n7nhp5!0h0_b!=|4R!%go!xc>-zPOyiyLs|XAyBgXei ziBOR?F_MMMX?cBo6EraLIf`<4tO`hJLK+lg_V^gGb&OqY!o<8u*TbY}LIT^yHz$c( nDMbs#^^u9@%htQ!Al1Fmv~MS@6;ln;aY8y1_*dM5=uZ3xD#){u diff --git a/i18n/de/LC_MESSAGES/ci_snapshots.po b/i18n/de/LC_MESSAGES/ci_snapshots.po deleted file mode 100644 index 0da5851..0000000 --- a/i18n/de/LC_MESSAGES/ci_snapshots.po +++ /dev/null @@ -1,207 +0,0 @@ -msgid "" -msgstr "" -"Project-Id-Version: \n" -"POT-Creation-Date: 2020-06-08 23:23-0700\n" -"PO-Revision-Date: 2020-06-10 19:20-0700\n" -"Language-Team: \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"X-Generator: Poedit 2.3.1\n" -"X-Poedit-Basepath: ../../..\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" -"Last-Translator: \n" -"Language: de\n" -"X-Poedit-SearchPath-0: .\n" -"X-Poedit-SearchPathExcluded-0: i18n\n" -"X-Poedit-SearchPathExcluded-1: wp-plugin\n" - -#: index.php:23 -msgid "Failed Request - File Read Error" -msgstr "Fehlgeschlagene Anforderung - Fehler beim Lesen der Datei" - -#: index.php:67 -msgid "Name" -msgstr "Dateiname" - -#: index.php:69 -msgid "Created" -msgstr "Erstellt" - -#: index.php:70 -msgid "Size" -msgstr "Größe" - -#: index.php:71 -msgid "Expires" -msgstr "Ablauf" - -# Number of Downloads -#: index.php:72 -msgid "DL #" -msgstr "DL #" - -#: index.php:73 -msgid "Github" -msgstr "Github" - -#: index.php:120 -msgid "Only" -msgstr "Nur" - -#: index.php:133 -msgid "View Pull Request on Github.com" -msgstr "Pull Request auf Github.com anzeigen" - -#: index.php:139 index.php:146 -msgid "View Commit on Github.com" -msgstr "Commit auf Github.com anzeigen" - -#: index.php:223 -msgid "Error while fetching Snapshot list!" -msgstr "Fehler beim Abrufen der Snapshot-Liste!" - -#: lib/functions.php:343 -msgid "Failed - Internal Server Error" -msgstr "Fehlgeschlagen - Interner Serverfehler" - -#: lib/functions.php:358 -msgid "404 Not Found" -msgstr "404 Nicht gefunden" - -#: lib/functions.php:360 -msgid "Not Found" -msgstr "Nicht gefunden" - -#: lib/functions.php:361 -msgid "The requested URL was not found on this server." -msgstr "Die angeforderte URL wurde auf diesem Server nicht gefunden." - -#: lib/functions.php:363 -msgid " Server at " -msgstr " Server bei " - -#: lib/functions.php:363 -msgid " Port " -msgstr " Port " - -#: lib/init.php:40 -msgid "Database Connection Error!" -msgstr "Datenbankverbindungsfehler!" - -#: tpl/index.lang.php:8 -msgid "Mudlet Pull-Request Snapshots" -msgstr "Mudlet Pull-Request Snapshots" - -#: tpl/index.lang.php:9 -msgid "Mudlet Logo" -msgstr "Mudlet Logo" - -#: tpl/index.lang.php:10 -msgid "Home" -msgstr "Zuhause" - -#: tpl/index.lang.php:11 -msgid "Wiki" -msgstr "Wiki" - -#: tpl/index.lang.php:12 -msgid "Forum" -msgstr "Forum" - -#: tpl/index.lang.php:13 -msgid "Available Snapshot Files" -msgstr "Verfügbare Snapshot-Dateien" - -#: tpl/index.lang.php:14 -msgid "" -"This page lists \"Snapshots\" of the Mudlet software built through " -"Continuous Integration services as a result of commits and pull requests to " -"the Mudlet repository on Github." -"com
These files are made available to enable easier testing of " -"software changes but cannot be stored indefinitely.
For Mudlet Release " -"Downloads visit: https://www." -"mudlet.org/download/" -msgstr "" -"Auf dieser Seite werden \"Snapshots\" der Mudlet-Software aufgelistet, die " -"durch Continuous Integration Services als Ergebnis von Commits und Pull-" -"Anforderungen an das Mudlet-" -"Repository auf Github.com erstellt wurden.
\n" -"Diese Dateien werden zur Verfügung gestellt, um das Testen von " -"Softwareänderungen zu vereinfachen, können jedoch nicht unbegrenzt " -"gespeichert werden.
\n" -"Downloads für Mudlet-Veröffentlichungen finden Sie unter: https://www.mudlet.org/download/" - -#: tpl/index.lang.php:23 -msgid "All times are shown in " -msgstr "Alle Zeiten werden in angezeigt " - -#: tpl/index.lang.php:24 -msgid "Latest PTB Snapshots" -msgstr "Neueste PTB Snapshots" - -# Filter controls text. Before platforms. -#: tpl/index.lang.php:26 -msgid "Show only files for: " -msgstr "Nur Dateien anzeigen für: " - -#: tpl/index.lang.php:27 -msgid "All Platforms" -msgstr "Alle Plattformen" - -# Filter controls text. After platforms. -#: tpl/index.lang.php:28 -msgid "in" -msgstr "im" - -#: tpl/index.lang.php:29 -msgid "Branches & PRs" -msgstr "Niederlassungen & PRs" - -#: tpl/index.lang.php:30 -msgid "Branches Only" -msgstr "Nur Zweige" - -#: tpl/index.lang.php:31 -msgid "Pull-Requests Only" -msgstr "Nur Pull-Requests" - -#: tpl/index.lang.php:32 -msgid "Remember via Cookie: " -msgstr "Erinnern Sie sich über Cookie: " - -#: tpl/index.lang.php:34 -msgid "All times are in " -msgstr "Alle Zeiten sind in " - -#: tpl/index.lang.php:35 -msgid "Storage Used" -msgstr "Speicher verwendet" - -#: tpl/index.lang.php:36 -msgid "" -"Made with heart, coffee, " -"and You." -msgstr "" -"Gemacht mit Herz, Kaffee " -"und Dir." - -#: tpl/index.lang.php:41 -msgid "" -"Terms & PrivacyAbout Mudlet" -msgstr "" -"Bedingungen & " -"PrivatsphäreÜber Mudlet" - -#: tpl/index.lang.php:45 -msgid "local time" -msgstr "ortszeit" diff --git a/i18n/ru/LC_MESSAGES/ci_snapshots.mo b/i18n/ru/LC_MESSAGES/ci_snapshots.mo new file mode 100644 index 0000000000000000000000000000000000000000..c4eae243d7e912bf79e2db2d685b502ed88f8dd4 GIT binary patch literal 5782 zcmb`KTdW*)9mhunf#U^4ykGctfl9Trm(v36_UsyZgK7)M(}I#1&79r;o*mhpS!QN? zTEhdqDcDL4iWn6TFEJq@IX(2y)7w@O6JHFOi4VRQ5+97b@L=NQ#TY-o|LpAU>Cy_3 z$(jAm{4c-D_jj59dG-1$p0v1b<^Cl1SKnb-w}C%>CqG=BcUjiE!H2;2fcwDrfrr67 z_yqVl@MZ9`;OpQk;76}W*Zmdz0Pp_*KLTELC3E17;Pv1JP~?3Pyb62(WU1wV9|s%Y zP2hq4{I9@I^Zo*;z~6&n-|OHe@bBOs!6JiV@6`zX6nFzDawoxSz&rZycYs z1AX-S^GCqI060vOc;2z|Gt0_#rRoJxc(Ek z4!jcQMeas$J?Mge1D^p!{`cOW;`87n@4p4tg8u?<0B^oF<#i*7Nb5_W`1v)k0@lDS z;B(-0;9tOv;6K4CSY%N6^T98Gd%@ShmqFpkuOTU;E*7-xg4Kdk7T29|oa$a6yN;uI4w#jarij zGI%rhE!@I~e3N{UTor#;bBiC)VUb!U zR{(RZ;`cM$;=f!{KN4HHB=;zV!G${}E|!}1gGgCww+{B|Ksh|GFRoYj_>tP~cfGnb z<)~($8a>teu}< zx%6(ot>4trNC)lE_8WGHA8t(-aw>A8magP>4eV5VWRdb4##t6A$E&O9z}@TA_QONV zt?kcM(?zSDc4t#ICl-v+Fu&4PXV&jVL#=npt4ej}s-thlojY4CHQjO|xNL`(yhUzX zhsp`Gay@m?NZ9n}y>xu9xu-}7`Wa(z#23j#k_Wf@TOO{A)Zj%rQUn;R-?xnabbeH}MI z>2{3Kb!Dp^9{hVwE6XAyB%7Ym4|R1Iu}n-=Guo-A%B;KGsM(#hc48RwQ_k#ckVS2} zJ4-l-7!#YjZmXWFPVd&YA1+tQ0nsR z*6Z4<QvZqylj6M*gllys%F7dLFTG!#}(B4Mnfa+(i-|JE~6q{ zG0-6i`GDWeTWRK~o&KDkJx_OAEjuGK3x8Lo>Rq+p|>oUe_V) z{)4VAD{D8IrNmj)kTg={ql~)W)Yk6E51cuz?hDD9Jx#5GG>!!PzL}kB9+ExMuOUqW z5h`lBp_1y;L7w%zbY#0^OsH}Ldvj8M5#stDhJGWOm$aWH$`Lbx-<@kpVn(jl^}C@- zuDQT5N!qV&3WYx`3cKt@wvU8t8dmvX}3f!npFu9bz0^t$g*6|YWX!fgY-9RI`F?iyRmoF z?fZ2Q(ss(KZTj=X^u6S^nD2Ww?TO@^EKU~f;vIIeq>8tfZ@Xh+rxQl@9vWRp=afWc z#GC7qW%eHJw99Iy8-}jq4Tj4q=uYg~v1{8TIqklck{{Xv)M*B5vK9HJk8tOW@B#i`|dzAf#h6_KvM=D&9VM`?`^BSrARMhI`~= zQC9t`RAK*f7Zz&n*9R~8jz1zub{G|t#SOy{?B+92YfcjcyGR(`Xky#GTB}>vby?t+ zHh7m^Q!1N`7v)#Q7vi3Z7vhtQEF}k$L+QCH zel9u4$U=NRUQWIhFDM?Cc(iiVwx-Oo@({{MflGej;9u z&&11@SikZ-ldHzxkI!P(qL`E%e^Z;@zEfEIi{t?6d#GPdj`vSB@sAlepB%;wLlp!r zW8vB47&i7G`C#hYI2S8@yX;vlex~1EjmPm5*g=3U2p5Fzu#g^~OO9IE%%TK1K9`10 z#f$1vGd;}f#V1VkUqaa9h&zC9X3Gy!`;$XxI-eXxC8{JR&L&3{CXg=ZI3t80pNu*v zksLYg#Xo0Gm|zVevlSLe*^5uX7b%w>{^DnnN0D}dl?x)ehv_F7AQ43k79|H1(+h0t z#iv!eSFB2otg=?@jxXTNvdMLHA4~vux+Hem5sjQ^b;Q z!i1Q*^j1!zD9qej7K0==rEUUu{C1pAC7sB`p%#hl z!Q^P(ihn}HUSyV{i!bzR#_AuQMh~D0N>YA$@fl)I^&Ge2r!Q`MJ!>?DSG=fEaiNul z8tvyqF4i0qnx)|~(n}sojwO!|kGK+BlW3aw=sc)PNo1{gdtVZ=|b8N=qmF?d2ewBRJ zSac3YOk~YBBwm8$r=>{K+0)FL(#XCZrY}h%EKzw~FU2R!7tLtIGDErSqK(jDaB?X% z!?bQX$D$JR4XKdJJWW;@l1?X<5?-Db*{ul2u)@vQnRK%R{#3dWVao)6>;v#WPy4vG z@WzMy_ytTL>4)u@BrzYGtXWHkBx|H+l2mwu{Q@rLNb>v%W0DF zYRDI!-deDTlJpr7px{fQO#d9oOsNla8}lU8!YqpkH~X@GD*6pjJ}bgFnJuXzpAA|W z=_JjS^ugm(QcEfCq&%+@i8NtyZ~s3Y@v}l0R!d== 2 && n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || (n%10 >= 5 && n%10 <=9)) || (n%100 >= 11 && n%100 <= 14)) ? 2 : 3));\n" +"X-Poedit-SearchPath-0: .\n" +"X-Poedit-SearchPathExcluded-0: i18n\n" +"X-Poedit-SearchPathExcluded-1: wp-plugin\n" +"X-Crowdin-Project: mudlet\n" +"X-Crowdin-Project-ID: 306263\n" +"X-Crowdin-Language: ru\n" +"X-Crowdin-File: /Mudlet snapshots/ci_snapshots.po\n" +"X-Crowdin-File-ID: 208\n" + +#: index.php:23 +msgid "Failed Request - File Read Error" +msgstr "Сбой Запроса - Ошибка Чтения Файла" + +#: index.php:67 +msgid "Name" +msgstr "Имя" + +#: index.php:69 +msgid "Created" +msgstr "Создан" + +#: index.php:70 +msgid "Size" +msgstr "Размер" + +#: index.php:71 +msgid "Expires" +msgstr "Истекает" + +# Number of Downloads +#: index.php:72 +msgid "DL #" +msgstr "DL #" + +#: index.php:73 +msgid "Github" +msgstr "GitHub" + +#: index.php:120 +msgid "Only" +msgstr "Только" + +#: index.php:133 +msgid "View Pull Request on Github.com" +msgstr "Показать Pull Request на Github.com" + +#: index.php:139 index.php:146 +msgid "View Commit on Github.com" +msgstr "Просмотреть коммит на Github.com" + +#: index.php:223 +msgid "Error while fetching Snapshot list!" +msgstr "Ошибка при получении списка снимков!" + +#: lib/functions.php:343 +msgid "Failed - Internal Server Error" +msgstr "Сбой-Внутренняя ошибка сервера" + +#: lib/functions.php:358 +msgid "404 Not Found" +msgstr "404 Страница не найдена" + +#: lib/functions.php:360 +msgid "Not Found" +msgstr "Не найдено" + +#: lib/functions.php:361 +msgid "The requested URL was not found on this server." +msgstr "Запрашиваемый URL не найден на этом сервере." + +#: lib/functions.php:363 +msgid " Server at " +msgstr " Сервер по адресу " + +#: lib/functions.php:363 +msgid " Port " +msgstr " Порт " + +#: lib/init.php:40 +msgid "Database Connection Error!" +msgstr "Ошибка соединения с базой данных!" + +#: tpl/index.lang.php:8 +msgid "Mudlet Pull-Request Snapshots" +msgstr "Mudlet Pull-Request Снимок" + +#: tpl/index.lang.php:9 +msgid "Mudlet Logo" +msgstr "Логотип Mudlet" + +#: tpl/index.lang.php:10 +msgid "Home" +msgstr "Главная" + +#: tpl/index.lang.php:11 +msgid "Wiki" +msgstr "Вики" + +#: tpl/index.lang.php:12 +msgid "Forum" +msgstr "Форум" + +#: tpl/index.lang.php:13 +msgid "Available Snapshot Files" +msgstr "Доступные файлы снимков" + +#: tpl/index.lang.php:14 +msgid "This page lists \"Snapshots\" of the Mudlet software built through Continuous Integration services as a result of commits and pull requests to the Mudlet repository on Github.com
These files are made available to enable easier testing of software changes but cannot be stored indefinitely.
For Mudlet Release Downloads visit: https://www.mudlet.org/download/" +msgstr "На этой странице перечислены \"Снимки\" программного обеспечения Mudlet, построенного с помощью служб непрерывной интеграции в результате коммитов и запросов к репозиторию Mudlet на Github.com
Эти файлы доступны для более легкого тестирования изменений программного обеспечения, но не могут храниться бесконечно.
Посетите раздел \"Загрузка релизов Mudlet\": https://www.mudlet.org/download/" + +#: tpl/index.lang.php:23 +msgid "All times are shown in " +msgstr "Все времена отображаются в " + +#: tpl/index.lang.php:24 +msgid "Latest PTB Snapshots" +msgstr "Последние PTB снимки" + +# Filter controls text. Before platforms. +#: tpl/index.lang.php:26 +msgid "Show only files for: " +msgstr "Показывать только файлы для: " + +#: tpl/index.lang.php:27 +msgid "All Platforms" +msgstr "Все платформы" + +# Filter controls text. After platforms. +#: tpl/index.lang.php:28 +msgid "in" +msgstr "войти" + +#: tpl/index.lang.php:29 +msgid "Branches & PRs" +msgstr "Ветки & PRs" + +#: tpl/index.lang.php:30 +msgid "Branches Only" +msgstr "Только ветки" + +#: tpl/index.lang.php:31 +msgid "Pull-Requests Only" +msgstr "Только Pull-Requests" + +#: tpl/index.lang.php:32 +msgid "Remember via Cookie: " +msgstr "Запомнить с помощью Cookie: " + +#: tpl/index.lang.php:34 +msgid "All times are in " +msgstr "Все времена в " + +#: tpl/index.lang.php:35 +msgid "Storage Used" +msgstr "Используемое хранилище" + +#: tpl/index.lang.php:36 +msgid "Made with heart, coffee, and You." +msgstr "Сделано с сердце, кофе, и ты." + +#: tpl/index.lang.php:41 +msgid "Terms & PrivacyAbout Mudlet" +msgstr "Условия & КонфиденциальностиО Mudlet" + +#: tpl/index.lang.php:45 +msgid "local time" +msgstr "местное время" + From 3372fa354a80460a26a0aebe520032053d84997e Mon Sep 17 00:00:00 2001 From: Fae Date: Wed, 9 Dec 2020 19:40:37 -0800 Subject: [PATCH 02/20] filter dig results. --- cron.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cron.php b/cron.php index 43db72b..c1e2d35 100644 --- a/cron.php +++ b/cron.php @@ -165,7 +165,11 @@ function getTravisIps() $data = array(); $io = popen('dig +short nat.travisci.net | sort', 'r'); while ($line = fgets($io)) { - $data[] = trim($line); + $line = trim($line); + $vIP = filter_var($line, FILTER_VALIDATE_IP); + if( $vIP !== false ) { + $data[] = $vIP; + } } pclose($io); return $data; From ffd6f180751f22ec3d4a4491c103afc4d30087fa Mon Sep 17 00:00:00 2001 From: Fae Date: Wed, 9 Dec 2020 19:42:34 -0800 Subject: [PATCH 03/20] add darkmode via media query perfers-color-scheme --- tpl/index.tpl.html | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tpl/index.tpl.html b/tpl/index.tpl.html index 53320e3..be16f63 100644 --- a/tpl/index.tpl.html +++ b/tpl/index.tpl.html @@ -200,6 +200,15 @@ display: inline-block; } +@media (prefers-color-scheme: dark) { + body { + filter: invert(1) hue-rotate(.5turn); + } + footer { + color: #000; + } +} + {L_TITLE} From fb002b11e8446263a1d9bf59a897d42fc1747ed4 Mon Sep 17 00:00:00 2001 From: Fae Date: Mon, 14 Dec 2020 16:20:02 -0800 Subject: [PATCH 04/20] clean up function library --- lib/functions.common.php | 81 +++++++++ lib/functions.db.php | 274 ++++++++++++++++++++++++++++ lib/functions.http.php | 93 ++++++++++ lib/functions.php | 378 +-------------------------------------- lib/init.php | 7 +- put.php | 28 +-- 6 files changed, 455 insertions(+), 406 deletions(-) create mode 100644 lib/functions.common.php create mode 100644 lib/functions.db.php create mode 100644 lib/functions.http.php diff --git a/lib/functions.common.php b/lib/functions.common.php new file mode 100644 index 0000000..7c879fb --- /dev/null +++ b/lib/functions.common.php @@ -0,0 +1,81 @@ +query($sql); +} + +function SnapshotsTableExists() +{ + global $dbh; + + $tbl_result = false; + try { + $tbl_result = $dbh->query("SELECT 1 FROM `Snapshots` LIMIT 1"); + } catch (PDOException $e) { + return false; + } + return $tbl_result !== false; +} + +function AddNewSnapshot($name, $key, $expires_in_days = 14, $max_downloads = 0) +{ + global $dbh; + + $exdate = new DateTime(); + $exdate->add(new DateInterval('P' . strval($expires_in_days) . 'D')); + $expire_date = $exdate->format("Y-m-d H:i:s"); + + $sql = "INSERT INTO `Snapshots` (`file_name`, `file_key`, `time_created`, `time_expires`, `max_downloads`) VALUES (:fname, :fkey, NOW(), :expires, :maxdl)"; + + $stmt = $dbh->prepare($sql); + $stmt->bindParam(':fname', $name); + $stmt->bindParam(':fkey', $key); + $stmt->bindParam(':expires', $expire_date, PDO::PARAM_STR); + $stmt->bindParam(':maxdl', $max_downloads, PDO::PARAM_INT); + $stmt->execute(); +} + +function RemoveSnapshotByID($id) +{ + global $dbh; + + $stmt = $dbh->prepare("DELETE FROM `Snapshots` WHERE `id`=:sid"); + $stmt->bindParam(':sid', $id, PDO::PARAM_INT); + $stmt->execute(); +} + +function CheckSnapshotExists($name, $key) +{ + global $dbh; + + $stmt = $dbh->prepare("SELECT `id` FROM `Snapshots` WHERE `file_name`=:fname AND `file_key`=:fkey"); + $stmt->bindParam(':fname', $name); + $stmt->bindParam(':fkey', $key); + $stmt->execute(); + + $res = $stmt->fetch(PDO::FETCH_ASSOC); + if ($res !== false) { + return $res['id']; + } + return false; +} + +function UpdateSnapshotDownloads($sid) +{ + global $dbh; + + $stmt = $dbh->prepare("UPDATE `Snapshots` SET `num_downloads` = `num_downloads` + 1 WHERE id=:sid"); + $stmt->bindParam(':sid', $sid, PDO::PARAM_INT); + $res = $stmt->execute(); + + if ($res === false) { + return false; + } else { + return true; + } +} + +function UsersTableExists() +{ + global $dbh; + + $tbl_result = false; + try { + $tbl_result = $dbh->query("SELECT 1 FROM `Users` LIMIT 1"); + } catch (PDOException $e) { + return false; + } + return $tbl_result !== false; +} + +function CreateUsersTable() +{ + global $dbh; + + $sql = "CREATE TABLE `Users` ( + `id` INTEGER NULL AUTO_INCREMENT DEFAULT NULL, + `name` VARCHAR(128) NOT NULL DEFAULT 'NULL', + `phash` VARCHAR(255) NOT NULL DEFAULT 'NULL', + PRIMARY KEY (`id`) + );"; + + $res = $dbh->query($sql); +} + +function CheckUserCredentials($name, $pass) +{ + global $dbh; + + if (empty($name) || empty($pass)) { + return false; + } + + $stmt = $dbh->prepare("SELECT `id`,`phash` FROM `Users` WHERE `name`=:user"); + $stmt->bindParam(':user', $name); + $r = $stmt->execute(); + if (!$r) { + return false; + } + $res = $stmt->fetch(PDO::FETCH_ASSOC); + + if (!password_verify($pass, $res['phash'])) { + return false; + } else { + if (session_status() == PHP_SESSION_ACTIVE) { + $_SESSION['mudletsnaps_user_id'] = $res['id']; + } + return true; + } +} + +function AddUserRecord($username, $passhash) +{ + global $dbh; + + $stmt = $dbh->prepare("INSERT INTO `Users` (`name`, `phash`) VALUES (:name, :phash);"); + $stmt->bindParam(':name', $username); + $stmt->bindParam(':phash', $passhash); + + return $stmt->execute(); +} + +function LogUploadsTableExists() +{ + global $dbh; + + $tbl_result = false; + try { + $tbl_result = $dbh->query("SELECT 1 FROM `LogUploads` LIMIT 1"); + } catch (PDOException $e) { + return false; + } + return $tbl_result !== false; +} + +function CreateLogUploadsTable() +{ + global $dbh; + + $sql = 'CREATE TABLE `LogUploads` ( + `id` INTEGER NULL AUTO_INCREMENT DEFAULT NULL, + `user_id` INTEGER NOT NULL DEFAULT 0, + `file_size` INTEGER NOT NULL, + `event_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `file_name` VARCHAR(255) NOT NULL, + `ip_addr` VARCHAR(39) NOT NULL, + PRIMARY KEY (`id`) + );'; + + $res = $dbh->query($sql); +} + +function AddUploadLogRecord($filepath) +{ + global $dbh; + + if (!is_file($filepath) || ! is_readable($filepath)) { + return false; + } + + $filename = basename($filepath); + $filesize = filesize($filepath); + $user_id = 0; + if (session_status() == PHP_SESSION_ACTIVE) { + $user_id = $_SESSION['mudletsnaps_user_id']; + } + + $stmt = $dbh->prepare( + 'INSERT INTO `LogUploads` (`user_id`, `file_size`, `file_name`, `ip_addr`) + VALUES (:uid, :size, :fname, :ip);' + ); + $stmt->bindParam(':uid', $user_id, PDO::PARAM_INT); + $stmt->bindParam(':size', $filesize, PDO::PARAM_INT); + $stmt->bindParam(':fname', $filename, PDO::PARAM_STR); + $stmt->bindParam(':ip', $_SERVER['REMOTE_ADDR'], PDO::PARAM_STR); + + return $stmt->execute(); +} + +function LogDownloadsTableExists() +{ + global $dbh; + + $tbl_result = false; + try { + $tbl_result = $dbh->query("SELECT 1 FROM `LogDownloads` LIMIT 1"); + } catch (PDOException $e) { + return false; + } + return $tbl_result !== false; +} + +function CreateLogDownloadsTable() +{ + global $dbh; + + $sql = 'CREATE TABLE `LogDownloads` ( + `id` INTEGER NULL AUTO_INCREMENT DEFAULT NULL, + `user_id` INTEGER NOT NULL DEFAULT 0, + `file_size` INTEGER NOT NULL, + `event_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `file_name` VARCHAR(255) NOT NULL, + `ip_addr` VARCHAR(39) NOT NULL, + PRIMARY KEY (`id`) + );'; + + $res = $dbh->query($sql); +} + +function AddDownloadLogRecord($filepath) +{ + global $dbh; + + if (!is_file($filepath) || ! is_readable($filepath)) { + return false; + } + + $filename = basename($filepath); + $filesize = filesize($filepath); + $user_id = 0; + if (session_status() == PHP_SESSION_ACTIVE) { + $user_id = $_SESSION['mudletsnaps_user_id']; + } + + $stmt = $dbh->prepare( + 'INSERT INTO `LogDownloads` (`user_id`, `file_size`, `file_name`, `ip_addr`) + VALUES (:uid, :size, :fname, :ip);' + ); + $stmt->bindParam(':uid', $user_id, PDO::PARAM_INT); + $stmt->bindParam(':size', $filesize, PDO::PARAM_INT); + $stmt->bindParam(':fname', $filename, PDO::PARAM_STR); + $stmt->bindParam(':ip', $_SERVER['REMOTE_ADDR'], PDO::PARAM_STR); + + return $stmt->execute(); +} + diff --git a/lib/functions.http.php b/lib/functions.http.php new file mode 100644 index 0000000..a1e48fc --- /dev/null +++ b/lib/functions.http.php @@ -0,0 +1,93 @@ + + +' . _('404 Not Found') . ' + +

' . _('Not Found') . '

+

' . _('The requested URL was not found on this server.') . '

+
+
' . apache_get_version() . _(' Server at ') . $_SERVER['SERVER_NAME'] . _(' Port ') . strval($_SERVER['SERVER_PORT']) . '
+'; + echo($page); + exit(); +} + +function ExitUnauthorized() +{ + header('WWW-Authenticate: Basic realm="Snapshots"'); + SetStatusHeader(401, 'Unauthorized'); + echo("Unauthorized\n"); + exit(); +} + +function ExitForbidden() +{ + SetStatusHeader(403, 'Forbidden'); + echo("Forbidden\n"); + exit(); +} + +function ExitFullStorage() +{ + SetStatusHeader(507, 'Insufficient Storage'); + echo("Insufficient Storage - Try Again Later\n"); + exit(); +} + + +if(!function_exists('apache_get_version')) +{ + function apache_get_version() + { + if(!isset($_SERVER['SERVER_SOFTWARE']) || strlen($_SERVER['SERVER_SOFTWARE']) == 0) + { + return 'Apache/HTTPD'; + } + return $_SERVER["SERVER_SOFTWARE"]; + } +} + diff --git a/lib/functions.php b/lib/functions.php index d9d88dc..b44c0ba 100644 --- a/lib/functions.php +++ b/lib/functions.php @@ -4,378 +4,6 @@ exit(); } -function isSafeExtension($ext) -{ - $safe = explode(',', ALLOWED_FILE_EXT); - if (in_array($ext, $safe)) { - return true; - } - return false; -} - -function human_filesize($bytes, $decimals = 2) -{ - $sz = 'BKMGT'; - $factor = floor((strlen($bytes) - 1) / 3); - $unit = @$sz[$factor]; - if ($unit == 'B') { - $decimals = 0; - } - return sprintf("%.{$decimals}f ", $bytes / pow(1024, $factor)) . $unit; -} - -function getSnapshotDirectorySize() -{ - global $ScriptPath; - $f = $ScriptPath . '/' . UPLOAD_DIR; - $io = popen('/usr/bin/du -sb ' . $f, 'r'); - $size = fgets($io, 4096); - $size = substr($size, 0, strpos($size, "\t")); - pclose($io); - return intval($size); -} - -function getTempFileName() -{ - global $ScriptPath; - $filepath = $ScriptPath . '/' . TEMP_DIR; - if (!is_dir($filepath)) { - mkdir($filepath, 0775, true); - } - $filename = tempnam($filepath, 'snps_'); - return $filename; -} - -function makeSnapshotFileIDs($filename) -{ - global $ScriptPath; - - $uid = substr(uniqid(), -6); - $url = SITE_URL . $uid . '/' . $filename; - $filepath = $ScriptPath . '/' . UPLOAD_DIR . $uid . '_' . $filename; - - return array($filepath, $url, $uid); -} - -function getSnapshotFilePath($filename, $key) -{ - global $ScriptPath; - $filepath = $ScriptPath . '/' . UPLOAD_DIR . $key . '_' . $filename; - return $filepath; -} - -function getSnapshotURL($filename, $key) -{ - $url = SITE_URL . $key . '/' . $filename; - return $url; -} - -function CreateSnapshotsTable() -{ - global $dbh; - - $sql = "CREATE TABLE `Snapshots` ( - `id` INTEGER NULL AUTO_INCREMENT DEFAULT NULL, - `file_name` VARCHAR(255) NOT NULL, - `file_key` VARCHAR(16) NOT NULL, - `time_created` DATETIME NOT NULL, - `time_expires` DATETIME NOT NULL, - `max_downloads` INTEGER NULL DEFAULT 0, - `num_downloads` INTEGER NULL DEFAULT 0, - PRIMARY KEY (`id`) - );"; - - $res = $dbh->query($sql); -} - -function SnapshotsTableExists() -{ - global $dbh; - - $tbl_result = false; - try { - $tbl_result = $dbh->query("SELECT 1 FROM `Snapshots` LIMIT 1"); - } catch (PDOException $e) { - return false; - } - return $tbl_result !== false; -} - -function AddNewSnapshot($name, $key, $expires_in_days = 14, $max_downloads = 0) -{ - global $dbh; - - $exdate = new DateTime(); - $exdate->add(new DateInterval('P' . strval($expires_in_days) . 'D')); - $expire_date = $exdate->format("Y-m-d H:i:s"); - - $sql = "INSERT INTO `Snapshots` (`file_name`, `file_key`, `time_created`, `time_expires`, `max_downloads`) VALUES (:fname, :fkey, NOW(), :expires, :maxdl)"; - - $stmt = $dbh->prepare($sql); - $stmt->bindParam(':fname', $name); - $stmt->bindParam(':fkey', $key); - $stmt->bindParam(':expires', $expire_date, PDO::PARAM_STR); - $stmt->bindParam(':maxdl', $max_downloads, PDO::PARAM_INT); - $stmt->execute(); -} - -function RemoveSnapshotByID($id) -{ - global $dbh; - - $stmt = $dbh->prepare("DELETE FROM `Snapshots` WHERE `id`=:sid"); - $stmt->bindParam(':sid', $id, PDO::PARAM_INT); - $stmt->execute(); -} - -function CheckSnapshotExists($name, $key) -{ - global $dbh; - - $stmt = $dbh->prepare("SELECT `id` FROM `Snapshots` WHERE `file_name`=:fname AND `file_key`=:fkey"); - $stmt->bindParam(':fname', $name); - $stmt->bindParam(':fkey', $key); - $stmt->execute(); - - $res = $stmt->fetch(PDO::FETCH_ASSOC); - if ($res !== false) { - return $res['id']; - } - return false; -} - -function UpdateSnapshotDownloads($sid) -{ - global $dbh; - - $stmt = $dbh->prepare("UPDATE `Snapshots` SET `num_downloads` = `num_downloads` + 1 WHERE id=:sid"); - $stmt->bindParam(':sid', $sid, PDO::PARAM_INT); - $res = $stmt->execute(); - - if ($res === false) { - return false; - } else { - return true; - } -} - -function UsersTableExists() -{ - global $dbh; - - $tbl_result = false; - try { - $tbl_result = $dbh->query("SELECT 1 FROM `Users` LIMIT 1"); - } catch (PDOException $e) { - return false; - } - return $tbl_result !== false; -} - -function CreateUsersTable() -{ - global $dbh; - - $sql = "CREATE TABLE `Users` ( - `id` INTEGER NULL AUTO_INCREMENT DEFAULT NULL, - `name` VARCHAR(128) NOT NULL DEFAULT 'NULL', - `phash` VARCHAR(255) NOT NULL DEFAULT 'NULL', - PRIMARY KEY (`id`) - );"; - - $res = $dbh->query($sql); -} - -function CheckUserCredentials($name, $pass) -{ - global $dbh; - - if (empty($name) || empty($pass)) { - return false; - } - - $stmt = $dbh->prepare("SELECT `id`,`phash` FROM `Users` WHERE `name`=:user"); - $stmt->bindParam(':user', $name); - $r = $stmt->execute(); - if (!$r) { - return false; - } - $res = $stmt->fetch(PDO::FETCH_ASSOC); - - if (!password_verify($pass, $res['phash'])) { - return false; - } else { - if (session_status() == PHP_SESSION_ACTIVE) { - $_SESSION['mudletsnaps_user_id'] = $res['id']; - } - return true; - } -} - -function AddUserRecord($username, $passhash) -{ - global $dbh; - - $stmt = $dbh->prepare("INSERT INTO `Users` (`name`, `phash`) VALUES (:name, :phash);"); - $stmt->bindParam(':name', $username); - $stmt->bindParam(':phash', $passhash); - - return $stmt->execute(); -} - -function LogUploadsTableExists() -{ - global $dbh; - - $tbl_result = false; - try { - $tbl_result = $dbh->query("SELECT 1 FROM `LogUploads` LIMIT 1"); - } catch (PDOException $e) { - return false; - } - return $tbl_result !== false; -} - -function CreateLogUploadsTable() -{ - global $dbh; - - $sql = 'CREATE TABLE `LogUploads` ( - `id` INTEGER NULL AUTO_INCREMENT DEFAULT NULL, - `user_id` INTEGER NOT NULL DEFAULT 0, - `file_size` INTEGER NOT NULL, - `event_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - `file_name` VARCHAR(255) NOT NULL, - `ip_addr` VARCHAR(39) NOT NULL, - PRIMARY KEY (`id`) - );'; - - $res = $dbh->query($sql); -} - -function AddUploadLogRecord($filepath) -{ - global $dbh; - - if (!is_file($filepath) || ! is_readable($filepath)) { - return false; - } - - $filename = basename($filepath); - $filesize = filesize($filepath); - $user_id = 0; - if (session_status() == PHP_SESSION_ACTIVE) { - $user_id = $_SESSION['mudletsnaps_user_id']; - } - - $stmt = $dbh->prepare( - 'INSERT INTO `LogUploads` (`user_id`, `file_size`, `file_name`, `ip_addr`) - VALUES (:uid, :size, :fname, :ip);' - ); - $stmt->bindParam(':uid', $user_id, PDO::PARAM_INT); - $stmt->bindParam(':size', $filesize, PDO::PARAM_INT); - $stmt->bindParam(':fname', $filename, PDO::PARAM_STR); - $stmt->bindParam(':ip', $_SERVER['REMOTE_ADDR'], PDO::PARAM_STR); - - return $stmt->execute(); -} - -function LogDownloadsTableExists() -{ - global $dbh; - - $tbl_result = false; - try { - $tbl_result = $dbh->query("SELECT 1 FROM `LogDownloads` LIMIT 1"); - } catch (PDOException $e) { - return false; - } - return $tbl_result !== false; -} - -function CreateLogDownloadsTable() -{ - global $dbh; - - $sql = 'CREATE TABLE `LogDownloads` ( - `id` INTEGER NULL AUTO_INCREMENT DEFAULT NULL, - `user_id` INTEGER NOT NULL DEFAULT 0, - `file_size` INTEGER NOT NULL, - `event_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - `file_name` VARCHAR(255) NOT NULL, - `ip_addr` VARCHAR(39) NOT NULL, - PRIMARY KEY (`id`) - );'; - - $res = $dbh->query($sql); -} - -function AddDownloadLogRecord($filepath) -{ - global $dbh; - - if (!is_file($filepath) || ! is_readable($filepath)) { - return false; - } - - $filename = basename($filepath); - $filesize = filesize($filepath); - $user_id = 0; - if (session_status() == PHP_SESSION_ACTIVE) { - $user_id = $_SESSION['mudletsnaps_user_id']; - } - - $stmt = $dbh->prepare( - 'INSERT INTO `LogDownloads` (`user_id`, `file_size`, `file_name`, `ip_addr`) - VALUES (:uid, :size, :fname, :ip);' - ); - $stmt->bindParam(':uid', $user_id, PDO::PARAM_INT); - $stmt->bindParam(':size', $filesize, PDO::PARAM_INT); - $stmt->bindParam(':fname', $filename, PDO::PARAM_STR); - $stmt->bindParam(':ip', $_SERVER['REMOTE_ADDR'], PDO::PARAM_STR); - - return $stmt->execute(); -} - -function ExitFailedRequest($msg) -{ - if (empty($msg)) { - $msg = _('Failed - Internal Server Error') . "\n"; - } - http_response_code(500); - header($_SERVER["SERVER_PROTOCOL"] . ' 500 Internal Server Error'); - echo($msg); - exit(); -} - -function ExitFileNotFound() -{ - http_response_code(404); - header($_SERVER["SERVER_PROTOCOL"] . " 404 Not Found"); - - $page = ' - -' . _('404 Not Found') . ' - -

' . _('Not Found') . '

-

' . _('The requested URL was not found on this server.') . '

-
-
' . apache_get_version() . _(' Server at ') . $_SERVER['SERVER_NAME'] . _(' Port ') . strval($_SERVER['SERVER_PORT']) . '
-'; - echo($page); - exit(); -} - - -if(!function_exists('apache_get_version')) -{ - function apache_get_version() - { - if(!isset($_SERVER['SERVER_SOFTWARE']) || strlen($_SERVER['SERVER_SOFTWARE']) == 0) - { - return 'Apache/HTTPD'; - } - return $_SERVER["SERVER_SOFTWARE"]; - } -} - +require_once('functions.common.php'); +require_once('functions.db.php'); +require_once('functions.http.php'); diff --git a/lib/init.php b/lib/init.php index f8f28ed..36b513a 100644 --- a/lib/init.php +++ b/lib/init.php @@ -10,6 +10,7 @@ require_once("config.php"); // Detect user localization, apply if available. +$i18n_locale = $i18n_lang_default; if (function_exists('gettext') && function_exists('locale_accept_from_http')) { $_lang_header = (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) ? $_SERVER['HTTP_ACCEPT_LANGUAGE'] : $i18n_lang_default; $i18n_locale_prefer = locale_accept_from_http($_lang_header); @@ -25,12 +26,6 @@ bindtextdomain($i18n_domain_name, $i18n_domain_path); textdomain($i18n_domain_name); -} elseif (!function_exists('_')) { - $i18n_locale = $i18n_lang_default; - function _($msg) - { - return $msg; - } } require_once("lib/functions.php"); diff --git a/put.php b/put.php index 13a6f22..65c0c5c 100644 --- a/put.php +++ b/put.php @@ -7,37 +7,15 @@ exit(); } -function ExitUnauthorized() -{ - header('WWW-Authenticate: Basic realm="Snapshots"'); - http_response_code(401); - header($_SERVER["SERVER_PROTOCOL"] . ' 401 Unauthorized'); - echo("Unauthorized\n"); - exit(); -} - -function ExitForbidden() -{ - http_response_code(403); - header($_SERVER["SERVER_PROTOCOL"] . ' 403 Forbidden'); - echo("Forbidden\n"); - exit(); -} - -function ExitFullStorage() -{ - http_response_code(507); - header($_SERVER["SERVER_PROTOCOL"] . ' 507 Insufficient Storage'); - echo("Insufficient Storage - Try Again Later\n"); - exit(); -} +define("CI_SNAPSHOTS", true); +require_once('lib/functions.common.php'); +require_once('lib/functions.http.php'); if ((!isset($_SERVER['PHP_AUTH_USER']) || !isset($_SERVER['PHP_AUTH_PW'])) && PUT_REQUIRES_AUTH) { ExitUnauthorized(); } // bootstrap web-app. -define("CI_SNAPSHOTS", true); require_once('lib/init.php'); From 8fe1a6a57a596745523d99c318e1bae7f0d3ba90 Mon Sep 17 00:00:00 2001 From: Fae Date: Tue, 15 Dec 2020 20:03:37 -0800 Subject: [PATCH 05/20] Add Github Artifacts support --- config.example.php | 18 ++++ github_artifact.php | 222 +++++++++++++++++++++++++++++++++++++++++ lib/functions.http.php | 136 +++++++++++++++++++++++++ 3 files changed, 376 insertions(+) create mode 100644 github_artifact.php diff --git a/config.example.php b/config.example.php index 314d7aa..aa75d3a 100644 --- a/config.example.php +++ b/config.example.php @@ -37,6 +37,24 @@ define('STRANDED_FILE_WINDOW', 2); +// URL to the base artifact listing URL. All URLs are built from this one. +define('GHA_LIST_URL', 'https://api.github.com/repos/Mudlet/Mudlet/actions/artifacts'); + +// Regular Expression used to validate and extract IDs from artifact URLs. +define('GHA_URL_REGEX', '#https?://api\.github\.com/repos/Mudlet/Mudlet/actions/artifacts/(\d+)(?:/|/zip/?)?#iu'); + +// Authentication token for Github API calls - OAuth or Person Access Token. +define('GHA_AUTH_TOKEN', 'token'); + +// Timeout in seconds for github artifact request execution. +// this value controls the max time a request/download can take. +define('GHA_CURL_TIMEOUT', 180); + +// Enforce HTTP Auth for GHA requests, similarly to PUT_REQUIRES_AUTH +define('GHA_REQUIRES_AUTH', false); + + + // Database connection details define('DB_HOST', ''); diff --git a/github_artifact.php b/github_artifact.php new file mode 100644 index 0000000..98cc940 --- /dev/null +++ b/github_artifact.php @@ -0,0 +1,222 @@ += MAX_CAPACITY_BYTES && MAX_CAPACITY_DELETE_OLDEST == false) { + ExitFullStorage(); +} + + +$unzip = false; +if ( isset($_REQUEST['unzip']) ) { + $unzip = filter_var($_REQUEST['unzip'], FILTER_VALIDATE_BOOLEAN); +} else { + ExitClientError('Missing required parameter unzip'); +} + + +$ghaid = 0; +$ghaurl = ''; +if (isset($_REQUEST['id']) && !empty($_REQUEST['id'])) { + // build artifact_url + $ghaid = filter_var($_REQUEST['id'], FILTER_VALIDATE_INT); + if ($ghaid === false) { + ExitClientError('id parameter is not acceptable'); + } + $ghaurl = GHA_LIST_URL . '/' . $ghaid ; +} else { + ExitClientError('Missing required parameter id'); +} + +$req = do_curl_get($ghaurl, array('Authorization: token ' . GHA_AUTH_TOKEN)); + +if ($req === false) { + ExitFailedRequest('Failed to fetch Artifact data'); +} +if ($req[2] === 404) { + ExitClientError('Artifact not found - ID# ' . strval($ghaid) . ' Returned: ' . strval($req[2]) ); +} +if ($req[2] !== 200) { + ExitClientError('Artifact fetch error - ID# ' . strval($ghaid) . ' Returned: ' . strval($req[2]) ); +} + + +$jdata = json_decode($req[0], true); +if ($jdata === null) { + $eid = json_last_error(); + ExitFailedRequest('Failed to parse Artifact JSON - ' . strval($eid)); +} + + +$dl_tmpname = getTempFileName(); +if ( isset($jdata['archive_download_url']) ) { + $dl_url = $jdata['archive_download_url']; + if (empty($dl_url) || false === preg_match(GHA_URL_REGEX, $dl_url)) { + @unlink($dl_tmpname); + ExitFailedRequest('Archive download url is invalid'); + } + + $dl_req = do_curl_download($dl_url, $dl_tmpname, array('Authorization: token ' . GHA_AUTH_TOKEN)); + if ($dl_req === false) { + @unlink($dl_tmpname); + ExitFailedRequest('Artifact archive download failed'); + } + if ($dl_req[2] === 404) { + @unlink($dl_tmpname); + ExitClientError('Artifact archive not found - ID# ' . strval($ghaid) . ' Returned: ' . strval($dl_req[2]) ); + } + if ($dl_req[2] !== 200) { + @unlink($dl_tmpname); + ExitClientError('Artifact archive fetch error - ID# ' . strval($ghaid) . ' Returned: ' . strval($dl_req[2]) ); + } +} else { + @unlink($dl_tmpname); + ExitFailedRequest('Artifact JSON data is missing `archive_download_url` member'); +} + + +// if we unzip, we must have only one file in the ZIP - exe, tar, etc. +// if we don't unzip, we need to build the artifact name using JSON data and .zip extension. +if ($unzip) { + $zip = new ZipArchive(); + $res = $zip->open($dl_tmpname, ZipArchive::RDONLY); + + if ( $res === false ) { + @$zip->close(); + @unlink($dl_tmpname); + ExitFailedRequest('Artifact archive failed to open'); + } + + if ( $zip->numFiles > 1 ) { + $nfiles = $zip->numFiles; + + @$zip->close(); + @unlink($dl_tmpname); + ExitFailedRequest('Artifact archive contains more than 1 file - ' . strval($nfiles) ); + } + + $fstat = $zip->statIndex( 0 ); + $artifact_filename = $fstat['name']; + + if ( strpos($artifact_filename, '/') !== false || strpos($artifact_filename, '\\') !== false ) { + @$zip->close(); + @unlink($dl_tmpname); + ExitFailedRequest('Artifact archive contains invalid filename'); + } + + $artifact_tmpname = dirname($dl_tmpname) . DIRECTORY_SEPARATOR . $artifact_filename; + $ext_dir = dirname( $dl_tmpname ); + + // extraction should result in a path matching that of $artifact_tmpname + $exs = $zip->extractTo($ext_dir, array($artifact_filename)); + $zip->close(); + @unlink($dl_tmpname); + + if ( $exs === false || !file_exists($artifact_tmpname) ) { + ExitFailedRequest('Artifact extraction failed'); + } + + // more checks here... crc32b + $hash = hash_file('crc32b', $artifact_tmpname); + $array = unpack('N', pack('H*', $hash)); + $crc32 = $array[1]; + if ( $crc32 != $fstat['crc'] ) { + @unlink($artifact_tmpname); + ExitFailedRequest('Artifact file failed crc'); + } + + @unlink($dl_tmpname); + $dl_tmpname = $artifact_tmpname; + $basename = basename($dl_tmpname); +} else { + $basename = $jdata['name'] . '.zip'; +} + + +//$basename = basename($_SERVER['REQUEST_URI']); +$uri_parts = pathinfo($basename); +if (! isSafeExtension($uri_parts['extension'])) { + ExitFailedRequest("Failed to save {$basename} - Bad Extension\n"); +} + +if (getSnapshotDirectorySize() >= MAX_CAPACITY_BYTES && MAX_CAPACITY_DELETE_OLDEST == true) { + $targetSize = filesize($dl_tmpname); + $clearedSize = 0; + + $stmt = $dbh->prepare("SELECT `file_name`, `file_key`, `time_created` + FROM `Snapshots` + ORDER BY `time_created` ASC + LIMIT 10 "); + $stmt->execute(); + + while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { + $delFilePath = getSnapshotFilePath($row['file_name'], $row['file_key']); + if (is_file($delFilePath)) { + $clearedSize = $clearedSize + filesize($delFilePath); + unlink($delFilePath); + } + + if ($clearedSize >= $targetSize) { + break; + } + } +} + +$snapshot_ids = makeSnapshotFileIDs($basename); +$snapshot_path = $snapshot_ids[0]; +$snapshot_url = $snapshot_ids[1]; +$snapshot_uid = $snapshot_ids[2]; +if (rename($dl_tmpname, $snapshot_path)) { + $maxdays = DEFAULT_FILE_LIFETIME_DAYS; + if (isset($_SERVER['HTTP_MAX_DAYS'])) { + if (preg_match('/^[0-9]+$/i', $_SERVER['HTTP_MAX_DAYS']) === 1) { + $maxdays = intval($_SERVER['HTTP_MAX_DAYS']); + } + } + $maxdl = 0; + if (isset($_SERVER['HTTP_MAX_DOWNLOADS'])) { + if (preg_match('/^[0-9]+$/i', $_SERVER['HTTP_MAX_DOWNLOADS']) === 1) { + $maxdl = intval($_SERVER['HTTP_MAX_DOWNLOADS']); + } + } + + echo($snapshot_url); + echo("\n"); + + AddNewSnapshot($basename, $snapshot_uid, $maxdays, $maxdl); + AddUploadLogRecord($snapshot_path); +} else { + ExitFailedRequest("Failed to save {$basename}\n"); + unlink($dl_tmpname); +} diff --git a/lib/functions.http.php b/lib/functions.http.php index a1e48fc..05f29bf 100644 --- a/lib/functions.http.php +++ b/lib/functions.http.php @@ -91,3 +91,139 @@ function apache_get_version() } } + +/* +* Wrapper function for curl. +* returns false on error. +* on successs returns an array with 4 members. +* array indexes: +* -0- response body data. +* -1- response headers array. +* -2- response HTTP status code. +* -3- time taken for request. +* +*/ +function do_curl_get($url, $send_headers=array(), $err400=false) { + + $url = filter_var($url, FILTER_VALIDATE_URL); + if ( $url === false || preg_match('/^https?:\/\/.+/iu',$url) === false ) { + trigger_error('Given $url is valid or not using http/https scheme.'); + return false; + } + + $timeout_limit = GHA_CURL_TIMEOUT; + set_time_limit($timeout_limit + 30); + + $rtime_start = time(); + $ch = curl_init( $url ); + $recv_headers = []; + curl_setopt($ch, CURLOPT_FAILONERROR, $err400); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 12); + curl_setopt($ch, CURLOPT_TIMEOUT, $timeout_limit); + curl_setopt($ch, CURLOPT_MAXREDIRS, 3); + curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (compatible; make.mudlet.org/0.1)"); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + if (!empty($send_headers) ) { + curl_setopt($ch, CURLOPT_HTTPHEADER, $send_headers); + } + + // catch every valid header that CURL gets, we'll need that info for rate-limits, etc. + curl_setopt($ch, CURLOPT_HEADERFUNCTION, function($curl, $header) use (&$recv_headers) + { + $len = strlen($header); + $header = explode(':', $header, 2); + if (count($header) < 2) // ignore invalid headers + return $len; + + $recv_headers[strtolower(trim($header[0]))][] = trim($header[1]); + + return $len; + }); + + $result = curl_exec($ch); + if ( $result === false ) { + //trigger_error(curl_error($ch)); + return false; + } + $httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + + curl_close($ch); + + $rtime = time() - $rtime_start; + return array($result, $recv_headers, $httpcode, $rtime); +} + +/* +* Wrapper function for curl. +* returns false on error. +* on successs returns an array with 4 members. +* array indexes: +* -0- response body data. +* -1- response headers array. +* -2- response HTTP status code. +* -3- time taken for request. +* +*/ +function do_curl_download($url, $filepath, $send_headers=array(), $err400=true) { + + $url = filter_var($url, FILTER_VALIDATE_URL); + if ( $url === false || preg_match('/^https?:\/\/.+/iu',$url) === false ) { + trigger_error('Given $url is valid or not using http/https scheme.'); + return false; + } + + if ( ! is_writable($filepath) ) { + trigger_error('Given $filepath is not writable!'); + return false; + } + + + $timeout_limit = GHA_CURL_TIMEOUT; + set_time_limit($timeout_limit + 30); + + $rtime_start = time(); + $fh = fopen($filepath, 'wb'); + $ch = curl_init( $url ); + $recv_headers = []; + curl_setopt($ch, CURLOPT_FAILONERROR, $err400); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 12); + curl_setopt($ch, CURLOPT_TIMEOUT, $timeout_limit); + curl_setopt($ch, CURLOPT_MAXREDIRS, 3); + curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (compatible; make.mudlet.org/0.1)"); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_FILE, $fh); + if (!empty($send_headers) ) { + curl_setopt($ch, CURLOPT_HTTPHEADER, $send_headers); + } + + // catch every valid header that CURL gets, we'll need that info for rate-limits, etc. + curl_setopt($ch, CURLOPT_HEADERFUNCTION, function($curl, $header) use (&$recv_headers) + { + $len = strlen($header); + $header = explode(':', $header, 2); + if (count($header) < 2) // ignore invalid headers + return $len; + + $recv_headers[strtolower(trim($header[0]))][] = trim($header[1]); + + return $len; + }); + + $result = curl_exec($ch); + if ( $result === false ) { + //trigger_error(curl_error($ch)); + return false; + } + $httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + + curl_close($ch); + fclose($fh); + + $rtime = time() - $rtime_start; + return array($result, $recv_headers, $httpcode, $rtime); +} + + + From 021bbf427315710b0ce4ed22bc451d6170824021 Mon Sep 17 00:00:00 2001 From: Fae Date: Tue, 15 Dec 2020 20:23:16 -0800 Subject: [PATCH 06/20] Add Github Artifacts usage to readme --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index 9413e9b..52681c0 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,19 @@ Optional URL Paramers are: The requested JSON list will show only entries which have matching values. +## Usage for Github Artifacts +CI Snapshots supports fetching Artifacts from Github Action Storage. +Use `/github_artifacts.php` to submit a fetch request. If successful, the endpoint will return a URL similarly to how PUT uploads work. +Required URL Paramers are: + - `id` -- a github artifact ID number. + - `unzip` -- 0 to use the zip as-is, or 1 to extract a file from the zip. + +When using `unzip=1` parameter, the artifact zip must contain only one file. The filename stored in the zip is also used as the snapshot filename. +When using `unzip=0` parameter, the snapshot filename is created using the artifact `name` json property and appending `.zip` to it. + +Optional settings via Headers `Max-Days` and `Max-Downloads` are also supported by this endpoint. + + ## Installation Requirements This software is powered by PHP and Apache with Mod_Rewrite. Internationalization requires Intl and gettext php support. Most Apache configurations may disable PUT method requests by default, so we need to make some configuration changes to Apache (in Server or VirtualHost areas) in order to enable PUT method requests as well as configure our RewriteMap directive for mod_rewrite. From ce1a3551b06f78ad9dcdfffea7cea09f1e878eee Mon Sep 17 00:00:00 2001 From: Fae Date: Thu, 17 Dec 2020 11:51:43 -0800 Subject: [PATCH 07/20] correct typo in url validation check --- github_artifact.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/github_artifact.php b/github_artifact.php index 98cc940..1a650b0 100644 --- a/github_artifact.php +++ b/github_artifact.php @@ -82,7 +82,7 @@ $dl_tmpname = getTempFileName(); if ( isset($jdata['archive_download_url']) ) { $dl_url = $jdata['archive_download_url']; - if (empty($dl_url) || false === preg_match(GHA_URL_REGEX, $dl_url)) { + if (empty($dl_url) || false == preg_match(GHA_URL_REGEX, $dl_url)) { @unlink($dl_tmpname); ExitFailedRequest('Archive download url is invalid'); } From c96dea2554c4fb52de540efa56484dfd6c9eefdf Mon Sep 17 00:00:00 2001 From: Fae Date: Thu, 17 Dec 2020 11:52:41 -0800 Subject: [PATCH 08/20] add explicit text mime --- github_artifact.php | 1 + put.php | 2 ++ 2 files changed, 3 insertions(+) diff --git a/github_artifact.php b/github_artifact.php index 1a650b0..9e84ea5 100644 --- a/github_artifact.php +++ b/github_artifact.php @@ -18,6 +18,7 @@ require_once('lib/functions.common.php'); require_once('lib/functions.http.php'); +header('Content-Type: text/plain'); if ((!isset($_SERVER['PHP_AUTH_USER']) || !isset($_SERVER['PHP_AUTH_PW'])) && GHA_REQUIRES_AUTH) { ExitUnauthorized(); diff --git a/put.php b/put.php index 65c0c5c..44a0505 100644 --- a/put.php +++ b/put.php @@ -7,6 +7,8 @@ exit(); } +header('Content-Type: text/plain'); + define("CI_SNAPSHOTS", true); require_once('lib/functions.common.php'); require_once('lib/functions.http.php'); From 3bc73e8c0b0e1da20879e03078dc4124d06601e5 Mon Sep 17 00:00:00 2001 From: Fae Date: Thu, 17 Dec 2020 17:31:22 -0800 Subject: [PATCH 09/20] Add Github artifact queue systems... --- README.md | 16 +++- config.example.php | 10 +++ gha_cron.php | 205 +++++++++++++++++++++++++++++++++++++++++++++ gha_queue.php | 136 ++++++++++++++++++++++++++++++ 4 files changed, 365 insertions(+), 2 deletions(-) create mode 100644 gha_cron.php create mode 100644 gha_queue.php diff --git a/README.md b/README.md index 52681c0..f62ab67 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ CI Snapshots provides an endpoint at `/json.php` for fetching snapshot data as J To use the JSON data, send a GET request to https://make.mudlet.org/snapshots/json.php All data available will be returned by default. Optional arguments can be supplied to filter the returned data. -Optional URL Paramers are: +Optional URL Parameters are: - `prid` -- a PR ID number from github. - `commitid` -- a Commit ID from Git/Github. - `platform` -- a string for the platform, which must be one of: `windows`, `linux`, or `macos` @@ -39,7 +39,7 @@ The requested JSON list will show only entries which have matching values. ## Usage for Github Artifacts CI Snapshots supports fetching Artifacts from Github Action Storage. Use `/github_artifacts.php` to submit a fetch request. If successful, the endpoint will return a URL similarly to how PUT uploads work. -Required URL Paramers are: +Required URL Parameters are: - `id` -- a github artifact ID number. - `unzip` -- 0 to use the zip as-is, or 1 to extract a file from the zip. @@ -48,6 +48,18 @@ When using `unzip=0` parameter, the snapshot filename is created using the artif Optional settings via Headers `Max-Days` and `Max-Downloads` are also supported by this endpoint. +### Using Artifact Queue +Due to limitations in Github Actions and Artifacts storage, while a job is running associated artifacts cannot be downloaded. +To work around this, workflows can enqueue artifacts by name - assuming the artifact name is unique among other artifacts. +Usage of `/gha_queue.php` is the same as `/github_artifacts.php` with one exception. +The `id` parameter is replaced with an `aname` parameter and must contain a unique Artifact name. + +Example usage: +`https://make.mudlet.org/snapshots/gha_queue.php?aname=Mudlet-4.10.1-testing-pr1111-a770ad4d-linux-x64&unzip=1` + +Optional settings via Headers are also supported. + +**Note:** Queue services require a separate cron job! ## Installation Requirements This software is powered by PHP and Apache with Mod_Rewrite. Internationalization requires Intl and gettext php support. diff --git a/config.example.php b/config.example.php index aa75d3a..21430e3 100644 --- a/config.example.php +++ b/config.example.php @@ -53,6 +53,16 @@ // Enforce HTTP Auth for GHA requests, similarly to PUT_REQUIRES_AUTH define('GHA_REQUIRES_AUTH', false); +// prefix for filenames used to store github artifact queue data. +// this must not be left empty! +define('GHA_QFILE_PREFIX', 'ghaq_'); + +// file extension to append to queue data filename. +define('GHA_QFILE_EXT', '.json'); + +// number of seconds a github artifact queue item may remain in the queue. +// a few hours would be reasonable. +define('GHA_QUEUE_TIMEOUT', 18000); // Database connection details diff --git a/gha_cron.php b/gha_cron.php new file mode 100644 index 0000000..f2b09a1 --- /dev/null +++ b/gha_cron.php @@ -0,0 +1,205 @@ +data = $jdata; + + if (isset($jdata['artifacts'])) { + $this->artifacts = $jdata['artifacts']; + } else { + return false; + } + + if (isset($jdata['total_count'])) { + $this->artifacts_total = $jdata['total_count']; + } else { + return false; + } + + } + + function countArtifactsByName( $artifact_name ) { + $found=0; + foreach( $this->artifacts as $idx => $a ) { + if ( $artifact_name == $a['name'] ) { + $found = $found + 1; + } + } + return $found; + } + + function getArtifactIdByName( $artifact_name ) { + $id=false; + foreach( $this->artifacts as $idx => $a ) { + if ( $artifact_name == $a['name'] ) { + $id = $a['id']; + } + } + return $id; + } +} + + +function processSnapshotFetch( $url, $headers ) { + echo("Fetching: $url \n"); + $r = do_curl_get($url, $headers); + + if ($r === false) { + echo("Error while fetching\n"); + return false; + } + + if ($r[2] !== 200) { + echo("Response not 200, is: ${r[2]}\n"); + echo("Error Text: $r[0] \n"); + return false; + } + + if ($r[2] === 200) { + echo("Response: ${r[0]} \n"); + } + + return true; +} + +function processQueueFile($filepath) { + global $ghalObj; + + $raw = file_get_contents($filepath); + $data = json_decode($raw, true); + + // unlink in cases of invalid data. + if ($data === false) { + echo('Error decoding JSON! - ' . json_last_error() . "\n" ); + return false; + } + if (!isset($data['name']) || !isset($data['unzip'])) { + echo("Data missing 'name' and 'unzip' required members!\n"); + return false; + } + + // we need to clean up carefuly here... + // unlink if we have more than one file + // if no files, we may need to either search deeper, + // or we need to wait for a later check - artifact may not exist yet! + $c = $ghalObj->countArtifactsByName( $data['name'] ); + if ($c > 1) { + $n = $data['name']; + echo("More than one artifact with the name: $n\n"); + return false; + } + + if ($c == 0) { + $n = $data['name']; + echo("No artifact with the name: $n \n"); + // in this case we check the queue entry time for expiry, otherwise leave the item. + $ex_time = $data['qtime'] + GHA_QUEUE_TIMEOUT; + if (time() > $ex_time) { + echo("Queue item has expired.\n"); + return false; + } + return true; + } + + + $queue_url = SITE_URL . 'github_artifact.php'; + $headers = array(); + $unzip = ($data['unzip']) ? '1' : '0'; + + if (isset($data['id'])) { + $id = $data['id']; + } else { + $id = $ghalObj->getArtifactIdByName( $data['name'] ); + } + + if (isset($data['maxdays'])) { + $headers[] = 'Max-Days: ' . strval( $data['maxdays'] ); + } + if (isset($data['maxdls'])) { + $headers[] = 'Max-Downloads: ' . strval( $data['maxdls'] ); + } + + $url = $queue_url . '?id=' . strval($id) . '&unzip=' . $unzip; + + if ( processSnapshotFetch( $url, $headers ) ) { + unlink($filepath); + } + + return true; +} + + +$timer_start = microtime(true); + +$ghalObj = new GithubArtifactList(); + +$TempDir = $ScriptPath . DIRECTORY_SEPARATOR . TEMP_DIR ; +$FileRegex = '/^' . preg_quote(GHA_QFILE_PREFIX, '/') . '(.+)' . preg_quote(GHA_QFILE_EXT, '/') . '$/iu'; + +$files = scandir($TempDir); +foreach( $files as $idx => $file ) { + if ($file == '.' || $file == '..') { + continue; + } + if ( false == preg_match($FileRegex, $file) ) { + continue; + } + + $ts = microtime(true); + echo("Processing queue file: $file \n"); + $filepath = $TempDir . $file; + $status = processQueueFile( $filepath ); + + if ($status === false) { + unlink( $filepath ); + } + $n = microtime(true) - $ts; + echo("Processing finished in $n seconds.\n\n"); + +} + +$t = microtime(true) - $timer_start; +printf("Finished in %4.2f seconds\n\n\n", $t); diff --git a/gha_queue.php b/gha_queue.php new file mode 100644 index 0000000..e2edb3e --- /dev/null +++ b/gha_queue.php @@ -0,0 +1,136 @@ += MAX_CAPACITY_BYTES && MAX_CAPACITY_DELETE_OLDEST == false) { + ExitFullStorage(); +} + + +$artifact_name = null; +if ( isset($_REQUEST['aname']) && !empty($_REQUEST['aname']) ) { + if ( false == preg_match('/^[a-z0-9-_\.]+$/iu', $_REQUEST['aname']) ) { + ExitClientError('Invalid artifact name'); + } else { + $artifact_name = trim($_REQUEST['aname']); + } +} else { + ExitClientError('Missing required parameter aname'); +} + +$unzip = false; +if ( isset($_REQUEST['unzip']) ) { + $unzip = filter_var($_REQUEST['unzip'], FILTER_VALIDATE_BOOLEAN); +} else { + ExitClientError('Missing required parameter unzip'); +} + + +$artifact_queue_file = $ScriptPath . DIRECTORY_SEPARATOR . TEMP_DIR . GHA_QFILE_PREFIX . $artifact_name . GHA_QFILE_EXT ; +$gha_list_url = GHA_LIST_URL . '?per_page=100&page=1'; + +if ( file_exists($artifact_queue_file) ) { + ExitFailedRequest('Artifact is already queued'); +} + +$list_req = do_curl_get($gha_list_url, array('Authorization: token ' . GHA_AUTH_TOKEN)); +if ( $list_req === false ) { + ExitFailedRequest('Error checking artifact list'); +} +if ( $list_req[2] !== 200 ) { + ExitFailedRequest('Failed to fetch artifact list - ' . strval($list_req[2]) ); +} + +$list_data = json_decode($list_req[0], true); + +if ( ! isset($list_data['artifacts']) ) { + ExitFailedRequest('Failed to find artifacts'); +} + +$artifact_is_unique = true; +$artifact_id = null; +if ( !empty($list_data['artifacts']) ) { + // check existing artifacts for 0 or 1 instance of the given name. + // hopefully we can get away with just checking the first 100 entries. + // it seems to list the latest fisrt anyways... + $found=0; + foreach( $list_data['artifacts'] as $idx => $arti ) { + if ( $artifact_name == $arti['name'] ) { + $found = $found + 1; + $artifact_id = $arti['id']; + } + + if ( $found > 1 ) { + $artifact_is_unique = false; + } + } +} + +if (!$artifact_is_unique) { + ExitClientError('Artifact is not unique'); +} + +$maxdays = 0; +if (isset($_SERVER['HTTP_MAX_DAYS'])) { + if (preg_match('/^[0-9]+$/i', $_SERVER['HTTP_MAX_DAYS']) === 1) { + $maxdays = intval($_SERVER['HTTP_MAX_DAYS']); + } +} +$maxdls = 0; +if (isset($_SERVER['HTTP_MAX_DOWNLOADS'])) { + if (preg_match('/^[0-9]+$/i', $_SERVER['HTTP_MAX_DOWNLOADS']) === 1) { + $maxdls = intval($_SERVER['HTTP_MAX_DOWNLOADS']); + } +} + + +$artifact_data = array( + 'name' => $artifact_name, + 'unzip' => $unzip, + 'qtime' => time() +); + +if ($artifact_id !== null) { + $artifact_data['id'] = $artifact_id; +} +if ($maxdays != 0) { + $artifact_data['maxdays'] = $maxdays; +} +if ($maxdls != 0) { + $artifact_data['maxdls'] = $maxdls; +} + +$queue_json = json_encode($artifact_data); +if ( false !== file_put_contents($artifact_queue_file, $queue_json, LOCK_EX) ) { + echo('Artifact enqueued'); +} else { + ExitFailedRequest('Failed to enqueue artifact'); +} + + + From b05ace483777d18608942a36148c87adc88bb89d Mon Sep 17 00:00:00 2001 From: Fae Date: Fri, 18 Dec 2020 09:09:16 -0800 Subject: [PATCH 10/20] Change parameter `aname` to `artifact_name` --- README.md | 4 ++-- gha_queue.php | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index f62ab67..2c8c3ec 100644 --- a/README.md +++ b/README.md @@ -52,10 +52,10 @@ Optional settings via Headers `Max-Days` and `Max-Downloads` are also supported Due to limitations in Github Actions and Artifacts storage, while a job is running associated artifacts cannot be downloaded. To work around this, workflows can enqueue artifacts by name - assuming the artifact name is unique among other artifacts. Usage of `/gha_queue.php` is the same as `/github_artifacts.php` with one exception. -The `id` parameter is replaced with an `aname` parameter and must contain a unique Artifact name. +The `id` parameter is replaced with an `artifact_name` parameter and must contain a unique Artifact name. Example usage: -`https://make.mudlet.org/snapshots/gha_queue.php?aname=Mudlet-4.10.1-testing-pr1111-a770ad4d-linux-x64&unzip=1` +`https://make.mudlet.org/snapshots/gha_queue.php?artifact_name=Mudlet-4.10.1-testing-pr1111-a770ad4d-linux-x64&unzip=1` Optional settings via Headers are also supported. diff --git a/gha_queue.php b/gha_queue.php index e2edb3e..1b9af19 100644 --- a/gha_queue.php +++ b/gha_queue.php @@ -33,14 +33,14 @@ $artifact_name = null; -if ( isset($_REQUEST['aname']) && !empty($_REQUEST['aname']) ) { - if ( false == preg_match('/^[a-z0-9-_\.]+$/iu', $_REQUEST['aname']) ) { +if ( isset($_REQUEST['artifact_name']) && !empty($_REQUEST['artifact_name']) ) { + if ( false == preg_match('/^[a-z0-9-_\.]+$/iu', $_REQUEST['artifact_name']) ) { ExitClientError('Invalid artifact name'); } else { - $artifact_name = trim($_REQUEST['aname']); + $artifact_name = trim($_REQUEST['artifact_name']); } } else { - ExitClientError('Missing required parameter aname'); + ExitClientError('Missing required parameter artifact_name'); } $unzip = false; From 9dd7ba6acd1b6c5f017dec1694f84369a4a1282d Mon Sep 17 00:00:00 2001 From: Fae Date: Fri, 18 Dec 2020 09:24:41 -0800 Subject: [PATCH 11/20] Add runtime lock to gha_cron job. --- gha_cron.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/gha_cron.php b/gha_cron.php index f2b09a1..2bca44b 100644 --- a/gha_cron.php +++ b/gha_cron.php @@ -172,6 +172,16 @@ function processQueueFile($filepath) { } +$job_lock = $ScriptPath . DIRECTORY_SEPARATOR . '.gha_cron.lock'; +if ( file_exists( $job_lock ) ) { + echo("Job already running, quitting. \n"); + exit(); +} elseif( touch($job_lock) === false ) { + echo("Could not set job lock, quitting. \n"); + exit(); +} + + $timer_start = microtime(true); $ghalObj = new GithubArtifactList(); @@ -201,5 +211,10 @@ function processQueueFile($filepath) { } +if ( file_exists($job_lock) ) { + echo("Removing job lock. \n"); + unlink($job_lock); +} + $t = microtime(true) - $timer_start; printf("Finished in %4.2f seconds\n\n\n", $t); From 089c3cddeffa063bbeec2d72a99b9685f5bf34d4 Mon Sep 17 00:00:00 2001 From: Fae Date: Fri, 18 Dec 2020 10:30:29 -0800 Subject: [PATCH 12/20] Update translations, add en_GB --- i18n/en_GB/LC_MESSAGES/ci_snapshots.mo | Bin 0 -> 4750 bytes i18n/en_GB/LC_MESSAGES/ci_snapshots.po | 182 +++++++++++++++++++++++++ i18n/ru/LC_MESSAGES/ci_snapshots.mo | Bin 5782 -> 5782 bytes i18n/ru/LC_MESSAGES/ci_snapshots.po | 2 +- 4 files changed, 183 insertions(+), 1 deletion(-) create mode 100644 i18n/en_GB/LC_MESSAGES/ci_snapshots.mo create mode 100644 i18n/en_GB/LC_MESSAGES/ci_snapshots.po diff --git a/i18n/en_GB/LC_MESSAGES/ci_snapshots.mo b/i18n/en_GB/LC_MESSAGES/ci_snapshots.mo new file mode 100644 index 0000000000000000000000000000000000000000..d15751e814eca8ca7dc86740a9684afb0be2a24c GIT binary patch literal 4750 zcmeH~O>7%Q6vqcBv<;svrF_#@7fOMy9lIYyPGYHPlM*#;t$Y+Jfy&rBwukP{xI43T zgE)YI3r8g2LT~hl#0{=UNN_*}i37I^aVeJ`kbnyp{%_ZInub;!IA-MW&olFO=Dq*C znQX3X-SCk>*@OB7>Z`X2aRB`Ec6_09?-1fH@EmwIcmcc@d=D&wAA&D{UxUws*TBo* zBO7wNtKb&2{{$ZfH{OXca0j>*90Pg2W8g;cGze26!N1~@ zziZ$)_y_niIEqg8cM~Rk65IyzycKXW_+r0(2xLEIz=y!oecJx```|t3{{Sq3pMaO4 z(YN5EXn%~!c7R`iEZL9X2>25?g^nvA`?nL5us?f1j(0!E`b~kX-#L)=y9ly=-+^rR z3BFjrOCam_CCK{y0J7h|f%u5O@dcN}=KJzEw}7nQc98WegFL_6NUNUbbZAS*lZ{VXc$%!SVnpCc};9gf1VY{xIw zLr~o&o&+tZu48*9?>{efBpZ>U1tYuY$1dqeIWd(;)9he| zzT@-Pb)7WPvN)V5=~XC>%+W4!M0(kfPSIg&jB0vq4NWJBO?HY5)azhYYAN6BXwzO< zEYi-y=0aB|Sab%?sE`7hHPGM4XplwOB!l63buLxT!;D>}c_qVqF>zG;PTCN2a17%U zve8HeyH-yd*beL%2BuOSg@syuaSGbOrZ)7)iQ_U<)WbY9p{W^3=Ozm+>8K@xj*^Kl zKvIq0It-PWEchfn%fXpHemRg)fgnZ;){iYHN-X^lGXvraK7CVZPd>^slOY?KogUANy*Wyb8 zQ;_dyM|}EJQiMGkU4EQ1#!+GDZ-LLB$8`M&oo)FZr+ovv>@kwqwB6yv^xC90by;$? z6PYDx|8(P^-~*3bif1rk)5fvJI;>w8It`q4Bf9&n^QbYNXjyke<_=j}sgohTZH)(}wa5 zTo+AgSki_fhjk&eHlb>1qrHljixc^xxM~O5&8rAAD>{PfA+j=b*j%_=#ZkK9&c5NY z3i}V><{sSYH?zpDt5aI_uqCYVYB5AM!s&){oged6VhL?TWSh7d_%k4;FFJIozC_sOT~6 z6j>H5Dj8R4+O&~woxQZgl7(~(Vuv;!npLqMXO2#d9G^Wtz2ZQ*I6A`VrVM6Z?qX%H zF8ES6lG=_tl@!L(`G5X)3)tfBqLE5T7^6q1$Qt6LR9uu5@X zv5b+t8BAfvy3%*5R4kUV&qzH=6Bz~A+1ORdbhDjqGwtT#zR~=z?<@ZRm*R0^ literal 0 HcmV?d00001 diff --git a/i18n/en_GB/LC_MESSAGES/ci_snapshots.po b/i18n/en_GB/LC_MESSAGES/ci_snapshots.po new file mode 100644 index 0000000..16b248d --- /dev/null +++ b/i18n/en_GB/LC_MESSAGES/ci_snapshots.po @@ -0,0 +1,182 @@ +msgid "" +msgstr "" +"Project-Id-Version: mudlet\n" +"POT-Creation-Date: 2020-06-08 23:23-0700\n" +"PO-Revision-Date: 2020-12-18 18:23\n" +"Last-Translator: \n" +"Language-Team: English, United Kingdom\n" +"Language: en_GB\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 2.3.1\n" +"X-Poedit-Basepath: ../../..\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Poedit-SearchPath-0: .\n" +"X-Poedit-SearchPathExcluded-0: i18n\n" +"X-Poedit-SearchPathExcluded-1: wp-plugin\n" +"X-Crowdin-Project: mudlet\n" +"X-Crowdin-Project-ID: 306263\n" +"X-Crowdin-Language: en-GB\n" +"X-Crowdin-File: /Mudlet snapshots/ci_snapshots.po\n" +"X-Crowdin-File-ID: 208\n" + +#: index.php:23 +msgid "Failed Request - File Read Error" +msgstr "Failed Request - File Read Error" + +#: index.php:67 +msgid "Name" +msgstr "Name" + +#: index.php:69 +msgid "Created" +msgstr "Created" + +#: index.php:70 +msgid "Size" +msgstr "Size" + +#: index.php:71 +msgid "Expires" +msgstr "Expires" + +# Number of Downloads +#: index.php:72 +msgid "DL #" +msgstr "DL #" + +#: index.php:73 +msgid "Github" +msgstr "Github" + +#: index.php:120 +msgid "Only" +msgstr "Only" + +#: index.php:133 +msgid "View Pull Request on Github.com" +msgstr "View Pull Request on Github.com" + +#: index.php:139 index.php:146 +msgid "View Commit on Github.com" +msgstr "View Commit on Github.com" + +#: index.php:223 +msgid "Error while fetching Snapshot list!" +msgstr "Error while fetching Snapshot list!" + +#: lib/functions.php:343 +msgid "Failed - Internal Server Error" +msgstr "Failed - Internal Server Error" + +#: lib/functions.php:358 +msgid "404 Not Found" +msgstr "404 Not Found" + +#: lib/functions.php:360 +msgid "Not Found" +msgstr "Not Found" + +#: lib/functions.php:361 +msgid "The requested URL was not found on this server." +msgstr "The requested URL was not found on this server." + +#: lib/functions.php:363 +msgid " Server at " +msgstr " Server at " + +#: lib/functions.php:363 +msgid " Port " +msgstr " Port " + +#: lib/init.php:40 +msgid "Database Connection Error!" +msgstr "Database Connection Error!" + +#: tpl/index.lang.php:8 +msgid "Mudlet Pull-Request Snapshots" +msgstr "Mudlet Pull-Request Snapshots" + +#: tpl/index.lang.php:9 +msgid "Mudlet Logo" +msgstr "Mudlet Logo" + +#: tpl/index.lang.php:10 +msgid "Home" +msgstr "Home" + +#: tpl/index.lang.php:11 +msgid "Wiki" +msgstr "Wiki" + +#: tpl/index.lang.php:12 +msgid "Forum" +msgstr "Forum" + +#: tpl/index.lang.php:13 +msgid "Available Snapshot Files" +msgstr "Available Snapshot Files" + +#: tpl/index.lang.php:14 +msgid "This page lists \"Snapshots\" of the Mudlet software built through Continuous Integration services as a result of commits and pull requests to the Mudlet repository on Github.com
These files are made available to enable easier testing of software changes but cannot be stored indefinitely.
For Mudlet Release Downloads visit: https://www.mudlet.org/download/" +msgstr "This page lists \"Snapshots\" of the Mudlet software built through Continuous Integration services as a result of commits and pull requests to the Mudlet repository on Github.com
These files are made available to enable easier testing of software changes but cannot be stored indefinitely.
For Mudlet Release Downloads visit: https://www.mudlet.org/download/" + +#: tpl/index.lang.php:23 +msgid "All times are shown in " +msgstr "All times are shown in " + +#: tpl/index.lang.php:24 +msgid "Latest PTB Snapshots" +msgstr "Latest PTB Snapshots" + +# Filter controls text. Before platforms. +#: tpl/index.lang.php:26 +msgid "Show only files for: " +msgstr "Show only files for: " + +#: tpl/index.lang.php:27 +msgid "All Platforms" +msgstr "All Platforms" + +# Filter controls text. After platforms. +#: tpl/index.lang.php:28 +msgid "in" +msgstr "in" + +#: tpl/index.lang.php:29 +msgid "Branches & PRs" +msgstr "Branches & PRs" + +#: tpl/index.lang.php:30 +msgid "Branches Only" +msgstr "Branches Only" + +#: tpl/index.lang.php:31 +msgid "Pull-Requests Only" +msgstr "Pull-Requests Only" + +#: tpl/index.lang.php:32 +msgid "Remember via Cookie: " +msgstr "Remember via Cookie: " + +#: tpl/index.lang.php:34 +msgid "All times are in " +msgstr "All times are in " + +#: tpl/index.lang.php:35 +msgid "Storage Used" +msgstr "Storage Used" + +#: tpl/index.lang.php:36 +msgid "Made with heart, coffee, and You." +msgstr "Made with heart, coffee, and You." + +#: tpl/index.lang.php:41 +msgid "Terms & PrivacyAbout Mudlet" +msgstr "Terms & PrivacyAbout Mudlet" + +#: tpl/index.lang.php:45 +msgid "local time" +msgstr "local time" + diff --git a/i18n/ru/LC_MESSAGES/ci_snapshots.mo b/i18n/ru/LC_MESSAGES/ci_snapshots.mo index c4eae243d7e912bf79e2db2d685b502ed88f8dd4..9b56de5fb5df17192e0d8d651c144df9c1f3a062 100644 GIT binary patch delta 24 fcmbQHJ56_k02jBRk*=YUf`Orxfx%{Ju4*0tPP7Hd delta 24 fcmbQHJ56_k02jA`xvqhsf`OTpsrhDUu4*0tPf!Ki diff --git a/i18n/ru/LC_MESSAGES/ci_snapshots.po b/i18n/ru/LC_MESSAGES/ci_snapshots.po index 0bd3844..9fc1017 100644 --- a/i18n/ru/LC_MESSAGES/ci_snapshots.po +++ b/i18n/ru/LC_MESSAGES/ci_snapshots.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: mudlet\n" "POT-Creation-Date: 2020-06-08 23:23-0700\n" -"PO-Revision-Date: 2020-07-01 06:57\n" +"PO-Revision-Date: 2020-12-12 01:00\n" "Last-Translator: \n" "Language-Team: Russian\n" "Language: ru\n" From aa224664db071f887af5590635d09673d27436e6 Mon Sep 17 00:00:00 2001 From: Fae Date: Wed, 6 Jan 2021 19:53:40 -0800 Subject: [PATCH 13/20] Add ping-back for processed artifacts. --- config.example.php | 4 +++ gha_cron.php | 62 +++++++++++++++++++++++++++++++++---- lib/functions.http.php | 69 +++++++++++++++++++++++++++++++++++++++++- 3 files changed, 128 insertions(+), 7 deletions(-) diff --git a/config.example.php b/config.example.php index 21430e3..b447a05 100644 --- a/config.example.php +++ b/config.example.php @@ -40,6 +40,10 @@ // URL to the base artifact listing URL. All URLs are built from this one. define('GHA_LIST_URL', 'https://api.github.com/repos/Mudlet/Mudlet/actions/artifacts'); +// URL for sending notification about processed artifacts. +// Uses POST method and sends application/json list of PR IDs. +define('GHA_QUEUE_NOTICE_URL', ''); + // Regular Expression used to validate and extract IDs from artifact URLs. define('GHA_URL_REGEX', '#https?://api\.github\.com/repos/Mudlet/Mudlet/actions/artifacts/(\d+)(?:/|/zip/?)?#iu'); diff --git a/gha_cron.php b/gha_cron.php index 2bca44b..52cf80f 100644 --- a/gha_cron.php +++ b/gha_cron.php @@ -24,6 +24,9 @@ require_once('lib/functions.http.php'); +$PRList_file = $ScriptPath . DIRECTORY_SEPARATOR . TEMP_DIR . '.pr-queue.json'; + + class GithubArtifactList { private $data = array(); public $artifacts_total = 0; @@ -99,7 +102,7 @@ function processSnapshotFetch( $url, $headers ) { } if ($r[2] === 200) { - echo("Response: ${r[0]} \n"); + echo("Response: ${r[0]} \n"); } return true; @@ -171,6 +174,31 @@ function processQueueFile($filepath) { return true; } +function loadPRIdsQueue() { + global $PRList_file; + + $list = array(); + if ( file_exists( $PRList_file ) ) { + $d = file_get_contents($PRList_file); + $list = json_decode($d); + } + + return $list; +} + +function savePRIdsQueue($PRList) { + global $PRList_file; + + $data = json_encode($PRList); + + file_put_contents($PRList_file, $data); +} + +function clearPRIdsQueue() { + global $PRList_file; + unlink($PRList_file); +} + $job_lock = $ScriptPath . DIRECTORY_SEPARATOR . '.gha_cron.lock'; if ( file_exists( $job_lock ) ) { @@ -181,25 +209,34 @@ function processQueueFile($filepath) { exit(); } - $timer_start = microtime(true); $ghalObj = new GithubArtifactList(); $TempDir = $ScriptPath . DIRECTORY_SEPARATOR . TEMP_DIR ; $FileRegex = '/^' . preg_quote(GHA_QFILE_PREFIX, '/') . '(.+)' . preg_quote(GHA_QFILE_EXT, '/') . '$/iu'; - +$PRIdList = loadPRIdsQueue(); $files = scandir($TempDir); foreach( $files as $idx => $file ) { if ($file == '.' || $file == '..') { continue; } - if ( false == preg_match($FileRegex, $file) ) { + $m = null; + if ( false == preg_match($FileRegex, $file, $m) ) { continue; } - + $ts = microtime(true); echo("Processing queue file: $file \n"); + + if ( !empty($m) ) { + $p = null; + $s = preg_match('/(?:-PR([0-9]+))?-([a-f0-9]{5,9})[\.-]{1}/i', $m[1], $p); + if ( $s === 1 && count($p) == 3 ) { + $PRIdList[] = intval($p[1]); + } + } + $filepath = $TempDir . $file; $status = processQueueFile( $filepath ); @@ -207,8 +244,21 @@ function processQueueFile($filepath) { unlink( $filepath ); } $n = microtime(true) - $ts; - echo("Processing finished in $n seconds.\n\n"); + printf("Processing finished in %4.2f seconds.\n\n", $n); +} + +if ( count($PRIdList) > 0 && !empty(GHA_QUEUE_NOTICE_URL) ) { + $post_data = json_encode($PRIdList); + echo("Sending PR link update notice for: \n - " . $post_data . "\n"); + $res = do_curl_post(GHA_QUEUE_NOTICE_URL, $post_data, array('Content-Type: application/json')); + if ( $res === false ) { + echo(" - Failed \n"); + savePRIdsQueue($PRIdList); + } elseif ( $res[2] == 204 ) { + echo(" - OK \n"); + clearPRIdsQueue(); + } } if ( file_exists($job_lock) ) { diff --git a/lib/functions.http.php b/lib/functions.http.php index 05f29bf..d766b0a 100644 --- a/lib/functions.http.php +++ b/lib/functions.http.php @@ -93,7 +93,7 @@ function apache_get_version() /* -* Wrapper function for curl. +* Wrapper function for curl GET requests. * returns false on error. * on successs returns an array with 4 members. * array indexes: @@ -154,6 +154,73 @@ function do_curl_get($url, $send_headers=array(), $err400=false) { return array($result, $recv_headers, $httpcode, $rtime); } + +/* +* Wrapper function for curl POST requests. +* $data may be an array if Content-Type can be 'multipart/form-data'. +* returns false on error. +* on successs returns an array with 4 members. +* array indexes: +* -0- response body data. +* -1- response headers array. +* -2- response HTTP status code. +* -3- time taken for request. +* +*/ +function do_curl_post($url, $data='', $send_headers=array(), $err400=true) { + + $url = filter_var($url, FILTER_VALIDATE_URL); + if ( $url === false || preg_match('/^https?:\/\/.+/iu',$url) === false ) { + trigger_error('Given $url is valid or not using http/https scheme.'); + return false; + } + + $timeout_limit = GHA_CURL_TIMEOUT; + set_time_limit($timeout_limit + 30); + + $rtime_start = time(); + $ch = curl_init( $url ); + $recv_headers = []; + curl_setopt($ch, CURLOPT_FAILONERROR, $err400); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 12); + curl_setopt($ch, CURLOPT_TIMEOUT, $timeout_limit); + curl_setopt($ch, CURLOPT_MAXREDIRS, 3); + curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (compatible; make.mudlet.org/0.1)"); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $data); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + if (!empty($send_headers) ) { + curl_setopt($ch, CURLOPT_HTTPHEADER, $send_headers); + } + + // catch every valid header that CURL gets, we'll need that info for rate-limits, etc. + curl_setopt($ch, CURLOPT_HEADERFUNCTION, function($curl, $header) use (&$recv_headers) + { + $len = strlen($header); + $header = explode(':', $header, 2); + if (count($header) < 2) // ignore invalid headers + return $len; + + $recv_headers[strtolower(trim($header[0]))][] = trim($header[1]); + + return $len; + }); + + $result = curl_exec($ch); + if ( $result === false ) { + //trigger_error(curl_error($ch)); + return false; + } + $httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + + curl_close($ch); + + $rtime = time() - $rtime_start; + return array($result, $recv_headers, $httpcode, $rtime); +} + + /* * Wrapper function for curl. * returns false on error. From c2a48765ba8c97f9461c9d19f5cc5063d73a049f Mon Sep 17 00:00:00 2001 From: Fae Date: Sat, 9 Jan 2021 14:04:04 -0800 Subject: [PATCH 14/20] be sure not to duplicate existing PR IDs... --- gha_cron.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/gha_cron.php b/gha_cron.php index 52cf80f..2e679f3 100644 --- a/gha_cron.php +++ b/gha_cron.php @@ -233,7 +233,10 @@ function clearPRIdsQueue() { $p = null; $s = preg_match('/(?:-PR([0-9]+))?-([a-f0-9]{5,9})[\.-]{1}/i', $m[1], $p); if ( $s === 1 && count($p) == 3 ) { - $PRIdList[] = intval($p[1]); + $prid = intval($p[1]); + if ( !in_array($prid, $PRIdList) ) { + $PRIdList[] = $prid; + } } } From 6c72905b9adf93e32544ff2030b71992483fb4a8 Mon Sep 17 00:00:00 2001 From: Fae Date: Sat, 9 Jan 2021 17:39:55 -0800 Subject: [PATCH 15/20] also exclude invalid ID 0 --- gha_cron.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gha_cron.php b/gha_cron.php index 2e679f3..492a4ea 100644 --- a/gha_cron.php +++ b/gha_cron.php @@ -234,7 +234,7 @@ function clearPRIdsQueue() { $s = preg_match('/(?:-PR([0-9]+))?-([a-f0-9]{5,9})[\.-]{1}/i', $m[1], $p); if ( $s === 1 && count($p) == 3 ) { $prid = intval($p[1]); - if ( !in_array($prid, $PRIdList) ) { + if ( !in_array($prid, $PRIdList) && $prid !== 0 ) { $PRIdList[] = $prid; } } From cf631b6b5fa83c5f43b0970c19ce182b58e074f1 Mon Sep 17 00:00:00 2001 From: Fae Date: Wed, 20 Jan 2021 11:32:31 -0800 Subject: [PATCH 16/20] Add file check to clearPRIdQueue. --- gha_cron.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gha_cron.php b/gha_cron.php index 492a4ea..3a631db 100644 --- a/gha_cron.php +++ b/gha_cron.php @@ -196,7 +196,9 @@ function savePRIdsQueue($PRList) { function clearPRIdsQueue() { global $PRList_file; - unlink($PRList_file); + if ( file_exists($PRList_file) ) { + unlink($PRList_file); + } } From 1a28dee439bc0ba6ec1d6d833a487e42095ec367 Mon Sep 17 00:00:00 2001 From: Fae Date: Wed, 20 Jan 2021 11:34:35 -0800 Subject: [PATCH 17/20] improve ID validation for PR ID Queue --- gha_cron.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gha_cron.php b/gha_cron.php index 3a631db..f6022ec 100644 --- a/gha_cron.php +++ b/gha_cron.php @@ -234,9 +234,11 @@ function clearPRIdsQueue() { if ( !empty($m) ) { $p = null; $s = preg_match('/(?:-PR([0-9]+))?-([a-f0-9]{5,9})[\.-]{1}/i', $m[1], $p); + $c = $ghalObj->countArtifactsByName( $m[1] ); + if ( $s === 1 && count($p) == 3 ) { $prid = intval($p[1]); - if ( !in_array($prid, $PRIdList) && $prid !== 0 ) { + if ( !in_array($prid, $PRIdList) && $prid !== 0 && $c > 0) { $PRIdList[] = $prid; } } From 483345a512588f55b1adf6b86e89e5eb50881db6 Mon Sep 17 00:00:00 2001 From: Fae Date: Wed, 20 Jan 2021 11:36:00 -0800 Subject: [PATCH 18/20] allow job lock to expire after 20 minutes. --- gha_cron.php | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/gha_cron.php b/gha_cron.php index f6022ec..be98eac 100644 --- a/gha_cron.php +++ b/gha_cron.php @@ -24,6 +24,7 @@ require_once('lib/functions.http.php'); +$JobLockMaxTTL = 1200; // seconds from lock creation to consider the job expired. $PRList_file = $ScriptPath . DIRECTORY_SEPARATOR . TEMP_DIR . '.pr-queue.json'; @@ -204,8 +205,16 @@ function clearPRIdsQueue() { $job_lock = $ScriptPath . DIRECTORY_SEPARATOR . '.gha_cron.lock'; if ( file_exists( $job_lock ) ) { - echo("Job already running, quitting. \n"); - exit(); + $lock_time = filemtime($job_lock); + $lock_exp_time = $lock_time + $JobLockMaxTTL; + if (time() < $lock_exp_time) { + echo("Job already running, quitting. \n"); + exit(); + } else { + echo("Job already running, but lock has expired! \n"); + unlink($job_lock); + exit(); + } } elseif( touch($job_lock) === false ) { echo("Could not set job lock, quitting. \n"); exit(); From 8e9517e62bf0d20920c0c07edf4c682de1748e81 Mon Sep 17 00:00:00 2001 From: keneanung Date: Sun, 26 Sep 2021 21:56:40 +0200 Subject: [PATCH 19/20] Add token requirements README --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2c8c3ec..be127a0 100644 --- a/README.md +++ b/README.md @@ -52,14 +52,15 @@ Optional settings via Headers `Max-Days` and `Max-Downloads` are also supported Due to limitations in Github Actions and Artifacts storage, while a job is running associated artifacts cannot be downloaded. To work around this, workflows can enqueue artifacts by name - assuming the artifact name is unique among other artifacts. Usage of `/gha_queue.php` is the same as `/github_artifacts.php` with one exception. -The `id` parameter is replaced with an `artifact_name` parameter and must contain a unique Artifact name. +The `id` parameter is replaced with an `artifact_name` parameter and must contain a unique Artifact name. Example usage: `https://make.mudlet.org/snapshots/gha_queue.php?artifact_name=Mudlet-4.10.1-testing-pr1111-a770ad4d-linux-x64&unzip=1` Optional settings via Headers are also supported. -**Note:** Queue services require a separate cron job! +**Note:** Queue services require a separate cron job! +**Note 2:** This feature requires a OAuth token with the `actions` scope or a personal access token with the `public_repos` scope. ## Installation Requirements This software is powered by PHP and Apache with Mod_Rewrite. Internationalization requires Intl and gettext php support. @@ -80,4 +81,4 @@ The required Apache Directives should be something similar to this: Download and unpack or Clone the software into a PHP-Enabled server directory. Copy and rename the file `config.exmaple.php` to `config.php` and edit the configuration. Likewise, copy the `ip_list.example` to `ip_list` and edit the tab-separated list data to suit your needs. Ensure that you have created an `.htaccess` file and are using the directives found within `.htaccess.example` to enable the rewrite rules and other security controls. -The software will automatically create the required tables in the database. Simply navigate to the index with a web browser before using `cron.php` or attempting to `PUT` files. \ No newline at end of file +The software will automatically create the required tables in the database. Simply navigate to the index with a web browser before using `cron.php` or attempting to `PUT` files. From 2cf0cd30148a21a3671744be805a5ab53a5111f0 Mon Sep 17 00:00:00 2001 From: keneanung Date: Sun, 26 Sep 2021 21:58:29 +0200 Subject: [PATCH 20/20] Document required scopes for OAuth or PAT --- config.example.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/config.example.php b/config.example.php index b447a05..ee07594 100644 --- a/config.example.php +++ b/config.example.php @@ -47,7 +47,9 @@ // Regular Expression used to validate and extract IDs from artifact URLs. define('GHA_URL_REGEX', '#https?://api\.github\.com/repos/Mudlet/Mudlet/actions/artifacts/(\d+)(?:/|/zip/?)?#iu'); -// Authentication token for Github API calls - OAuth or Person Access Token. +// Authentication token for Github API calls - OAuth or Personal Access Token. +// An OAuth token needs the "actions" scope +// A personal access token needs the "public_repos" scope define('GHA_AUTH_TOKEN', 'token'); // Timeout in seconds for github artifact request execution.