diff --git a/.github/instructions/instructions.instructions.md b/.github/instructions/instructions.instructions.md index c72cb2c4..7a744482 100644 --- a/.github/instructions/instructions.instructions.md +++ b/.github/instructions/instructions.instructions.md @@ -102,6 +102,7 @@ code example here - **Include Examples**: Real code snippets are more effective than descriptions - **Stay Current**: Reference current versions and best practices - **Link Resources**: Include official documentation and authoritative sources +- **Capture validated shell quirks**: For environment-specific instruction files, record proven terminal behaviors, quoting pitfalls, failing command patterns, and the known-good command form that replaced them ### Instruction Altitude (Goldilocks Zone) diff --git a/.github/instructions/regalamiunsorriso-83-149-164-4.instructions.md b/.github/instructions/regalamiunsorriso-83-149-164-4.instructions.md index 1259fa34..7b153171 100644 --- a/.github/instructions/regalamiunsorriso-83-149-164-4.instructions.md +++ b/.github/instructions/regalamiunsorriso-83-149-164-4.instructions.md @@ -35,6 +35,12 @@ If you need a single elevated command: ssh -tt -i C:\Users\Maddo\.ssh\id_rsa -p 410 marco@83.149.164.4 "sudo tcsh -c 'command here'" ``` +From PowerShell on Windows, prefer invoking the SSH binary directly instead of wrapping it in `cmd /c`: + +```powershell +& 'C:\Windows\System32\OpenSSH\ssh.exe' -tt -i 'C:\Users\Maddo\.ssh\id_rsa' -p 410 'marco@83.149.164.4' +``` + ## Shell Behavior On This Host - The remote login shell behaves as `tcsh`. @@ -42,11 +48,15 @@ ssh -tt -i C:\Users\Maddo\.ssh\id_rsa -p 410 marco@83.149.164.4 "sudo tcsh -c 'c - The server `sh` does not support `-l`, so use `sh -c`, not `sh -lc`. - `tcsh` treats redirection and pipelines differently from POSIX shells; commands like `find ... 2>/dev/null | head` can fail with `Ambiguous output redirect` unless the whole payload runs under `sh -c`. - Prefer one remote command per SSH invocation when doing reconnaissance. Complex commands with pipes, grouped expressions, or escaped parentheses are much more likely to break under PowerShell-to-SSH-to-`tcsh` quoting. +- On Windows PowerShell, avoid `cmd /c "ssh ..."` and `cmd /c "scp ..."` wrappers for anything nontrivial. Nested quoting can collapse before SSH runs and spill later tokens into the local PowerShell session, which leads to misleading local errors such as `sudo: The term 'sudo' is not recognized` or local attempts to run `cksum`. +- Prefer the PowerShell call operator form `& 'C:\Windows\System32\OpenSSH\ssh.exe' ...` and pass the remote command as a single argument when you must stay non-interactive. - If PowerShell shows the continuation prompt `? >`, the command was malformed locally before SSH executed it. Cancel it and rerun a simpler command instead of trying to answer the prompt. - If `sudo` reports that a terminal is required, reconnect with `-tt`. - When running remote commands from PowerShell, quoting can break if the command contains both nested quotes and file paths with spaces. - For read-only verification commands from PowerShell, prefer `ssh ... --% ` so the remote command is passed verbatim. - For `promote-file.sh` calls that target paths with spaces, prefer a local PowerShell loop that passes the full remote command as a single SSH argument instead of building one long nested quoted command. +- For multi-step privileged work, prefer opening one interactive SSH session, then running `sudo tcsh`, then issuing commands sequentially inside that shell. This is more reliable than trying to encode several `sudo tcsh -c 'a ; b ; c'` operations through PowerShell quoting. +- In an interactive `tcsh` root shell, do not re-send a password or any other text starting with `!` after the password prompt has already succeeded. `tcsh` interprets `!` as history expansion and will emit `Event not found`. - If repeated SSH commands start cancelling or interleaving poorly in the same terminal, rerun them sequentially instead of in parallel. ## Mail Template Runtime Notes @@ -88,6 +98,14 @@ ssh -tt -i C:\Users\Maddo\.ssh\id_rsa -p 410 marco@83.149.164.4 "sudo tcsh -c 'c - Incoming staging root: `/home/marco/regalamiunsorriso/incoming/www` - Live site root: `/home/sites/regalamiunsorriso/www` +## Tomcat Logs And Runtime Clues + +- The active Tomcat installation on this host is under `/usr/local/apache-tomcat-9.0`. +- The most useful live runtime log is `/usr/local/apache-tomcat-9.0/logs/catalina.out`. +- Rotated Tomcat logs are under `/usr/local/apache-tomcat-9.0/logs/`, including files such as `catalina.YYYY-MM-DD.log` and `localhost.YYYY-MM-DD.log`. +- Access to generated JSP work files under `/usr/local/apache-tomcat-9.0/work` may require root. +- A broken JSP on this host can still return `HTTP 200` with a visibly truncated HTML body instead of a clean 500 response; when that happens, fetch part of the page body with `curl -L | head -n ...` and compare the cutoff point with recent `catalina.out` output. + ## Staging Workflow When `www/**` files need deployment: @@ -103,6 +121,9 @@ Example staging command pattern: tar -cf - -C K:\various\regalamiunsorriso | ssh -i C:\Users\Maddo\.ssh\id_rsa -p 410 marco@83.149.164.4 "tar -xf - -C /home/marco/regalamiunsorriso/incoming" ``` +- The streamed tar extraction into `/home/marco/regalamiunsorriso/incoming` works as the unprivileged `marco` user and avoids the permission problems seen when uploading an archive and trying to unpack it with `sudo tar`. +- Do not rely on `sudo tar` for staging on this host. `marco` is not permitted to run that extraction as root. + ## Promotion Rules - Promotion to the live site must happen through `sudo tcsh`. @@ -136,6 +157,13 @@ $remote = "sudo tcsh -c \"/home/marco/promote-file.sh '' ' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Regalami Un Sorriso ETS - Competitions + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+
+

