@echo off
rem yoUPC-companion-update.cmd - replaces this folder's contents with the build
rem companion.youpc.org publishes. It leaves Chrome's entry alone, so every case the extension
rem has stored survives the update (#170). Removing the entry is what deletes them (#172).
rem
rem It lives inside the extension folder and updates its siblings, so there is nothing to
rem configure and nothing to type (d-cfd19d22). It refuses any folder that does not hold this
rem extension's manifest.json, and it writes nowhere but this folder and its own directory
rem under %TEMP%.
rem
rem Plain batch, so no PowerShell execution policy applies (d-e34ba7d8). curl.exe, certutil.exe
rem and tar.exe all ship with Windows 10 and 11.
rem
rem Contract with extension/deploy/Dockerfile: latest.json carries version, zip and sha256, one
rem field per line with two-space indentation, which is also how manifest.json is written. One
rem line reader below reads either. tests/companion-updater_test.sh pins both sides.
rem
rem companion.youpc.org still serves this same script at the pre-#200 name too, for a bookmark or
rem a link already out there. Every successful run renames itself to CANON_NAME (:rename_self), so
rem a folder that starts with the old name ends the run holding only the new one.

setlocal EnableExtensions

set "SITE=https://companion.youpc.org"
set "CANON_NAME=yoUPC-companion-update.cmd"
set "WORK=%TEMP%\upc-companion-update"
set "TARGET=%~dp0"
if "%TARGET:~-1%"=="\" set "TARGET=%TARGET:~0,-1%"

echo.
echo yoUPCms extension updater
echo   folder: %TARGET%
echo.

where /q curl.exe || goto :no_tool
where /q certutil.exe || goto :no_tool
where /q tar.exe || goto :no_tool

rem curl.exe on Windows verifies revocation through schannel and fails the whole transfer when
rem it cannot reach the revocation server, which a network that blocks the CRL and OCSP hosts
rem does: CRYPT_E_NO_REVOCATION_CHECK (0x80092012) and no download at all (#222). The option
rem below lets that case pass and still refuses a revoked certificate; --ssl-no-revoke, which
rem switches the check off, is deliberately not used. It needs curl 7.70 or later and Windows 10
rem 1803 shipped 7.55, where an unknown option would end the run before any request, so it is
rem passed only where this curl.exe lists it. The payload is verified against the published
rem sha256 either way.
set "CURL_TLS="
curl.exe --help all 2>nul | findstr /c:ssl-revoke-best-effort >nul && set "CURL_TLS=--ssl-revoke-best-effort"

rem The folder this script sits in must be the extension's own, or it stops here. Two markers in
rem one manifest.json: ours is the only extension whose manifest targets the CMS host, a check
rem anchored on the display name would break on the next rebrand (#193).
if "%TARGET:~-1%"==":" goto :not_extension
if not exist "%TARGET%\manifest.json" goto :not_extension
findstr /c:manifest_version "%TARGET%\manifest.json" >nul || goto :not_extension
findstr /c:cms.unifiedpatentcourt.org "%TARGET%\manifest.json" >nul || goto :not_extension

call :read_field "%TARGET%\manifest.json" version
set "HAVE_VER=%FIELD%"
if defined HAVE_VER echo   installed: %HAVE_VER%

rd /s /q "%WORK%" >nul 2>&1
mkdir "%WORK%" || goto :no_temp

echo   reading %SITE%/latest.json
curl.exe -fsS %CURL_TLS% --connect-timeout 10 --max-time 30 -o "%WORK%\latest.json" "%SITE%/latest.json" || goto :no_site

call :read_field "%WORK%\latest.json" version
set "NEW_VER=%FIELD%"
call :read_field "%WORK%\latest.json" zip
set "NEW_ZIP=%FIELD%"
call :read_field "%WORK%\latest.json" sha256
set "NEW_SHA=%FIELD%"

rem Every one of the three is checked against the shape it must have. The zip's name becomes a URL
rem and a file name, so it is pinned to a bare name in the site root and can carry no path.
if not defined NEW_VER goto :bad_latest
if not defined NEW_ZIP goto :bad_latest
if not defined NEW_SHA goto :bad_latest
echo %NEW_VER%|findstr /r "^[0-9][0-9.]*$" >nul || goto :bad_latest
echo %NEW_ZIP%|findstr /r "^upc-companion-v[0-9][0-9.]*\.zip$" >nul || goto :bad_latest
echo %NEW_SHA%|findstr /r "^[0-9a-fA-F][0-9a-fA-F]*$" >nul || goto :bad_latest

echo   published: %NEW_VER%
if /i "%NEW_VER%"=="%HAVE_VER%" goto :already_current

echo   downloading %NEW_ZIP%
curl.exe -fsS %CURL_TLS% --connect-timeout 10 --max-time 600 -o "%WORK%\%NEW_ZIP%" "%SITE%/%NEW_ZIP%" || goto :no_zip
set "ZIP_BYTES="
for %%F in ("%WORK%\%NEW_ZIP%") do set "ZIP_BYTES=%%~zF"
if not defined ZIP_BYTES goto :no_zip
if %ZIP_BYTES% LSS 200000 goto :zip_too_small

rem certutil writes the hash on its second line, as one run of hex on Windows 10 and 11 and as
rem space-separated byte pairs on older releases; stripping the spaces reads either.
set "ACTUAL="
for /f "skip=1 delims=" %%H in ('certutil -hashfile "%WORK%\%NEW_ZIP%" SHA256') do if not defined ACTUAL set "ACTUAL=%%H"
if not defined ACTUAL goto :no_hash
set "ACTUAL=%ACTUAL: =%"
if /i not "%ACTUAL%"=="%NEW_SHA%" goto :bad_checksum
echo   checksum ok

mkdir "%WORK%\new" || goto :no_temp
tar.exe -x -f "%WORK%\%NEW_ZIP%" -C "%WORK%\new" || goto :bad_zip
if not exist "%WORK%\new\manifest.json" goto :bad_payload
findstr /c:cms.unifiedpatentcourt.org "%WORK%\new\manifest.json" >nul || goto :bad_payload
call :read_field "%WORK%\new\manifest.json" version
if not "%FIELD%"=="%NEW_VER%" goto :bad_payload

rem Nothing in the folder is touched before the download is verified. Everything goes except this
rem script, so a build's renamed chunks cannot accumulate; a file Chrome still holds open survives
rem the delete and stops the run before anything is copied in.
echo   replacing the folder's contents
for /d %%D in ("%TARGET%\*") do rd /s /q "%%~fD" >nul 2>&1
for %%F in ("%TARGET%\*") do if /i not "%%~nxF"=="%~nx0" del /f /q "%%~fF" >nul 2>&1
set "LEFTOVER="
for /d %%D in ("%TARGET%\*") do set "LEFTOVER=1"
for %%F in ("%TARGET%\*") do if /i not "%%~nxF"=="%~nx0" set "LEFTOVER=1"
if defined LEFTOVER goto :locked

xcopy "%WORK%\new\*" "%TARGET%\" /e /i /q /y >nul || goto :copy_failed
if not exist "%TARGET%\manifest.json" goto :copy_failed
call :read_field "%TARGET%\manifest.json" version
if not "%FIELD%"=="%NEW_VER%" goto :copy_failed

rd /s /q "%WORK%" >nul 2>&1
call :rename_self
echo.
echo   done - this folder now holds version %NEW_VER%.
echo.
echo   Open the extension's viewer and press the reload it offers, or open
echo   chrome://extensions and press this extension's reload arrow.
echo.
echo   Keep this folder and leave the entry in chrome://extensions in place. Your
echo   cases live in the browser and Chrome deletes them when the entry goes.
echo.
pause
exit /b 0

rem rename_self -> nothing
rem A file handle Windows opened by path stays open on a rename within the same volume, so the
rem rest of this run keeps reading the script it started with. Nothing calls this before TARGET's
rem contents are already the new build, so the folder never holds two updaters at once (#200).
:rename_self
if /i "%~nx0"=="%CANON_NAME%" exit /b 0
move /y "%~f0" "%TARGET%\%CANON_NAME%" >nul 2>&1
exit /b 0

rem read_field <file> <key> -> FIELD
rem The regex spells each quote as a dot, so the pattern carries no quotes of its own and needs no
rem escaping. "^...version.:" matches `  "version": ...` and not `  "manifest_version": ...`,
rem because the three leading dots cover the indentation and the opening quote exactly.
:read_field
set "FIELD="
for /f "tokens=2 delims=:," %%A in ('findstr /r "^...%~2.:" %1') do set "FIELD=%%A"
set "FIELD=%FIELD: =%"
set FIELD=%FIELD:"=%
exit /b 0

:already_current
rd /s /q "%WORK%" >nul 2>&1
call :rename_self
echo.
echo   this folder already holds version %NEW_VER%. Nothing to do.
echo.
pause
exit /b 0

:no_tool
echo   REFUSED: curl.exe, certutil.exe and tar.exe must all be on PATH. All three ship
echo            with Windows 10 and 11.
goto :fail

:not_extension
echo   REFUSED: %TARGET% does not hold this extension. Put this script in the folder
echo            Chrome loaded, beside its manifest.json, and run it from there.
goto :fail

:no_temp
echo   REFUSED: cannot create the working folder %WORK%.
goto :fail

:no_site
echo   FAILED: cannot read %SITE%/latest.json. Check the connection and try again.
goto :fail

:bad_latest
echo   REFUSED: %SITE%/latest.json does not carry a version, a zip name and a sha256
echo            in the form this script reads. Nothing was downloaded.
goto :fail

:no_zip
echo   FAILED: cannot download %SITE%/%NEW_ZIP%.
goto :fail

:zip_too_small
echo   REFUSED: the download is only %ZIP_BYTES% bytes, which is no extension build.
goto :fail

:no_hash
echo   FAILED: certutil did not report a hash for the download.
goto :fail

:bad_checksum
echo   REFUSED: the download does not match the published checksum.
echo            published %NEW_SHA%
echo            download  %ACTUAL%
goto :fail

:bad_zip
echo   FAILED: cannot unpack the download.
goto :fail

:bad_payload
echo   REFUSED: the unpacked build is not version %NEW_VER% of this extension.
goto :fail

:fail
echo.
echo   Nothing in %TARGET% was changed.
echo.
pause
exit /b 1

:locked
echo   STOPPED: a file in %TARGET% could not be removed, which is Chrome still holding it.
echo            The folder is unchanged. Switch the extension off on chrome://extensions,
echo            run this script again, then switch it back on.
echo.
pause
exit /b 1

:copy_failed
echo   FAILED: the new build could not be copied into %TARGET%, which is now incomplete.
echo           The unpacked build is in %WORK%\new - copy its contents into the folder
echo           by hand, or run this script again.
echo.
pause
exit /b 1