Running // Running // 42 HALF MARATHON FIRENZE

+
+
+ + +
+
+ +
+
+ +
+ + + + + + + + +
+ + + +
+

Search your photos

+
+
+
+ + +
+
+
+
+ + +
+
+ +
+
+ +
+
+
+ +
+
+
+
+
+
+
+
+
+

They have been found 35389 photo + + +

+

+
+
+ +
+
+
+ + + go to page + + +
+
+ + +
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ +
+
+
+ +

ATTENTION! GOOGLE ADVERTISING + + + +

+ + + + + +
+ + + + + + + + + + + + + + +
+ +
+ +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/faceai/authenticated_output.html b/faceai/authenticated_output.html new file mode 100644 index 00000000..e14e1696 --- /dev/null +++ b/faceai/authenticated_output.html @@ -0,0 +1,1120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Regalami Un Sorriso ETS - Competitions + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+
+

Running // Running // 42 HALF MARATHON FIRENZE

+
+
+ + +
+
+ +
+
+ +
+ + + + + + + + +
+ + + +
+

Search your photos

+
+
+
+ + +
+
+
+
+ + +
+
+ +
+
+ +
+
+
+ +
+
+
+
+
+
+
+
+
+

They have been found 35389 photo + + +

+

+
+
+ +
+
+
+ + + go to page + + +
+
+ + +
+
+ + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + +
+
+
+ +

ATTENTION! GOOGLE ADVERTISING + + + +

+ + + + + +
+ + + + + + + + + + + + + + +
+ +
+ +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/faceai/headers.txt b/faceai/headers.txt new file mode 100644 index 00000000..e69de29b diff --git a/faceai/login_response.html b/faceai/login_response.html new file mode 100644 index 00000000..ebb9936e --- /dev/null +++ b/faceai/login_response.html @@ -0,0 +1,359 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Regalami Un Sorriso ETS - User Area + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+

User Area

+ +

HI Giacomelli Piero
+ Address: Fosso del Masi n. 24, Prato 59100 (PO)
+ Company belonging to: REGALAMI UN SORRISO ONLUS
+ Telephone: 3487258208
+ E-mail: [email protected]
+ Tax ID code: GCMPRI58H25G999U
+ Expiration Date: 15/06/2030
+ No. of photos displayed/No. of photos max: 37045/0
+ Number of photos viewed today: 0

+

Please verify that the information is correct. For changes, please write to [email protected] or go to Edit Data
+ Please note that tacit consent to the accuracy of the member personal data is required.

+ +

.Mancano. 1518 .giorni alla scadenza..
+

+ + +
+
+ +
+
Login ok
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/faceai/package.json b/faceai/package.json index 4a363e79..473af682 100644 --- a/faceai/package.json +++ b/faceai/package.json @@ -16,7 +16,10 @@ "start:processor": "npm run start --workspace @regalami/faceai-processor", "test:e2e": "playwright test", "test:e2e:headed": "playwright test --headed", - "test:e2e:install": "playwright install chromium" + "test:e2e:install": "playwright install chromium", + "test:live": "playwright test -c playwright.live.config.js", + "test:live:headed": "playwright test -c playwright.live.config.js --headed", + "test:live:install": "playwright install chromium" }, "devDependencies": { "@playwright/test": "^1.59.1", diff --git a/faceai/playwright.live.config.js b/faceai/playwright.live.config.js new file mode 100644 index 00000000..4795474a --- /dev/null +++ b/faceai/playwright.live.config.js @@ -0,0 +1,38 @@ +const path = require('path'); +const { defineConfig } = require('@playwright/test'); + +const authFile = path.join(__dirname, 'tests/live-site/.auth/user.json'); + +module.exports = defineConfig({ + testDir: './tests/live-site', + timeout: 2 * 60 * 1000, + expect: { + timeout: 20 * 1000 + }, + fullyParallel: false, + workers: 1, + reporter: [['list'], ['html', { open: 'never', outputFolder: 'playwright-report/live-site' }]], + outputDir: 'test-results/live-site', + use: { + baseURL: process.env.LIVE_SITE_BASE_URL || 'https://www.regalamiunsorriso.it', + browserName: 'chromium', + headless: true, + trace: 'retain-on-failure', + screenshot: 'only-on-failure', + video: 'retain-on-failure' + }, + projects: [ + { + name: 'setup', + testMatch: /.*\.setup\.js/ + }, + { + name: 'live-chromium', + dependencies: ['setup'], + testIgnore: /.*\.setup\.js/, + use: { + storageState: authFile + } + } + ] +}); \ No newline at end of file diff --git a/faceai/probe.js b/faceai/probe.js new file mode 100644 index 00000000..e20ca7bb --- /dev/null +++ b/faceai/probe.js @@ -0,0 +1,47 @@ +const { chromium } = require('playwright'); +const fs = require('fs'); + +async function run() { + const browser = await chromium.launch(); + const context = await browser.newContext(); + const page = await context.newPage(); + + try { + const loginUrl = process.env.LIVE_SITE_LOGIN_URL; + const raceUrl = process.env.LIVE_SITE_RACE_URL; + + console.log('--- Step 1: Navigating to Login ---'); + await page.goto(loginUrl, { waitUntil: 'load' }); + + console.log('--- Step 2: Filling credentials ---'); + await page.type('#login', process.env.LIVE_SITE_USERNAME); + await page.type('#pwd', process.env.LIVE_SITE_PASSWORD); + + console.log('--- Step 3: Clicking Accedi ---'); + await page.click('button:has-text("Accedi")'); + await page.waitForTimeout(5000); // Give it time to process and redirect + + console.log('--- Step 4: Navigating to Race Page ---'); + const response = await page.goto(raceUrl, { waitUntil: 'load' }); + + console.log('Race Page URL:', page.url()); + console.log('HTTP Status:', response.status()); + + const cookies = await context.cookies(); + const cookieNames = cookies.map(c => c.name); + console.log('Cookies:', cookieNames.join(', ')); + console.log('rus_faceai_identity:', cookieNames.includes('rus_faceai_identity')); + console.log('JSESSIONID:', cookieNames.includes('JSESSIONID')); + + const logoutCount = await page.locator('a[href*="logout"]').count(); + const loginCount = await page.locator('a[href*="login"]').count(); + console.log('Logout Link Count:', logoutCount); + console.log('Login Link Count:', loginCount); + + } catch (err) { + console.error('Error occurred:', err.message); + } finally { + await browser.close(); + } +} +run(); diff --git a/faceai/response.html b/faceai/response.html new file mode 100644 index 00000000..0d0514e7 --- /dev/null +++ b/faceai/response.html @@ -0,0 +1,1100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Regalami Un Sorriso ETS - Gare + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+
+

Podismo // Podismo // 42 HALF MARATHON FIRENZE

+
+
+ + +
+
+ +
+
+ +
+ + + + + + + + +
+ + + +
+

Cerca le tue foto

+
+
+
+ + +
+
+
+
+ + +
+
+ +
+
+ +
+
+
+ +
+
+
+
+
+
+
+
+
+

Sono state trovate 35389 foto + + +

+

+
+
+ +
+
+
+ + + vai a pag. + + +
+
+ + +
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ +
+
+
+ +

ATTENZIONE ! PUBBLICITA' GOOGLE + + + +

+ + + + + +
+ + + + + + + + + + + + + + +
+ +
+ +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/faceai/tests/live-site/auth.setup.js b/faceai/tests/live-site/auth.setup.js new file mode 100644 index 00000000..c0d47555 --- /dev/null +++ b/faceai/tests/live-site/auth.setup.js @@ -0,0 +1,12 @@ +const { test } = require('@playwright/test'); +const { + AUTH_FILE, + ensureAuthDirectory, + performLiveLogin +} = require('./live-site-test-utils'); + +test('authenticate against the live site', async ({ page }) => { + ensureAuthDirectory(); + await performLiveLogin(page); + await page.context().storageState({ path: AUTH_FILE }); +}); \ No newline at end of file diff --git a/faceai/tests/live-site/live-race.spec.js b/faceai/tests/live-site/live-race.spec.js new file mode 100644 index 00000000..1ae3a2d0 --- /dev/null +++ b/faceai/tests/live-site/live-race.spec.js @@ -0,0 +1,33 @@ +const { test, expect } = require('@playwright/test'); +const { + LIVE_SITE_RACE_URL, + dismissCookieBanner, + expectRacePageLoaded, + performLiveLogin, + waitForLoggedInUi +} = require('./live-site-test-utils'); + +test('loads a live race page with an authenticated session', async ({ page }) => { + await page.goto(LIVE_SITE_RACE_URL, { waitUntil: 'domcontentloaded' }); + await dismissCookieBanner(page); + + try { + await waitForLoggedInUi(page); + } catch (error) { + await performLiveLogin(page); + await page.goto(LIVE_SITE_RACE_URL, { waitUntil: 'domcontentloaded' }); + await dismissCookieBanner(page); + await waitForLoggedInUi(page); + } + + await expectRacePageLoaded(page); + await expect(page.locator('h1')).toContainText(/HALF MARATHON FIRENZE|Competitions|Gare/i); + + const cookies = await page.context().cookies(LIVE_SITE_RACE_URL); + const faceAiIdentityCookie = cookies.find((cookie) => cookie.name === 'rus_faceai_identity'); + + expect(faceAiIdentityCookie, 'Expected the race page to mint the FaceAI identity cookie for the authenticated session.').toBeTruthy(); + expect(faceAiIdentityCookie.httpOnly).toBe(true); + expect(faceAiIdentityCookie.secure).toBe(true); + expect(faceAiIdentityCookie.value).toMatch(/\./); +}); \ No newline at end of file diff --git a/faceai/tests/live-site/live-site-test-utils.js b/faceai/tests/live-site/live-site-test-utils.js new file mode 100644 index 00000000..f7f99061 --- /dev/null +++ b/faceai/tests/live-site/live-site-test-utils.js @@ -0,0 +1,92 @@ +const fs = require('fs'); +const path = require('path'); +const { expect } = require('@playwright/test'); + +const LIVE_SITE_BASE_URL = process.env.LIVE_SITE_BASE_URL || 'https://www.regalamiunsorriso.it'; +const LIVE_SITE_LOGIN_URL = process.env.LIVE_SITE_LOGIN_URL || `${LIVE_SITE_BASE_URL}/login_clienti-it.html`; +const LIVE_SITE_RACE_URL = process.env.LIVE_SITE_RACE_URL || `${LIVE_SITE_BASE_URL}/42%20HALF%20MARATHON%20FIRENZE_gara-1018545---96-1.html`; +const LIVE_SITE_USERNAME = process.env.LIVE_SITE_USERNAME || ''; +const LIVE_SITE_PASSWORD = process.env.LIVE_SITE_PASSWORD || ''; +const AUTH_FILE = path.join(__dirname, '.auth', 'user.json'); + +function ensureAuthDirectory() { + fs.mkdirSync(path.dirname(AUTH_FILE), { recursive: true }); +} + +function requireCredentials() { + if (!LIVE_SITE_USERNAME || !LIVE_SITE_PASSWORD) { + throw new Error('LIVE_SITE_USERNAME and LIVE_SITE_PASSWORD must be set before running the live-site Playwright suite.'); + } +} + +async function dismissCookieBanner(page) { + const cookieButton = page.getByRole('button', { name: /^(Accetto|Accept)$/i }); + if (await cookieButton.count()) { + await cookieButton.first().click({ timeout: 5000 }).catch(() => {}); + return; + } + + const fallbackButton = page.locator('.cc-btn, .cc-dismiss, .cc-allow').filter({ hasText: /Accetto|Accept/i }); + if (await fallbackButton.count()) { + await fallbackButton.first().click({ timeout: 5000 }).catch(() => {}); + } +} + +function loginSubmitLocator(page) { + return page.locator('a.btn').filter({ hasText: /Accedi|Sign in/i }).first(); +} + +async function performLiveLogin(page) { + requireCredentials(); + + await page.goto(LIVE_SITE_LOGIN_URL, { waitUntil: 'domcontentloaded' }); + await dismissCookieBanner(page); + await page.locator('#login').fill(LIVE_SITE_USERNAME); + await page.locator('#pwd').fill(LIVE_SITE_PASSWORD); + await loginSubmitLocator(page).click(); + await waitForLoggedInUi(page); +} + +async function waitForLoggedInUi(page) { + const accountMenu = page.locator('#navbarDropdownMenuLink'); + const accountLink = page.locator('a[href*="dettaglio_clienti"]'); + const logoutLink = page.locator('a[href*="user_logout"]'); + + await expect.poll(async () => { + if (await accountMenu.count() && await accountMenu.first().isVisible().catch(() => false)) { + return 'account-menu'; + } + if (await accountLink.count() && await accountLink.first().isVisible().catch(() => false)) { + return 'account-link'; + } + if (await logoutLink.count() && await logoutLink.first().isVisible().catch(() => false)) { + return 'logout-link'; + } + return ''; + }, { + timeout: 30 * 1000, + message: 'Expected the logged-in account UI to appear after authenticating.' + }).not.toBe(''); +} + +async function expectRacePageLoaded(page) { + await expect(page.locator('form[onsubmit="return searching()"]')).toBeVisible(); + await expect(page.locator('#id_gara')).toHaveValue(/\d+/); + await expect(page.locator('script[src*="_js/rus-ecom-240621.js"]')).toHaveCount(1); +} + +module.exports = { + AUTH_FILE, + LIVE_SITE_BASE_URL, + LIVE_SITE_LOGIN_URL, + LIVE_SITE_PASSWORD, + LIVE_SITE_RACE_URL, + LIVE_SITE_USERNAME, + dismissCookieBanner, + ensureAuthDirectory, + expectRacePageLoaded, + loginSubmitLocator, + performLiveLogin, + requireCredentials, + waitForLoggedInUi +}; \ No newline at end of file diff --git a/faceai/tmp-live-state-inspection.json b/faceai/tmp-live-state-inspection.json new file mode 100644 index 00000000..31a25038 --- /dev/null +++ b/faceai/tmp-live-state-inspection.json @@ -0,0 +1,45 @@ +{ + "cookies": [ + { + "name": "JSESSIONID", + "value": "C78CCBD1771ACB52852817531958E96D", + "domain": "www.regalamiunsorriso.it", + "path": "/", + "expires": -1, + "httpOnly": true, + "secure": false, + "sameSite": "Lax" + }, + { + "name": "_pk_id.6.42c1", + "value": "5710c1018f76b02c.1776585670.", + "domain": "www.regalamiunsorriso.it", + "path": "/", + "expires": 1810540870, + "httpOnly": false, + "secure": false, + "sameSite": "Lax" + }, + { + "name": "_pk_ses.6.42c1", + "value": "1", + "domain": "www.regalamiunsorriso.it", + "path": "/", + "expires": 1776587471, + "httpOnly": false, + "secure": false, + "sameSite": "Lax" + }, + { + "name": "g_state", + "value": "{\"i_l\":0,\"i_ll\":1776585670175,\"i_b\":\"TwramoDGp33YsAyHyO2OJCLtRobG3bzu5wR3/nYmZ0E\",\"i_e\":{\"enable_itp_optimization\":0},\"i_et\":1776585670056}", + "domain": "www.regalamiunsorriso.it", + "path": "/", + "expires": 1792137670, + "httpOnly": false, + "secure": false, + "sameSite": "Lax" + } + ], + "origins": [] +} \ No newline at end of file diff --git a/sync/www-deploy-manifest.md b/sync/www-deploy-manifest.md index b1e6ae06..dc174365 100644 --- a/sync/www-deploy-manifest.md +++ b/sync/www-deploy-manifest.md @@ -10,20 +10,17 @@ All files in this rollout are deployed from the current working tree. ## New Files -- None in this rollout. +- `www/_inc_faceai_identity.jsp` +- `www/_js/lang.js` ## Updated Files -- `www/faceai_config.php` -- `www/faceai_handoff.php` -- `www/faceai_return.php` -- `www/fotoCR.jsp` - `www/fotoCR-en.jsp` +- `www/fotoCR.jsp` ## Excluded Files -- `www/faceai_simulator.php` -- `www/faceai_simulator_view.php` +- None in this rollout. ## Remote Copy Target @@ -31,7 +28,7 @@ All files in this rollout are deployed from the current working tree. - Remote host: `marco@83.149.164.4:410` - Remote staging path: `/home/marco/regalamiunsorriso/incoming/www` - Remote live path: `/home/sites/regalamiunsorriso/www` -- Total files in this manifest: `5` +- Total files in this manifest: `4` ## Transfer Method diff --git a/www/_inc_faceai_identity.jsp b/www/_inc_faceai_identity.jsp new file mode 100644 index 00000000..7d3cb783 --- /dev/null +++ b/www/_inc_faceai_identity.jsp @@ -0,0 +1,260 @@ +<%@ page language="java" import="java.nio.charset.StandardCharsets" %> +<%@ page language="java" import="java.util.Base64" %> +<%@ page language="java" import="java.lang.reflect.Method" %> +<%@ page language="java" import="javax.crypto.Mac" %> +<%@ page language="java" import="javax.crypto.spec.SecretKeySpec" %> +<%! +private String faceAiCookieEnv(String key, String defaultValue) { + String value = System.getenv(key); + if (value == null || value.trim().length() == 0) { + value = System.getProperty(key, defaultValue); + } + if (value == null) { + return defaultValue; + } + value = value.trim(); + return value.length() == 0 ? defaultValue : value; +} + +private String faceAiBase64Url(byte[] value) { + return Base64.getUrlEncoder().withoutPadding().encodeToString(value); +} + +private String faceAiJsonEscape(String value) { + if (value == null) { + return ""; + } + + StringBuilder builder = new StringBuilder(value.length() + 16); + for (int index = 0; index < value.length(); index++) { + char current = value.charAt(index); + switch (current) { + case '\\': + builder.append("\\\\"); + break; + case '"': + builder.append("\\\""); + break; + case '\b': + builder.append("\\b"); + break; + case '\f': + builder.append("\\f"); + break; + case '\n': + builder.append("\\n"); + break; + case '\r': + builder.append("\\r"); + break; + case '\t': + builder.append("\\t"); + break; + default: + if (current < 0x20) { + String hex = Integer.toHexString(current); + builder.append("\\u"); + for (int padding = hex.length(); padding < 4; padding++) { + builder.append('0'); + } + builder.append(hex); + } else { + builder.append(current); + } + break; + } + } + return builder.toString(); +} + +private Object faceAiInvoke(Object bean, String methodName) { + if (bean == null || methodName == null || methodName.length() == 0) { + return null; + } + + try { + Method method = bean.getClass().getMethod(methodName, new Class[0]); + return method.invoke(bean, new Object[0]); + } catch (Exception ignored) { + return null; + } +} + +private long faceAiUserId(Object user) { + Object value = faceAiInvoke(user, "getId_users"); + if (value instanceof Number) { + return ((Number) value).longValue(); + } + if (value != null) { + try { + return Long.parseLong(String.valueOf(value)); + } catch (NumberFormatException ignored) { + return 0L; + } + } + return 0L; +} + +private String faceAiUserString(Object user, String methodName) { + Object value = faceAiInvoke(user, methodName); + return value == null ? "" : String.valueOf(value).trim(); +} + +private boolean faceAiUserDaRinnovare(Object user) { + Object value = faceAiInvoke(user, "isDaRinnovare"); + return value instanceof Boolean ? ((Boolean) value).booleanValue() : false; +} + +private String faceAiCookieDisplayName(Object user) { + String nome = faceAiUserString(user, "getNome"); + String cognome = faceAiUserString(user, "getCognome"); + String displayName = (nome + " " + cognome).trim(); + if (displayName.length() > 0) { + return displayName; + } + + String email = faceAiUserString(user, "getEMail"); + if (email.length() > 0) { + return email; + } + + return String.valueOf(faceAiUserId(user)); +} + +private String faceAiIdentityToken(Object user, String secret, long expiresAt) throws Exception { + String email = faceAiUserString(user, "getEMail"); + String membershipStatus = faceAiUserDaRinnovare(user) ? "inactive" : "active"; + String payload = "{" + + "\"type\":\"legacy-identity\"," + + "\"userId\":\"" + faceAiJsonEscape(String.valueOf(faceAiUserId(user))) + "\"," + + "\"displayName\":\"" + faceAiJsonEscape(faceAiCookieDisplayName(user)) + "\"," + + "\"email\":\"" + faceAiJsonEscape(email) + "\"," + + "\"membershipStatus\":\"" + membershipStatus + "\"," + + "\"expiresAt\":" + expiresAt + + "}"; + + String body = faceAiBase64Url(payload.getBytes(StandardCharsets.UTF_8)); + Mac mac = Mac.getInstance("HmacSHA256"); + mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256")); + String signature = faceAiBase64Url(mac.doFinal(body.getBytes(StandardCharsets.UTF_8))); + return body + "." + signature; +} + +private boolean faceAiRequestIsSecure(javax.servlet.http.HttpServletRequest request) { + if (request == null) { + return false; + } + if (request.isSecure()) { + return true; + } + + String forwardedProto = request.getHeader("X-Forwarded-Proto"); + if (forwardedProto != null && "https".equalsIgnoreCase(forwardedProto.trim())) { + return true; + } + + String frontEndHttps = request.getHeader("Front-End-Https"); + return frontEndHttps != null && "on".equalsIgnoreCase(frontEndHttps.trim()); +} + +private void faceAiWriteCookieHeader(javax.servlet.http.HttpServletResponse response, String cookieName, String cookieValue, int maxAgeSeconds, boolean secureCookie) { + StringBuilder headerValue = new StringBuilder(); + headerValue.append(cookieName).append('=').append(cookieValue == null ? "" : cookieValue); + headerValue.append("; Max-Age=").append(maxAgeSeconds); + headerValue.append("; Path=/; HttpOnly; SameSite=Lax"); + if (secureCookie) { + headerValue.append("; Secure"); + } + response.addHeader("Set-Cookie", headerValue.toString()); +} + +private Object faceAiResolveUser(javax.servlet.jsp.PageContext pageContext) { + if (pageContext == null) { + return null; + } + + Object[] candidates = new Object[] { + pageContext.findAttribute("user"), + pageContext.findAttribute("utenteLogon") + }; + + for (int index = 0; index < candidates.length; index++) { + Object candidate = candidates[index]; + if (candidate != null && faceAiUserId(candidate) > 0L) { + return candidate; + } + } + + return null; +} + +private Object faceAiResolveUserFromSession(javax.servlet.jsp.PageContext pageContext) { + if (pageContext == null || pageContext.getSession() == null) { + return null; + } + + Object loginUserId = pageContext.getSession().getAttribute("loginUser_id"); + long resolvedLoginUserId = 0L; + if (loginUserId instanceof Number) { + resolvedLoginUserId = ((Number) loginUserId).longValue(); + } else if (loginUserId != null) { + try { + resolvedLoginUserId = Long.parseLong(String.valueOf(loginUserId)); + } catch (NumberFormatException ignored) { + resolvedLoginUserId = 0L; + } + } + + if (resolvedLoginUserId <= 0L) { + return null; + } + + Object sessionUser = pageContext.findAttribute("utenteLogon"); + if (sessionUser == null) { + return null; + } + + if (faceAiUserId(sessionUser) == resolvedLoginUserId) { + return sessionUser; + } + + try { + Method findByPrimaryKey = sessionUser.getClass().getMethod("findByPrimaryKey", long.class); + findByPrimaryKey.invoke(sessionUser, Long.valueOf(resolvedLoginUserId)); + } catch (NoSuchMethodException missingPrimitiveOverload) { + try { + Method findByPrimaryKey = sessionUser.getClass().getMethod("findByPrimaryKey", Long.class); + findByPrimaryKey.invoke(sessionUser, new Long(resolvedLoginUserId)); + } catch (Exception ignored) { + return null; + } + } catch (Exception ignored) { + return null; + } + + return faceAiUserId(sessionUser) > 0L ? sessionUser : null; +} +%> +<% +String faceAiCookieName = faceAiCookieEnv("FACEAI_IDENTITY_COOKIE", "rus_faceai_identity"); +String faceAiCookieSecret = faceAiCookieEnv("FACEAI_SHARED_SECRET", "disagio-spaghetti-science-lol-boh"); +Object faceAiRequestUser = pageContext.findAttribute("user"); +Object faceAiSessionUser = pageContext.findAttribute("utenteLogon"); +long faceAiRequestUserId = faceAiUserId(faceAiRequestUser); +long faceAiSessionUserId = faceAiUserId(faceAiSessionUser); +Object faceAiCookieUser = faceAiRequestUserId > 0L ? faceAiRequestUser : (faceAiSessionUserId > 0L ? faceAiSessionUser : faceAiResolveUserFromSession(pageContext)); +boolean faceAiSecureCookie = faceAiRequestIsSecure(request); + +if (faceAiFeatureEnabled && faceAiCookieUser != null && faceAiUserId(faceAiCookieUser) > 0L) { + long faceAiExpiresAt = System.currentTimeMillis() + (30L * 60L * 1000L); + try { + String faceAiToken = faceAiIdentityToken(faceAiCookieUser, faceAiCookieSecret, faceAiExpiresAt); + faceAiWriteCookieHeader(response, faceAiCookieName, faceAiToken, 30 * 60, faceAiSecureCookie); + } catch (Exception faceAiIdentityError) { + faceAiWriteCookieHeader(response, faceAiCookieName, "", 0, faceAiSecureCookie); + log("Unable to mint FaceAI identity cookie", faceAiIdentityError); + } +} else { + faceAiWriteCookieHeader(response, faceAiCookieName, "", 0, faceAiSecureCookie); +} +%> \ No newline at end of file diff --git a/www/_js/lang.js b/www/_js/lang.js new file mode 100644 index 00000000..f6cb59e0 --- /dev/null +++ b/www/_js/lang.js @@ -0,0 +1,55 @@ +// JavaScript Document +//////////////////////////////////////////// +// gestione cambio lingua +//////////////////////////////////////////// +var userLang = navigator.language || navigator.userLanguage; +console.log(userLang); + +function changeLang(lang) { + var page = window.location.href; + var idx = page.lastIndexOf("."); + + if (idx > 0) { + var ext = page.substring(idx, page.length); + + if (ext.indexOf("eu") >= 0 || ext.indexOf("com") >= 0 || ext.indexOf("net") >= 0 || ext.indexOf("it") >= 0) { + ext = ""; + var page1 = page; + } + else + + var page1 = page.substring(0, idx); + } + else { + // caso di pagina root www.xxxx.com/ + var ext = ""; + var page1 = page; + } + // 1 se ho pagine del tipo xxx.html --> aggiungo -lang.html + // 2 se ho pagine del tipo xxx_xxx-xxx-xxx-.html --> sostituisco l'ultima parte con xxx-lang.html + // 3 se ho pagine del tipo xxx_xxx-xx-xx-lang.html --> sostituisco l'ultima parte + // 4 se non ho estensione sono nella root e finisce con /(www.xxxx.com/) --> vado su index-lang + // 5 altrimenti vado su index-lang.jsp + // 6 caso Ordine.abl --> ritorno alla home + + if (ext == ".abl") { + theSvlt = "index-" + lang + ".html"; + } + else if (ext == "") { + theSvlt = page1 + "index-" + lang + ".html"; + } + else if (page1.lastIndexOf("-") == (page1.length - 1)) { + theSvlt = page1 + lang + ext; + } + else if (page1.lastIndexOf("-") == (page1.length - 3)) { + theSvlt = page1.substring(0, page1.length - 2) + lang + ext; + } + else if (page1.substring(page1.length - 1, page1.length) != "-") { + theSvlt = page1 + "-" + lang + ext; + } + else { + theSvlt = "index-" + lang + ".jsp"; + } + + location.href = theSvlt; +} \ No newline at end of file diff --git a/www/fotoCR-en.jsp b/www/fotoCR-en.jsp index 17ad6e47..f275b270 100644 --- a/www/fotoCR-en.jsp +++ b/www/fotoCR-en.jsp @@ -8,7 +8,6 @@ <%@ taglib uri="/WEB-INF/cc.tld" prefix="cc" %> - @@ -57,6 +56,10 @@ if (faceAiFeatureEnabledValue == null || faceAiFeatureEnabledValue.trim().length } String faceAiFeatureEnabledNormalized = faceAiFeatureEnabledValue != null ? faceAiFeatureEnabledValue.trim() : ""; boolean faceAiFeatureEnabled = !("0".equals(faceAiFeatureEnabledNormalized) || "false".equalsIgnoreCase(faceAiFeatureEnabledNormalized) || "no".equalsIgnoreCase(faceAiFeatureEnabledNormalized) || "off".equalsIgnoreCase(faceAiFeatureEnabledNormalized)); +%> +<%@ include file="_inc_faceai_identity.jsp" %> + +<% java.util.Date faceAiRaceDate = CR.getGara().getDataGaraInizio(); String faceAiRacePathBase = CR.getGara().getPathBase() != null ? CR.getGara().getPathBase().trim() : ""; String faceAiRaceYear = ""; diff --git a/www/fotoCR.jsp b/www/fotoCR.jsp index 76b6aaf7..596c240f 100644 --- a/www/fotoCR.jsp +++ b/www/fotoCR.jsp @@ -8,7 +8,6 @@ <%@ taglib uri="/WEB-INF/cc.tld" prefix="cc" %> - @@ -57,6 +56,10 @@ if (faceAiFeatureEnabledValue == null || faceAiFeatureEnabledValue.trim().length } String faceAiFeatureEnabledNormalized = faceAiFeatureEnabledValue != null ? faceAiFeatureEnabledValue.trim() : ""; boolean faceAiFeatureEnabled = !("0".equals(faceAiFeatureEnabledNormalized) || "false".equalsIgnoreCase(faceAiFeatureEnabledNormalized) || "no".equalsIgnoreCase(faceAiFeatureEnabledNormalized) || "off".equalsIgnoreCase(faceAiFeatureEnabledNormalized)); +%> +<%@ include file="_inc_faceai_identity.jsp" %> + +<% java.util.Date faceAiRaceDate = CR.getGara().getDataGaraInizio(); String faceAiRacePathBase = CR.getGara().getPathBase() != null ? CR.getGara().getPathBase().trim() : ""; String faceAiRaceYear = ""; diff --git a/www_staged.tar b/www_staged.tar new file mode 100644 index 00000000..4e6853ed Binary files /dev/null and b/www_staged.tar differ