mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-06 15:59:52 +03:00
Compare commits
32 Commits
9f467ecec1
...
https_36
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6f8581f989 | ||
|
|
4c60a88221 | ||
|
|
d093886571 | ||
|
|
9e251fd3de | ||
|
|
89fd16d48f | ||
|
|
75fc8c201a | ||
|
|
f1a3bf0e6a | ||
|
|
86afa20470 | ||
|
|
32e3f69961 | ||
|
|
1b7d999fc4 | ||
|
|
6e6023cfd1 | ||
|
|
e64a374174 | ||
|
|
400100759a | ||
|
|
3e49dc113e | ||
|
|
7b96baa9b8 | ||
|
|
8bdd01f73f | ||
|
|
b36477cc8a | ||
|
|
ebfc4a64e2 | ||
|
|
fd48fb63d8 | ||
|
|
20d747330c | ||
|
|
781fb67a4e | ||
|
|
2b44155869 | ||
|
|
9ca0782ae7 | ||
|
|
4b831e864c | ||
|
|
16dc82da1c | ||
|
|
8cd0a9a761 | ||
|
|
b928ad89d9 | ||
|
|
12671f7ed3 | ||
|
|
22184f13f2 | ||
|
|
778bbe3b81 | ||
|
|
23d4f608c5 | ||
|
|
73a51ae4ad |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -61,3 +61,4 @@ bw-output/
|
|||||||
# dump_entities.csv
|
# dump_entities.csv
|
||||||
# dump_entities.xls*
|
# dump_entities.xls*
|
||||||
|
|
||||||
|
benchmark/*.log
|
||||||
|
|||||||
@@ -10,6 +10,8 @@
|
|||||||
- heatpump energy meters [#1463](https://github.com/emsesp/EMS-ESP32/issues/1463)
|
- heatpump energy meters [#1463](https://github.com/emsesp/EMS-ESP32/issues/1463)
|
||||||
- heatpump max power [#1475](https://github.com/emsesp/EMS-ESP32/issues/1475)
|
- heatpump max power [#1475](https://github.com/emsesp/EMS-ESP32/issues/1475)
|
||||||
- checkbox for MQTT-TLS enable [#1474](https://github.com/emsesp/EMS-ESP32/issues/1474)
|
- checkbox for MQTT-TLS enable [#1474](https://github.com/emsesp/EMS-ESP32/issues/1474)
|
||||||
|
- added SK (Slovencina) language. Thanks @misa1515
|
||||||
|
- CPU info [#1497](https://github.com/emsesp/EMS-ESP32/pull/1497)
|
||||||
|
|
||||||
## Fixed
|
## Fixed
|
||||||
|
|
||||||
|
|||||||
@@ -47,7 +47,6 @@ EMS-ESP is a project owned and maintained by [proddy](https://github.com/proddy)
|
|||||||
- [uuid-\*](https://github.com/nomis/mcu-uuid-console) from @nomis. The console, syslog, telnet and logging are based off these open source libraries
|
- [uuid-\*](https://github.com/nomis/mcu-uuid-console) from @nomis. The console, syslog, telnet and logging are based off these open source libraries
|
||||||
- [ArduinoJson](https://github.com/bblanchon/ArduinoJson) for all the JSON
|
- [ArduinoJson](https://github.com/bblanchon/ArduinoJson) for all the JSON
|
||||||
- [espMqttClient](https://github.com/bertmelis/espMqttClient) for the MQTT client, with custom modifications from @MichaelDvP and @proddy
|
- [espMqttClient](https://github.com/bertmelis/espMqttClient) for the MQTT client, with custom modifications from @MichaelDvP and @proddy
|
||||||
- ESPAsyncWebServer and AsyncTCP for the Web server and TCP backends, with custom modifications for performance
|
|
||||||
|
|
||||||
## **License**
|
## **License**
|
||||||
|
|
||||||
|
|||||||
39
benchmark/http-client-test.js
Executable file
39
benchmark/http-client-test.js
Executable file
@@ -0,0 +1,39 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const axios = require('axios');
|
||||||
|
|
||||||
|
const url = 'http://10.10.10.135/api/system/commands';
|
||||||
|
const queryParams = {
|
||||||
|
entity: 'commands',
|
||||||
|
id: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
const totalRequests = 1000000;
|
||||||
|
const requestsPerCount = 100;
|
||||||
|
|
||||||
|
let requestCount = 0;
|
||||||
|
|
||||||
|
function fetchData() {
|
||||||
|
axios
|
||||||
|
.get(url, { params: queryParams })
|
||||||
|
.then((response) => {
|
||||||
|
requestCount++;
|
||||||
|
|
||||||
|
if (requestCount % requestsPerCount === 0) {
|
||||||
|
console.log(`Requests completed: ${requestCount}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requestCount < totalRequests) {
|
||||||
|
fetchData();
|
||||||
|
} else {
|
||||||
|
console.log('All requests completed.');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Error making request:', error.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start making requests
|
||||||
|
console.log(`Starting test`);
|
||||||
|
fetchData();
|
||||||
38
benchmark/loadtest-http.sh
Executable file
38
benchmark/loadtest-http.sh
Executable file
@@ -0,0 +1,38 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Install:
|
||||||
|
# npm install -g autocannon
|
||||||
|
# yarn global add autocannon
|
||||||
|
#
|
||||||
|
# or run https://github.com/nearform/autocannon-ui
|
||||||
|
|
||||||
|
TEST_IP="10.10.10.135"
|
||||||
|
TEST_TIME=60
|
||||||
|
LOG_FILE=http-loadtest.log
|
||||||
|
TIMEOUT=10000
|
||||||
|
PROTOCOL=http
|
||||||
|
#PROTOCOL=https
|
||||||
|
|
||||||
|
if test -f "$LOG_FILE"; then
|
||||||
|
rm $LOG_FILE
|
||||||
|
fi
|
||||||
|
|
||||||
|
# for CONCURRENCY in 1 2 3 4 5 6 7 8 9 10 15 20
|
||||||
|
for CONCURRENCY in 1
|
||||||
|
#for CONCURRENCY in 20
|
||||||
|
do
|
||||||
|
printf "\n\nCLIENTS: *** $CONCURRENCY ***\n\n" >> $LOG_FILE
|
||||||
|
echo "Testing $CONCURRENCY clients on $PROTOCOL://$TEST_IP/"
|
||||||
|
autocannon -c $CONCURRENCY -w 1 -d $TEST_TIME --renderStatusCodes "$PROTOCOL://$TEST_IP/" >> $LOG_FILE 2>&1
|
||||||
|
printf "\n\n----------------\n\n" >> $LOG_FILE
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
echo "Testing $CONCURRENCY clients on $PROTOCOL://$TEST_IP/api/system/commands"
|
||||||
|
autocannon -c $CONCURRENCY -w 1 -d $TEST_TIME --renderStatusCodes "$PROTOCOL://$TEST_IP/api/system/commands" >> $LOG_FILE 2>&1
|
||||||
|
printf "\n\n----------------\n\n" >> $LOG_FILE
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
echo "Testing $CONCURRENCY clients on $PROTOCOL://$TEST_IP/app/icon.png"
|
||||||
|
autocannon -c $CONCURRENCY -w 1 -d $TEST_TIME --renderStatusCodes "$PROTOCOL://$TEST_IP/app/icon.png" >> $LOG_FILE 2>&1
|
||||||
|
printf "\n\n----------------\n\n" >> $LOG_FILE
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
8
benchmark/package.json
Normal file
8
benchmark/package.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"autocannon": "^7.14.0",
|
||||||
|
"axios": "^1.6.2",
|
||||||
|
"eventsource": "^2.0.2",
|
||||||
|
"ws": "^8.14.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,20 +20,20 @@
|
|||||||
"lint": "eslint . --cache --fix"
|
"lint": "eslint . --cache --fix"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@alova/adapter-xhr": "^1.0.1",
|
"@alova/adapter-xhr": "^1.0.2",
|
||||||
"@babel/core": "^7.23.6",
|
"@babel/core": "^7.23.7",
|
||||||
"@emotion/react": "^11.11.1",
|
"@emotion/react": "^11.11.3",
|
||||||
"@emotion/styled": "^11.11.0",
|
"@emotion/styled": "^11.11.0",
|
||||||
"@mui/icons-material": "^5.15.0",
|
"@mui/icons-material": "^5.15.2",
|
||||||
"@mui/material": "^5.15.0",
|
"@mui/material": "^5.15.2",
|
||||||
"@table-library/react-table-library": "4.1.7",
|
"@table-library/react-table-library": "4.1.7",
|
||||||
"@types/imagemin": "^8.0.5",
|
"@types/imagemin": "^8.0.5",
|
||||||
"@types/lodash-es": "^4.17.12",
|
"@types/lodash-es": "^4.17.12",
|
||||||
"@types/node": "^20.10.4",
|
"@types/node": "^20.10.6",
|
||||||
"@types/react": "^18.2.43",
|
"@types/react": "^18.2.46",
|
||||||
"@types/react-dom": "^18.2.17",
|
"@types/react-dom": "^18.2.18",
|
||||||
"@types/react-router-dom": "^5.3.3",
|
"@types/react-router-dom": "^5.3.3",
|
||||||
"alova": "^2.16.0",
|
"alova": "^2.16.2",
|
||||||
"async-validator": "^4.2.5",
|
"async-validator": "^4.2.5",
|
||||||
"history": "^5.3.0",
|
"history": "^5.3.0",
|
||||||
"jwt-decode": "^4.0.0",
|
"jwt-decode": "^4.0.0",
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
"react-dom": "latest",
|
"react-dom": "latest",
|
||||||
"react-dropzone": "^14.2.3",
|
"react-dropzone": "^14.2.3",
|
||||||
"react-icons": "^4.12.0",
|
"react-icons": "^4.12.0",
|
||||||
"react-router-dom": "^6.20.1",
|
"react-router-dom": "^6.21.1",
|
||||||
"react-toastify": "^9.1.3",
|
"react-toastify": "^9.1.3",
|
||||||
"sockette": "^2.0.6",
|
"sockette": "^2.0.6",
|
||||||
"typesafe-i18n": "^5.26.2",
|
"typesafe-i18n": "^5.26.2",
|
||||||
@@ -52,27 +52,27 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@preact/compat": "^17.1.2",
|
"@preact/compat": "^17.1.2",
|
||||||
"@preact/preset-vite": "^2.7.0",
|
"@preact/preset-vite": "^2.7.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.14.0",
|
"@typescript-eslint/eslint-plugin": "^6.17.0",
|
||||||
"@typescript-eslint/parser": "^6.14.0",
|
"@typescript-eslint/parser": "^6.17.0",
|
||||||
"concurrently": "^8.2.2",
|
"concurrently": "^8.2.2",
|
||||||
"eslint": "^8.55.0",
|
"eslint": "^8.56.0",
|
||||||
"eslint-config-airbnb": "^19.0.4",
|
"eslint-config-airbnb": "^19.0.4",
|
||||||
"eslint-config-airbnb-typescript": "^17.1.0",
|
"eslint-config-airbnb-typescript": "^17.1.0",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-import-resolver-typescript": "^3.6.1",
|
"eslint-import-resolver-typescript": "^3.6.1",
|
||||||
"eslint-plugin-autofix": "^1.1.0",
|
"eslint-plugin-autofix": "^1.1.0",
|
||||||
"eslint-plugin-import": "^2.29.0",
|
"eslint-plugin-import": "^2.29.1",
|
||||||
"eslint-plugin-jsx-a11y": "^6.8.0",
|
"eslint-plugin-jsx-a11y": "^6.8.0",
|
||||||
"eslint-plugin-prettier": "alpha",
|
"eslint-plugin-prettier": "alpha",
|
||||||
"eslint-plugin-react": "^7.33.2",
|
"eslint-plugin-react": "^7.33.2",
|
||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
"preact": "^10.19.3",
|
"preact": "^10.19.3",
|
||||||
"prettier": "^3.1.1",
|
"prettier": "^3.1.1",
|
||||||
"rollup-plugin-visualizer": "^5.11.0",
|
"rollup-plugin-visualizer": "^5.12.0",
|
||||||
"terser": "^5.26.0",
|
"terser": "^5.26.0",
|
||||||
"vite": "^5.0.8",
|
"vite": "^5.0.10",
|
||||||
"vite-plugin-imagemin": "^0.6.1",
|
"vite-plugin-imagemin": "^0.6.1",
|
||||||
"vite-tsconfig-paths": "^4.2.2"
|
"vite-tsconfig-paths": "^4.2.3"
|
||||||
},
|
},
|
||||||
"packageManager": "yarn@4.0.2"
|
"packageManager": "yarn@4.0.2"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ const bytesPerLine = 20;
|
|||||||
var totalSize = 0;
|
var totalSize = 0;
|
||||||
|
|
||||||
const generateWWWClass = () =>
|
const generateWWWClass = () =>
|
||||||
`typedef std::function<void(const String& uri, const String& contentType, const uint8_t * content, size_t len)> RouteRegistrationHandler;
|
`typedef std::function<void(const String &, const String & contentType, const uint8_t * content, size_t len)> RouteRegistrationHandler;
|
||||||
|
// Total size is ${totalSize} bytes
|
||||||
|
|
||||||
class WWWData {
|
class WWWData {
|
||||||
${indent}public:
|
${indent}public:
|
||||||
|
|||||||
Binary file not shown.
@@ -22,6 +22,7 @@ import ITflag from 'i18n/IT.svg';
|
|||||||
import NLflag from 'i18n/NL.svg';
|
import NLflag from 'i18n/NL.svg';
|
||||||
import NOflag from 'i18n/NO.svg';
|
import NOflag from 'i18n/NO.svg';
|
||||||
import PLflag from 'i18n/PL.svg';
|
import PLflag from 'i18n/PL.svg';
|
||||||
|
import SKflag from 'i18n/SK.svg';
|
||||||
import SVflag from 'i18n/SV.svg';
|
import SVflag from 'i18n/SV.svg';
|
||||||
import TRflag from 'i18n/TR.svg';
|
import TRflag from 'i18n/TR.svg';
|
||||||
import { I18nContext } from 'i18n/i18n-react';
|
import { I18nContext } from 'i18n/i18n-react';
|
||||||
@@ -142,6 +143,10 @@ const SignIn: FC = () => {
|
|||||||
<img src={PLflag} style={{ width: 16, verticalAlign: 'middle' }} />
|
<img src={PLflag} style={{ width: 16, verticalAlign: 'middle' }} />
|
||||||
PL
|
PL
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
<MenuItem key="sk" value="sk">
|
||||||
|
<img src={SKflag} style={{ width: 16, verticalAlign: 'middle' }} />
|
||||||
|
SK
|
||||||
|
</MenuItem>
|
||||||
<MenuItem key="sv" value="sv">
|
<MenuItem key="sv" value="sv">
|
||||||
<img src={SVflag} style={{ width: 16, verticalAlign: 'middle' }} />
|
<img src={SVflag} style={{ width: 16, verticalAlign: 'middle' }} />
|
||||||
SV
|
SV
|
||||||
|
|||||||
@@ -14,11 +14,9 @@ import {
|
|||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { useState, useContext } from 'react';
|
import { useState, useContext } from 'react';
|
||||||
import type { TypographyProps } from '@mui/material';
|
import type { TypographyProps } from '@mui/material';
|
||||||
|
|
||||||
import type { Locales } from 'i18n/i18n-types';
|
import type { Locales } from 'i18n/i18n-types';
|
||||||
import type { FC, ChangeEventHandler } from 'react';
|
import type { FC, ChangeEventHandler } from 'react';
|
||||||
import { AuthenticatedContext } from 'contexts/authentication';
|
import { AuthenticatedContext } from 'contexts/authentication';
|
||||||
|
|
||||||
import DEflag from 'i18n/DE.svg';
|
import DEflag from 'i18n/DE.svg';
|
||||||
import FRflag from 'i18n/FR.svg';
|
import FRflag from 'i18n/FR.svg';
|
||||||
import GBflag from 'i18n/GB.svg';
|
import GBflag from 'i18n/GB.svg';
|
||||||
@@ -26,8 +24,10 @@ import ITflag from 'i18n/IT.svg';
|
|||||||
import NLflag from 'i18n/NL.svg';
|
import NLflag from 'i18n/NL.svg';
|
||||||
import NOflag from 'i18n/NO.svg';
|
import NOflag from 'i18n/NO.svg';
|
||||||
import PLflag from 'i18n/PL.svg';
|
import PLflag from 'i18n/PL.svg';
|
||||||
|
import SKflag from 'i18n/SK.svg';
|
||||||
import SVflag from 'i18n/SV.svg';
|
import SVflag from 'i18n/SV.svg';
|
||||||
import TRflag from 'i18n/TR.svg';
|
import TRflag from 'i18n/TR.svg';
|
||||||
|
|
||||||
import { I18nContext } from 'i18n/i18n-react';
|
import { I18nContext } from 'i18n/i18n-react';
|
||||||
import { loadLocaleAsync } from 'i18n/i18n-util.async';
|
import { loadLocaleAsync } from 'i18n/i18n-util.async';
|
||||||
|
|
||||||
@@ -102,6 +102,10 @@ const LayoutAuthMenu: FC = () => {
|
|||||||
<img src={PLflag} style={{ width: 16, verticalAlign: 'middle' }} />
|
<img src={PLflag} style={{ width: 16, verticalAlign: 'middle' }} />
|
||||||
PL
|
PL
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
<MenuItem key="sk" value="sk">
|
||||||
|
<img src={SKflag} style={{ width: 16, verticalAlign: 'middle' }} />
|
||||||
|
SK
|
||||||
|
</MenuItem>
|
||||||
<MenuItem key="sv" value="sv">
|
<MenuItem key="sv" value="sv">
|
||||||
<img src={SVflag} style={{ width: 16, verticalAlign: 'middle' }} />
|
<img src={SVflag} style={{ width: 16, verticalAlign: 'middle' }} />
|
||||||
SV
|
SV
|
||||||
|
|||||||
@@ -313,7 +313,7 @@ const WiFiSettingsForm: FC = () => {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{restartNeeded && (
|
{restartNeeded && (
|
||||||
<MessageBox my={2} level="warning" message={LL.RESTART_TEXT()}>
|
<MessageBox my={2} level="warning" message={LL.RESTART_TEXT(0)}>
|
||||||
<Button startIcon={<PowerSettingsNewIcon />} variant="contained" color="error" onClick={restart}>
|
<Button startIcon={<PowerSettingsNewIcon />} variant="contained" color="error" onClick={restart}>
|
||||||
{LL.RESTART()}
|
{LL.RESTART()}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import AppsIcon from '@mui/icons-material/Apps';
|
import AppsIcon from '@mui/icons-material/Apps';
|
||||||
import BuildIcon from '@mui/icons-material/Build';
|
import BuildIcon from '@mui/icons-material/Build';
|
||||||
import CancelIcon from '@mui/icons-material/Cancel';
|
import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
|
import DeveloperBoardIcon from '@mui/icons-material/DeveloperBoard';
|
||||||
import DevicesIcon from '@mui/icons-material/Devices';
|
import DevicesIcon from '@mui/icons-material/Devices';
|
||||||
import FolderIcon from '@mui/icons-material/Folder';
|
import FolderIcon from '@mui/icons-material/Folder';
|
||||||
import MemoryIcon from '@mui/icons-material/Memory';
|
import MemoryIcon from '@mui/icons-material/Memory';
|
||||||
@@ -9,7 +10,6 @@ import RefreshIcon from '@mui/icons-material/Refresh';
|
|||||||
import SdCardAlertIcon from '@mui/icons-material/SdCardAlert';
|
import SdCardAlertIcon from '@mui/icons-material/SdCardAlert';
|
||||||
import SdStorageIcon from '@mui/icons-material/SdStorage';
|
import SdStorageIcon from '@mui/icons-material/SdStorage';
|
||||||
import SettingsBackupRestoreIcon from '@mui/icons-material/SettingsBackupRestore';
|
import SettingsBackupRestoreIcon from '@mui/icons-material/SettingsBackupRestore';
|
||||||
import ShowChartIcon from '@mui/icons-material/ShowChart';
|
|
||||||
import TimerIcon from '@mui/icons-material/Timer';
|
import TimerIcon from '@mui/icons-material/Timer';
|
||||||
import {
|
import {
|
||||||
Avatar,
|
Avatar,
|
||||||
@@ -200,15 +200,6 @@ const SystemStatusForm: FC = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<Divider variant="inset" component="li" />
|
<Divider variant="inset" component="li" />
|
||||||
<ListItem>
|
|
||||||
<ListItemAvatar>
|
|
||||||
<Avatar>
|
|
||||||
<DevicesIcon />
|
|
||||||
</Avatar>
|
|
||||||
</ListItemAvatar>
|
|
||||||
<ListItemText primary={LL.PLATFORM()} secondary={data.esp_platform + ' / ' + data.sdk_version} />
|
|
||||||
</ListItem>
|
|
||||||
<Divider variant="inset" component="li" />
|
|
||||||
<ListItem>
|
<ListItem>
|
||||||
<ListItemAvatar>
|
<ListItemAvatar>
|
||||||
<Avatar>
|
<Avatar>
|
||||||
@@ -221,10 +212,31 @@ const SystemStatusForm: FC = () => {
|
|||||||
<ListItem>
|
<ListItem>
|
||||||
<ListItemAvatar>
|
<ListItemAvatar>
|
||||||
<Avatar>
|
<Avatar>
|
||||||
<ShowChartIcon />
|
<DevicesIcon />
|
||||||
</Avatar>
|
</Avatar>
|
||||||
</ListItemAvatar>
|
</ListItemAvatar>
|
||||||
<ListItemText primary={LL.CPU_FREQ()} secondary={data.cpu_freq_mhz + ' MHz'} />
|
<ListItemText primary="SDK" secondary={data.arduino_version + ' / ESP-IDF v' + data.sdk_version} />
|
||||||
|
</ListItem>
|
||||||
|
<Divider variant="inset" component="li" />
|
||||||
|
<ListItem>
|
||||||
|
<ListItemAvatar>
|
||||||
|
<Avatar>
|
||||||
|
<DeveloperBoardIcon />
|
||||||
|
</Avatar>
|
||||||
|
</ListItemAvatar>
|
||||||
|
<ListItemText
|
||||||
|
primary="CPU"
|
||||||
|
secondary={
|
||||||
|
data.cpu_type +
|
||||||
|
' (rev.' +
|
||||||
|
data.cpu_rev +
|
||||||
|
', ' +
|
||||||
|
(data.cpu_cores == 1 ? 'single-core)' : 'dual-core)') +
|
||||||
|
' @ ' +
|
||||||
|
data.cpu_freq_mhz +
|
||||||
|
' Mhz'
|
||||||
|
}
|
||||||
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<Divider variant="inset" component="li" />
|
<Divider variant="inset" component="li" />
|
||||||
<ListItem>
|
<ListItem>
|
||||||
@@ -277,7 +289,9 @@ const SystemStatusForm: FC = () => {
|
|||||||
</ListItemAvatar>
|
</ListItemAvatar>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={LL.APPSIZE()}
|
primary={LL.APPSIZE()}
|
||||||
secondary={formatNumber(data.app_used) + ' KB / ' + formatNumber(data.app_free) + ' KB'}
|
secondary={
|
||||||
|
data.partition + ': ' + formatNumber(data.app_used) + ' KB / ' + formatNumber(data.app_free) + ' KB'
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<Divider variant="inset" component="li" />
|
<Divider variant="inset" component="li" />
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ const UploadFileForm: FC = () => {
|
|||||||
const { send: getSchedule, onSuccess: onSuccessGetSchedule } = useRequest(EMSESP.getSchedule(), {
|
const { send: getSchedule, onSuccess: onSuccessGetSchedule } = useRequest(EMSESP.getSchedule(), {
|
||||||
immediate: false
|
immediate: false
|
||||||
});
|
});
|
||||||
const { send: getAPI, onSuccess: onGetAPI } = useRequest((data) => EMSESP.API(data), {
|
const { send: getSystemAPI, onSuccess: onSystemAPI } = useRequest((data) => EMSESP.APIcall('system', data), {
|
||||||
immediate: false
|
immediate: false
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -89,8 +89,8 @@ const UploadFileForm: FC = () => {
|
|||||||
onSuccessGetSchedule((event) => {
|
onSuccessGetSchedule((event) => {
|
||||||
saveFile(event.data, 'schedule.json');
|
saveFile(event.data, 'schedule.json');
|
||||||
});
|
});
|
||||||
onGetAPI((event) => {
|
onSystemAPI((event) => {
|
||||||
saveFile(event.data, event.sendArgs[0].device + '_' + event.sendArgs[0].entity + '.txt');
|
saveFile(event.data, event.sendArgs[0].entity + '.txt');
|
||||||
});
|
});
|
||||||
|
|
||||||
const downloadSettings = async () => {
|
const downloadSettings = async () => {
|
||||||
@@ -121,8 +121,8 @@ const UploadFileForm: FC = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const callAPI = async (device: string, entity: string) => {
|
const callSystemAPI = async (entity: string) => {
|
||||||
await getAPI({ device, entity, id: 0 }).catch((error) => {
|
await getSystemAPI({ entity, id: 0 }).catch((error) => {
|
||||||
toast.error(error.message);
|
toast.error(error.message);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -134,9 +134,10 @@ const UploadFileForm: FC = () => {
|
|||||||
</Typography>
|
</Typography>
|
||||||
<Box mb={2} color="warning.main">
|
<Box mb={2} color="warning.main">
|
||||||
<Typography variant="body2">
|
<Typography variant="body2">
|
||||||
{LL.UPLOAD_TEXT()}
|
{LL.UPLOAD_TEXT()}.
|
||||||
<br />
|
<br />
|
||||||
{LL.RESTART_TEXT()}.
|
<br />
|
||||||
|
{LL.RESTART_TEXT(1)}.
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
{md5 && (
|
{md5 && (
|
||||||
@@ -148,33 +149,28 @@ const UploadFileForm: FC = () => {
|
|||||||
{!isUploading && (
|
{!isUploading && (
|
||||||
<>
|
<>
|
||||||
<Typography sx={{ pt: 4, pb: 2 }} variant="h6" color="primary">
|
<Typography sx={{ pt: 4, pb: 2 }} variant="h6" color="primary">
|
||||||
{LL.DOWNLOAD(0)} {LL.SUPPORT_INFORMATION()}
|
{LL.DOWNLOAD(0)} {LL.SUPPORT_INFORMATION(1)}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Box color="warning.main">
|
<Box color="warning.main">
|
||||||
<Typography mb={1} variant="body2">
|
<Typography mb={1} variant="body2">
|
||||||
{LL.HELP_INFORMATION_4()}
|
{LL.HELP_INFORMATION_4()}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Button
|
<Button startIcon={<DownloadIcon />} variant="outlined" color="primary" onClick={() => callSystemAPI('info')}>
|
||||||
startIcon={<DownloadIcon />}
|
{LL.SUPPORT_INFORMATION(0)}
|
||||||
variant="outlined"
|
|
||||||
color="primary"
|
|
||||||
onClick={() => callAPI('system', 'info')}
|
|
||||||
>
|
|
||||||
{LL.SUPPORT_INFORMATION()}
|
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
sx={{ ml: 2 }}
|
sx={{ ml: 2 }}
|
||||||
startIcon={<DownloadIcon />}
|
startIcon={<DownloadIcon />}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
color="primary"
|
color="primary"
|
||||||
onClick={() => callAPI('system', 'allvalues')}
|
onClick={() => callSystemAPI('allvalues')}
|
||||||
>
|
>
|
||||||
All Values
|
All Values
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Typography sx={{ pt: 4, pb: 2 }} variant="h6" color="primary">
|
<Typography sx={{ pt: 4, pb: 2 }} variant="h6" color="primary">
|
||||||
{LL.DOWNLOAD(0)} {LL.SETTINGS()}
|
{LL.DOWNLOAD(0)} {LL.SETTINGS(1)}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Box color="warning.main">
|
<Box color="warning.main">
|
||||||
<Typography mb={1} variant="body2">
|
<Typography mb={1} variant="body2">
|
||||||
|
|||||||
1
interface/src/i18n/SK.svg
Normal file
1
interface/src/i18n/SK.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 85.333 512 341.333"><path fill="#FFF" d="M0 85.337h512v341.326H0z"/><path fill="#0052B4" d="M0 196.641h512v118.717H0z"/><path fill="#D80027" d="M0 315.359h512v111.304H0z"/><path fill="#FFF" d="M129.468 181.799v85.136c0 48.429 63.267 63.267 63.267 63.267S256 315.362 256 266.935v-85.136H129.468z"/><path fill="#D80027" d="M146.126 184.294v81.941c0 5.472 1.215 10.64 3.623 15.485h85.97c2.408-4.844 3.623-10.012 3.623-15.485v-81.941h-93.216z"/><path fill="#FFF" d="M221.301 241.427h-21.425v-14.283h14.284v-14.283h-14.284v-14.284h-14.283v14.284h-14.282v14.283h14.282v14.283h-21.426v14.284h21.426v14.283h14.283v-14.283h21.425z"/><path fill="#0052B4" d="M169.232 301.658c9.204 5.783 18.66 9.143 23.502 10.636 4.842-1.494 14.298-4.852 23.502-10.636 9.282-5.833 15.79-12.506 19.484-19.939a24.878 24.878 0 0 0-14.418-4.583c-1.956 0-3.856.232-5.682.657-3.871-8.796-12.658-14.94-22.884-14.94-10.227 0-19.013 6.144-22.884 14.94a25.048 25.048 0 0 0-5.682-.657 24.88 24.88 0 0 0-14.418 4.583c3.691 7.433 10.198 14.106 19.48 19.939z"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -194,13 +194,11 @@ const de: Translation = {
|
|||||||
RELEASE_IS: 'release ist', // TODO translate
|
RELEASE_IS: 'release ist', // TODO translate
|
||||||
RELEASE_NOTES: 'Versionshinweise',
|
RELEASE_NOTES: 'Versionshinweise',
|
||||||
EMS_ESP_VER: 'EMS-ESP Version',
|
EMS_ESP_VER: 'EMS-ESP Version',
|
||||||
PLATFORM: 'Platform (Platform / SDK)',
|
|
||||||
UPTIME: 'System Betriebszeit',
|
UPTIME: 'System Betriebszeit',
|
||||||
CPU_FREQ: 'CPU Frequenz',
|
|
||||||
HEAP: 'freier RAM Speicher (Gesamt / max. Block)',
|
HEAP: 'freier RAM Speicher (Gesamt / max. Block)',
|
||||||
PSRAM: 'PSRAM (Größe / Frei)',
|
PSRAM: 'PSRAM (Größe / Frei)',
|
||||||
FLASH: 'Flash Speicher (Größe / Geschwindigkeit)',
|
FLASH: 'Flash Speicher (Größe / Geschwindigkeit)',
|
||||||
APPSIZE: 'Programm (Genutzt / Frei)',
|
APPSIZE: 'Programm (Partition: Genutzt / Frei)',
|
||||||
FILESYSTEM: 'Dateisystem (Genutzt / Frei)',
|
FILESYSTEM: 'Dateisystem (Genutzt / Frei)',
|
||||||
BUFFER_SIZE: 'max. Puffergröße',
|
BUFFER_SIZE: 'max. Puffergröße',
|
||||||
COMPACT: 'Kompakte Darstellung',
|
COMPACT: 'Kompakte Darstellung',
|
||||||
|
|||||||
@@ -196,11 +196,10 @@ const en: Translation = {
|
|||||||
EMS_ESP_VER: 'EMS-ESP Version',
|
EMS_ESP_VER: 'EMS-ESP Version',
|
||||||
PLATFORM: 'Device (Platform / SDK)',
|
PLATFORM: 'Device (Platform / SDK)',
|
||||||
UPTIME: 'System Uptime',
|
UPTIME: 'System Uptime',
|
||||||
CPU_FREQ: 'CPU Frequency',
|
|
||||||
HEAP: 'Heap (Free / Max Alloc)',
|
HEAP: 'Heap (Free / Max Alloc)',
|
||||||
PSRAM: 'PSRAM (Size / Free)',
|
PSRAM: 'PSRAM (Size / Free)',
|
||||||
FLASH: 'Flash Chip (Size / Speed)',
|
FLASH: 'Flash Chip (Size / Speed)',
|
||||||
APPSIZE: 'Application (Used / Free)',
|
APPSIZE: 'Application (Partition: Used / Free)',
|
||||||
FILESYSTEM: 'File System (Used / Free)',
|
FILESYSTEM: 'File System (Used / Free)',
|
||||||
BUFFER_SIZE: 'Max Buffer Size',
|
BUFFER_SIZE: 'Max Buffer Size',
|
||||||
COMPACT: 'Compact',
|
COMPACT: 'Compact',
|
||||||
|
|||||||
@@ -194,13 +194,11 @@ const fr: Translation = {
|
|||||||
RELEASE_IS: 'release est', // TODO translate
|
RELEASE_IS: 'release est', // TODO translate
|
||||||
RELEASE_NOTES: 'notes de version',
|
RELEASE_NOTES: 'notes de version',
|
||||||
EMS_ESP_VER: 'Version EMS-ESP',
|
EMS_ESP_VER: 'Version EMS-ESP',
|
||||||
PLATFORM: 'Appareil (Plateforme / SDK)',
|
|
||||||
UPTIME: 'Durée de fonctionnement du système',
|
UPTIME: 'Durée de fonctionnement du système',
|
||||||
CPU_FREQ: 'Fréquence du CPU',
|
|
||||||
HEAP: 'Heap (Libre / Max Allouée)',
|
HEAP: 'Heap (Libre / Max Allouée)',
|
||||||
PSRAM: 'PSRAM (Taille / Libre)',
|
PSRAM: 'PSRAM (Taille / Libre)',
|
||||||
FLASH: 'Flash Chip (Taille / Vitesse)',
|
FLASH: 'Flash Chip (Taille / Vitesse)',
|
||||||
APPSIZE: 'Application (Utilisée / Libre)',
|
APPSIZE: 'Application (Partition: Utilisée / Libre)',
|
||||||
FILESYSTEM: 'File System (Utilisée / Libre)',
|
FILESYSTEM: 'File System (Utilisée / Libre)',
|
||||||
BUFFER_SIZE: 'Max taille du buffer',
|
BUFFER_SIZE: 'Max taille du buffer',
|
||||||
COMPACT: 'Compact',
|
COMPACT: 'Compact',
|
||||||
|
|||||||
@@ -196,13 +196,11 @@ const it: Translation = {
|
|||||||
RELEASE_IS: 'rilascio é',
|
RELEASE_IS: 'rilascio é',
|
||||||
RELEASE_NOTES: 'note rilascio',
|
RELEASE_NOTES: 'note rilascio',
|
||||||
EMS_ESP_VER: 'Versione EMS-ESP',
|
EMS_ESP_VER: 'Versione EMS-ESP',
|
||||||
PLATFORM: 'Dispositivo (Piattaforma / SDK)',
|
|
||||||
UPTIME: 'Tempo di attività del sistema',
|
UPTIME: 'Tempo di attività del sistema',
|
||||||
CPU_FREQ: 'Frequenza CPU ',
|
|
||||||
HEAP: 'Heap (Free / Max Alloc)',
|
HEAP: 'Heap (Free / Max Alloc)',
|
||||||
PSRAM: 'PSRAM (Size / Free)',
|
PSRAM: 'PSRAM (Size / Free)',
|
||||||
FLASH: 'Flash Chip (Size / Speed)',
|
FLASH: 'Flash Chip (Size / Speed)',
|
||||||
APPSIZE: 'Applicazione (Usata / Libera)',
|
APPSIZE: 'Applicazione (Partizione: Usata / Libera)',
|
||||||
FILESYSTEM: 'Memoria Sistema (Usata / Libera)',
|
FILESYSTEM: 'Memoria Sistema (Usata / Libera)',
|
||||||
BUFFER_SIZE: 'Max Buffer Size',
|
BUFFER_SIZE: 'Max Buffer Size',
|
||||||
COMPACT: 'Compact',
|
COMPACT: 'Compact',
|
||||||
|
|||||||
@@ -194,13 +194,11 @@ const nl: Translation = {
|
|||||||
RELEASE_IS: 'release is',
|
RELEASE_IS: 'release is',
|
||||||
RELEASE_NOTES: 'release notes',
|
RELEASE_NOTES: 'release notes',
|
||||||
EMS_ESP_VER: 'EMS-ESP Versie',
|
EMS_ESP_VER: 'EMS-ESP Versie',
|
||||||
PLATFORM: 'Apparaat (Platform / SDK)',
|
|
||||||
UPTIME: 'Systeem Uptime',
|
UPTIME: 'Systeem Uptime',
|
||||||
CPU_FREQ: 'CPU Frequency',
|
|
||||||
HEAP: 'Heap (Free / Max Alloc)',
|
HEAP: 'Heap (Free / Max Alloc)',
|
||||||
PSRAM: 'PSRAM (Size / Free)',
|
PSRAM: 'PSRAM (Size / Free)',
|
||||||
FLASH: 'Flash Chip (Size / Speed)',
|
FLASH: 'Flash Chip (Size / Speed)',
|
||||||
APPSIZE: 'Application (Used / Free)',
|
APPSIZE: 'Application (Partition: Used / Free)',
|
||||||
FILESYSTEM: 'File System (Used / Free)',
|
FILESYSTEM: 'File System (Used / Free)',
|
||||||
BUFFER_SIZE: 'Max Buffer Size',
|
BUFFER_SIZE: 'Max Buffer Size',
|
||||||
COMPACT: 'Compact',
|
COMPACT: 'Compact',
|
||||||
|
|||||||
@@ -194,13 +194,11 @@ const no: Translation = {
|
|||||||
RELEASE_IS: 'release er',
|
RELEASE_IS: 'release er',
|
||||||
RELEASE_NOTES: 'release notes',
|
RELEASE_NOTES: 'release notes',
|
||||||
EMS_ESP_VER: 'EMS-ESP Version',
|
EMS_ESP_VER: 'EMS-ESP Version',
|
||||||
PLATFORM: 'Enhet (Platform / SDK)',
|
|
||||||
UPTIME: 'System Oppetid',
|
UPTIME: 'System Oppetid',
|
||||||
CPU_FREQ: 'CPU Frekvens',
|
|
||||||
HEAP: 'Heap (Ledig / Max Allokert)',
|
HEAP: 'Heap (Ledig / Max Allokert)',
|
||||||
PSRAM: 'PSRAM (Størrelse / Ledig)',
|
PSRAM: 'PSRAM (Størrelse / Ledig)',
|
||||||
FLASH: 'Flash Chip (Størrelse / Hastighet)',
|
FLASH: 'Flash Chip (Størrelse / Hastighet)',
|
||||||
APPSIZE: 'Applikasjon (Brukt / Ledig)',
|
APPSIZE: 'Applikasjon (Partition: Brukt / Ledig)',
|
||||||
FILESYSTEM: 'File System (Brukt / Ledig)',
|
FILESYSTEM: 'File System (Brukt / Ledig)',
|
||||||
BUFFER_SIZE: 'Max Buffer Størrelse',
|
BUFFER_SIZE: 'Max Buffer Størrelse',
|
||||||
COMPACT: 'Komprimere',
|
COMPACT: 'Komprimere',
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ const pl: BaseTranslation = {
|
|||||||
PROBLEM_LOADING: 'Problem z załadowaniem!',
|
PROBLEM_LOADING: 'Problem z załadowaniem!',
|
||||||
ANALOG_SENSOR: '{{u|u||ustawienia u|ustawień u}}rządzeni{{a podłączonego do EMS-ESP|e||a podłączonego do EMS-ESP|a podłączonego do EMS-ESP}}',
|
ANALOG_SENSOR: '{{u|u||ustawienia u|ustawień u}}rządzeni{{a podłączonego do EMS-ESP|e||a podłączonego do EMS-ESP|a podłączonego do EMS-ESP}}',
|
||||||
ANALOG_SENSORS: 'Urządzenia podłączone do EMS-ESP',
|
ANALOG_SENSORS: 'Urządzenia podłączone do EMS-ESP',
|
||||||
SETTINGS: 'ustawienia',
|
SETTINGS: 'ustawie{{nia|ń|}}',
|
||||||
UPDATED_OF: 'Zaktualizowano {0}.',
|
UPDATED_OF: 'Zaktualizowano {0}.',
|
||||||
UPDATE_OF: 'Aktualizacja {0}',
|
UPDATE_OF: 'Aktualizacja {0}',
|
||||||
REMOVED_OF: 'Usunięto ustawienia {0}.',
|
REMOVED_OF: 'Usunięto ustawienia {0}.',
|
||||||
@@ -126,7 +126,7 @@ const pl: BaseTranslation = {
|
|||||||
BYPASS_TOKEN: 'Pomiń autoryzację tokenem w wywołaniach API',
|
BYPASS_TOKEN: 'Pomiń autoryzację tokenem w wywołaniach API',
|
||||||
READONLY: 'Tryb pracy "tylko do odczytu" (blokuje wszystkie komendy zapisu na magistralę EMS)',
|
READONLY: 'Tryb pracy "tylko do odczytu" (blokuje wszystkie komendy zapisu na magistralę EMS)',
|
||||||
UNDERCLOCK_CPU: 'Obniż taktowanie CPU',
|
UNDERCLOCK_CPU: 'Obniż taktowanie CPU',
|
||||||
HEATINGOFF: 'Uruchom boiler z wymuszonym wyłączonym grzaniem',
|
HEATINGOFF: 'Uruchom kocioł z wymuszonym wyłączonym grzaniem',
|
||||||
ENABLE_SHOWER_TIMER: 'Aktywuj minutnik prysznica',
|
ENABLE_SHOWER_TIMER: 'Aktywuj minutnik prysznica',
|
||||||
ENABLE_SHOWER_ALERT: 'Aktywuj alarm prysznica',
|
ENABLE_SHOWER_ALERT: 'Aktywuj alarm prysznica',
|
||||||
TRIGGER_TIME: 'Wyzwalaj po czasie',
|
TRIGGER_TIME: 'Wyzwalaj po czasie',
|
||||||
@@ -146,13 +146,13 @@ const pl: BaseTranslation = {
|
|||||||
MINUTES: 'minut',
|
MINUTES: 'minut',
|
||||||
HOURS: 'godzin',
|
HOURS: 'godzin',
|
||||||
RESTART: 'Restart',
|
RESTART: 'Restart',
|
||||||
RESTART_TEXT: 'Aby zastosować wprowadzone zmiany interfejs EMS-ESP musi zostać zrestartowany.',
|
RESTART_TEXT: 'Aby zastosować wprowadzone zmiany, interfejs EMS-ESP {{musi zostać|zostanie|}} uruchomiony ponowni{{e.|e|}}',
|
||||||
RESTART_CONFIRM: 'Na pewno chcesz zrestartować interfejs EMS-ESP?',
|
RESTART_CONFIRM: 'Na pewno chcesz zrestartować interfejs EMS-ESP?',
|
||||||
COMMAND: '{{Komenda|KOMENDA|}}',
|
COMMAND: '{{Komenda|KOMENDA|}}',
|
||||||
CUSTOMIZATIONS_RESTART: 'Wszystkie personalizacje zostały usunięte. Restartuję...',
|
CUSTOMIZATIONS_RESTART: 'Wszystkie personalizacje zostały usunięte. Restartuję...',
|
||||||
CUSTOMIZATIONS_FULL: 'Wybrano za dużo obiektów. Wprowadź zmiany w mniejszych partiach.',
|
CUSTOMIZATIONS_FULL: 'Wybrano za dużo obiektów. Wprowadź zmiany w mniejszych partiach.',
|
||||||
CUSTOMIZATIONS_SAVED: 'Personalizacje zostały zapisane.',
|
CUSTOMIZATIONS_SAVED: 'Personalizacje zostały zapisane.',
|
||||||
CUSTOMIZATIONS_HELP_1: 'Wybierz urządzenie EMS, dostosuj opcje lub kliknij by zmienić nazwę encji.',
|
CUSTOMIZATIONS_HELP_1: 'Wybierz urządzenie EMS, a następnie dostosuj opcje lub kliknij na nazwie encji by tę nazwę zmienić',
|
||||||
CUSTOMIZATIONS_HELP_2: 'oznacz jako ulubioną',
|
CUSTOMIZATIONS_HELP_2: 'oznacz jako ulubioną',
|
||||||
CUSTOMIZATIONS_HELP_3: 'zablokuj akcje zapisu',
|
CUSTOMIZATIONS_HELP_3: 'zablokuj akcje zapisu',
|
||||||
CUSTOMIZATIONS_HELP_4: 'wyklucz z MQTT i API',
|
CUSTOMIZATIONS_HELP_4: 'wyklucz z MQTT i API',
|
||||||
@@ -164,12 +164,12 @@ const pl: BaseTranslation = {
|
|||||||
NAME: '{{Nazwa|nazwa|}}',
|
NAME: '{{Nazwa|nazwa|}}',
|
||||||
CUSTOMIZATIONS_RESET: 'Na pewno chcesz usunąć wszystkie personalizacje łącznie z ustawieniami dla czujników temperatury 1-Wire® i urządzeń podłączonych do EMS-ESP?',
|
CUSTOMIZATIONS_RESET: 'Na pewno chcesz usunąć wszystkie personalizacje łącznie z ustawieniami dla czujników temperatury 1-Wire® i urządzeń podłączonych do EMS-ESP?',
|
||||||
DEVICE_ENTITIES: 'Encje urządzenia',
|
DEVICE_ENTITIES: 'Encje urządzenia',
|
||||||
SUPPORT_INFORMATION: 'Informacje dotyczące wsparcia',
|
SUPPORT_INFORMATION: '{{I|i|}}nformacj{{e|i|}} o systemie',
|
||||||
CLICK_HERE: 'Kliknij tu',
|
CLICK_HERE: 'Kliknij tu',
|
||||||
HELP_INFORMATION_1: 'Aby uzyskać instrukcje dotyczące konfiguracji EMS-ESP skorzystaj z wiki w internecie',
|
HELP_INFORMATION_1: 'Aby uzyskać instrukcje dotyczące konfiguracji EMS-ESP, skorzystaj z wiki w internecie',
|
||||||
HELP_INFORMATION_2: 'Aby dołączyć do naszego serwera Discord i komunikować się na żywo ze społecznością',
|
HELP_INFORMATION_2: 'Aby dołączyć do naszego serwera Discord i komunikować się na żywo ze społecznością',
|
||||||
HELP_INFORMATION_3: 'Aby zaproponować nową funkcjonalność lub zgłosić problem',
|
HELP_INFORMATION_3: 'Aby zaproponować nową funkcjonalność lub zgłosić problem',
|
||||||
HELP_INFORMATION_4: 'Zgłaszając problem, nie zapomnij dołączyć informacji o swoim systemie!',
|
HELP_INFORMATION_4: 'Zgłaszając problem, nie zapomnij pobrać i dołączyć informacji o swoim systemie!',
|
||||||
HELP_INFORMATION_5: 'EMS-ESP jest darmowym projektem typu open-source. Aby go wesprzeć, rozważ przyznanie nam gwiazdki na Github!',
|
HELP_INFORMATION_5: 'EMS-ESP jest darmowym projektem typu open-source. Aby go wesprzeć, rozważ przyznanie nam gwiazdki na Github!',
|
||||||
UPLOAD: 'Wysyłanie',
|
UPLOAD: 'Wysyłanie',
|
||||||
DOWNLOAD: '{{P|p||P}}obier{{anie|z||z}}',
|
DOWNLOAD: '{{P|p||P}}obier{{anie|z||z}}',
|
||||||
@@ -194,21 +194,19 @@ const pl: BaseTranslation = {
|
|||||||
RELEASE_IS: 'wydanie to',
|
RELEASE_IS: 'wydanie to',
|
||||||
RELEASE_NOTES: 'lista zmian',
|
RELEASE_NOTES: 'lista zmian',
|
||||||
EMS_ESP_VER: 'Wersja EMS-ESP',
|
EMS_ESP_VER: 'Wersja EMS-ESP',
|
||||||
PLATFORM: 'Urządzenie (platforma / SDK)',
|
|
||||||
UPTIME: 'Czas działania systemu',
|
UPTIME: 'Czas działania systemu',
|
||||||
CPU_FREQ: 'Taktowanie CPU',
|
|
||||||
HEAP: 'HEAP (wolne / maksymalny przydział)',
|
HEAP: 'HEAP (wolne / maksymalny przydział)',
|
||||||
PSRAM: 'PSRAM (rozmiar / wolne)',
|
PSRAM: 'PSRAM (rozmiar / wolne)',
|
||||||
FLASH: 'FLASH (rozmiar / taktowanie)',
|
FLASH: 'FLASH (rozmiar / taktowanie)',
|
||||||
APPSIZE: 'Aplikacja (wykorzystane / wolne)',
|
APPSIZE: 'Aplikacja (partycja: wykorzystane / wolne)',
|
||||||
FILESYSTEM: 'System plików (wykorzystane / wolne)',
|
FILESYSTEM: 'System plików (wykorzystane / wolne)',
|
||||||
BUFFER_SIZE: 'Maksymalna pojemność bufora (ilość wpisów)',
|
BUFFER_SIZE: 'Maksymalna pojemność bufora (ilość wpisów)',
|
||||||
COMPACT: 'Kompaktowy',
|
COMPACT: 'Kompaktowy',
|
||||||
ENABLE_OTA: 'Aktywuj aktualizację OTA',
|
ENABLE_OTA: 'Aktywuj aktualizację OTA',
|
||||||
DOWNLOAD_CUSTOMIZATION_TEXT: 'Pobierz personalizacje.',
|
DOWNLOAD_CUSTOMIZATION_TEXT: 'Pobierz personalizacje.',
|
||||||
DOWNLOAD_SCHEDULE_TEXT: 'Pobierz harmonogram zdarzeń.',
|
DOWNLOAD_SCHEDULE_TEXT: 'Pobierz harmonogram zdarzeń.',
|
||||||
DOWNLOAD_SETTINGS_TEXT: 'Pobierz ustawienia aplikacji. Uważaj jeśli udostępniasz plik z ustawieniami, ponieważ zawiera on hasła oraz inne wrażliwe informacje!',
|
DOWNLOAD_SETTINGS_TEXT: 'Pobierz ustawienia aplikacji. Uwaga! Plik z ustawieniami zawiera hasła oraz inne wrażliwe informacje systemowe! Nie udostepniaj go pochopnie!',
|
||||||
UPLOAD_TEXT: 'Wyślij firmware (.bin), ustawienia lub personalizacje (.json). Opcjonalnie, wyślij wcześniej plik walidacji (.md5).',
|
UPLOAD_TEXT: 'Wyślij firmware (.bin), ustawienia lub personalizacje (.json). Opcjonalnie, wyślij wcześniej plik walidacji z sumą kontrolną (.md5).',
|
||||||
UPLOADING: 'Wysłano',
|
UPLOADING: 'Wysłano',
|
||||||
UPLOAD_DROP_TEXT: 'Przeciągnij tutaj plik lub kliknij',
|
UPLOAD_DROP_TEXT: 'Przeciągnij tutaj plik lub kliknij',
|
||||||
ERROR: 'Nieoczekiwany błąd, spróbuj ponownie!',
|
ERROR: 'Nieoczekiwany błąd, spróbuj ponownie!',
|
||||||
@@ -320,10 +318,10 @@ const pl: BaseTranslation = {
|
|||||||
CUSTOM_ENTITIES: '{{N|n|}}iestandardowe{{|j|}} encj{{e|i|}}',
|
CUSTOM_ENTITIES: '{{N|n|}}iestandardowe{{|j|}} encj{{e|i|}}',
|
||||||
ENTITIES_HELP_1: 'Zdefiniuj niestandardowe encje dla magistrali EMS.',
|
ENTITIES_HELP_1: 'Zdefiniuj niestandardowe encje dla magistrali EMS.',
|
||||||
ENTITIES_UPDATED: 'Niestandardowe encje zostały uaktualnione.',
|
ENTITIES_UPDATED: 'Niestandardowe encje zostały uaktualnione.',
|
||||||
WRITEABLE: 'zapisywalna',
|
WRITEABLE: 'Zapisywalna',
|
||||||
SHOWING: 'Wyświetlane',
|
SHOWING: 'Wyświetlane',
|
||||||
SEARCH: 'Szukaj',
|
SEARCH: 'Szukaj',
|
||||||
CERT: 'Certyfikat główny TLS (pozostaw puste zby wyłączyć TLS-insecure)',
|
CERT: 'Certyfikat główny TLS (pozostaw puste dla TLS-insecure)',
|
||||||
ENABLE_TLS: 'Włącz wsparcie dla TLS',
|
ENABLE_TLS: 'Włącz wsparcie dla TLS',
|
||||||
ON: 'włączony',
|
ON: 'włączony',
|
||||||
OFF: 'wyłączony',
|
OFF: 'wyłączony',
|
||||||
|
|||||||
335
interface/src/i18n/sk/index.ts
Normal file
335
interface/src/i18n/sk/index.ts
Normal file
@@ -0,0 +1,335 @@
|
|||||||
|
import type { Translation } from '../i18n-types';
|
||||||
|
/* prettier-ignore */
|
||||||
|
/* eslint-disable */
|
||||||
|
|
||||||
|
const sk: Translation = {
|
||||||
|
LANGUAGE: 'Jazyk',
|
||||||
|
RETRY: 'Opakovať',
|
||||||
|
LOADING: 'Načítanie',
|
||||||
|
IS_REQUIRED: '{0} je požadovaných',
|
||||||
|
SIGN_IN: 'Prihlásiť sa',
|
||||||
|
SIGN_OUT: 'Odhlásiť sa',
|
||||||
|
USERNAME: 'Užívateľské meno',
|
||||||
|
PASSWORD: 'Heslo',
|
||||||
|
SU_PASSWORD: 'su heslo',
|
||||||
|
DASHBOARD: 'Panel',
|
||||||
|
SETTINGS_OF: '{0} Nastavenia',
|
||||||
|
HELP_OF: '{0} Pomoc',
|
||||||
|
LOGGED_IN: 'Prihlásený ako {name}',
|
||||||
|
PLEASE_SIGNIN: 'Ak chcete pokračovať, prihláste sa',
|
||||||
|
UPLOAD_SUCCESSFUL: 'Nahratie úspešné',
|
||||||
|
DOWNLOAD_SUCCESSFUL: 'Stiahnutie úspešné',
|
||||||
|
INVALID_LOGIN: 'Nesprávne prihlasovacie údaje',
|
||||||
|
NETWORK: 'Sieť',
|
||||||
|
SECURITY: 'Zabezpečenie',
|
||||||
|
ONOFF_CAP: 'ZAP/VYP',
|
||||||
|
ONOFF: 'zap/vyp',
|
||||||
|
TYPE: 'Typ',
|
||||||
|
DESCRIPTION: 'Popis',
|
||||||
|
ENTITIES: 'Entity',
|
||||||
|
REFRESH: 'Obnoviť',
|
||||||
|
EXPORT: 'Export',
|
||||||
|
DEVICE_DETAILS: 'Detaily zariadenia',
|
||||||
|
ID_OF: '{0} ID',
|
||||||
|
DEVICE: 'Zariadenie',
|
||||||
|
PRODUCT: 'Produkt',
|
||||||
|
VERSION: 'Verzia',
|
||||||
|
BRAND: 'Značka',
|
||||||
|
ENTITY_NAME: 'Názov entity',
|
||||||
|
VALUE: '{{Value|value}}',
|
||||||
|
DEVICE_DATA: 'Dáta zariadenia',
|
||||||
|
SENSOR_DATA: 'Dáta snímača',
|
||||||
|
DEVICES: 'Zariadenia',
|
||||||
|
SENSORS: 'Snímače',
|
||||||
|
RUN_COMMAND: 'Volať príkaz',
|
||||||
|
CHANGE_VALUE: 'Zmena hodnoty',
|
||||||
|
CANCEL: 'Zrušiť',
|
||||||
|
RESET: 'Reset',
|
||||||
|
APPLY_CHANGES: 'Aplikovať zmeny ({0})',
|
||||||
|
UPDATE: 'Aktualizovať',
|
||||||
|
EXECUTE: 'Spustiť',
|
||||||
|
REMOVE: 'Odstrániť',
|
||||||
|
PROBLEM_UPDATING: 'Problém s aktualizáciou',
|
||||||
|
PROBLEM_LOADING: 'Problém s načítaním',
|
||||||
|
ANALOG_SENSOR: 'Analógový snímač',
|
||||||
|
ANALOG_SENSORS: 'Analógové snímače',
|
||||||
|
SETTINGS: 'Nastavenia',
|
||||||
|
UPDATED_OF: '{0} aktualizovaných',
|
||||||
|
UPDATE_OF: '{0} aktualizované',
|
||||||
|
REMOVED_OF: '{0} odstránených',
|
||||||
|
DELETION_OF: '{0} zmazaných',
|
||||||
|
OFFSET: 'Ofset',
|
||||||
|
FACTOR: 'Faktor',
|
||||||
|
FREQ: 'Frekvencia',
|
||||||
|
DUTY_CYCLE: 'Duty Cycle',
|
||||||
|
UNIT: 'UoM',
|
||||||
|
STARTVALUE: 'Počiatočná hodnota',
|
||||||
|
WARN_GPIO: 'Upozornenie: Buďte opatrní pri priraďovaní GPIO!',
|
||||||
|
EDIT: 'Editovať',
|
||||||
|
SENSOR: 'Snímač',
|
||||||
|
TEMP_SENSOR: 'Snímač teploty',
|
||||||
|
TEMP_SENSORS: 'Snímače teploty',
|
||||||
|
WRITE_CMD_SENT: 'Príkaz zápisu bol odoslaný',
|
||||||
|
EMS_BUS_WARNING: 'Zbernica EMS odpojená. Ak toto upozornenie pretrváva aj po niekoľkých sekundách, skontrolujte nastavenia a profil dosky',
|
||||||
|
EMS_BUS_SCANNING: 'Zisťovanie EMS zariadení...',
|
||||||
|
CONNECTED: 'Pripojené',
|
||||||
|
TX_ISSUES: 'Problémy s Tx – skontrolujte Tx režim',
|
||||||
|
DISCONNECTED: 'Odpojené',
|
||||||
|
EMS_SCAN: 'Naozaj chcete spustiť úplnú kontrolu zariadenia zbernice EMS?',
|
||||||
|
EMS_BUS_STATUS: 'Stav zbernice EMS',
|
||||||
|
ACTIVE_DEVICES: 'Aktívne zariadenia a snímače',
|
||||||
|
EMS_DEVICE: 'EMS zariadenie',
|
||||||
|
SUCCESS: 'ÚSPEŠNÉ',
|
||||||
|
FAIL: 'ZLYHANIE',
|
||||||
|
QUALITY: 'KVALITA',
|
||||||
|
SCAN_DEVICES: 'Scan pre nové zariadenia',
|
||||||
|
EMS_BUS_STATUS_TITLE: 'EMS zbernica & stav aktivity',
|
||||||
|
SCAN: 'Scan',
|
||||||
|
STATUS_NAMES: [
|
||||||
|
'EMS Telegramy prijaté (Rx)',
|
||||||
|
'EMS Čítania (Tx)',
|
||||||
|
'EMS Zápisy (Tx)',
|
||||||
|
'Čítanie snímača teploty',
|
||||||
|
'Analógové snímanie',
|
||||||
|
'MQTT Publikovanie',
|
||||||
|
'API volania',
|
||||||
|
'Syslog správy'
|
||||||
|
],
|
||||||
|
NUM_DEVICES: '{num} Zariadenia{{s}}',
|
||||||
|
NUM_TEMP_SENSORS: '{num} Teplotné snímače{{s}}',
|
||||||
|
NUM_ANALOG_SENSORS: '{num} Analógové snímače{{s}}',
|
||||||
|
NUM_DAYS: '{num} dní{{s}}',
|
||||||
|
NUM_SECONDS: '{num} sekúnd{{s}}',
|
||||||
|
NUM_HOURS: '{num} hodín{{s}}',
|
||||||
|
NUM_MINUTES: '{num} minút{{s}}',
|
||||||
|
APPLICATION_SETTINGS: 'Nastavenia aplikácie',
|
||||||
|
CUSTOMIZATIONS: 'Prispôsobenia',
|
||||||
|
APPLICATION_RESTARTING: 'EMS-ESP sa reštartuje',
|
||||||
|
INTERFACE_BOARD_PROFILE: 'Profil boardu rozhrania',
|
||||||
|
BOARD_PROFILE_TEXT: 'Vyberte vopred nakonfigurovaný profil dosky rozhrania zo zoznamu nižšie alebo vyberte možnosť Vlastné a nakonfigurujte svoje vlastné hardvérové nastavenia',
|
||||||
|
BOARD_PROFILE: 'Board profil',
|
||||||
|
CUSTOM: 'Vlastné',
|
||||||
|
GPIO_OF: '{0} GPIO',
|
||||||
|
BUTTON: 'Tlačidlo',
|
||||||
|
TEMPERATURE: 'Teplota',
|
||||||
|
PHY_TYPE: 'Eth PHY Typ',
|
||||||
|
DISABLED: 'zakázané',
|
||||||
|
TX_MODE: 'Tx režim',
|
||||||
|
HARDWARE: 'Hardware',
|
||||||
|
EMS_BUS: '{{BUS|EMS BUS}}',
|
||||||
|
GENERAL_OPTIONS: 'Všeobecné možnosti',
|
||||||
|
LANGUAGE_ENTITIES: 'Jazyk (pre entity zariadenia)',
|
||||||
|
HIDE_LED: 'Skryť LED',
|
||||||
|
ENABLE_TELNET: 'Povoliť Telnet konzolu',
|
||||||
|
ENABLE_ANALOG: 'Povoliť analógové snímače',
|
||||||
|
CONVERT_FAHRENHEIT: 'Previesť hodnoty teploty na fahrenheity',
|
||||||
|
BYPASS_TOKEN: 'Vynechajte autorizáciu prístupového tokenu pri volaniach API',
|
||||||
|
READONLY: 'Povoliť režim len na čítanie (blokuje všetky odchádzajúce príkazy EMS Tx Write)',
|
||||||
|
UNDERCLOCK_CPU: 'Podtaktovanie rýchlosti procesora',
|
||||||
|
HEATINGOFF: 'Spustite kotol s núteným vykurovaním',
|
||||||
|
ENABLE_SHOWER_TIMER: 'Povoliť časovač sprchovania',
|
||||||
|
ENABLE_SHOWER_ALERT: 'Povoliť upozornenie na sprchu',
|
||||||
|
TRIGGER_TIME: 'Čas spustenia',
|
||||||
|
COLD_SHOT_DURATION: 'Trvanie studeného záberu',
|
||||||
|
FORMATTING_OPTIONS: 'Možnosti formátovania',
|
||||||
|
BOOLEAN_FORMAT_DASHBOARD: 'Panel Boolean formát',
|
||||||
|
BOOLEAN_FORMAT_API: 'Boolean formát API/MQTT',
|
||||||
|
ENUM_FORMAT: 'Enum formát API/MQTT',
|
||||||
|
INDEX: 'Index',
|
||||||
|
ENABLE_PARASITE: 'Povolenie parazitného napájania',
|
||||||
|
LOGGING: 'Logovanie',
|
||||||
|
LOG_HEX: 'Záznam telegramov EMS v hexadecimálnej sústave',
|
||||||
|
ENABLE_SYSLOG: 'Povoliť Syslog',
|
||||||
|
LOG_LEVEL: 'Log úroveň',
|
||||||
|
MARK_INTERVAL: 'Označenie intervalu',
|
||||||
|
SECONDS: 'sekundy',
|
||||||
|
MINUTES: 'minúty',
|
||||||
|
HOURS: 'hodiny',
|
||||||
|
RESTART: 'Reštart',
|
||||||
|
RESTART_TEXT: 'EMS-ESP sa musí reštartovať, aby sa použili zmenené systémové nastavenia',
|
||||||
|
RESTART_CONFIRM: 'Ste si istí, že chcete reštartovať EMS-ESP?',
|
||||||
|
COMMAND: 'Príkaz',
|
||||||
|
CUSTOMIZATIONS_RESTART: 'Ste si istí, že chcete reštartovať EMS-ESP?',
|
||||||
|
CUSTOMIZATIONS_FULL: 'Vybrané subjekty prekročili limit. Prosím, ukladajte v dávkach',
|
||||||
|
CUSTOMIZATIONS_SAVED: 'Uložené prispôsobenia',
|
||||||
|
CUSTOMIZATIONS_HELP_1: 'Vyberte zariadenie a prispôsobte možnosti entít alebo kliknutím premenujte',
|
||||||
|
CUSTOMIZATIONS_HELP_2: 'označiť ako obľúbené',
|
||||||
|
CUSTOMIZATIONS_HELP_3: 'zakázať akciu zápisu',
|
||||||
|
CUSTOMIZATIONS_HELP_4: 'vylúčiť z MQTT a API',
|
||||||
|
CUSTOMIZATIONS_HELP_5: 'skryť z panela',
|
||||||
|
CUSTOMIZATIONS_HELP_6: 'odstrániť z pamäte',
|
||||||
|
SELECT_DEVICE: 'Zvoliť zariadenie',
|
||||||
|
SET_ALL: 'nastaviť všetko',
|
||||||
|
OPTIONS: 'Možnosti',
|
||||||
|
NAME: 'Názov',
|
||||||
|
CUSTOMIZATIONS_RESET: 'Naozaj chcete odstrániť všetky prispôsobenia vrátane vlastných nastavení snímačov teploty a analógových snímačov?',
|
||||||
|
DEVICE_ENTITIES: 'Entity zariadenia',
|
||||||
|
SUPPORT_INFORMATION: 'Informácie o podpore',
|
||||||
|
CLICK_HERE: 'Kliknite tu',
|
||||||
|
HELP_INFORMATION_1: 'Navštívte online wiki, kde nájdete pokyny na konfiguráciu EMS-ESP',
|
||||||
|
HELP_INFORMATION_2: 'Pre živý komunitný chat sa pripojte na náš Discord server',
|
||||||
|
HELP_INFORMATION_3: 'Ak chcete požiadať o funkciu alebo nahlásiť chybu',
|
||||||
|
HELP_INFORMATION_4: 'nezabudnite si stiahnuť a pripojiť informácie o vašom systéme, aby ste mohli rýchlejšie reagovať pri nahlasovaní problému',
|
||||||
|
HELP_INFORMATION_5: 'EMS-ESP je bezplatný a open source projekt. Podporte jeho budúci vývoj tým, že mu dáte hviezdičku na Github!',
|
||||||
|
UPLOAD: 'Nahrať',
|
||||||
|
DOWNLOAD: '{{S|s|s}}tiahnuť',
|
||||||
|
ABORTED: 'zrušené',
|
||||||
|
FAILED: 'chybné',
|
||||||
|
SUCCESSFUL: 'úspešné',
|
||||||
|
SYSTEM: 'Systém',
|
||||||
|
LOG_OF: '{0} Log',
|
||||||
|
STATUS_OF: '{0} Stav',
|
||||||
|
UPLOAD_DOWNLOAD: 'Nahrať/Stiahnuť',
|
||||||
|
VERSION_ON: 'Momentálne ste vo verzii',
|
||||||
|
SYSTEM_APPLY_FIRMWARE: 'na použitie nového firmvéru',
|
||||||
|
CLOSE: 'Zatvoriť',
|
||||||
|
USE: 'Použiť',
|
||||||
|
FACTORY_RESET: 'Továrenské nastavenia',
|
||||||
|
SYSTEM_FACTORY_TEXT: 'Zariadenie bolo obnovené z výroby a teraz sa reštartuje',
|
||||||
|
SYSTEM_FACTORY_TEXT_DIALOG: 'Naozaj chcete resetovať EMS-ESP na predvolené výrobné nastavenia?',
|
||||||
|
VERSION_CHECK: 'Kontrola verzie',
|
||||||
|
THE_LATEST: 'Posledná',
|
||||||
|
OFFICIAL: 'officiálna',
|
||||||
|
DEVELOPMENT: 'vývojárska',
|
||||||
|
RELEASE_IS: 'vydanie je',
|
||||||
|
RELEASE_NOTES: 'poznámky k vydaniu',
|
||||||
|
EMS_ESP_VER: 'EMS-ESP verzia',
|
||||||
|
UPTIME: 'Beh systému',
|
||||||
|
HEAP: 'Zásobník (voľné / max pridelenie)',
|
||||||
|
PSRAM: 'PSRAM (Veľkosť / Voľné)',
|
||||||
|
FLASH: 'Flash chip (Veľkosť / Rýchlosť)',
|
||||||
|
APPSIZE: 'Applikácia (Priečka: Použité / Voľné)',
|
||||||
|
FILESYSTEM: 'Súborový systém (Použité / Voľné)',
|
||||||
|
BUFFER_SIZE: 'Maximálna veľkosť vyrovnávacej pamäte',
|
||||||
|
COMPACT: 'Kompaktné',
|
||||||
|
ENABLE_OTA: 'Povoliť OTA aktualizácie',
|
||||||
|
DOWNLOAD_CUSTOMIZATION_TEXT: 'Stiahnutie prispôsobení entity',
|
||||||
|
DOWNLOAD_SCHEDULE_TEXT: 'Stiahnutie plánovača udalostí',
|
||||||
|
DOWNLOAD_SETTINGS_TEXT: 'Stiahnite si nastavenia aplikácie. Pri zdieľaní nastavení buďte opatrní, pretože tento súbor obsahuje heslá a iné citlivé systémové informácie.',
|
||||||
|
UPLOAD_TEXT: 'Najskôr nahrajte nový súbor firmvéru (.bin), nastavenia alebo prispôsobenia (.json), pre voliteľné overenie nahrajte súbor (.md5)',
|
||||||
|
UPLOADING: 'Nahrávanie',
|
||||||
|
UPLOAD_DROP_TEXT: 'Zahodiť súbor alebo kliknúť sem',
|
||||||
|
ERROR: 'Neočakávaná chyba, prosím skúste to znova',
|
||||||
|
TIME_SET: 'Nastavený čas',
|
||||||
|
MANAGE_USERS: 'Správa používateľov',
|
||||||
|
IS_ADMIN: 'je Admin',
|
||||||
|
USER_WARNING: 'Musíte mať nakonfigurovaného aspoň jedného používateľa administrátora',
|
||||||
|
ADD: 'Pridať',
|
||||||
|
ACCESS_TOKEN_FOR: 'Prístupový token pre',
|
||||||
|
ACCESS_TOKEN_TEXT: 'Nižšie uvedený token sa používa pri volaniach REST API, ktoré vyžadujú autorizáciu. Môže byť odovzdaný buď ako token Bearer v hlavičke Authorization (Autorizácia), alebo v parametri dotazu URL access_token.',
|
||||||
|
GENERATING_TOKEN: 'Generovanie tokenu',
|
||||||
|
USER: 'Užívateľ',
|
||||||
|
MODIFY: 'Upraviť',
|
||||||
|
SU_TEXT: 'Heslo su (superužívateľ) sa používa na podpisovanie autentifikačných tokenov a tiež na povolenie oprávnení správcu v rámci konzoly.',
|
||||||
|
NOT_ENABLED: 'Nie je povolené',
|
||||||
|
ERRORS_OF: '{0} errory',
|
||||||
|
DISCONNECT_REASON: 'Dôvod odpojenia',
|
||||||
|
ENABLE_MQTT: 'Povoliť MQTT',
|
||||||
|
BROKER: 'Broker',
|
||||||
|
CLIENT: 'Klient',
|
||||||
|
BASE_TOPIC: 'Base',
|
||||||
|
OPTIONAL: 'voliteľné',
|
||||||
|
FORMATTING: 'Formátovanie',
|
||||||
|
MQTT_FORMAT: 'Formát témy/záťaže',
|
||||||
|
MQTT_NEST_1: 'Vnorené do jednej témy',
|
||||||
|
MQTT_NEST_2: 'Ako jednotlivé témy',
|
||||||
|
MQTT_RESPONSE: 'Publikovanie výstupu príkazu do témy `response`',
|
||||||
|
MQTT_PUBLISH_TEXT_1: 'Zverejňovanie tém jednotlivých hodnôt pri zmene',
|
||||||
|
MQTT_PUBLISH_TEXT_2: 'Publikovanie do tém príkazov (ioBroker)',
|
||||||
|
MQTT_PUBLISH_TEXT_3: 'Povolenie zisťovania MQTT',
|
||||||
|
MQTT_PUBLISH_TEXT_4: 'Predpona tém Discovery',
|
||||||
|
MQTT_PUBLISH_TEXT_5: 'Typ zistenia',
|
||||||
|
MQTT_PUBLISH_INTERVALS: 'Intervaly zverejňovania',
|
||||||
|
MQTT_INT_BOILER: 'Kotly a tepelné čerpadlá',
|
||||||
|
MQTT_INT_THERMOSTATS: 'Termostaty',
|
||||||
|
MQTT_INT_SOLAR: 'Solárne moduly',
|
||||||
|
MQTT_INT_MIXER: 'Zmiešavacie moduley',
|
||||||
|
MQTT_QUEUE: 'Fronta MQTT',
|
||||||
|
DEFAULT: 'Predvolené',
|
||||||
|
MQTT_ENTITY_FORMAT: 'ID formát entity',
|
||||||
|
MQTT_ENTITY_FORMAT_0: 'Jedna inštancia, dlhý názov (v3.4)',
|
||||||
|
MQTT_ENTITY_FORMAT_1: 'Jedna inštancia, krátky názov',
|
||||||
|
MQTT_ENTITY_FORMAT_2: 'Viacero inštancií, krátky názov',
|
||||||
|
MQTT_CLEAN_SESSION: 'Nastavenie čistej relácie',
|
||||||
|
MQTT_RETAIN_FLAG: 'Vždy nastaviť príznak Retain',
|
||||||
|
INACTIVE: 'Neaktívne',
|
||||||
|
ACTIVE: 'Aktívne',
|
||||||
|
UNKNOWN: 'Neznáme',
|
||||||
|
SET_TIME: 'Nastavený čas',
|
||||||
|
SET_TIME_TEXT: 'Na nastavenie času zadajte miestny dátum a čas nižšie',
|
||||||
|
LOCAL_TIME: 'Lokálny čas',
|
||||||
|
UTC_TIME: 'UTC čas',
|
||||||
|
ENABLE_NTP: 'Povoliť NTP',
|
||||||
|
NTP_SERVER: 'NTP Server',
|
||||||
|
TIME_ZONE: 'Časová zóna',
|
||||||
|
ACCESS_POINT: 'Prístupový bod',
|
||||||
|
AP_PROVIDE: 'Povoliť prístupový bod',
|
||||||
|
AP_PROVIDE_TEXT_1: 'vždy',
|
||||||
|
AP_PROVIDE_TEXT_2: 'keď WiFi je odpojená',
|
||||||
|
AP_PROVIDE_TEXT_3: 'nikdy',
|
||||||
|
AP_PREFERRED_CHANNEL: 'Preferovaný kanál',
|
||||||
|
AP_HIDE_SSID: 'Skryť SSID',
|
||||||
|
AP_CLIENTS: 'AP klienti',
|
||||||
|
AP_MAX_CLIENTS: 'Max klientov',
|
||||||
|
AP_LOCAL_IP: 'Lokálna IP',
|
||||||
|
NETWORK_SCAN: 'Scan WiFi siete',
|
||||||
|
IDLE: 'Nečinné',
|
||||||
|
LOST: 'Stratené',
|
||||||
|
SCANNING: 'Scanovanie',
|
||||||
|
SCAN_AGAIN: 'Scanovať znova',
|
||||||
|
NETWORK_SCANNER: 'Sieťový scanner',
|
||||||
|
NETWORK_NO_WIFI: 'WiFi siete nenájdené',
|
||||||
|
NETWORK_BLANK_SSID: 'nechajte prázdne, ak chcete zakázať WiFi a povoliť ETH',
|
||||||
|
NETWORK_BLANK_BSSID: 'ponechajte prázdne, ak chcete používať iba SSID',
|
||||||
|
TX_POWER: 'Tx výkon',
|
||||||
|
HOSTNAME: 'Hostname',
|
||||||
|
NETWORK_DISABLE_SLEEP: 'Zakázanie režimu spánku WiFi',
|
||||||
|
NETWORK_LOW_BAND: 'Používanie menšej šírky pásma WiFi',
|
||||||
|
NETWORK_USE_DNS: 'Povoliť mDNS službu',
|
||||||
|
NETWORK_ENABLE_CORS: 'Povoliť CORS',
|
||||||
|
NETWORK_CORS_ORIGIN: 'CORS origin',
|
||||||
|
NETWORK_ENABLE_IPV6: 'Povoliť podporu IPv6',
|
||||||
|
NETWORK_FIXED_IP: 'Použiť fixnú IP adresu',
|
||||||
|
NETWORK_GATEWAY: 'Brána',
|
||||||
|
NETWORK_SUBNET: 'Maska podsiete',
|
||||||
|
NETWORK_DNS: 'DNS servery',
|
||||||
|
ADDRESS_OF: '{0} adries',
|
||||||
|
ADMIN: 'Admin',
|
||||||
|
GUEST: 'Hosť',
|
||||||
|
NEW: 'Nová',
|
||||||
|
NEW_NAME_OF: 'Nových {0} názvov',
|
||||||
|
ENTITY: 'entita',
|
||||||
|
MIN: 'min',
|
||||||
|
MAX: 'max',
|
||||||
|
BLOCK_NAVIGATE_1: 'Máte neuložené zmeny',
|
||||||
|
BLOCK_NAVIGATE_2: 'Ak prejdete na inú stránku, neuložené zmeny sa stratia. Ste si istí, že chcete opustiť túto stránku?',
|
||||||
|
STAY: 'Zostať',
|
||||||
|
LEAVE: 'Opustiť',
|
||||||
|
SCHEDULER: 'Plánovač',
|
||||||
|
SCHEDULER_HELP_1: 'Automatizujte príkazy pridaním naplánovaných udalostí nižšie. Nastavte jedinečné meno na aktiváciu/deaktiváciu cez API/MQTT.',
|
||||||
|
SCHEDULER_HELP_2: 'Použite 00:00 na jednorazové spustenie pri štarte',
|
||||||
|
SCHEDULE: 'Plánovať',
|
||||||
|
TIME: 'Čas',
|
||||||
|
TIMER: 'Časovač',
|
||||||
|
SCHEDULE_UPDATED: 'Plánovanie aktualizované',
|
||||||
|
SCHEDULE_TIMER_1: 'pri spustení',
|
||||||
|
SCHEDULE_TIMER_2: 'každú minútu',
|
||||||
|
SCHEDULE_TIMER_3: 'každú hodinu',
|
||||||
|
CUSTOM_ENTITIES: 'Vlastné entity',
|
||||||
|
ENTITIES_HELP_1: 'Získavanie vlastných entít zo zbernice EMS',
|
||||||
|
ENTITIES_UPDATED: 'Aktualizované entity',
|
||||||
|
WRITEABLE: 'Zapísateľný',
|
||||||
|
SHOWING: 'Zobrazenie',
|
||||||
|
SEARCH: 'Vyhľadať',
|
||||||
|
CERT: 'Koreňový certifikát TLS (ak chcete vypnúť TLS, nechajte prázdne)',
|
||||||
|
ENABLE_TLS: 'Povoliť TLS',
|
||||||
|
ON: 'Zap',
|
||||||
|
OFF: 'Vyp',
|
||||||
|
POLARITY: 'Polarita',
|
||||||
|
ACTIVEHIGH: 'Aktívny Vysoký',
|
||||||
|
ACTIVELOW: 'Aktívny Nízky',
|
||||||
|
UNCHANGED: 'Nezmenené',
|
||||||
|
ALWAYS: 'Vždy'
|
||||||
|
};
|
||||||
|
|
||||||
|
export default sk;
|
||||||
@@ -194,13 +194,11 @@ const sv: Translation = {
|
|||||||
RELEASE_IS: 'release är', // TODO translate
|
RELEASE_IS: 'release är', // TODO translate
|
||||||
RELEASE_NOTES: 'release-logg',
|
RELEASE_NOTES: 'release-logg',
|
||||||
EMS_ESP_VER: 'EMS-ESP Version',
|
EMS_ESP_VER: 'EMS-ESP Version',
|
||||||
PLATFORM: 'Enhet (Plattform / SDK)',
|
|
||||||
UPTIME: 'Systemets Upptid',
|
UPTIME: 'Systemets Upptid',
|
||||||
CPU_FREQ: 'CPU-frekvens',
|
|
||||||
HEAP: 'Heap (Ledigt / Max allokerat)',
|
HEAP: 'Heap (Ledigt / Max allokerat)',
|
||||||
PSRAM: 'PSRAM (Storlek / Ledigt)',
|
PSRAM: 'PSRAM (Storlek / Ledigt)',
|
||||||
FLASH: 'Flashminne (Storlek / Hastighet)',
|
FLASH: 'Flashminne (Storlek / Hastighet)',
|
||||||
APPSIZE: 'Applikationer (Använt / Ledigt)',
|
APPSIZE: 'Applikationer (Partition: Använt / Ledigt)',
|
||||||
FILESYSTEM: 'Filsystem (Använt / Ledigt)',
|
FILESYSTEM: 'Filsystem (Använt / Ledigt)',
|
||||||
BUFFER_SIZE: 'Max Bufferstorlek',
|
BUFFER_SIZE: 'Max Bufferstorlek',
|
||||||
COMPACT: 'Komprimera',
|
COMPACT: 'Komprimera',
|
||||||
|
|||||||
@@ -194,13 +194,11 @@ const tr: Translation = {
|
|||||||
RELEASE_IS: 'release is', // TODO translate
|
RELEASE_IS: 'release is', // TODO translate
|
||||||
RELEASE_NOTES: 'yayınlanma notları',
|
RELEASE_NOTES: 'yayınlanma notları',
|
||||||
EMS_ESP_VER: 'EMS-ESP Sürümü',
|
EMS_ESP_VER: 'EMS-ESP Sürümü',
|
||||||
PLATFORM: 'Cihaz (Platform / SDK)',
|
|
||||||
UPTIME: 'Sistem Çalışma Süresi',
|
UPTIME: 'Sistem Çalışma Süresi',
|
||||||
CPU_FREQ: 'İşlemci frekansı',
|
|
||||||
HEAP: 'Yığın (Boş / Maksimum Tahsis)',
|
HEAP: 'Yığın (Boş / Maksimum Tahsis)',
|
||||||
PSRAM: 'PSRAM (Boyut / Boş)',
|
PSRAM: 'PSRAM (Boyut / Boş)',
|
||||||
FLASH: 'Flash Çipi (Boyut / Hız)',
|
FLASH: 'Flash Çipi (Boyut / Hız)',
|
||||||
APPSIZE: 'Uygulama (Kullanılmış / Boş)',
|
APPSIZE: 'Uygulama (Bölme: Kullanılmış / Boş)',
|
||||||
FILESYSTEM: 'Dosya Sistemi (Kullanılmış / Boş)',
|
FILESYSTEM: 'Dosya Sistemi (Kullanılmış / Boş)',
|
||||||
BUFFER_SIZE: 'En fazla bellek boyutu',
|
BUFFER_SIZE: 'En fazla bellek boyutu',
|
||||||
COMPACT: 'Sıkışık',
|
COMPACT: 'Sıkışık',
|
||||||
|
|||||||
@@ -420,6 +420,7 @@ const DashboardDevices: FC = () => {
|
|||||||
<MessageBox my={2} level="warning" message={LL.EMS_BUS_SCANNING()} />
|
<MessageBox my={2} level="warning" message={LL.EMS_BUS_SCANNING()} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{coreData.connected && (
|
||||||
<Table data={{ nodes: coreData.devices }} select={device_select} theme={device_theme} layout={{ custom: true }}>
|
<Table data={{ nodes: coreData.devices }} select={device_select} theme={device_theme} layout={{ custom: true }}>
|
||||||
{(tableList: any) => (
|
{(tableList: any) => (
|
||||||
<>
|
<>
|
||||||
@@ -447,6 +448,7 @@ const DashboardDevices: FC = () => {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Table>
|
</Table>
|
||||||
|
)}
|
||||||
</IconContext.Provider>
|
</IconContext.Provider>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ const DashboardDevicesDialog = ({
|
|||||||
return (
|
return (
|
||||||
<Dialog sx={dialogStyle} open={open} onClose={close}>
|
<Dialog sx={dialogStyle} open={open} onClose={close}>
|
||||||
<DialogTitle>
|
<DialogTitle>
|
||||||
{selectedItem.v === '' && selectedItem.c ? LL.RUN_COMMAND() : writeable ? LL.CHANGE_VALUE() : LL.VALUE(1)}
|
{selectedItem.v === '' && selectedItem.c ? LL.RUN_COMMAND() : writeable ? LL.CHANGE_VALUE() : LL.VALUE(0)}
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogContent dividers>
|
<DialogContent dividers>
|
||||||
<Box color="warning.main" p={0} pl={0} pr={0} mt={0} mb={2}>
|
<Box color="warning.main" p={0} pl={0} pr={0} mt={0} mb={2}>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ const Help: FC = () => {
|
|||||||
const { LL } = useI18nContext();
|
const { LL } = useI18nContext();
|
||||||
useLayoutTitle(LL.HELP_OF(''));
|
useLayoutTitle(LL.HELP_OF(''));
|
||||||
|
|
||||||
const { send: getAPI, onSuccess: onGetAPI } = useRequest((data) => EMSESP.API(data), {
|
const { send: getSystemAPI, onSuccess: onGetAPI } = useRequest((data) => EMSESP.APIcall('system', data), {
|
||||||
immediate: false
|
immediate: false
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -26,20 +26,20 @@ const Help: FC = () => {
|
|||||||
type: 'text/plain'
|
type: 'text/plain'
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
anchor.download = 'emsesp_' + event.sendArgs[0].device + '_' + event.sendArgs[0].entity + '.txt';
|
anchor.download = 'emsesp_' + event.sendArgs[0].entity + '.txt';
|
||||||
anchor.click();
|
anchor.click();
|
||||||
URL.revokeObjectURL(anchor.href);
|
URL.revokeObjectURL(anchor.href);
|
||||||
toast.info(LL.DOWNLOAD_SUCCESSFUL());
|
toast.info(LL.DOWNLOAD_SUCCESSFUL());
|
||||||
});
|
});
|
||||||
|
|
||||||
const callAPI = async (device: string, entity: string) => {
|
const callSystemAPI = async (entity: string) => {
|
||||||
await getAPI({ device, entity, id: 0 }).catch((error) => {
|
await getSystemAPI({ entity, id: 0 }).catch((error) => {
|
||||||
toast.error(error.message);
|
toast.error(error.message);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SectionContent title={LL.SUPPORT_INFORMATION()} titleGutter>
|
<SectionContent title={LL.SUPPORT_INFORMATION(0)} titleGutter>
|
||||||
<List>
|
<List>
|
||||||
<ListItem>
|
<ListItem>
|
||||||
<ListItemAvatar>
|
<ListItemAvatar>
|
||||||
@@ -89,15 +89,15 @@ const Help: FC = () => {
|
|||||||
{LL.HELP_INFORMATION_4()}
|
{LL.HELP_INFORMATION_4()}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Button startIcon={<DownloadIcon />} variant="outlined" color="primary" onClick={() => callAPI('system', 'info')}>
|
<Button startIcon={<DownloadIcon />} variant="outlined" color="primary" onClick={() => callSystemAPI('info')}>
|
||||||
{LL.SUPPORT_INFORMATION()}
|
{LL.SUPPORT_INFORMATION(0)}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
sx={{ ml: 2 }}
|
sx={{ ml: 2 }}
|
||||||
startIcon={<DownloadIcon />}
|
startIcon={<DownloadIcon />}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
color="primary"
|
color="primary"
|
||||||
onClick={() => callAPI('system', 'allvalues')}
|
onClick={() => callSystemAPI('allvalues')}
|
||||||
>
|
>
|
||||||
All Values
|
All Values
|
||||||
</Button>
|
</Button>
|
||||||
@@ -111,7 +111,7 @@ const Help: FC = () => {
|
|||||||
{'github.com/emsesp/EMS-ESP32'}
|
{'github.com/emsesp/EMS-ESP32'}
|
||||||
</Link>
|
</Link>
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography color="white" align="center">
|
<Typography color="white" variant="subtitle2" align="center">
|
||||||
@proddy @MichaelDvP
|
@proddy @MichaelDvP
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -384,6 +384,7 @@ const SettingsApplication: FC = () => {
|
|||||||
<MenuItem value="nl">Nederlands (NL)</MenuItem>
|
<MenuItem value="nl">Nederlands (NL)</MenuItem>
|
||||||
<MenuItem value="no">Norsk (NO)</MenuItem>
|
<MenuItem value="no">Norsk (NO)</MenuItem>
|
||||||
<MenuItem value="pl">Polski (PL)</MenuItem>
|
<MenuItem value="pl">Polski (PL)</MenuItem>
|
||||||
|
<MenuItem value="sk">Slovenčina (SK)</MenuItem>
|
||||||
<MenuItem value="sv">Svenska (SV)</MenuItem>
|
<MenuItem value="sv">Svenska (SV)</MenuItem>
|
||||||
<MenuItem value="tr">Türk (TR)</MenuItem>
|
<MenuItem value="tr">Türk (TR)</MenuItem>
|
||||||
</TextField>
|
</TextField>
|
||||||
@@ -644,7 +645,7 @@ const SettingsApplication: FC = () => {
|
|||||||
</Grid>
|
</Grid>
|
||||||
)}
|
)}
|
||||||
{restartNeeded && (
|
{restartNeeded && (
|
||||||
<MessageBox my={2} level="warning" message={LL.RESTART_TEXT()}>
|
<MessageBox my={2} level="warning" message={LL.RESTART_TEXT(0)}>
|
||||||
<Button startIcon={<PowerSettingsNewIcon />} variant="contained" color="error" onClick={restart}>
|
<Button startIcon={<PowerSettingsNewIcon />} variant="contained" color="error" onClick={restart}>
|
||||||
{LL.RESTART()}
|
{LL.RESTART()}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -514,7 +514,7 @@ const SettingsCustomization: FC = () => {
|
|||||||
{devices && renderDeviceList()}
|
{devices && renderDeviceList()}
|
||||||
{deviceEntities && renderDeviceData()}
|
{deviceEntities && renderDeviceData()}
|
||||||
{restartNeeded && (
|
{restartNeeded && (
|
||||||
<MessageBox my={2} level="warning" message={LL.RESTART_TEXT()}>
|
<MessageBox my={2} level="warning" message={LL.RESTART_TEXT(0)}>
|
||||||
<Button startIcon={<PowerSettingsNewIcon />} variant="contained" color="error" onClick={restart}>
|
<Button startIcon={<PowerSettingsNewIcon />} variant="contained" color="error" onClick={restart}>
|
||||||
{LL.RESTART()}
|
{LL.RESTART()}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ const SettingsCustomizationDialog = ({ open, onClose, onSave, selectedItem }: Se
|
|||||||
<DialogContent dividers>
|
<DialogContent dividers>
|
||||||
<Grid container direction="row">
|
<Grid container direction="row">
|
||||||
<Typography variant="body2" color="warning.main">
|
<Typography variant="body2" color="warning.main">
|
||||||
{LL.ENTITY() + ' ID'}:
|
{LL.ID_OF(LL.ENTITY())}:
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2">{editItem.id}</Typography>
|
<Typography variant="body2">{editItem.id}</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|||||||
@@ -208,7 +208,7 @@ const SettingsScheduler: FC = () => {
|
|||||||
<HeaderCell stiff>{LL.SCHEDULE(0)}</HeaderCell>
|
<HeaderCell stiff>{LL.SCHEDULE(0)}</HeaderCell>
|
||||||
<HeaderCell stiff>{LL.TIME(0)}</HeaderCell>
|
<HeaderCell stiff>{LL.TIME(0)}</HeaderCell>
|
||||||
<HeaderCell stiff>{LL.COMMAND(0)}</HeaderCell>
|
<HeaderCell stiff>{LL.COMMAND(0)}</HeaderCell>
|
||||||
<HeaderCell stiff>{LL.VALUE(0)}</HeaderCell>
|
<HeaderCell stiff>{LL.VALUE(1)}</HeaderCell>
|
||||||
<HeaderCell stiff>{LL.NAME(0)}</HeaderCell>
|
<HeaderCell stiff>{LL.NAME(0)}</HeaderCell>
|
||||||
</HeaderRow>
|
</HeaderRow>
|
||||||
</Header>
|
</Header>
|
||||||
|
|||||||
@@ -210,7 +210,7 @@ const SettingsSchedulerDialog = ({
|
|||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
name="value"
|
name="value"
|
||||||
label={LL.VALUE(1)}
|
label={LL.VALUE(0)}
|
||||||
multiline
|
multiline
|
||||||
margin="normal"
|
margin="normal"
|
||||||
fullWidth
|
fullWidth
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type {
|
import type {
|
||||||
APIcall,
|
APIdata,
|
||||||
Settings,
|
Settings,
|
||||||
Status,
|
Status,
|
||||||
CoreData,
|
CoreData,
|
||||||
@@ -20,7 +20,7 @@ export const readCoreData = () => alovaInstance.Get<CoreData>(`/rest/coreData`);
|
|||||||
export const readDeviceData = (id: number) =>
|
export const readDeviceData = (id: number) =>
|
||||||
alovaInstance.Get<DeviceData>('/rest/deviceData', {
|
alovaInstance.Get<DeviceData>('/rest/deviceData', {
|
||||||
// alovaInstance.Get<DeviceData>(`/rest/deviceData/${id}`, {
|
// alovaInstance.Get<DeviceData>(`/rest/deviceData/${id}`, {
|
||||||
params: { id }, // TODO remove later
|
params: { id }, // TODO replace params later
|
||||||
responseType: 'arraybuffer' // uses msgpack
|
responseType: 'arraybuffer' // uses msgpack
|
||||||
});
|
});
|
||||||
export const writeDeviceValue = (data: any) => alovaInstance.Post('/rest/writeDeviceValue', data);
|
export const writeDeviceValue = (data: any) => alovaInstance.Post('/rest/writeDeviceValue', data);
|
||||||
@@ -44,7 +44,7 @@ export const readStatus = () => alovaInstance.Get<Status>('/rest/status');
|
|||||||
export const scanDevices = () => alovaInstance.Post('/rest/scanDevices');
|
export const scanDevices = () => alovaInstance.Post('/rest/scanDevices');
|
||||||
|
|
||||||
// HelpInformation
|
// HelpInformation
|
||||||
export const API = (apiCall: APIcall) => alovaInstance.Post('/api', apiCall);
|
export const APIcall = (device: string, apiData: APIdata) => alovaInstance.Post(`/api/${device}`, apiData);
|
||||||
|
|
||||||
// UploadFileForm
|
// UploadFileForm
|
||||||
export const getSettings = () => alovaInstance.Get('/rest/getSettings');
|
export const getSettings = () => alovaInstance.Get('/rest/getSettings');
|
||||||
@@ -56,7 +56,7 @@ export const getSchedule = () => alovaInstance.Get('/rest/getSchedule');
|
|||||||
export const readDeviceEntities = (id: number) =>
|
export const readDeviceEntities = (id: number) =>
|
||||||
// alovaInstance.Get<DeviceEntity[]>(`/rest/deviceEntities/${id}`, {
|
// alovaInstance.Get<DeviceEntity[]>(`/rest/deviceEntities/${id}`, {
|
||||||
alovaInstance.Get<DeviceEntity[]>(`/rest/deviceEntities`, {
|
alovaInstance.Get<DeviceEntity[]>(`/rest/deviceEntities`, {
|
||||||
params: { id }, // TODO remove later
|
params: { id }, // TODO replace params later
|
||||||
responseType: 'arraybuffer',
|
responseType: 'arraybuffer',
|
||||||
transformData(data: any) {
|
transformData(data: any) {
|
||||||
return data.map((de: DeviceEntity) => ({ ...de, o_m: de.m, o_cn: de.cn, o_mi: de.mi, o_ma: de.ma }));
|
return data.map((de: DeviceEntity) => ({ ...de, o_m: de.m, o_cn: de.cn, o_mi: de.mi, o_ma: de.ma }));
|
||||||
|
|||||||
@@ -266,8 +266,7 @@ export interface BoardProfile {
|
|||||||
eth_clock_mode: number;
|
eth_clock_mode: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface APIcall {
|
export interface APIdata {
|
||||||
device: string;
|
|
||||||
entity: string;
|
entity: string;
|
||||||
id: any;
|
id: any;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,14 @@ export interface SystemStatus {
|
|||||||
emsesp_version: string;
|
emsesp_version: string;
|
||||||
esp_platform: string;
|
esp_platform: string;
|
||||||
max_alloc_heap: number;
|
max_alloc_heap: number;
|
||||||
|
cpu_type: string;
|
||||||
|
cpu_rev: number;
|
||||||
|
cpu_cores: number;
|
||||||
cpu_freq_mhz: number;
|
cpu_freq_mhz: number;
|
||||||
free_heap: number;
|
free_heap: number;
|
||||||
|
arduino_version: string;
|
||||||
sdk_version: string;
|
sdk_version: string;
|
||||||
|
partition: string;
|
||||||
flash_chip_size: number;
|
flash_chip_size: number;
|
||||||
flash_chip_speed: number;
|
flash_chip_speed: number;
|
||||||
app_used: number;
|
app_used: number;
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export const useRest = <D>({ read, update }: RestRequestOptions2<D>) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
onWriteSuccess(() => {
|
onWriteSuccess(() => {
|
||||||
toast.success(LL.UPDATED_OF(LL.SETTINGS()));
|
toast.success(LL.UPDATED_OF(LL.SETTINGS(0)));
|
||||||
setDirtyFlags([]);
|
setDirtyFlags([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/*
|
||||||
import { debounce } from 'lodash-es';
|
import { debounce } from 'lodash-es';
|
||||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import Sockette from 'sockette';
|
import Sockette from 'sockette';
|
||||||
@@ -89,3 +90,4 @@ export const useWs = <D>(wsUrl: string, wsThrottle = 100) => {
|
|||||||
|
|
||||||
return { connected, data, updateData } as const;
|
return { connected, data, updateData } as const;
|
||||||
};
|
};
|
||||||
|
*/
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { defineConfig } from 'vite';
|
import { defineConfig, splitVendorChunkPlugin } from 'vite';
|
||||||
import viteTsconfigPaths from 'vite-tsconfig-paths';
|
import viteTsconfigPaths from 'vite-tsconfig-paths';
|
||||||
import preact from '@preact/preset-vite';
|
import preact from '@preact/preset-vite';
|
||||||
import viteImagemin from 'vite-plugin-imagemin';
|
import viteImagemin from 'vite-plugin-imagemin';
|
||||||
@@ -44,6 +44,7 @@ export default defineConfig(({ command, mode }) => {
|
|||||||
plugins: [
|
plugins: [
|
||||||
preact(),
|
preact(),
|
||||||
viteTsconfigPaths(),
|
viteTsconfigPaths(),
|
||||||
|
splitVendorChunkPlugin(),
|
||||||
{
|
{
|
||||||
...viteImagemin({
|
...viteImagemin({
|
||||||
verbose: false,
|
verbose: false,
|
||||||
@@ -112,6 +113,20 @@ export default defineConfig(({ command, mode }) => {
|
|||||||
nameCache: null,
|
nameCache: null,
|
||||||
safari10: false,
|
safari10: false,
|
||||||
toplevel: false
|
toplevel: false
|
||||||
|
},
|
||||||
|
|
||||||
|
rollupOptions: {
|
||||||
|
output: {
|
||||||
|
manualChunks(id: string) {
|
||||||
|
if (id.includes('node_modules')) {
|
||||||
|
// creating a chunk to react routes deps. Reducing the vendor chunk size
|
||||||
|
if (id.includes('react-router-dom') || id.includes('@remix-run') || id.includes('react-router')) {
|
||||||
|
return '@react-router';
|
||||||
|
}
|
||||||
|
return 'vendor';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,10 +12,10 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@alova/adapter-xhr@npm:^1.0.1":
|
"@alova/adapter-xhr@npm:^1.0.2":
|
||||||
version: 1.0.1
|
version: 1.0.2
|
||||||
resolution: "@alova/adapter-xhr@npm:1.0.1"
|
resolution: "@alova/adapter-xhr@npm:1.0.2"
|
||||||
checksum: 10fb023fd30408bf47433491679057458c599194040077d30c8427531b6ee4a6549bf468a7132d8ae91e99c8bdcea27dc4706b5b982033ac820f779a5016be7d
|
checksum: a57d178e89e3b655191bebccbc34d22760813b97b430e16f77b6ad561e3bb4ad8a34948aa2d724f5833d675f21a337ab769a3e5f73878430c3139374c6afb6ea
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@@ -86,26 +86,26 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@babel/core@npm:^7.23.6":
|
"@babel/core@npm:^7.23.7":
|
||||||
version: 7.23.6
|
version: 7.23.7
|
||||||
resolution: "@babel/core@npm:7.23.6"
|
resolution: "@babel/core@npm:7.23.7"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@ampproject/remapping": "npm:^2.2.0"
|
"@ampproject/remapping": "npm:^2.2.0"
|
||||||
"@babel/code-frame": "npm:^7.23.5"
|
"@babel/code-frame": "npm:^7.23.5"
|
||||||
"@babel/generator": "npm:^7.23.6"
|
"@babel/generator": "npm:^7.23.6"
|
||||||
"@babel/helper-compilation-targets": "npm:^7.23.6"
|
"@babel/helper-compilation-targets": "npm:^7.23.6"
|
||||||
"@babel/helper-module-transforms": "npm:^7.23.3"
|
"@babel/helper-module-transforms": "npm:^7.23.3"
|
||||||
"@babel/helpers": "npm:^7.23.6"
|
"@babel/helpers": "npm:^7.23.7"
|
||||||
"@babel/parser": "npm:^7.23.6"
|
"@babel/parser": "npm:^7.23.6"
|
||||||
"@babel/template": "npm:^7.22.15"
|
"@babel/template": "npm:^7.22.15"
|
||||||
"@babel/traverse": "npm:^7.23.6"
|
"@babel/traverse": "npm:^7.23.7"
|
||||||
"@babel/types": "npm:^7.23.6"
|
"@babel/types": "npm:^7.23.6"
|
||||||
convert-source-map: "npm:^2.0.0"
|
convert-source-map: "npm:^2.0.0"
|
||||||
debug: "npm:^4.1.0"
|
debug: "npm:^4.1.0"
|
||||||
gensync: "npm:^1.0.0-beta.2"
|
gensync: "npm:^1.0.0-beta.2"
|
||||||
json5: "npm:^2.2.3"
|
json5: "npm:^2.2.3"
|
||||||
semver: "npm:^6.3.1"
|
semver: "npm:^6.3.1"
|
||||||
checksum: a72ba71d2f557d09ff58a5f0846344b9cea9dfcbd7418729a3a74d5b0f37a5ca024942fef4d19f248de751928a1be3d5cb0488746dd8896009dd55b974bb552e
|
checksum: 956841695ea801c8b4196d01072e6c1062335960715a6fcfd4009831003b526b00627c78b373ed49b1658c3622c71142f7ff04235fe839cac4a1a25ed51b90aa
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@@ -304,14 +304,14 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@babel/helpers@npm:^7.23.6":
|
"@babel/helpers@npm:^7.23.7":
|
||||||
version: 7.23.6
|
version: 7.23.7
|
||||||
resolution: "@babel/helpers@npm:7.23.6"
|
resolution: "@babel/helpers@npm:7.23.7"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/template": "npm:^7.22.15"
|
"@babel/template": "npm:^7.22.15"
|
||||||
"@babel/traverse": "npm:^7.23.6"
|
"@babel/traverse": "npm:^7.23.7"
|
||||||
"@babel/types": "npm:^7.23.6"
|
"@babel/types": "npm:^7.23.6"
|
||||||
checksum: 2a85fd2bcbc15a6c94dbe7b9e94d8920f9de76d164179d6895fee89c4339079d9e3e56f572bf19b5e7d1e6f1997d7fbaeaa686b47d35136852631dfd09e85c2f
|
checksum: ec07061dc871d406ed82c8757c4d7a510aaf15145799fb0a2c3bd3c72ca101fe82a02dd5f83ca604fbbba5de5408dd731bb1452150562bed4f3b0a2846f81f61
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@@ -401,7 +401,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@babel/runtime@npm:^7.23.5":
|
"@babel/runtime@npm:^7.23.6":
|
||||||
version: 7.23.6
|
version: 7.23.6
|
||||||
resolution: "@babel/runtime@npm:7.23.6"
|
resolution: "@babel/runtime@npm:7.23.6"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -439,9 +439,9 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@babel/traverse@npm:^7.23.6":
|
"@babel/traverse@npm:^7.23.7":
|
||||||
version: 7.23.6
|
version: 7.23.7
|
||||||
resolution: "@babel/traverse@npm:7.23.6"
|
resolution: "@babel/traverse@npm:7.23.7"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/code-frame": "npm:^7.23.5"
|
"@babel/code-frame": "npm:^7.23.5"
|
||||||
"@babel/generator": "npm:^7.23.6"
|
"@babel/generator": "npm:^7.23.6"
|
||||||
@@ -453,7 +453,7 @@ __metadata:
|
|||||||
"@babel/types": "npm:^7.23.6"
|
"@babel/types": "npm:^7.23.6"
|
||||||
debug: "npm:^4.3.1"
|
debug: "npm:^4.3.1"
|
||||||
globals: "npm:^11.1.0"
|
globals: "npm:^11.1.0"
|
||||||
checksum: ee4434a3ce792ee8956b64d76843caa1dda4779bb621ed9f951dd3551965bf1f292f097011c9730ecbc0b57f02434b1fa5a771610a2ef570726b0df0fc3332d9
|
checksum: 3215e59429963c8dac85c26933372cdd322952aa9930e4bc5ef2d0e4bd7a1510d1ecf8f8fd860ace5d4d9fe496d23805a1ea019a86410aee4111de5f63ee84f9
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@@ -534,14 +534,14 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@emotion/react@npm:^11.11.1":
|
"@emotion/react@npm:^11.11.3":
|
||||||
version: 11.11.1
|
version: 11.11.3
|
||||||
resolution: "@emotion/react@npm:11.11.1"
|
resolution: "@emotion/react@npm:11.11.3"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime": "npm:^7.18.3"
|
"@babel/runtime": "npm:^7.18.3"
|
||||||
"@emotion/babel-plugin": "npm:^11.11.0"
|
"@emotion/babel-plugin": "npm:^11.11.0"
|
||||||
"@emotion/cache": "npm:^11.11.0"
|
"@emotion/cache": "npm:^11.11.0"
|
||||||
"@emotion/serialize": "npm:^1.1.2"
|
"@emotion/serialize": "npm:^1.1.3"
|
||||||
"@emotion/use-insertion-effect-with-fallbacks": "npm:^1.0.1"
|
"@emotion/use-insertion-effect-with-fallbacks": "npm:^1.0.1"
|
||||||
"@emotion/utils": "npm:^1.2.1"
|
"@emotion/utils": "npm:^1.2.1"
|
||||||
"@emotion/weak-memoize": "npm:^0.3.1"
|
"@emotion/weak-memoize": "npm:^0.3.1"
|
||||||
@@ -551,7 +551,7 @@ __metadata:
|
|||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
"@types/react":
|
"@types/react":
|
||||||
optional: true
|
optional: true
|
||||||
checksum: dfc140718d0a8051a74e51c379226d9de6b19f6a5dd595fb282ef72f4413695a2d012ba919f1e9eeff761c6659e6f7398da8e0e36eb7997a4fdf54cef88644ae
|
checksum: f7b98557b7d5236296dda48c2fc8a6cde4af7399758496e9f710f85a80c7d66fee1830966caabd7b237601bfdaca4e1add8c681d1ae4cc3d497fe88958d541c4
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@@ -568,6 +568,19 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@emotion/serialize@npm:^1.1.3":
|
||||||
|
version: 1.1.3
|
||||||
|
resolution: "@emotion/serialize@npm:1.1.3"
|
||||||
|
dependencies:
|
||||||
|
"@emotion/hash": "npm:^0.9.1"
|
||||||
|
"@emotion/memoize": "npm:^0.8.1"
|
||||||
|
"@emotion/unitless": "npm:^0.8.1"
|
||||||
|
"@emotion/utils": "npm:^1.2.1"
|
||||||
|
csstype: "npm:^3.0.2"
|
||||||
|
checksum: 48d88923663273ae70359bc1a1f30454136716cbe0ddd9664be08e257ce56acedab911f125b627627358e37c9f450bbac3ea09b534ef42f9f67325d47b1e2a7b
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@emotion/sheet@npm:^1.2.2":
|
"@emotion/sheet@npm:^1.2.2":
|
||||||
version: 1.2.2
|
version: 1.2.2
|
||||||
resolution: "@emotion/sheet@npm:1.2.2"
|
resolution: "@emotion/sheet@npm:1.2.2"
|
||||||
@@ -821,10 +834,10 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@eslint/js@npm:8.55.0":
|
"@eslint/js@npm:8.56.0":
|
||||||
version: 8.55.0
|
version: 8.56.0
|
||||||
resolution: "@eslint/js@npm:8.55.0"
|
resolution: "@eslint/js@npm:8.56.0"
|
||||||
checksum: 34b001a95b16501fd64f525b1de3ab0e4c252e5820b74069004934cb13977fc04ba4522a3e8f8074bd6af49da10d3444cd49fa711819f425ad73d6bf46eea82d
|
checksum: 97a4b5ccf7e24f4d205a1fb0f21cdcd610348ecf685f6798a48dd41ba443f2c1eedd3050ff5a0b8f30b8cf6501ab512aa9b76e531db15e59c9ebaa41f3162e37
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@@ -957,14 +970,14 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@mui/base@npm:5.0.0-beta.27":
|
"@mui/base@npm:5.0.0-beta.29":
|
||||||
version: 5.0.0-beta.27
|
version: 5.0.0-beta.29
|
||||||
resolution: "@mui/base@npm:5.0.0-beta.27"
|
resolution: "@mui/base@npm:5.0.0-beta.29"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime": "npm:^7.23.5"
|
"@babel/runtime": "npm:^7.23.6"
|
||||||
"@floating-ui/react-dom": "npm:^2.0.4"
|
"@floating-ui/react-dom": "npm:^2.0.4"
|
||||||
"@mui/types": "npm:^7.2.11"
|
"@mui/types": "npm:^7.2.11"
|
||||||
"@mui/utils": "npm:^5.15.0"
|
"@mui/utils": "npm:^5.15.2"
|
||||||
"@popperjs/core": "npm:^2.11.8"
|
"@popperjs/core": "npm:^2.11.8"
|
||||||
clsx: "npm:^2.0.0"
|
clsx: "npm:^2.0.0"
|
||||||
prop-types: "npm:^15.8.1"
|
prop-types: "npm:^15.8.1"
|
||||||
@@ -975,22 +988,22 @@ __metadata:
|
|||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
"@types/react":
|
"@types/react":
|
||||||
optional: true
|
optional: true
|
||||||
checksum: 944f2a020cb6b58f5dccde55cdc25ec486b26a1f89ee18d108b555e9e8855834890664e4b67eb6e3d1961c3847cd14ec725dd6a28d7e462a26f933c5ef8472b3
|
checksum: a651464968af6ebb775c24d2b9badc735b1d595e526ff7f8181186e6eed0735b14af8324db22a8744039ad79ce6dbb7c62920bb92a57959a66cf8e72d68af9aa
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@mui/core-downloads-tracker@npm:^5.15.0":
|
"@mui/core-downloads-tracker@npm:^5.15.2":
|
||||||
version: 5.15.0
|
version: 5.15.2
|
||||||
resolution: "@mui/core-downloads-tracker@npm:5.15.0"
|
resolution: "@mui/core-downloads-tracker@npm:5.15.2"
|
||||||
checksum: a7aadd4071ff715e618b8db647137579ca63cab4bf6e3acfe86a7d461f71605fc7ce44eeea5b1e789faae8546617b1f7d14c72a0c00fa3d59951b6eee42a6c5d
|
checksum: 8c88ac73a1d87c8ce565f6295dcd084c643580848e8f59159402e9db89975263da06305a0e605d3744479e917c2d297319496534bca9df8338e203162f1e7c33
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@mui/icons-material@npm:^5.15.0":
|
"@mui/icons-material@npm:^5.15.2":
|
||||||
version: 5.15.0
|
version: 5.15.2
|
||||||
resolution: "@mui/icons-material@npm:5.15.0"
|
resolution: "@mui/icons-material@npm:5.15.2"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime": "npm:^7.23.5"
|
"@babel/runtime": "npm:^7.23.6"
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
"@mui/material": ^5.0.0
|
"@mui/material": ^5.0.0
|
||||||
"@types/react": ^17.0.0 || ^18.0.0
|
"@types/react": ^17.0.0 || ^18.0.0
|
||||||
@@ -998,21 +1011,21 @@ __metadata:
|
|||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
"@types/react":
|
"@types/react":
|
||||||
optional: true
|
optional: true
|
||||||
checksum: 48a1c32a334bafe53723445ce29f73f920150b9421118909fbca37857f635ab802e3f49a247b7bbaf06f0d2b879deed9a9462df4dbd8d3c06cec29638c6a2d12
|
checksum: 6dad9fa436889ab89217d428f38b1f7868eb5db0b8aa2b16086f6e81666763767a29db8897e76d078919df7349a149c6e16da1aea1b3ae48ca0b7ee1e0d9d458
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@mui/material@npm:^5.15.0":
|
"@mui/material@npm:^5.15.2":
|
||||||
version: 5.15.0
|
version: 5.15.2
|
||||||
resolution: "@mui/material@npm:5.15.0"
|
resolution: "@mui/material@npm:5.15.2"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime": "npm:^7.23.5"
|
"@babel/runtime": "npm:^7.23.6"
|
||||||
"@mui/base": "npm:5.0.0-beta.27"
|
"@mui/base": "npm:5.0.0-beta.29"
|
||||||
"@mui/core-downloads-tracker": "npm:^5.15.0"
|
"@mui/core-downloads-tracker": "npm:^5.15.2"
|
||||||
"@mui/system": "npm:^5.15.0"
|
"@mui/system": "npm:^5.15.2"
|
||||||
"@mui/types": "npm:^7.2.11"
|
"@mui/types": "npm:^7.2.11"
|
||||||
"@mui/utils": "npm:^5.15.0"
|
"@mui/utils": "npm:^5.15.2"
|
||||||
"@types/react-transition-group": "npm:^4.4.9"
|
"@types/react-transition-group": "npm:^4.4.10"
|
||||||
clsx: "npm:^2.0.0"
|
clsx: "npm:^2.0.0"
|
||||||
csstype: "npm:^3.1.2"
|
csstype: "npm:^3.1.2"
|
||||||
prop-types: "npm:^15.8.1"
|
prop-types: "npm:^15.8.1"
|
||||||
@@ -1031,16 +1044,16 @@ __metadata:
|
|||||||
optional: true
|
optional: true
|
||||||
"@types/react":
|
"@types/react":
|
||||||
optional: true
|
optional: true
|
||||||
checksum: 2ecffcebaa854de521dad6eef7b6d19db15f4cfd17462f6db548db2485bfe2ece8c4347c16a4ba40c621cd1c5c6a823d5936c3af1baf6550883d156d41bbc027
|
checksum: 1ce902070022c40009e01208e95d0d61205ffdbcf4fadd16e6337acdfccfb1c66004525ffe277691c7f3fbdfcebb998f1544c054a31164d580cb040e8a7d2d80
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@mui/private-theming@npm:^5.15.0":
|
"@mui/private-theming@npm:^5.15.2":
|
||||||
version: 5.15.0
|
version: 5.15.2
|
||||||
resolution: "@mui/private-theming@npm:5.15.0"
|
resolution: "@mui/private-theming@npm:5.15.2"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime": "npm:^7.23.5"
|
"@babel/runtime": "npm:^7.23.6"
|
||||||
"@mui/utils": "npm:^5.15.0"
|
"@mui/utils": "npm:^5.15.2"
|
||||||
prop-types: "npm:^15.8.1"
|
prop-types: "npm:^15.8.1"
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
"@types/react": ^17.0.0 || ^18.0.0
|
"@types/react": ^17.0.0 || ^18.0.0
|
||||||
@@ -1048,15 +1061,15 @@ __metadata:
|
|||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
"@types/react":
|
"@types/react":
|
||||||
optional: true
|
optional: true
|
||||||
checksum: f02eee2c460a3d9ea288743dcd2fcb3d1e254c9428b9aae1d7cc37295d6ea530f6641071636922bbf8f36b8ee150330a71d643ca88420b18431d769c3a3cf413
|
checksum: 2b1665044fd77286068100bd5c67ba3a31320084b442788e1c0224359b6e8e3213505676fa1db451c970b2e432811b12cbcf2f882c9063d37497dbfcfcd8811e
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@mui/styled-engine@npm:^5.15.0":
|
"@mui/styled-engine@npm:^5.15.2":
|
||||||
version: 5.15.0
|
version: 5.15.2
|
||||||
resolution: "@mui/styled-engine@npm:5.15.0"
|
resolution: "@mui/styled-engine@npm:5.15.2"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime": "npm:^7.23.5"
|
"@babel/runtime": "npm:^7.23.6"
|
||||||
"@emotion/cache": "npm:^11.11.0"
|
"@emotion/cache": "npm:^11.11.0"
|
||||||
csstype: "npm:^3.1.2"
|
csstype: "npm:^3.1.2"
|
||||||
prop-types: "npm:^15.8.1"
|
prop-types: "npm:^15.8.1"
|
||||||
@@ -1069,19 +1082,19 @@ __metadata:
|
|||||||
optional: true
|
optional: true
|
||||||
"@emotion/styled":
|
"@emotion/styled":
|
||||||
optional: true
|
optional: true
|
||||||
checksum: 02de548366cf52461ba253fad81af00e1eeca828537e9647410583316a7585900467daa62258ec7e4f49143f6c7a114efc987a44b574b83d3e158243606eeaa1
|
checksum: c004a37f4343139896059a706e96175a0f8975cc8807bcea96c099a68a94cf24d7869e685b06511389c9a6e4412acac5ef07614659983a7782f203012b78315b
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@mui/system@npm:^5.15.0":
|
"@mui/system@npm:^5.15.2":
|
||||||
version: 5.15.0
|
version: 5.15.2
|
||||||
resolution: "@mui/system@npm:5.15.0"
|
resolution: "@mui/system@npm:5.15.2"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime": "npm:^7.23.5"
|
"@babel/runtime": "npm:^7.23.6"
|
||||||
"@mui/private-theming": "npm:^5.15.0"
|
"@mui/private-theming": "npm:^5.15.2"
|
||||||
"@mui/styled-engine": "npm:^5.15.0"
|
"@mui/styled-engine": "npm:^5.15.2"
|
||||||
"@mui/types": "npm:^7.2.11"
|
"@mui/types": "npm:^7.2.11"
|
||||||
"@mui/utils": "npm:^5.15.0"
|
"@mui/utils": "npm:^5.15.2"
|
||||||
clsx: "npm:^2.0.0"
|
clsx: "npm:^2.0.0"
|
||||||
csstype: "npm:^3.1.2"
|
csstype: "npm:^3.1.2"
|
||||||
prop-types: "npm:^15.8.1"
|
prop-types: "npm:^15.8.1"
|
||||||
@@ -1097,7 +1110,7 @@ __metadata:
|
|||||||
optional: true
|
optional: true
|
||||||
"@types/react":
|
"@types/react":
|
||||||
optional: true
|
optional: true
|
||||||
checksum: eeaaf11f5b63e53bec8e8bdc4eff187cea13d2ec0dbb0b2b3d1a392b264cc31f3b5e6cba69835aa8b02da29ec7e2c3522946b2c738321a14cd3e995f04c45312
|
checksum: 05335cc7856750a930e5eef4eaf3e935c1d6dd78add48e86d1d976736adea71c5f37f3c329fd0a8f5fd9d11e40775ab5a62192dc056d240cb365416ad4db5568
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@@ -1113,11 +1126,11 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@mui/utils@npm:^5.15.0":
|
"@mui/utils@npm:^5.15.2":
|
||||||
version: 5.15.0
|
version: 5.15.2
|
||||||
resolution: "@mui/utils@npm:5.15.0"
|
resolution: "@mui/utils@npm:5.15.2"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime": "npm:^7.23.5"
|
"@babel/runtime": "npm:^7.23.6"
|
||||||
"@types/prop-types": "npm:^15.7.11"
|
"@types/prop-types": "npm:^15.7.11"
|
||||||
prop-types: "npm:^15.8.1"
|
prop-types: "npm:^15.8.1"
|
||||||
react-is: "npm:^18.2.0"
|
react-is: "npm:^18.2.0"
|
||||||
@@ -1127,7 +1140,7 @@ __metadata:
|
|||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
"@types/react":
|
"@types/react":
|
||||||
optional: true
|
optional: true
|
||||||
checksum: 241ce42bc2f18df46f2adbbac640f16a64afd55a7829b10ef944d6648d45223ecad825ee56a44db29f6c387384e3977e30a966ea4425bda667e92989dd218b0a
|
checksum: 9ede26d8e2b456a5ecf088d4e2d6903613be57eae97fcd30a9f31ff2c35a0e4329c728bd20c94c6f3468038935c3101a040c2cfb7dd6ff7a490811af0675d90a
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@@ -1262,10 +1275,10 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@remix-run/router@npm:1.13.1":
|
"@remix-run/router@npm:1.14.1":
|
||||||
version: 1.13.1
|
version: 1.14.1
|
||||||
resolution: "@remix-run/router@npm:1.13.1"
|
resolution: "@remix-run/router@npm:1.14.1"
|
||||||
checksum: bf1ff266744352e71fc414f983a9f7772c10ec55cf4b978d851026e6c12b39c0084f99e4e45de706b800a71889ef09f652b8e7c43e21800351cc14c5ada8c834
|
checksum: caed61639006444a66ca832f1e500bac2fcf02695183e967ff1452d3172f888f2bb40591b239c85f9003b9628383cfd4c8ef55cde800d14276905c7031c9f0b9
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@@ -1553,12 +1566,12 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@types/node@npm:^20.10.4":
|
"@types/node@npm:^20.10.6":
|
||||||
version: 20.10.4
|
version: 20.10.6
|
||||||
resolution: "@types/node@npm:20.10.4"
|
resolution: "@types/node@npm:20.10.6"
|
||||||
dependencies:
|
dependencies:
|
||||||
undici-types: "npm:~5.26.4"
|
undici-types: "npm:~5.26.4"
|
||||||
checksum: c10c1dd13f5c2341ad866777dc32946538a99e1ebd203ae127730814b8e5fa4aedfbcb01cb3e24a5466f1af64bcdfa16e7de6e745ff098fff0942aa779b7fe03
|
checksum: 08471220d3cbbb6669835c4b78541edf5eface8f2c2e36c550cfa4ff73da73071c90e200a06359fac25d6564127597c23e178128058fb676824ec23d5178a017
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@@ -1583,12 +1596,12 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@types/react-dom@npm:^18.2.17":
|
"@types/react-dom@npm:^18.2.18":
|
||||||
version: 18.2.17
|
version: 18.2.18
|
||||||
resolution: "@types/react-dom@npm:18.2.17"
|
resolution: "@types/react-dom@npm:18.2.18"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/react": "npm:*"
|
"@types/react": "npm:*"
|
||||||
checksum: fe0dbb3224b48515da8fe25559e3777d756a27c3f22903f0b1b020de8d68bd57eb1f0af62b52ee65d9632637950afed8cbad24d158c4f3d910d083d49bd73fba
|
checksum: 4ef7725b4cebd4a32e049097ddfdfd855a178e63ead97ab6d3084872e7d6c1acd71aa923488123cd1015f0e0b11489d2b44f674a1df8fe82d7827eabbec6dbf1
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@@ -1613,12 +1626,12 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@types/react-transition-group@npm:^4.4.9":
|
"@types/react-transition-group@npm:^4.4.10":
|
||||||
version: 4.4.9
|
version: 4.4.10
|
||||||
resolution: "@types/react-transition-group@npm:4.4.9"
|
resolution: "@types/react-transition-group@npm:4.4.10"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/react": "npm:*"
|
"@types/react": "npm:*"
|
||||||
checksum: 74ed0985380544bd1d63d8865a452a859ed7122b35dd2cf919fa7d1f31936345671995d36c89263456f27dbb5940eac8d4607be969e27187102eecff1cc64ba3
|
checksum: b429f3bd54d9aea6c0395943ce2dda6b76fb458e902365bd91fd99bf72064fb5d59e2b74e78d10f2871908501d350da63e230d81bda2b616c967cab8dc51bd16
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@@ -1633,14 +1646,14 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@types/react@npm:^18.2.43":
|
"@types/react@npm:^18.2.46":
|
||||||
version: 18.2.43
|
version: 18.2.46
|
||||||
resolution: "@types/react@npm:18.2.43"
|
resolution: "@types/react@npm:18.2.46"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/prop-types": "npm:*"
|
"@types/prop-types": "npm:*"
|
||||||
"@types/scheduler": "npm:*"
|
"@types/scheduler": "npm:*"
|
||||||
csstype: "npm:^3.0.2"
|
csstype: "npm:^3.0.2"
|
||||||
checksum: a9d90a93380bb67623f27eba83e2d05b548109f7eb6fd591f5c4a3716bc257cc7cb078455db7ea4308d5f2ff6b4fe48d9a4a560145d9384069a2b5121bc93937
|
checksum: 10fb28a5b8504106512ce3b154c45d1ac045c31633786773a29f003b3079b434060368bb56f95ef6c39510835ceec4fb8fdc271d6ca2b9cdd979379cf53f126b
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@@ -1676,15 +1689,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@typescript-eslint/eslint-plugin@npm:^6.14.0":
|
"@typescript-eslint/eslint-plugin@npm:^6.17.0":
|
||||||
version: 6.14.0
|
version: 6.17.0
|
||||||
resolution: "@typescript-eslint/eslint-plugin@npm:6.14.0"
|
resolution: "@typescript-eslint/eslint-plugin@npm:6.17.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@eslint-community/regexpp": "npm:^4.5.1"
|
"@eslint-community/regexpp": "npm:^4.5.1"
|
||||||
"@typescript-eslint/scope-manager": "npm:6.14.0"
|
"@typescript-eslint/scope-manager": "npm:6.17.0"
|
||||||
"@typescript-eslint/type-utils": "npm:6.14.0"
|
"@typescript-eslint/type-utils": "npm:6.17.0"
|
||||||
"@typescript-eslint/utils": "npm:6.14.0"
|
"@typescript-eslint/utils": "npm:6.17.0"
|
||||||
"@typescript-eslint/visitor-keys": "npm:6.14.0"
|
"@typescript-eslint/visitor-keys": "npm:6.17.0"
|
||||||
debug: "npm:^4.3.4"
|
debug: "npm:^4.3.4"
|
||||||
graphemer: "npm:^1.4.0"
|
graphemer: "npm:^1.4.0"
|
||||||
ignore: "npm:^5.2.4"
|
ignore: "npm:^5.2.4"
|
||||||
@@ -1697,44 +1710,44 @@ __metadata:
|
|||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
typescript:
|
typescript:
|
||||||
optional: true
|
optional: true
|
||||||
checksum: d420277bed0104713fb4a3c2e0fed32b300919708db3f2e3d13bc83e80a9aec181bfc4e1e6012c65408c318f3ac113926fc77e6667d7657e34fa0d5a2c21ee32
|
checksum: f2a5774e9cc03e491a5a488501e5622c7eebd766f5a4fc2c30642864a3b89b0807946bde33a678f326ba7032f3f6a51aa0bf9c2d10adc823804fc9fb47db55a6
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@typescript-eslint/parser@npm:^6.14.0":
|
"@typescript-eslint/parser@npm:^6.17.0":
|
||||||
version: 6.14.0
|
version: 6.17.0
|
||||||
resolution: "@typescript-eslint/parser@npm:6.14.0"
|
resolution: "@typescript-eslint/parser@npm:6.17.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@typescript-eslint/scope-manager": "npm:6.14.0"
|
"@typescript-eslint/scope-manager": "npm:6.17.0"
|
||||||
"@typescript-eslint/types": "npm:6.14.0"
|
"@typescript-eslint/types": "npm:6.17.0"
|
||||||
"@typescript-eslint/typescript-estree": "npm:6.14.0"
|
"@typescript-eslint/typescript-estree": "npm:6.17.0"
|
||||||
"@typescript-eslint/visitor-keys": "npm:6.14.0"
|
"@typescript-eslint/visitor-keys": "npm:6.17.0"
|
||||||
debug: "npm:^4.3.4"
|
debug: "npm:^4.3.4"
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
eslint: ^7.0.0 || ^8.0.0
|
eslint: ^7.0.0 || ^8.0.0
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
typescript:
|
typescript:
|
||||||
optional: true
|
optional: true
|
||||||
checksum: 34f46aa8aaadb0d0ecb7d791a8436fcf44ec04af33ee9d198bcf6f7ca3927d8caa79d4756e0c4ef0d50979d895df0b8f1a2473fc83104423c96856e9d56047f3
|
checksum: 2ed0ed4a5b30e953430ce3279df3655af09fa1caed2abf11804d239717daefc32a22864f6620ef57bb9c684c74a99a13241384fea5096e961385e3678fc2e920
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@typescript-eslint/scope-manager@npm:6.14.0":
|
"@typescript-eslint/scope-manager@npm:6.17.0":
|
||||||
version: 6.14.0
|
version: 6.17.0
|
||||||
resolution: "@typescript-eslint/scope-manager@npm:6.14.0"
|
resolution: "@typescript-eslint/scope-manager@npm:6.17.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@typescript-eslint/types": "npm:6.14.0"
|
"@typescript-eslint/types": "npm:6.17.0"
|
||||||
"@typescript-eslint/visitor-keys": "npm:6.14.0"
|
"@typescript-eslint/visitor-keys": "npm:6.17.0"
|
||||||
checksum: fbe945169fe092df5953a54a552a9e8d9dc3dc158a39cd99de7f1843a169c82d3ba59e314b7d0f5b8110dbbe8c37c9e62dc2dda91a31536fe054221d5d8972c3
|
checksum: fe09c628553c9336e6a36d32c1d34e78ebd20aa02130a6bf535329621ba5a98aaac171f607bc6e4d17b3478c42e7de6476376636897ce3f227c754eb99acd07e
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@typescript-eslint/type-utils@npm:6.14.0":
|
"@typescript-eslint/type-utils@npm:6.17.0":
|
||||||
version: 6.14.0
|
version: 6.17.0
|
||||||
resolution: "@typescript-eslint/type-utils@npm:6.14.0"
|
resolution: "@typescript-eslint/type-utils@npm:6.17.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@typescript-eslint/typescript-estree": "npm:6.14.0"
|
"@typescript-eslint/typescript-estree": "npm:6.17.0"
|
||||||
"@typescript-eslint/utils": "npm:6.14.0"
|
"@typescript-eslint/utils": "npm:6.17.0"
|
||||||
debug: "npm:^4.3.4"
|
debug: "npm:^4.3.4"
|
||||||
ts-api-utils: "npm:^1.0.1"
|
ts-api-utils: "npm:^1.0.1"
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -1742,59 +1755,60 @@ __metadata:
|
|||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
typescript:
|
typescript:
|
||||||
optional: true
|
optional: true
|
||||||
checksum: 52c2a380d694f629ed2d37ce9decc5d8f6d276b030dcb8ee2d0a21b667d789e0d50c8a4d06fa60a053cbcc162b50c3708260f569ccd765609f17499d5294c19d
|
checksum: dc7938429193acfda61b7282197ec046039e2c4da41cdcddf4daaf300d10229e4e23bb0fcf0503b19c0b99a874849c8a9f5bb35ce106260f56a14106d2b41d8c
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@typescript-eslint/types@npm:6.14.0":
|
"@typescript-eslint/types@npm:6.17.0":
|
||||||
version: 6.14.0
|
version: 6.17.0
|
||||||
resolution: "@typescript-eslint/types@npm:6.14.0"
|
resolution: "@typescript-eslint/types@npm:6.17.0"
|
||||||
checksum: bcb32d69ac4a570634e37a3f149b7653a85334ac7b1d736961b627647ceff74797c4ac30b1405c508ede9462fad53b0b4442dbdf21877bf91263390c6e426e95
|
checksum: 87ab1b5a3270ab34b917c22a2fb90a9ad7d9f3b19d73a337bc9efbe65f924da13482c97e8ccbe3bd3d081aa96039eeff50de41c1da2a2128066429b931cdb21d
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@typescript-eslint/typescript-estree@npm:6.14.0":
|
"@typescript-eslint/typescript-estree@npm:6.17.0":
|
||||||
version: 6.14.0
|
version: 6.17.0
|
||||||
resolution: "@typescript-eslint/typescript-estree@npm:6.14.0"
|
resolution: "@typescript-eslint/typescript-estree@npm:6.17.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@typescript-eslint/types": "npm:6.14.0"
|
"@typescript-eslint/types": "npm:6.17.0"
|
||||||
"@typescript-eslint/visitor-keys": "npm:6.14.0"
|
"@typescript-eslint/visitor-keys": "npm:6.17.0"
|
||||||
debug: "npm:^4.3.4"
|
debug: "npm:^4.3.4"
|
||||||
globby: "npm:^11.1.0"
|
globby: "npm:^11.1.0"
|
||||||
is-glob: "npm:^4.0.3"
|
is-glob: "npm:^4.0.3"
|
||||||
|
minimatch: "npm:9.0.3"
|
||||||
semver: "npm:^7.5.4"
|
semver: "npm:^7.5.4"
|
||||||
ts-api-utils: "npm:^1.0.1"
|
ts-api-utils: "npm:^1.0.1"
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
typescript:
|
typescript:
|
||||||
optional: true
|
optional: true
|
||||||
checksum: 870f00e81de428c0afae3f753c04229170aeec76d62dcded0e22cff1c733fe60a350cf68571c889f87ea7a6008b73f7c62a079e91ab056d79aa2b9803a5b7150
|
checksum: 1671b0d2f2fdf07074fb1e2524d61935cec173bd8db6e482cc5b2dcc77aed3ffa831396736ffa0ee2fdbddd8585ae9ca8d6c97bcaea1385b23907a1ec0508f83
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@typescript-eslint/utils@npm:6.14.0":
|
"@typescript-eslint/utils@npm:6.17.0":
|
||||||
version: 6.14.0
|
version: 6.17.0
|
||||||
resolution: "@typescript-eslint/utils@npm:6.14.0"
|
resolution: "@typescript-eslint/utils@npm:6.17.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@eslint-community/eslint-utils": "npm:^4.4.0"
|
"@eslint-community/eslint-utils": "npm:^4.4.0"
|
||||||
"@types/json-schema": "npm:^7.0.12"
|
"@types/json-schema": "npm:^7.0.12"
|
||||||
"@types/semver": "npm:^7.5.0"
|
"@types/semver": "npm:^7.5.0"
|
||||||
"@typescript-eslint/scope-manager": "npm:6.14.0"
|
"@typescript-eslint/scope-manager": "npm:6.17.0"
|
||||||
"@typescript-eslint/types": "npm:6.14.0"
|
"@typescript-eslint/types": "npm:6.17.0"
|
||||||
"@typescript-eslint/typescript-estree": "npm:6.14.0"
|
"@typescript-eslint/typescript-estree": "npm:6.17.0"
|
||||||
semver: "npm:^7.5.4"
|
semver: "npm:^7.5.4"
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
eslint: ^7.0.0 || ^8.0.0
|
eslint: ^7.0.0 || ^8.0.0
|
||||||
checksum: fec7338edc31d89d5413ec49ce690e05741511ba1ba2a8c59ce14321f5026e73e0584dc9f35645ab4100561bcf8ecef8a08c042388743db53fe73f047132a150
|
checksum: 37c63afcf66124bf84808699997953b8c84a378aa2c490a771b611d82cdac8499c58fac8eeb8378528e97660b59563d99297bfec4b982cd68760b0ffe54aa714
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@typescript-eslint/visitor-keys@npm:6.14.0":
|
"@typescript-eslint/visitor-keys@npm:6.17.0":
|
||||||
version: 6.14.0
|
version: 6.17.0
|
||||||
resolution: "@typescript-eslint/visitor-keys@npm:6.14.0"
|
resolution: "@typescript-eslint/visitor-keys@npm:6.17.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@typescript-eslint/types": "npm:6.14.0"
|
"@typescript-eslint/types": "npm:6.17.0"
|
||||||
eslint-visitor-keys: "npm:^3.4.1"
|
eslint-visitor-keys: "npm:^3.4.1"
|
||||||
checksum: 404f87a121b4375b13e59ffc11ac2fe3c8e40025d0ef5cd6738ab7b3648ce1d41378414b1130ee68e0b454d7e6ec1937540799cdaa4ea572e2447b04d737ee44
|
checksum: a2aed0e1437fdab8858ab9c7c8e355f8b72a5fa4b0adc54f28b8a2bbc29d4bb93214968ee940f83d013d0a4b83d00cd4eeeb05fb4c2c7d0ead324c6793f7d6d4
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@@ -1809,33 +1823,33 @@ __metadata:
|
|||||||
version: 0.0.0-use.local
|
version: 0.0.0-use.local
|
||||||
resolution: "EMS-ESP@workspace:."
|
resolution: "EMS-ESP@workspace:."
|
||||||
dependencies:
|
dependencies:
|
||||||
"@alova/adapter-xhr": "npm:^1.0.1"
|
"@alova/adapter-xhr": "npm:^1.0.2"
|
||||||
"@babel/core": "npm:^7.23.6"
|
"@babel/core": "npm:^7.23.7"
|
||||||
"@emotion/react": "npm:^11.11.1"
|
"@emotion/react": "npm:^11.11.3"
|
||||||
"@emotion/styled": "npm:^11.11.0"
|
"@emotion/styled": "npm:^11.11.0"
|
||||||
"@mui/icons-material": "npm:^5.15.0"
|
"@mui/icons-material": "npm:^5.15.2"
|
||||||
"@mui/material": "npm:^5.15.0"
|
"@mui/material": "npm:^5.15.2"
|
||||||
"@preact/compat": "npm:^17.1.2"
|
"@preact/compat": "npm:^17.1.2"
|
||||||
"@preact/preset-vite": "npm:^2.7.0"
|
"@preact/preset-vite": "npm:^2.7.0"
|
||||||
"@table-library/react-table-library": "npm:4.1.7"
|
"@table-library/react-table-library": "npm:4.1.7"
|
||||||
"@types/imagemin": "npm:^8.0.5"
|
"@types/imagemin": "npm:^8.0.5"
|
||||||
"@types/lodash-es": "npm:^4.17.12"
|
"@types/lodash-es": "npm:^4.17.12"
|
||||||
"@types/node": "npm:^20.10.4"
|
"@types/node": "npm:^20.10.6"
|
||||||
"@types/react": "npm:^18.2.43"
|
"@types/react": "npm:^18.2.46"
|
||||||
"@types/react-dom": "npm:^18.2.17"
|
"@types/react-dom": "npm:^18.2.18"
|
||||||
"@types/react-router-dom": "npm:^5.3.3"
|
"@types/react-router-dom": "npm:^5.3.3"
|
||||||
"@typescript-eslint/eslint-plugin": "npm:^6.14.0"
|
"@typescript-eslint/eslint-plugin": "npm:^6.17.0"
|
||||||
"@typescript-eslint/parser": "npm:^6.14.0"
|
"@typescript-eslint/parser": "npm:^6.17.0"
|
||||||
alova: "npm:^2.16.0"
|
alova: "npm:^2.16.2"
|
||||||
async-validator: "npm:^4.2.5"
|
async-validator: "npm:^4.2.5"
|
||||||
concurrently: "npm:^8.2.2"
|
concurrently: "npm:^8.2.2"
|
||||||
eslint: "npm:^8.55.0"
|
eslint: "npm:^8.56.0"
|
||||||
eslint-config-airbnb: "npm:^19.0.4"
|
eslint-config-airbnb: "npm:^19.0.4"
|
||||||
eslint-config-airbnb-typescript: "npm:^17.1.0"
|
eslint-config-airbnb-typescript: "npm:^17.1.0"
|
||||||
eslint-config-prettier: "npm:^9.1.0"
|
eslint-config-prettier: "npm:^9.1.0"
|
||||||
eslint-import-resolver-typescript: "npm:^3.6.1"
|
eslint-import-resolver-typescript: "npm:^3.6.1"
|
||||||
eslint-plugin-autofix: "npm:^1.1.0"
|
eslint-plugin-autofix: "npm:^1.1.0"
|
||||||
eslint-plugin-import: "npm:^2.29.0"
|
eslint-plugin-import: "npm:^2.29.1"
|
||||||
eslint-plugin-jsx-a11y: "npm:^6.8.0"
|
eslint-plugin-jsx-a11y: "npm:^6.8.0"
|
||||||
eslint-plugin-prettier: "npm:alpha"
|
eslint-plugin-prettier: "npm:alpha"
|
||||||
eslint-plugin-react: "npm:^7.33.2"
|
eslint-plugin-react: "npm:^7.33.2"
|
||||||
@@ -1850,16 +1864,16 @@ __metadata:
|
|||||||
react-dom: "npm:latest"
|
react-dom: "npm:latest"
|
||||||
react-dropzone: "npm:^14.2.3"
|
react-dropzone: "npm:^14.2.3"
|
||||||
react-icons: "npm:^4.12.0"
|
react-icons: "npm:^4.12.0"
|
||||||
react-router-dom: "npm:^6.20.1"
|
react-router-dom: "npm:^6.21.1"
|
||||||
react-toastify: "npm:^9.1.3"
|
react-toastify: "npm:^9.1.3"
|
||||||
rollup-plugin-visualizer: "npm:^5.11.0"
|
rollup-plugin-visualizer: "npm:^5.12.0"
|
||||||
sockette: "npm:^2.0.6"
|
sockette: "npm:^2.0.6"
|
||||||
terser: "npm:^5.26.0"
|
terser: "npm:^5.26.0"
|
||||||
typesafe-i18n: "npm:^5.26.2"
|
typesafe-i18n: "npm:^5.26.2"
|
||||||
typescript: "npm:^5.3.3"
|
typescript: "npm:^5.3.3"
|
||||||
vite: "npm:^5.0.8"
|
vite: "npm:^5.0.10"
|
||||||
vite-plugin-imagemin: "npm:^0.6.1"
|
vite-plugin-imagemin: "npm:^0.6.1"
|
||||||
vite-tsconfig-paths: "npm:^4.2.2"
|
vite-tsconfig-paths: "npm:^4.2.3"
|
||||||
languageName: unknown
|
languageName: unknown
|
||||||
linkType: soft
|
linkType: soft
|
||||||
|
|
||||||
@@ -1928,10 +1942,10 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"alova@npm:^2.16.0":
|
"alova@npm:^2.16.2":
|
||||||
version: 2.16.0
|
version: 2.16.2
|
||||||
resolution: "alova@npm:2.16.0"
|
resolution: "alova@npm:2.16.2"
|
||||||
checksum: 476aaf451c6760f46822f0e8bf834c86700ccd6e8f28b28f0381afa43a4d2bc361fa1414fe30e8494e6a5a5a49a6f744b9a59ec7f0ad179f4cf5b985a0a638ed
|
checksum: 06fafddf380d4d8e8e5dd172ebcaa0bc229c76c11b2675cfb2c0ab884a36d4818159267adb14ec7a3cbe681464793085b0386d7741e6a6a732c764b14c8783a8
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@@ -3814,9 +3828,9 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"eslint-plugin-import@npm:^2.29.0":
|
"eslint-plugin-import@npm:^2.29.1":
|
||||||
version: 2.29.0
|
version: 2.29.1
|
||||||
resolution: "eslint-plugin-import@npm:2.29.0"
|
resolution: "eslint-plugin-import@npm:2.29.1"
|
||||||
dependencies:
|
dependencies:
|
||||||
array-includes: "npm:^3.1.7"
|
array-includes: "npm:^3.1.7"
|
||||||
array.prototype.findlastindex: "npm:^1.2.3"
|
array.prototype.findlastindex: "npm:^1.2.3"
|
||||||
@@ -3834,10 +3848,10 @@ __metadata:
|
|||||||
object.groupby: "npm:^1.0.1"
|
object.groupby: "npm:^1.0.1"
|
||||||
object.values: "npm:^1.1.7"
|
object.values: "npm:^1.1.7"
|
||||||
semver: "npm:^6.3.1"
|
semver: "npm:^6.3.1"
|
||||||
tsconfig-paths: "npm:^3.14.2"
|
tsconfig-paths: "npm:^3.15.0"
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8
|
eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8
|
||||||
checksum: d6e8d016f38369892c85b866f762c03dee2b337d4f12031756e30d7490879261d1192a3c2f682fd7c4d2b923465f7a1e3d22cfdad5da1b1391c3bd39ea87af1a
|
checksum: 5865f05c38552145423c535326ec9a7113ab2305c7614c8b896ff905cfabc859c8805cac21e979c9f6f742afa333e6f62f812eabf891a7e8f5f0b853a32593c1
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@@ -3945,14 +3959,14 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"eslint@npm:^8.55.0":
|
"eslint@npm:^8.56.0":
|
||||||
version: 8.55.0
|
version: 8.56.0
|
||||||
resolution: "eslint@npm:8.55.0"
|
resolution: "eslint@npm:8.56.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@eslint-community/eslint-utils": "npm:^4.2.0"
|
"@eslint-community/eslint-utils": "npm:^4.2.0"
|
||||||
"@eslint-community/regexpp": "npm:^4.6.1"
|
"@eslint-community/regexpp": "npm:^4.6.1"
|
||||||
"@eslint/eslintrc": "npm:^2.1.4"
|
"@eslint/eslintrc": "npm:^2.1.4"
|
||||||
"@eslint/js": "npm:8.55.0"
|
"@eslint/js": "npm:8.56.0"
|
||||||
"@humanwhocodes/config-array": "npm:^0.11.13"
|
"@humanwhocodes/config-array": "npm:^0.11.13"
|
||||||
"@humanwhocodes/module-importer": "npm:^1.0.1"
|
"@humanwhocodes/module-importer": "npm:^1.0.1"
|
||||||
"@nodelib/fs.walk": "npm:^1.2.8"
|
"@nodelib/fs.walk": "npm:^1.2.8"
|
||||||
@@ -3989,7 +4003,7 @@ __metadata:
|
|||||||
text-table: "npm:^0.2.0"
|
text-table: "npm:^0.2.0"
|
||||||
bin:
|
bin:
|
||||||
eslint: bin/eslint.js
|
eslint: bin/eslint.js
|
||||||
checksum: afd016cfbe9e9d667b3f98c14c681a7e518808f6c30856e56cbb02248900eac5bf6dc5e577a7eaec259539486db48ef7d16ef58fb14b1585ba7c84b35490c53c
|
checksum: ef6193c6e4cef20774b985a5cc2fd4bf6d3c4decd423117cbc4a0196617861745db291217ad3c537bc3a160650cca965bc818f55e1f3e446af1fcb293f9940a5
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@@ -6164,6 +6178,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"minimatch@npm:9.0.3, minimatch@npm:^9.0.1":
|
||||||
|
version: 9.0.3
|
||||||
|
resolution: "minimatch@npm:9.0.3"
|
||||||
|
dependencies:
|
||||||
|
brace-expansion: "npm:^2.0.1"
|
||||||
|
checksum: c81b47d28153e77521877649f4bab48348d10938df9e8147a58111fe00ef89559a2938de9f6632910c4f7bf7bb5cd81191a546167e58d357f0cfb1e18cecc1c5
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"minimatch@npm:^3.0.5, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2":
|
"minimatch@npm:^3.0.5, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2":
|
||||||
version: 3.1.2
|
version: 3.1.2
|
||||||
resolution: "minimatch@npm:3.1.2"
|
resolution: "minimatch@npm:3.1.2"
|
||||||
@@ -6173,15 +6196,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"minimatch@npm:^9.0.1":
|
|
||||||
version: 9.0.3
|
|
||||||
resolution: "minimatch@npm:9.0.3"
|
|
||||||
dependencies:
|
|
||||||
brace-expansion: "npm:^2.0.1"
|
|
||||||
checksum: c81b47d28153e77521877649f4bab48348d10938df9e8147a58111fe00ef89559a2938de9f6632910c4f7bf7bb5cd81191a546167e58d357f0cfb1e18cecc1c5
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"minimist@npm:^1.1.3, minimist@npm:^1.2.0, minimist@npm:^1.2.6":
|
"minimist@npm:^1.1.3, minimist@npm:^1.2.0, minimist@npm:^1.2.6":
|
||||||
version: 1.2.8
|
version: 1.2.8
|
||||||
resolution: "minimist@npm:1.2.8"
|
resolution: "minimist@npm:1.2.8"
|
||||||
@@ -7130,27 +7144,27 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"react-router-dom@npm:^6.20.1":
|
"react-router-dom@npm:^6.21.1":
|
||||||
version: 6.20.1
|
version: 6.21.1
|
||||||
resolution: "react-router-dom@npm:6.20.1"
|
resolution: "react-router-dom@npm:6.21.1"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@remix-run/router": "npm:1.13.1"
|
"@remix-run/router": "npm:1.14.1"
|
||||||
react-router: "npm:6.20.1"
|
react-router: "npm:6.21.1"
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ">=16.8"
|
react: ">=16.8"
|
||||||
react-dom: ">=16.8"
|
react-dom: ">=16.8"
|
||||||
checksum: 27efb05af0025bdcd7ecc85d2df2f53ca90bbf4db1dd4319002714b5be4e23c9434f95932d79f14a42d7c347ca882e9a0bba74a4d6331de8f7fb527c21f3f069
|
checksum: 2d75bd889828fa5516ad076b44506656d826c365645e7079138cd0ef899db28a1b212f708a6c6e3b543ae11b96b2031f01201cc2fe1733dd4d9c5cbdd4d734ef
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"react-router@npm:6.20.1":
|
"react-router@npm:6.21.1":
|
||||||
version: 6.20.1
|
version: 6.21.1
|
||||||
resolution: "react-router@npm:6.20.1"
|
resolution: "react-router@npm:6.21.1"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@remix-run/router": "npm:1.13.1"
|
"@remix-run/router": "npm:1.14.1"
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ">=16.8"
|
react: ">=16.8"
|
||||||
checksum: 96c25c8ca782dfa5b501540b9a491d8dca67c829a90fda237238a22881c695226fd5bbe14fcb2793bd5877aec2514d932c3293bf1f2463606fb3f2326628d766
|
checksum: 1220cc75e0c915a26dde9dbb6509a8f0b0163d96e5ad591af91d9bb5a92a18401718f8d872a03d1cb366e7a6216c165a5cadd12375adf97943f37d7f5c487a90
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@@ -7436,9 +7450,9 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"rollup-plugin-visualizer@npm:^5.11.0":
|
"rollup-plugin-visualizer@npm:^5.12.0":
|
||||||
version: 5.11.0
|
version: 5.12.0
|
||||||
resolution: "rollup-plugin-visualizer@npm:5.11.0"
|
resolution: "rollup-plugin-visualizer@npm:5.12.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
open: "npm:^8.4.0"
|
open: "npm:^8.4.0"
|
||||||
picomatch: "npm:^2.3.1"
|
picomatch: "npm:^2.3.1"
|
||||||
@@ -7451,7 +7465,7 @@ __metadata:
|
|||||||
optional: true
|
optional: true
|
||||||
bin:
|
bin:
|
||||||
rollup-plugin-visualizer: dist/bin/cli.js
|
rollup-plugin-visualizer: dist/bin/cli.js
|
||||||
checksum: 947238aa22706a47a4d3e8ce616855f0e5cb969ed9f61b9a268eaede0a86f461ecb38e27b4e6bf00f4b5e3f63677667f65e0d4af89a659a5160f74add1f192bb
|
checksum: 47358feb672291d6edcfd94197577c192a84c24cb644119425dae8241fb6f5a52556efd0c501f38b276c07534642a80c0885ef681babb474e83c7b5a3b475b84
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@@ -8363,15 +8377,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"tsconfig-paths@npm:^3.14.2":
|
"tsconfig-paths@npm:^3.15.0":
|
||||||
version: 3.14.2
|
version: 3.15.0
|
||||||
resolution: "tsconfig-paths@npm:3.14.2"
|
resolution: "tsconfig-paths@npm:3.15.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/json5": "npm:^0.0.29"
|
"@types/json5": "npm:^0.0.29"
|
||||||
json5: "npm:^1.0.2"
|
json5: "npm:^1.0.2"
|
||||||
minimist: "npm:^1.2.6"
|
minimist: "npm:^1.2.6"
|
||||||
strip-bom: "npm:^3.0.0"
|
strip-bom: "npm:^3.0.0"
|
||||||
checksum: 17f23e98612a60cf23b80dc1d3b7b840879e41fcf603868fc3618a30f061ac7b463ef98cad8c28b68733b9bfe0cc40ffa2bcf29e94cf0d26e4f6addf7ac8527d
|
checksum: 2041beaedc6c271fc3bedd12e0da0cc553e65d030d4ff26044b771fac5752d0460944c0b5e680f670c2868c95c664a256cec960ae528888db6ded83524e33a14
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@@ -8660,9 +8674,9 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"vite-tsconfig-paths@npm:^4.2.2":
|
"vite-tsconfig-paths@npm:^4.2.3":
|
||||||
version: 4.2.2
|
version: 4.2.3
|
||||||
resolution: "vite-tsconfig-paths@npm:4.2.2"
|
resolution: "vite-tsconfig-paths@npm:4.2.3"
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: "npm:^4.1.1"
|
debug: "npm:^4.1.1"
|
||||||
globrex: "npm:^0.1.2"
|
globrex: "npm:^0.1.2"
|
||||||
@@ -8672,13 +8686,13 @@ __metadata:
|
|||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
vite:
|
vite:
|
||||||
optional: true
|
optional: true
|
||||||
checksum: 790b9a48dd69b6e93bc41455ef0cc63fc8149b40a6d344784067fc2487b0a02f2d6a6d71396214dab7537a52c5e1ddfc88c363232fa707377db161d05e8f68cd
|
checksum: ba6abe5d18fc1c1e494e1f1d8a7db56445c2a40e15aadb5d47a9c66cc5372d6f69b94ff0b1e47b67659d6ecaeddebab0a9d11e40b1c3c36c0115800736a6c760
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"vite@npm:^5.0.8":
|
"vite@npm:^5.0.10":
|
||||||
version: 5.0.8
|
version: 5.0.10
|
||||||
resolution: "vite@npm:5.0.8"
|
resolution: "vite@npm:5.0.10"
|
||||||
dependencies:
|
dependencies:
|
||||||
esbuild: "npm:^0.19.3"
|
esbuild: "npm:^0.19.3"
|
||||||
fsevents: "npm:~2.3.3"
|
fsevents: "npm:~2.3.3"
|
||||||
@@ -8712,7 +8726,7 @@ __metadata:
|
|||||||
optional: true
|
optional: true
|
||||||
bin:
|
bin:
|
||||||
vite: bin/vite.js
|
vite: bin/vite.js
|
||||||
checksum: ea36e34fa45401d8e29317c3355f4d7081df09b412578bd7b6a26d44bccace9d130625f7f317a3cbc20ad2aadc5881d01d1508e8d9e36060ae44d974f505dd7e
|
checksum: 5421e9c7f8cf3152eace9a8b528269141635f367e5dc63c5f1fe2712a766d9757f8197733cf3f28be590afdd520130d38de90c955e6dba6edfa6f9056c1e5ea7
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
# AsyncTCP
|
|
||||||
[](https://travis-ci.org/me-no-dev/AsyncTCP)  [](https://www.codacy.com/manual/me-no-dev/AsyncTCP?utm_source=github.com&utm_medium=referral&utm_content=me-no-dev/AsyncTCP&utm_campaign=Badge_Grade)
|
|
||||||
|
|
||||||
### Async TCP Library for ESP32 Arduino
|
|
||||||
|
|
||||||
[](https://gitter.im/me-no-dev/ESPAsyncWebServer?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
|
||||||
|
|
||||||
This is a fully asynchronous TCP library, aimed at enabling trouble-free, multi-connection network environment for Espressif's ESP32 MCUs.
|
|
||||||
|
|
||||||
This library is the base for [ESPAsyncWebServer](https://github.com/me-no-dev/ESPAsyncWebServer)
|
|
||||||
|
|
||||||
## AsyncClient and AsyncServer
|
|
||||||
The base classes on which everything else is built. They expose all possible scenarios, but are really raw and require more skills to use.
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
name=AsyncTCP
|
|
||||||
version=1.1.1
|
|
||||||
author=Me-No-Dev
|
|
||||||
maintainer=Me-No-Dev
|
|
||||||
sentence=Async TCP Library for ESP32
|
|
||||||
paragraph=Async TCP Library for ESP32
|
|
||||||
category=Other
|
|
||||||
url=https://github.com/me-no-dev/AsyncTCP
|
|
||||||
architectures=*
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,250 +0,0 @@
|
|||||||
/*
|
|
||||||
Asynchronous TCP library for Espressif MCUs
|
|
||||||
|
|
||||||
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
|
|
||||||
This file is part of the esp8266 core for Arduino environment.
|
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
|
||||||
modify it under the terms of the GNU Lesser General Public
|
|
||||||
License as published by the Free Software Foundation; either
|
|
||||||
version 2.1 of the License, or (at your option) any later version.
|
|
||||||
|
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
Lesser General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU Lesser General Public
|
|
||||||
License along with this library; if not, write to the Free Software
|
|
||||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef ASYNCTCP_H_
|
|
||||||
#define ASYNCTCP_H_
|
|
||||||
|
|
||||||
#include "IPAddress.h"
|
|
||||||
#include "IPv6Address.h"
|
|
||||||
#include "sdkconfig.h"
|
|
||||||
#include <functional>
|
|
||||||
extern "C" {
|
|
||||||
#include "freertos/FreeRTOS.h"
|
|
||||||
#include "freertos/semphr.h"
|
|
||||||
#include "lwip/pbuf.h"
|
|
||||||
#include "lwip/ip_addr.h"
|
|
||||||
#include "lwip/ip6_addr.h"
|
|
||||||
}
|
|
||||||
|
|
||||||
//If core is not defined, then we are running in Arduino or PIO
|
|
||||||
#ifndef CONFIG_ASYNC_TCP_RUNNING_CORE
|
|
||||||
#define CONFIG_ASYNC_TCP_RUNNING_CORE -1 //any available core
|
|
||||||
#define CONFIG_ASYNC_TCP_USE_WDT 0 //if enabled, adds between 33us and 200us per event
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef CONFIG_ASYNC_TCP_TASK_PRIORITY
|
|
||||||
#define CONFIG_ASYNC_TCP_TASK_PRIORITY 5
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef CONFIG_ASYNC_TCP_STACK
|
|
||||||
#define CONFIG_ASYNC_TCP_STACK 8192
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef CONFIG_ASYNC_TCP_QUEUE
|
|
||||||
#define CONFIG_ASYNC_TCP_QUEUE 128
|
|
||||||
#endif
|
|
||||||
|
|
||||||
class AsyncClient;
|
|
||||||
|
|
||||||
#define ASYNC_MAX_ACK_TIME 5000
|
|
||||||
#define ASYNC_WRITE_FLAG_COPY 0x01 //will allocate new buffer to hold the data while sending (else will hold reference to the data given)
|
|
||||||
#define ASYNC_WRITE_FLAG_MORE 0x02 //will not send PSH flag, meaning that there should be more data to be sent before the application should react.
|
|
||||||
|
|
||||||
typedef std::function<void(void *, AsyncClient *)> AcConnectHandler;
|
|
||||||
typedef std::function<void(void *, AsyncClient *, size_t len, uint32_t time)> AcAckHandler;
|
|
||||||
typedef std::function<void(void *, AsyncClient *, int8_t error)> AcErrorHandler;
|
|
||||||
typedef std::function<void(void *, AsyncClient *, void * data, size_t len)> AcDataHandler;
|
|
||||||
typedef std::function<void(void *, AsyncClient *, struct pbuf * pb)> AcPacketHandler;
|
|
||||||
typedef std::function<void(void *, AsyncClient *, uint32_t time)> AcTimeoutHandler;
|
|
||||||
|
|
||||||
struct tcp_pcb;
|
|
||||||
struct ip_addr;
|
|
||||||
|
|
||||||
class AsyncClient {
|
|
||||||
public:
|
|
||||||
AsyncClient(tcp_pcb * pcb = 0);
|
|
||||||
~AsyncClient();
|
|
||||||
|
|
||||||
AsyncClient & operator=(const AsyncClient & other);
|
|
||||||
AsyncClient & operator+=(const AsyncClient & other);
|
|
||||||
|
|
||||||
bool operator==(const AsyncClient & other);
|
|
||||||
|
|
||||||
bool operator!=(const AsyncClient & other) {
|
|
||||||
return !(*this == other);
|
|
||||||
}
|
|
||||||
bool connect(IPAddress ip, uint16_t port);
|
|
||||||
bool connect(IPv6Address ip, uint16_t port);
|
|
||||||
bool connect(const char * host, uint16_t port);
|
|
||||||
void close(bool now = false);
|
|
||||||
void stop();
|
|
||||||
int8_t abort();
|
|
||||||
bool free();
|
|
||||||
|
|
||||||
bool canSend(); //ack is not pending
|
|
||||||
size_t space(); //space available in the TCP window
|
|
||||||
size_t add(const char * data, size_t size, uint8_t apiflags = ASYNC_WRITE_FLAG_COPY); //add for sending
|
|
||||||
bool send(); //send all data added with the method above
|
|
||||||
|
|
||||||
//write equals add()+send()
|
|
||||||
size_t write(const char * data);
|
|
||||||
size_t write(const char * data, size_t size, uint8_t apiflags = ASYNC_WRITE_FLAG_COPY); //only when canSend() == true
|
|
||||||
|
|
||||||
uint8_t state();
|
|
||||||
bool connecting();
|
|
||||||
bool connected();
|
|
||||||
bool disconnecting();
|
|
||||||
bool disconnected();
|
|
||||||
bool freeable(); //disconnected or disconnecting
|
|
||||||
|
|
||||||
uint16_t getMss();
|
|
||||||
|
|
||||||
uint32_t getRxTimeout();
|
|
||||||
void setRxTimeout(uint32_t timeout); //no RX data timeout for the connection in seconds
|
|
||||||
|
|
||||||
uint32_t getAckTimeout();
|
|
||||||
void setAckTimeout(uint32_t timeout); //no ACK timeout for the last sent packet in milliseconds
|
|
||||||
|
|
||||||
void setNoDelay(bool nodelay);
|
|
||||||
bool getNoDelay();
|
|
||||||
|
|
||||||
void setKeepAlive(uint32_t ms, uint8_t cnt);
|
|
||||||
|
|
||||||
uint32_t getRemoteAddress();
|
|
||||||
ip6_addr_t getRemoteAddress6();
|
|
||||||
uint16_t getRemotePort();
|
|
||||||
uint32_t getLocalAddress();
|
|
||||||
ip6_addr_t getLocalAddress6();
|
|
||||||
uint16_t getLocalPort();
|
|
||||||
|
|
||||||
//compatibility
|
|
||||||
IPAddress remoteIP();
|
|
||||||
IPv6Address remoteIP6();
|
|
||||||
uint16_t remotePort();
|
|
||||||
IPAddress localIP();
|
|
||||||
IPv6Address localIP6();
|
|
||||||
uint16_t localPort();
|
|
||||||
|
|
||||||
void onConnect(AcConnectHandler cb, void * arg = 0); //on successful connect
|
|
||||||
void onDisconnect(AcConnectHandler cb, void * arg = 0); //disconnected
|
|
||||||
void onAck(AcAckHandler cb, void * arg = 0); //ack received
|
|
||||||
void onError(AcErrorHandler cb, void * arg = 0); //unsuccessful connect or error
|
|
||||||
void onData(AcDataHandler cb, void * arg = 0); //data received (called if onPacket is not used)
|
|
||||||
void onPacket(AcPacketHandler cb, void * arg = 0); //data received
|
|
||||||
void onTimeout(AcTimeoutHandler cb, void * arg = 0); //ack timeout
|
|
||||||
void onPoll(AcConnectHandler cb, void * arg = 0); //every 125ms when connected
|
|
||||||
|
|
||||||
void ackPacket(struct pbuf * pb); //ack pbuf from onPacket
|
|
||||||
size_t ack(size_t len); //ack data that you have not acked using the method below
|
|
||||||
void ackLater() {
|
|
||||||
_ack_pcb = false;
|
|
||||||
} //will not ack the current packet. Call from onData
|
|
||||||
|
|
||||||
const char * errorToString(int8_t error);
|
|
||||||
const char * stateToString();
|
|
||||||
|
|
||||||
//Do not use any of the functions below!
|
|
||||||
static int8_t _s_poll(void * arg, struct tcp_pcb * tpcb);
|
|
||||||
static int8_t _s_recv(void * arg, struct tcp_pcb * tpcb, struct pbuf * pb, int8_t err);
|
|
||||||
static int8_t _s_fin(void * arg, struct tcp_pcb * tpcb, int8_t err);
|
|
||||||
static int8_t _s_lwip_fin(void * arg, struct tcp_pcb * tpcb, int8_t err);
|
|
||||||
static void _s_error(void * arg, int8_t err);
|
|
||||||
static int8_t _s_sent(void * arg, struct tcp_pcb * tpcb, uint16_t len);
|
|
||||||
static int8_t _s_connected(void * arg, void * tpcb, int8_t err);
|
|
||||||
static void _s_dns_found(const char * name, struct ip_addr * ipaddr, void * arg);
|
|
||||||
|
|
||||||
int8_t _recv(tcp_pcb * pcb, pbuf * pb, int8_t err);
|
|
||||||
tcp_pcb * pcb() {
|
|
||||||
return _pcb;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
|
||||||
bool _connect(ip_addr_t addr, uint16_t port);
|
|
||||||
|
|
||||||
tcp_pcb * _pcb;
|
|
||||||
int8_t _closed_slot;
|
|
||||||
|
|
||||||
AcConnectHandler _connect_cb;
|
|
||||||
void * _connect_cb_arg;
|
|
||||||
AcConnectHandler _discard_cb;
|
|
||||||
void * _discard_cb_arg;
|
|
||||||
AcAckHandler _sent_cb;
|
|
||||||
void * _sent_cb_arg;
|
|
||||||
AcErrorHandler _error_cb;
|
|
||||||
void * _error_cb_arg;
|
|
||||||
AcDataHandler _recv_cb;
|
|
||||||
void * _recv_cb_arg;
|
|
||||||
AcPacketHandler _pb_cb;
|
|
||||||
void * _pb_cb_arg;
|
|
||||||
AcTimeoutHandler _timeout_cb;
|
|
||||||
void * _timeout_cb_arg;
|
|
||||||
AcConnectHandler _poll_cb;
|
|
||||||
void * _poll_cb_arg;
|
|
||||||
|
|
||||||
bool _pcb_busy;
|
|
||||||
uint32_t _pcb_sent_at;
|
|
||||||
bool _ack_pcb;
|
|
||||||
uint32_t _rx_ack_len;
|
|
||||||
uint32_t _rx_last_packet;
|
|
||||||
uint32_t _rx_since_timeout;
|
|
||||||
uint32_t _ack_timeout;
|
|
||||||
uint16_t _connect_port;
|
|
||||||
|
|
||||||
int8_t _close();
|
|
||||||
void _free_closed_slot();
|
|
||||||
void _allocate_closed_slot();
|
|
||||||
int8_t _connected(void * pcb, int8_t err);
|
|
||||||
void _error(int8_t err);
|
|
||||||
int8_t _poll(tcp_pcb * pcb);
|
|
||||||
int8_t _sent(tcp_pcb * pcb, uint16_t len);
|
|
||||||
int8_t _fin(tcp_pcb * pcb, int8_t err);
|
|
||||||
int8_t _lwip_fin(tcp_pcb * pcb, int8_t err);
|
|
||||||
void _dns_found(struct ip_addr * ipaddr);
|
|
||||||
|
|
||||||
public:
|
|
||||||
AsyncClient * prev;
|
|
||||||
AsyncClient * next;
|
|
||||||
};
|
|
||||||
|
|
||||||
class AsyncServer {
|
|
||||||
public:
|
|
||||||
AsyncServer(IPAddress addr, uint16_t port);
|
|
||||||
AsyncServer(IPv6Address addr, uint16_t port);
|
|
||||||
AsyncServer(uint16_t port);
|
|
||||||
~AsyncServer();
|
|
||||||
void onClient(AcConnectHandler cb, void * arg);
|
|
||||||
void begin();
|
|
||||||
void end();
|
|
||||||
void setNoDelay(bool nodelay);
|
|
||||||
bool getNoDelay();
|
|
||||||
uint8_t status();
|
|
||||||
|
|
||||||
//Do not use any of the functions below!
|
|
||||||
static int8_t _s_accept(void * arg, tcp_pcb * newpcb, int8_t err);
|
|
||||||
static int8_t _s_accepted(void * arg, AsyncClient * client);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
uint16_t _port;
|
|
||||||
bool _bind4 = false;
|
|
||||||
bool _bind6 = false;
|
|
||||||
IPAddress _addr;
|
|
||||||
IPv6Address _addr6;
|
|
||||||
bool _noDelay;
|
|
||||||
tcp_pcb * _pcb;
|
|
||||||
AcConnectHandler _connect_cb;
|
|
||||||
void * _connect_cb_arg;
|
|
||||||
|
|
||||||
int8_t _accept(tcp_pcb * newpcb, int8_t err);
|
|
||||||
int8_t _accepted(AsyncClient * client);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
#endif /* ASYNCTCP_H_ */
|
|
||||||
@@ -1,369 +0,0 @@
|
|||||||
/*
|
|
||||||
Asynchronous WebServer library for Espressif MCUs
|
|
||||||
|
|
||||||
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
|
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
|
||||||
modify it under the terms of the GNU Lesser General Public
|
|
||||||
License as published by the Free Software Foundation; either
|
|
||||||
version 2.1 of the License, or (at your option) any later version.
|
|
||||||
|
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
Lesser General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU Lesser General Public
|
|
||||||
License along with this library; if not, write to the Free Software
|
|
||||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
||||||
*/
|
|
||||||
#include "Arduino.h"
|
|
||||||
#include "AsyncEventSource.h"
|
|
||||||
|
|
||||||
static String generateEventMessage(const char *message, const char *event, uint32_t id, uint32_t reconnect){
|
|
||||||
String ev;
|
|
||||||
|
|
||||||
if(reconnect){
|
|
||||||
ev += F("retry: ");
|
|
||||||
ev += reconnect;
|
|
||||||
ev += F("\r\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
if(id){
|
|
||||||
ev += F("id: ");
|
|
||||||
ev += String(id);
|
|
||||||
ev += F("\r\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
if(event != NULL){
|
|
||||||
ev += F("event: ");
|
|
||||||
ev += String(event);
|
|
||||||
ev += F("\r\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
if(message != NULL){
|
|
||||||
size_t messageLen = strlen(message);
|
|
||||||
char * lineStart = (char *)message;
|
|
||||||
char * lineEnd;
|
|
||||||
do {
|
|
||||||
char * nextN = strchr(lineStart, '\n');
|
|
||||||
char * nextR = strchr(lineStart, '\r');
|
|
||||||
if(nextN == NULL && nextR == NULL){
|
|
||||||
size_t llen = ((char *)message + messageLen) - lineStart;
|
|
||||||
char * ldata = (char *)malloc(llen+1);
|
|
||||||
if(ldata != NULL){
|
|
||||||
memcpy(ldata, lineStart, llen);
|
|
||||||
ldata[llen] = 0;
|
|
||||||
ev += F("data: ");
|
|
||||||
ev += ldata;
|
|
||||||
ev += F("\r\n\r\n");
|
|
||||||
free(ldata);
|
|
||||||
}
|
|
||||||
lineStart = (char *)message + messageLen;
|
|
||||||
} else {
|
|
||||||
char * nextLine = NULL;
|
|
||||||
if(nextN != NULL && nextR != NULL){
|
|
||||||
if(nextR < nextN){
|
|
||||||
lineEnd = nextR;
|
|
||||||
if(nextN == (nextR + 1))
|
|
||||||
nextLine = nextN + 1;
|
|
||||||
else
|
|
||||||
nextLine = nextR + 1;
|
|
||||||
} else {
|
|
||||||
lineEnd = nextN;
|
|
||||||
if(nextR == (nextN + 1))
|
|
||||||
nextLine = nextR + 1;
|
|
||||||
else
|
|
||||||
nextLine = nextN + 1;
|
|
||||||
}
|
|
||||||
} else if(nextN != NULL){
|
|
||||||
lineEnd = nextN;
|
|
||||||
nextLine = nextN + 1;
|
|
||||||
} else {
|
|
||||||
lineEnd = nextR;
|
|
||||||
nextLine = nextR + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t llen = lineEnd - lineStart;
|
|
||||||
char * ldata = (char *)malloc(llen+1);
|
|
||||||
if(ldata != NULL){
|
|
||||||
memcpy(ldata, lineStart, llen);
|
|
||||||
ldata[llen] = 0;
|
|
||||||
ev += F("data: ");
|
|
||||||
ev += ldata;
|
|
||||||
ev += F("\r\n");
|
|
||||||
free(ldata);
|
|
||||||
}
|
|
||||||
lineStart = nextLine;
|
|
||||||
if(lineStart == ((char *)message + messageLen))
|
|
||||||
ev += F("\r\n");
|
|
||||||
}
|
|
||||||
} while(lineStart < ((char *)message + messageLen));
|
|
||||||
}
|
|
||||||
|
|
||||||
return ev;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Message
|
|
||||||
|
|
||||||
AsyncEventSourceMessage::AsyncEventSourceMessage(const char * data, size_t len)
|
|
||||||
: _data(nullptr), _len(len), _sent(0), _acked(0)
|
|
||||||
{
|
|
||||||
_data = (uint8_t*)malloc(_len+1);
|
|
||||||
if(_data == nullptr){
|
|
||||||
_len = 0;
|
|
||||||
} else {
|
|
||||||
memcpy(_data, data, len);
|
|
||||||
_data[_len] = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncEventSourceMessage::~AsyncEventSourceMessage() {
|
|
||||||
if(_data != NULL)
|
|
||||||
free(_data);
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t AsyncEventSourceMessage::ack(size_t len, uint32_t time) {
|
|
||||||
(void)time;
|
|
||||||
// If the whole message is now acked...
|
|
||||||
if(_acked + len > _len){
|
|
||||||
// Return the number of extra bytes acked (they will be carried on to the next message)
|
|
||||||
const size_t extra = _acked + len - _len;
|
|
||||||
_acked = _len;
|
|
||||||
return extra;
|
|
||||||
}
|
|
||||||
// Return that no extra bytes left.
|
|
||||||
_acked += len;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t AsyncEventSourceMessage::send(AsyncClient *client) {
|
|
||||||
const size_t len = _len - _sent;
|
|
||||||
if(client->space() < len){
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
size_t sent = client->add((const char *)_data, len);
|
|
||||||
if(client->canSend())
|
|
||||||
client->send();
|
|
||||||
_sent += sent;
|
|
||||||
return sent;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Client
|
|
||||||
|
|
||||||
AsyncEventSourceClient::AsyncEventSourceClient(AsyncWebServerRequest *request, AsyncEventSource *server)
|
|
||||||
: _messageQueue(LinkedList<AsyncEventSourceMessage *>([](AsyncEventSourceMessage *m){ delete m; }))
|
|
||||||
{
|
|
||||||
_client = request->client();
|
|
||||||
_server = server;
|
|
||||||
_lastId = 0;
|
|
||||||
if(request->hasHeader(F("Last-Event-ID")))
|
|
||||||
_lastId = atoi(request->getHeader(F("Last-Event-ID"))->value().c_str());
|
|
||||||
|
|
||||||
_client->setRxTimeout(0);
|
|
||||||
_client->onError(NULL, NULL);
|
|
||||||
_client->onAck([](void *r, AsyncClient* c, size_t len, uint32_t time){ (void)c; ((AsyncEventSourceClient*)(r))->_onAck(len, time); }, this);
|
|
||||||
_client->onPoll([](void *r, AsyncClient* c){ (void)c; ((AsyncEventSourceClient*)(r))->_onPoll(); }, this);
|
|
||||||
_client->onData(NULL, NULL);
|
|
||||||
_client->onTimeout([this](void *r, AsyncClient* c __attribute__((unused)), uint32_t time){ ((AsyncEventSourceClient*)(r))->_onTimeout(time); }, this);
|
|
||||||
_client->onDisconnect([this](void *r, AsyncClient* c){ ((AsyncEventSourceClient*)(r))->_onDisconnect(); delete c; }, this);
|
|
||||||
|
|
||||||
_server->_addClient(this);
|
|
||||||
delete request;
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncEventSourceClient::~AsyncEventSourceClient(){
|
|
||||||
_messageQueue.free();
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncEventSourceClient::_queueMessage(AsyncEventSourceMessage *dataMessage){
|
|
||||||
if(dataMessage == NULL)
|
|
||||||
return;
|
|
||||||
if(!connected()){
|
|
||||||
delete dataMessage;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(_messageQueue.length() >= SSE_MAX_QUEUED_MESSAGES){
|
|
||||||
// ets_printf(String(F("ERROR: Too many messages queued\n")).c_str());
|
|
||||||
delete dataMessage;
|
|
||||||
} else {
|
|
||||||
_messageQueue.add(dataMessage);
|
|
||||||
}
|
|
||||||
if(_client->canSend())
|
|
||||||
_runQueue();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncEventSourceClient::_onAck(size_t len, uint32_t time){
|
|
||||||
while(len && !_messageQueue.isEmpty()){
|
|
||||||
len = _messageQueue.front()->ack(len, time);
|
|
||||||
if(_messageQueue.front()->finished())
|
|
||||||
_messageQueue.remove(_messageQueue.front());
|
|
||||||
}
|
|
||||||
|
|
||||||
_runQueue();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncEventSourceClient::_onPoll(){
|
|
||||||
if(!_messageQueue.isEmpty()){
|
|
||||||
_runQueue();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void AsyncEventSourceClient::_onTimeout(uint32_t time __attribute__((unused))){
|
|
||||||
_client->close(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncEventSourceClient::_onDisconnect(){
|
|
||||||
_client = NULL;
|
|
||||||
_server->_handleDisconnect(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncEventSourceClient::close(){
|
|
||||||
if(_client != NULL)
|
|
||||||
_client->close();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncEventSourceClient::write(const char * message, size_t len){
|
|
||||||
_queueMessage(new AsyncEventSourceMessage(message, len));
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncEventSourceClient::send(const char *message, const char *event, uint32_t id, uint32_t reconnect){
|
|
||||||
String ev = generateEventMessage(message, event, id, reconnect);
|
|
||||||
_queueMessage(new AsyncEventSourceMessage(ev.c_str(), ev.length()));
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncEventSourceClient::_runQueue(){
|
|
||||||
while(!_messageQueue.isEmpty() && _messageQueue.front()->finished()){
|
|
||||||
_messageQueue.remove(_messageQueue.front());
|
|
||||||
}
|
|
||||||
|
|
||||||
for(auto i = _messageQueue.begin(); i != _messageQueue.end(); ++i)
|
|
||||||
{
|
|
||||||
if(!(*i)->sent())
|
|
||||||
(*i)->send(_client);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Handler
|
|
||||||
|
|
||||||
AsyncEventSource::AsyncEventSource(const String& url)
|
|
||||||
: _url(url)
|
|
||||||
, _clients(LinkedList<AsyncEventSourceClient *>([](AsyncEventSourceClient *c){ delete c; }))
|
|
||||||
, _connectcb(NULL)
|
|
||||||
{}
|
|
||||||
|
|
||||||
AsyncEventSource::~AsyncEventSource(){
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncEventSource::onConnect(ArEventHandlerFunction cb){
|
|
||||||
_connectcb = cb;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncEventSource::_addClient(AsyncEventSourceClient * client){
|
|
||||||
/*char * temp = (char *)malloc(2054);
|
|
||||||
if(temp != NULL){
|
|
||||||
memset(temp+1,' ',2048);
|
|
||||||
temp[0] = ':';
|
|
||||||
temp[2049] = '\r';
|
|
||||||
temp[2050] = '\n';
|
|
||||||
temp[2051] = '\r';
|
|
||||||
temp[2052] = '\n';
|
|
||||||
temp[2053] = 0;
|
|
||||||
client->write((const char *)temp, 2053);
|
|
||||||
free(temp);
|
|
||||||
}*/
|
|
||||||
|
|
||||||
_clients.add(client);
|
|
||||||
if(_connectcb)
|
|
||||||
_connectcb(client);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncEventSource::_handleDisconnect(AsyncEventSourceClient * client){
|
|
||||||
_clients.remove(client);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncEventSource::close(){
|
|
||||||
for(const auto &c: _clients){
|
|
||||||
if(c->connected())
|
|
||||||
c->close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// pmb fix
|
|
||||||
size_t AsyncEventSource::avgPacketsWaiting() const {
|
|
||||||
if(_clients.isEmpty())
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
size_t aql=0;
|
|
||||||
uint32_t nConnectedClients=0;
|
|
||||||
|
|
||||||
for(const auto &c: _clients){
|
|
||||||
if(c->connected()) {
|
|
||||||
aql+=c->packetsWaiting();
|
|
||||||
++nConnectedClients;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// return aql / nConnectedClients;
|
|
||||||
return ((aql) + (nConnectedClients/2))/(nConnectedClients); // round up
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncEventSource::send(const char *message, const char *event, uint32_t id, uint32_t reconnect){
|
|
||||||
|
|
||||||
|
|
||||||
String ev = generateEventMessage(message, event, id, reconnect);
|
|
||||||
for(const auto &c: _clients){
|
|
||||||
if(c->connected()) {
|
|
||||||
c->write(ev.c_str(), ev.length());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t AsyncEventSource::count() const {
|
|
||||||
return _clients.count_if([](AsyncEventSourceClient *c){
|
|
||||||
return c->connected();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AsyncEventSource::canHandle(AsyncWebServerRequest *request){
|
|
||||||
if(request->method() != HTTP_GET || !request->url().equals(_url)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
request->addInterestingHeader(F("Last-Event-ID"));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncEventSource::handleRequest(AsyncWebServerRequest *request){
|
|
||||||
if((_username.length() && _password.length()) && !request->authenticate(_username.c_str(), _password.c_str())) {
|
|
||||||
return request->requestAuthentication();
|
|
||||||
}
|
|
||||||
request->send(new AsyncEventSourceResponse(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Response
|
|
||||||
|
|
||||||
AsyncEventSourceResponse::AsyncEventSourceResponse(AsyncEventSource *server){
|
|
||||||
_server = server;
|
|
||||||
_code = 200;
|
|
||||||
_contentType = F("text/event-stream");
|
|
||||||
_sendContentLength = false;
|
|
||||||
addHeader(F("Cache-Control"), F("no-cache"));
|
|
||||||
addHeader(F("Connection"), F("keep-alive"));
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncEventSourceResponse::_respond(AsyncWebServerRequest *request){
|
|
||||||
String out = _assembleHead(request->version());
|
|
||||||
request->client()->write(out.c_str(), _headLength);
|
|
||||||
_state = RESPONSE_WAIT_ACK;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t AsyncEventSourceResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time __attribute__((unused))){
|
|
||||||
if(len){
|
|
||||||
new AsyncEventSourceClient(request, _server);
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,133 +0,0 @@
|
|||||||
/*
|
|
||||||
Asynchronous WebServer library for Espressif MCUs
|
|
||||||
|
|
||||||
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
|
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
|
||||||
modify it under the terms of the GNU Lesser General Public
|
|
||||||
License as published by the Free Software Foundation; either
|
|
||||||
version 2.1 of the License, or (at your option) any later version.
|
|
||||||
|
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
Lesser General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU Lesser General Public
|
|
||||||
License along with this library; if not, write to the Free Software
|
|
||||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
||||||
*/
|
|
||||||
#ifndef ASYNCEVENTSOURCE_H_
|
|
||||||
#define ASYNCEVENTSOURCE_H_
|
|
||||||
|
|
||||||
#include <Arduino.h>
|
|
||||||
#ifdef ESP32
|
|
||||||
#include <AsyncTCP.h>
|
|
||||||
#define SSE_MAX_QUEUED_MESSAGES 32
|
|
||||||
#else
|
|
||||||
#include <ESPAsyncTCP.h>
|
|
||||||
#define SSE_MAX_QUEUED_MESSAGES 8
|
|
||||||
#endif
|
|
||||||
#include <ESPAsyncWebServer.h>
|
|
||||||
|
|
||||||
#include "AsyncWebSynchronization.h"
|
|
||||||
|
|
||||||
#ifdef ESP8266
|
|
||||||
#include <Hash.h>
|
|
||||||
#ifdef CRYPTO_HASH_h // include Hash.h from espressif framework if the first include was from the crypto library
|
|
||||||
#include <../src/Hash.h>
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef ESP32
|
|
||||||
#define DEFAULT_MAX_SSE_CLIENTS 8
|
|
||||||
#else
|
|
||||||
#define DEFAULT_MAX_SSE_CLIENTS 4
|
|
||||||
#endif
|
|
||||||
|
|
||||||
class AsyncEventSource;
|
|
||||||
class AsyncEventSourceResponse;
|
|
||||||
class AsyncEventSourceClient;
|
|
||||||
typedef std::function<void(AsyncEventSourceClient *client)> ArEventHandlerFunction;
|
|
||||||
|
|
||||||
class AsyncEventSourceMessage {
|
|
||||||
private:
|
|
||||||
uint8_t * _data;
|
|
||||||
size_t _len;
|
|
||||||
size_t _sent;
|
|
||||||
//size_t _ack;
|
|
||||||
size_t _acked;
|
|
||||||
public:
|
|
||||||
AsyncEventSourceMessage(const char * data, size_t len);
|
|
||||||
~AsyncEventSourceMessage();
|
|
||||||
size_t ack(size_t len, uint32_t time __attribute__((unused)));
|
|
||||||
size_t send(AsyncClient *client);
|
|
||||||
bool finished(){ return _acked == _len; }
|
|
||||||
bool sent() { return _sent == _len; }
|
|
||||||
};
|
|
||||||
|
|
||||||
class AsyncEventSourceClient {
|
|
||||||
private:
|
|
||||||
AsyncClient *_client;
|
|
||||||
AsyncEventSource *_server;
|
|
||||||
uint32_t _lastId;
|
|
||||||
LinkedList<AsyncEventSourceMessage *> _messageQueue;
|
|
||||||
void _queueMessage(AsyncEventSourceMessage *dataMessage);
|
|
||||||
void _runQueue();
|
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
AsyncEventSourceClient(AsyncWebServerRequest *request, AsyncEventSource *server);
|
|
||||||
~AsyncEventSourceClient();
|
|
||||||
|
|
||||||
AsyncClient* client(){ return _client; }
|
|
||||||
void close();
|
|
||||||
void write(const char * message, size_t len);
|
|
||||||
void send(const char *message, const char *event=NULL, uint32_t id=0, uint32_t reconnect=0);
|
|
||||||
bool connected() const { return (_client != NULL) && _client->connected(); }
|
|
||||||
uint32_t lastId() const { return _lastId; }
|
|
||||||
size_t packetsWaiting() const { return _messageQueue.length(); }
|
|
||||||
|
|
||||||
//system callbacks (do not call)
|
|
||||||
void _onAck(size_t len, uint32_t time);
|
|
||||||
void _onPoll();
|
|
||||||
void _onTimeout(uint32_t time);
|
|
||||||
void _onDisconnect();
|
|
||||||
};
|
|
||||||
|
|
||||||
class AsyncEventSource: public AsyncWebHandler {
|
|
||||||
private:
|
|
||||||
String _url;
|
|
||||||
LinkedList<AsyncEventSourceClient *> _clients;
|
|
||||||
ArEventHandlerFunction _connectcb;
|
|
||||||
public:
|
|
||||||
AsyncEventSource(const String& url);
|
|
||||||
~AsyncEventSource();
|
|
||||||
|
|
||||||
const char * url() const { return _url.c_str(); }
|
|
||||||
void close();
|
|
||||||
void onConnect(ArEventHandlerFunction cb);
|
|
||||||
void send(const char *message, const char *event=NULL, uint32_t id=0, uint32_t reconnect=0);
|
|
||||||
size_t count() const; //number clinets connected
|
|
||||||
size_t avgPacketsWaiting() const;
|
|
||||||
|
|
||||||
//system callbacks (do not call)
|
|
||||||
void _addClient(AsyncEventSourceClient * client);
|
|
||||||
void _handleDisconnect(AsyncEventSourceClient * client);
|
|
||||||
virtual bool canHandle(AsyncWebServerRequest *request) override final;
|
|
||||||
virtual void handleRequest(AsyncWebServerRequest *request) override final;
|
|
||||||
};
|
|
||||||
|
|
||||||
class AsyncEventSourceResponse: public AsyncWebServerResponse {
|
|
||||||
private:
|
|
||||||
String _content;
|
|
||||||
AsyncEventSource *_server;
|
|
||||||
public:
|
|
||||||
AsyncEventSourceResponse(AsyncEventSource *server);
|
|
||||||
void _respond(AsyncWebServerRequest *request);
|
|
||||||
size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time);
|
|
||||||
bool _sourceValid() const { return true; }
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
#endif /* ASYNCEVENTSOURCE_H_ */
|
|
||||||
@@ -1,254 +0,0 @@
|
|||||||
// AsyncJson.h
|
|
||||||
/*
|
|
||||||
Async Response to use with ArduinoJson and AsyncWebServer
|
|
||||||
Written by Andrew Melvin (SticilFace) with help from me-no-dev and BBlanchon.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef ASYNC_JSON_H_
|
|
||||||
#define ASYNC_JSON_H_
|
|
||||||
#include <ArduinoJson.h>
|
|
||||||
#include <ESPAsyncWebServer.h>
|
|
||||||
#include <Print.h>
|
|
||||||
|
|
||||||
#define DYNAMIC_JSON_DOCUMENT_SIZE 1024
|
|
||||||
|
|
||||||
constexpr const char * JSON_MIMETYPE = "application/json";
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Json Response
|
|
||||||
*/
|
|
||||||
|
|
||||||
class ChunkPrint : public Print {
|
|
||||||
private:
|
|
||||||
uint8_t * _destination;
|
|
||||||
size_t _to_skip;
|
|
||||||
size_t _to_write;
|
|
||||||
size_t _pos;
|
|
||||||
|
|
||||||
public:
|
|
||||||
ChunkPrint(uint8_t * destination, size_t from, size_t len)
|
|
||||||
: _destination(destination)
|
|
||||||
, _to_skip(from)
|
|
||||||
, _to_write(len)
|
|
||||||
, _pos{0} {
|
|
||||||
}
|
|
||||||
virtual ~ChunkPrint() {
|
|
||||||
}
|
|
||||||
size_t write(uint8_t c) {
|
|
||||||
if (_to_skip > 0) {
|
|
||||||
_to_skip--;
|
|
||||||
return 1;
|
|
||||||
} else if (_to_write > 0) {
|
|
||||||
_to_write--;
|
|
||||||
_destination[_pos++] = c;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
size_t write(const uint8_t * buffer, size_t size) {
|
|
||||||
return this->Print::write(buffer, size);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// added by Proddy
|
|
||||||
class MsgpackAsyncJsonResponse : public AsyncAbstractResponse {
|
|
||||||
protected:
|
|
||||||
DynamicJsonDocument _jsonBuffer;
|
|
||||||
JsonVariant _root;
|
|
||||||
bool _isValid;
|
|
||||||
|
|
||||||
public:
|
|
||||||
MsgpackAsyncJsonResponse(bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE)
|
|
||||||
: _jsonBuffer(maxJsonBufferSize)
|
|
||||||
, _isValid{false} {
|
|
||||||
_code = 200;
|
|
||||||
_contentType = JSON_MIMETYPE;
|
|
||||||
if (isArray)
|
|
||||||
_root = _jsonBuffer.createNestedArray();
|
|
||||||
else
|
|
||||||
_root = _jsonBuffer.createNestedObject();
|
|
||||||
}
|
|
||||||
|
|
||||||
~MsgpackAsyncJsonResponse() {
|
|
||||||
}
|
|
||||||
JsonVariant & getRoot() {
|
|
||||||
return _root;
|
|
||||||
}
|
|
||||||
bool _sourceValid() const {
|
|
||||||
return _isValid;
|
|
||||||
}
|
|
||||||
size_t setLength() {
|
|
||||||
_contentLength = measureMsgPack(_root);
|
|
||||||
// EMS-ESP
|
|
||||||
//_headers.add(new AsyncWebHeader("Json-Length", String(_jsonBuffer.memoryUsage()))); // For determining size of EMSESP_JSON_SIZE_XXLARGE (Sunbuzz)
|
|
||||||
// Json-Length: 10635
|
|
||||||
if (_contentLength) {
|
|
||||||
_isValid = true;
|
|
||||||
}
|
|
||||||
return _contentLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t getSize() {
|
|
||||||
return _jsonBuffer.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t _fillBuffer(uint8_t * data, size_t len) {
|
|
||||||
ChunkPrint dest(data, _sentLength, len);
|
|
||||||
serializeMsgPack(_root, dest);
|
|
||||||
return len;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class AsyncJsonResponse : public AsyncAbstractResponse {
|
|
||||||
protected:
|
|
||||||
DynamicJsonDocument _jsonBuffer;
|
|
||||||
|
|
||||||
JsonVariant _root;
|
|
||||||
bool _isValid;
|
|
||||||
|
|
||||||
public:
|
|
||||||
AsyncJsonResponse(bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE)
|
|
||||||
: _jsonBuffer(maxJsonBufferSize)
|
|
||||||
, _isValid{false} {
|
|
||||||
_code = 200;
|
|
||||||
_contentType = JSON_MIMETYPE;
|
|
||||||
if (isArray)
|
|
||||||
_root = _jsonBuffer.createNestedArray();
|
|
||||||
else
|
|
||||||
_root = _jsonBuffer.createNestedObject();
|
|
||||||
}
|
|
||||||
|
|
||||||
~AsyncJsonResponse() {
|
|
||||||
}
|
|
||||||
JsonVariant & getRoot() {
|
|
||||||
return _root;
|
|
||||||
}
|
|
||||||
bool _sourceValid() const {
|
|
||||||
return _isValid;
|
|
||||||
}
|
|
||||||
size_t setLength() {
|
|
||||||
_contentLength = measureJson(_root);
|
|
||||||
if (_contentLength) {
|
|
||||||
_isValid = true;
|
|
||||||
}
|
|
||||||
return _contentLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t getSize() {
|
|
||||||
return _jsonBuffer.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t _fillBuffer(uint8_t * data, size_t len) {
|
|
||||||
ChunkPrint dest(data, _sentLength, len);
|
|
||||||
serializeJson(_root, dest);
|
|
||||||
return len;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class PrettyAsyncJsonResponse : public AsyncJsonResponse {
|
|
||||||
public:
|
|
||||||
PrettyAsyncJsonResponse(bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE)
|
|
||||||
: AsyncJsonResponse{isArray, maxJsonBufferSize} {
|
|
||||||
}
|
|
||||||
size_t setLength() {
|
|
||||||
_contentLength = measureJsonPretty(_root);
|
|
||||||
if (_contentLength) {
|
|
||||||
_isValid = true;
|
|
||||||
}
|
|
||||||
return _contentLength;
|
|
||||||
}
|
|
||||||
size_t _fillBuffer(uint8_t * data, size_t len) {
|
|
||||||
ChunkPrint dest(data, _sentLength, len);
|
|
||||||
serializeJsonPretty(_root, dest);
|
|
||||||
return len;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef std::function<void(AsyncWebServerRequest * request, JsonVariant & json)> ArJsonRequestHandlerFunction;
|
|
||||||
|
|
||||||
class AsyncCallbackJsonWebHandler : public AsyncWebHandler {
|
|
||||||
private:
|
|
||||||
protected:
|
|
||||||
const String _uri;
|
|
||||||
WebRequestMethodComposite _method;
|
|
||||||
ArJsonRequestHandlerFunction _onRequest;
|
|
||||||
size_t _contentLength;
|
|
||||||
#ifndef ARDUINOJSON_5_COMPATIBILITY
|
|
||||||
size_t _maxJsonBufferSize;
|
|
||||||
#endif
|
|
||||||
size_t _maxContentLength;
|
|
||||||
|
|
||||||
public:
|
|
||||||
AsyncCallbackJsonWebHandler(const String & uri, ArJsonRequestHandlerFunction onRequest, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE)
|
|
||||||
: _uri(uri)
|
|
||||||
, _method(HTTP_POST | HTTP_PUT | HTTP_PATCH)
|
|
||||||
, _onRequest(onRequest)
|
|
||||||
, _maxJsonBufferSize(maxJsonBufferSize)
|
|
||||||
, _maxContentLength(16384) {
|
|
||||||
}
|
|
||||||
|
|
||||||
void setMethod(WebRequestMethodComposite method) {
|
|
||||||
_method = method;
|
|
||||||
}
|
|
||||||
void setMaxContentLength(int maxContentLength) {
|
|
||||||
_maxContentLength = maxContentLength;
|
|
||||||
}
|
|
||||||
void setMaxJsonBufferSize(size_t maxJsonBufferSize) {
|
|
||||||
_maxJsonBufferSize = maxJsonBufferSize;
|
|
||||||
}
|
|
||||||
void onRequest(ArJsonRequestHandlerFunction fn) {
|
|
||||||
_onRequest = fn;
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual bool canHandle(AsyncWebServerRequest * request) override final {
|
|
||||||
if (!_onRequest)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!(_method & request->method()))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri + "/")))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!request->contentType().equalsIgnoreCase(JSON_MIMETYPE))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
request->addInterestingHeader("ANY");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual void handleRequest(AsyncWebServerRequest * request) override final {
|
|
||||||
if (_onRequest) {
|
|
||||||
if (request->_tempObject != NULL) {
|
|
||||||
DynamicJsonDocument jsonBuffer(this->_maxJsonBufferSize);
|
|
||||||
DeserializationError error = deserializeJson(jsonBuffer, (uint8_t *)(request->_tempObject));
|
|
||||||
if (!error) {
|
|
||||||
JsonVariant json = jsonBuffer.as<JsonVariant>();
|
|
||||||
_onRequest(request, json);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
request->send(_contentLength > _maxContentLength ? 413 : 400);
|
|
||||||
} else {
|
|
||||||
request->send(500);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
virtual void handleUpload(AsyncWebServerRequest * request, const String & filename, size_t index, uint8_t * data, size_t len, bool final) override final {
|
|
||||||
}
|
|
||||||
virtual void handleBody(AsyncWebServerRequest * request, uint8_t * data, size_t len, size_t index, size_t total) override final {
|
|
||||||
if (_onRequest) {
|
|
||||||
_contentLength = total;
|
|
||||||
if (total > 0 && request->_tempObject == NULL && total < _maxContentLength) {
|
|
||||||
request->_tempObject = malloc(total);
|
|
||||||
}
|
|
||||||
if (request->_tempObject != NULL) {
|
|
||||||
memcpy((uint8_t *)(request->_tempObject) + index, data, len);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
virtual bool isRequestHandlerTrivial() override final {
|
|
||||||
return _onRequest ? false : true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,448 +0,0 @@
|
|||||||
/*
|
|
||||||
Asynchronous WebServer library for Espressif MCUs
|
|
||||||
|
|
||||||
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
|
|
||||||
This file is part of the esp8266 core for Arduino environment.
|
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
|
||||||
modify it under the terms of the GNU Lesser General Public
|
|
||||||
License as published by the Free Software Foundation; either
|
|
||||||
version 2.1 of the License, or (at your option) any later version.
|
|
||||||
|
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
Lesser General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU Lesser General Public
|
|
||||||
License along with this library; if not, write to the Free Software
|
|
||||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
||||||
*/
|
|
||||||
#ifndef ASYNCWEBSOCKET_H_
|
|
||||||
#define ASYNCWEBSOCKET_H_
|
|
||||||
|
|
||||||
#include <Arduino.h>
|
|
||||||
#ifdef ESP32
|
|
||||||
#include <AsyncTCP.h>
|
|
||||||
#ifndef WS_MAX_QUEUED_MESSAGES
|
|
||||||
#define WS_MAX_QUEUED_MESSAGES 32
|
|
||||||
#endif
|
|
||||||
#ifndef WS_MAX_QUEUED_MESSAGES_SIZE
|
|
||||||
#define WS_MAX_QUEUED_MESSAGES_SIZE 65535
|
|
||||||
#endif
|
|
||||||
#else
|
|
||||||
#include <ESPAsyncTCP.h>
|
|
||||||
#ifndef WS_MAX_QUEUED_MESSAGES
|
|
||||||
#define WS_MAX_QUEUED_MESSAGES 8
|
|
||||||
#endif
|
|
||||||
#ifndef WS_MAX_QUEUED_MESSAGES_SIZE
|
|
||||||
#define WS_MAX_QUEUED_MESSAGES_SIZE 8192
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
#include <ESPAsyncWebServer.h>
|
|
||||||
|
|
||||||
// 0 = disable
|
|
||||||
#ifndef WS_MAX_QUEUED_MESSAGES_MIN_HEAP
|
|
||||||
#define WS_MAX_QUEUED_MESSAGES_MIN_HEAP 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "AsyncWebSynchronization.h"
|
|
||||||
|
|
||||||
#ifdef ESP8266
|
|
||||||
#include <Hash.h>
|
|
||||||
#ifdef CRYPTO_HASH_h // include Hash.h from espressif framework if the first include was from the crypto library
|
|
||||||
#include <../src/Hash.h>
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef ESP32
|
|
||||||
#define DEFAULT_MAX_WS_CLIENTS 8
|
|
||||||
#else
|
|
||||||
#define DEFAULT_MAX_WS_CLIENTS 4
|
|
||||||
#endif
|
|
||||||
|
|
||||||
class AsyncWebSocket;
|
|
||||||
class AsyncWebSocketResponse;
|
|
||||||
class AsyncWebSocketClient;
|
|
||||||
class AsyncWebSocketControl;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
/** Message type as defined by enum AwsFrameType.
|
|
||||||
* Note: Applications will only see WS_TEXT and WS_BINARY.
|
|
||||||
* All other types are handled by the library. */
|
|
||||||
uint8_t message_opcode;
|
|
||||||
/** Frame number of a fragmented message. */
|
|
||||||
uint32_t num;
|
|
||||||
/** Is this the last frame in a fragmented message ?*/
|
|
||||||
uint8_t final;
|
|
||||||
/** Is this frame masked? */
|
|
||||||
uint8_t masked;
|
|
||||||
/** Message type as defined by enum AwsFrameType.
|
|
||||||
* This value is the same as message_opcode for non-fragmented
|
|
||||||
* messages, but may also be WS_CONTINUATION in a fragmented message. */
|
|
||||||
uint8_t opcode;
|
|
||||||
/** Length of the current frame.
|
|
||||||
* This equals the total length of the message if num == 0 && final == true */
|
|
||||||
uint64_t len;
|
|
||||||
/** Mask key */
|
|
||||||
uint8_t mask[4];
|
|
||||||
/** Offset of the data inside the current frame. */
|
|
||||||
uint64_t index;
|
|
||||||
} AwsFrameInfo;
|
|
||||||
|
|
||||||
typedef enum { WS_DISCONNECTED, WS_CONNECTED, WS_DISCONNECTING } AwsClientStatus;
|
|
||||||
typedef enum { WS_CONTINUATION, WS_TEXT, WS_BINARY, WS_DISCONNECT = 0x08, WS_PING, WS_PONG } AwsFrameType;
|
|
||||||
typedef enum { WS_MSG_SENDING, WS_MSG_SENT, WS_MSG_ERROR } AwsMessageStatus;
|
|
||||||
typedef enum { WS_EVT_CONNECT, WS_EVT_DISCONNECT, WS_EVT_PONG, WS_EVT_ERROR, WS_EVT_DATA } AwsEventType;
|
|
||||||
|
|
||||||
class AsyncWebSocketMessageBuffer {
|
|
||||||
private:
|
|
||||||
uint8_t * _data;
|
|
||||||
size_t _len;
|
|
||||||
bool _lock;
|
|
||||||
uint32_t _count;
|
|
||||||
|
|
||||||
public:
|
|
||||||
AsyncWebSocketMessageBuffer();
|
|
||||||
AsyncWebSocketMessageBuffer(size_t size);
|
|
||||||
AsyncWebSocketMessageBuffer(uint8_t * data, size_t size);
|
|
||||||
AsyncWebSocketMessageBuffer(const AsyncWebSocketMessageBuffer &);
|
|
||||||
AsyncWebSocketMessageBuffer(AsyncWebSocketMessageBuffer &&);
|
|
||||||
~AsyncWebSocketMessageBuffer();
|
|
||||||
void operator ++(int i) { (void)i; _count++; }
|
|
||||||
void operator --(int i) { (void)i; if (_count > 0) { _count--; } ; }
|
|
||||||
bool reserve(size_t size);
|
|
||||||
void lock() { _lock = true; }
|
|
||||||
void unlock() { _lock = false; }
|
|
||||||
uint8_t * get() { return _data; }
|
|
||||||
size_t length() { return _len; }
|
|
||||||
uint32_t count() { return _count; }
|
|
||||||
bool canDelete() { return (!_count && !_lock); }
|
|
||||||
|
|
||||||
friend AsyncWebSocket;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
class AsyncWebSocketMessage {
|
|
||||||
protected:
|
|
||||||
uint8_t _opcode;
|
|
||||||
bool _mask;
|
|
||||||
AwsMessageStatus _status;
|
|
||||||
public:
|
|
||||||
AsyncWebSocketMessage():_opcode(WS_TEXT),_mask(false),_status(WS_MSG_ERROR){}
|
|
||||||
virtual ~AsyncWebSocketMessage(){}
|
|
||||||
virtual void ack(size_t len __attribute__((unused)), uint32_t time __attribute__((unused))){}
|
|
||||||
virtual size_t send(AsyncClient *client __attribute__((unused))){ return 0; }
|
|
||||||
virtual bool finished(){ return _status != WS_MSG_SENDING; }
|
|
||||||
virtual bool betweenFrames() const { return false; }
|
|
||||||
};
|
|
||||||
|
|
||||||
class AsyncWebSocketBasicMessage: public AsyncWebSocketMessage {
|
|
||||||
private:
|
|
||||||
size_t _len;
|
|
||||||
size_t _sent;
|
|
||||||
size_t _ack;
|
|
||||||
size_t _acked;
|
|
||||||
uint8_t * _data;
|
|
||||||
public:
|
|
||||||
AsyncWebSocketBasicMessage(const char * data, size_t len, uint8_t opcode=WS_TEXT, bool mask=false);
|
|
||||||
AsyncWebSocketBasicMessage(uint8_t opcode=WS_TEXT, bool mask=false);
|
|
||||||
virtual ~AsyncWebSocketBasicMessage() override;
|
|
||||||
virtual bool betweenFrames() const override { return _acked == _ack; }
|
|
||||||
virtual void ack(size_t len, uint32_t time) override ;
|
|
||||||
virtual size_t send(AsyncClient *client) override ;
|
|
||||||
};
|
|
||||||
|
|
||||||
class AsyncWebSocketMultiMessage: public AsyncWebSocketMessage {
|
|
||||||
private:
|
|
||||||
uint8_t * _data;
|
|
||||||
size_t _len;
|
|
||||||
size_t _sent;
|
|
||||||
size_t _ack;
|
|
||||||
size_t _acked;
|
|
||||||
AsyncWebSocketMessageBuffer * _WSbuffer;
|
|
||||||
public:
|
|
||||||
AsyncWebSocketMultiMessage(AsyncWebSocketMessageBuffer * buffer, uint8_t opcode=WS_TEXT, bool mask=false);
|
|
||||||
virtual ~AsyncWebSocketMultiMessage() override;
|
|
||||||
virtual bool betweenFrames() const override { return _acked == _ack; }
|
|
||||||
virtual void ack(size_t len, uint32_t time) override ;
|
|
||||||
virtual size_t send(AsyncClient *client) override ;
|
|
||||||
};
|
|
||||||
|
|
||||||
class AsyncWebSocketMessageBufferLinkedList : public LinkedList<AsyncWebSocketMessageBuffer *> {
|
|
||||||
public:
|
|
||||||
using T = AsyncWebSocketMessageBuffer *;
|
|
||||||
using LT = LinkedList<T>;
|
|
||||||
|
|
||||||
private:
|
|
||||||
void onRemove(AsyncWebSocketMessageBuffer *t) {
|
|
||||||
_totalSize -= t->length();
|
|
||||||
_totalCount--;
|
|
||||||
delete t;
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
AsyncWebSocketMessageBufferLinkedList() : LT([this](AsyncWebSocketMessageBuffer *b){ this->onRemove(b); }) {
|
|
||||||
}
|
|
||||||
|
|
||||||
void add(const T& t){
|
|
||||||
_totalSize += t->length();
|
|
||||||
_totalCount++;
|
|
||||||
LT::add(t);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
friend class AsyncWebSocket;
|
|
||||||
|
|
||||||
// counters for all web sockets
|
|
||||||
static size_t _totalCount;
|
|
||||||
static size_t _totalSize;
|
|
||||||
};
|
|
||||||
|
|
||||||
class AsyncWebSocketClient {
|
|
||||||
private:
|
|
||||||
AsyncClient *_client;
|
|
||||||
AsyncWebSocket *_server;
|
|
||||||
uint32_t _clientId;
|
|
||||||
AwsClientStatus _status;
|
|
||||||
|
|
||||||
LinkedList<AsyncWebSocketControl *> _controlQueue;
|
|
||||||
LinkedList<AsyncWebSocketMessage *> _messageQueue;
|
|
||||||
|
|
||||||
uint8_t _pstate;
|
|
||||||
AwsFrameInfo _pinfo;
|
|
||||||
|
|
||||||
uint32_t _lastMessageTime;
|
|
||||||
uint32_t _keepAlivePeriod;
|
|
||||||
|
|
||||||
void _queueMessage(AsyncWebSocketMessage *dataMessage);
|
|
||||||
void _queueControl(AsyncWebSocketControl *controlMessage);
|
|
||||||
void _runQueue();
|
|
||||||
|
|
||||||
public:
|
|
||||||
void *_tempObject;
|
|
||||||
|
|
||||||
AsyncWebSocketClient(AsyncWebServerRequest *request, AsyncWebSocket *server);
|
|
||||||
~AsyncWebSocketClient();
|
|
||||||
|
|
||||||
//client id increments for the given server
|
|
||||||
uint32_t id(){ return _clientId; }
|
|
||||||
AwsClientStatus status(){ return _status; }
|
|
||||||
AsyncClient* client(){ return _client; }
|
|
||||||
AsyncWebSocket *server(){ return _server; }
|
|
||||||
AwsFrameInfo const &pinfo() const { return _pinfo; }
|
|
||||||
|
|
||||||
IPAddress remoteIP();
|
|
||||||
uint16_t remotePort();
|
|
||||||
|
|
||||||
//control frames
|
|
||||||
void close(uint16_t code=0, const char * message=NULL);
|
|
||||||
void ping(uint8_t *data=NULL, size_t len=0);
|
|
||||||
|
|
||||||
//set auto-ping period in seconds. disabled if zero (default)
|
|
||||||
void keepAlivePeriod(uint16_t seconds){
|
|
||||||
_keepAlivePeriod = seconds * 1000;
|
|
||||||
}
|
|
||||||
uint16_t keepAlivePeriod(){
|
|
||||||
return (uint16_t)(_keepAlivePeriod / 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
//data packets
|
|
||||||
void message(AsyncWebSocketMessage *message){ _queueMessage(message); }
|
|
||||||
bool queueIsFull();
|
|
||||||
|
|
||||||
size_t printf(const char *format, ...) __attribute__ ((format (printf, 2, 3)));
|
|
||||||
#ifndef ESP32
|
|
||||||
size_t printf_P(PGM_P formatP, ...) __attribute__ ((format (printf, 2, 3)));
|
|
||||||
#endif
|
|
||||||
void text(const char * message, size_t len);
|
|
||||||
void text(const char * message);
|
|
||||||
void text(uint8_t * message, size_t len);
|
|
||||||
void text(char * message);
|
|
||||||
void text(const String &message);
|
|
||||||
void text(const __FlashStringHelper *data);
|
|
||||||
void text(AsyncWebSocketMessageBuffer *buffer);
|
|
||||||
|
|
||||||
void binary(const char * message, size_t len);
|
|
||||||
void binary(const char * message);
|
|
||||||
void binary(uint8_t * message, size_t len);
|
|
||||||
void binary(char * message);
|
|
||||||
void binary(const String &message);
|
|
||||||
void binary(const __FlashStringHelper *data, size_t len);
|
|
||||||
void binary(AsyncWebSocketMessageBuffer *buffer);
|
|
||||||
|
|
||||||
bool canSend() { return !_queueIsFull(); }
|
|
||||||
|
|
||||||
bool _queueIsFull() const;
|
|
||||||
|
|
||||||
//system callbacks (do not call)
|
|
||||||
void _onAck(size_t len, uint32_t time);
|
|
||||||
void _onError(int8_t);
|
|
||||||
void _onPoll();
|
|
||||||
void _onTimeout(uint32_t time);
|
|
||||||
void _onDisconnect();
|
|
||||||
void _onData(void *pbuf, size_t plen);
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef std::function<void(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len)> AwsEventHandler;
|
|
||||||
|
|
||||||
//WebServer Handler implementation that plays the role of a socket server
|
|
||||||
class AsyncWebSocket: public AsyncWebHandler {
|
|
||||||
public:
|
|
||||||
typedef LinkedList<AsyncWebSocketClient *> AsyncWebSocketClientLinkedList;
|
|
||||||
private:
|
|
||||||
String _url;
|
|
||||||
AsyncWebSocketClientLinkedList _clients;
|
|
||||||
uint32_t _cNextId;
|
|
||||||
AwsEventHandler _eventHandler;
|
|
||||||
bool _enabled;
|
|
||||||
AsyncWebLock _lock;
|
|
||||||
|
|
||||||
public:
|
|
||||||
AsyncWebSocket(const String& url);
|
|
||||||
~AsyncWebSocket();
|
|
||||||
const char * url() const { return _url.c_str(); }
|
|
||||||
void enable(bool e){ _enabled = e; }
|
|
||||||
bool enabled() const { return _enabled; }
|
|
||||||
bool availableForWriteAll();
|
|
||||||
bool availableForWrite(uint32_t id);
|
|
||||||
|
|
||||||
size_t count() const;
|
|
||||||
AsyncWebSocketClient * client(uint32_t id);
|
|
||||||
bool hasClient(uint32_t id){ return client(id) != NULL; }
|
|
||||||
|
|
||||||
void close(uint32_t id, uint16_t code=0, const char * message=NULL);
|
|
||||||
void closeAll(uint16_t code=0, const char * message=NULL);
|
|
||||||
void cleanupClients(uint16_t maxClients = DEFAULT_MAX_WS_CLIENTS);
|
|
||||||
|
|
||||||
void ping(uint32_t id, uint8_t *data=NULL, size_t len=0);
|
|
||||||
void pingAll(uint8_t *data=NULL, size_t len=0); // done
|
|
||||||
|
|
||||||
void text(uint32_t id, const char * message, size_t len);
|
|
||||||
void text(uint32_t id, const char * message);
|
|
||||||
void text(uint32_t id, uint8_t * message, size_t len);
|
|
||||||
void text(uint32_t id, char * message);
|
|
||||||
void text(uint32_t id, const String &message);
|
|
||||||
void text(uint32_t id, const __FlashStringHelper *message);
|
|
||||||
|
|
||||||
void textAll(const char * message, size_t len);
|
|
||||||
void textAll(const char * message);
|
|
||||||
void textAll(uint8_t * message, size_t len);
|
|
||||||
void textAll(char * message);
|
|
||||||
void textAll(const String &message);
|
|
||||||
void textAll(const __FlashStringHelper *message); // need to convert
|
|
||||||
void textAll(AsyncWebSocketMessageBuffer * buffer);
|
|
||||||
|
|
||||||
void binary(uint32_t id, const char * message, size_t len);
|
|
||||||
void binary(uint32_t id, const char * message);
|
|
||||||
void binary(uint32_t id, uint8_t * message, size_t len);
|
|
||||||
void binary(uint32_t id, char * message);
|
|
||||||
void binary(uint32_t id, const String &message);
|
|
||||||
void binary(uint32_t id, const __FlashStringHelper *message, size_t len);
|
|
||||||
|
|
||||||
void binaryAll(const char * message, size_t len);
|
|
||||||
void binaryAll(const char * message);
|
|
||||||
void binaryAll(uint8_t * message, size_t len);
|
|
||||||
void binaryAll(char * message);
|
|
||||||
void binaryAll(const String &message);
|
|
||||||
void binaryAll(const __FlashStringHelper *message, size_t len);
|
|
||||||
void binaryAll(AsyncWebSocketMessageBuffer * buffer);
|
|
||||||
|
|
||||||
void message(uint32_t id, AsyncWebSocketMessage *message);
|
|
||||||
void messageAll(AsyncWebSocketMultiMessage *message);
|
|
||||||
|
|
||||||
size_t printf(uint32_t id, const char *format, ...) __attribute__ ((format (printf, 3, 4)));
|
|
||||||
size_t printfAll(const char *format, ...) __attribute__ ((format (printf, 2, 3)));
|
|
||||||
#ifndef ESP32
|
|
||||||
size_t printf_P(uint32_t id, PGM_P formatP, ...) __attribute__ ((format (printf, 3, 4)));
|
|
||||||
#endif
|
|
||||||
size_t printfAll_P(PGM_P formatP, ...) __attribute__ ((format (printf, 2, 3)));
|
|
||||||
|
|
||||||
//event listener
|
|
||||||
void onEvent(AwsEventHandler handler){
|
|
||||||
_eventHandler = handler;
|
|
||||||
}
|
|
||||||
|
|
||||||
//system callbacks (do not call)
|
|
||||||
uint32_t _getNextId(){ return _cNextId++; }
|
|
||||||
void _addClient(AsyncWebSocketClient * client);
|
|
||||||
void _handleDisconnect(AsyncWebSocketClient * client);
|
|
||||||
void _handleEvent(AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len);
|
|
||||||
virtual bool canHandle(AsyncWebServerRequest *request) override final;
|
|
||||||
virtual void handleRequest(AsyncWebServerRequest *request) override final;
|
|
||||||
|
|
||||||
|
|
||||||
// messagebuffer functions/objects.
|
|
||||||
AsyncWebSocketMessageBuffer * makeBuffer(size_t size = 0);
|
|
||||||
AsyncWebSocketMessageBuffer * makeBuffer(uint8_t * data, size_t size);
|
|
||||||
AsyncWebSocketMessageBufferLinkedList _buffers;
|
|
||||||
void _cleanBuffers();
|
|
||||||
|
|
||||||
AsyncWebSocketClientLinkedList getClients() const;
|
|
||||||
|
|
||||||
public:
|
|
||||||
#ifndef DEBUG_AWS_QUEUE_COUNTERS
|
|
||||||
#define DEBUG_AWS_QUEUE_COUNTERS 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if DEBUG_AWS_QUEUE_COUNTERS
|
|
||||||
// total for all sockets
|
|
||||||
size_t getQueuedMessageCount() const {
|
|
||||||
_verifyCounters();
|
|
||||||
return AsyncWebSocketMessageBufferLinkedList::_totalCount;
|
|
||||||
}
|
|
||||||
size_t getQueuedMessageSize() const {
|
|
||||||
_verifyCounters();
|
|
||||||
return AsyncWebSocketMessageBufferLinkedList::_totalSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
static size_t _getQueuedMessageCount() {
|
|
||||||
return AsyncWebSocketMessageBufferLinkedList::_totalCount;
|
|
||||||
}
|
|
||||||
static size_t _getQueuedMessageSize() {
|
|
||||||
return AsyncWebSocketMessageBufferLinkedList::_totalSize;
|
|
||||||
}
|
|
||||||
private:
|
|
||||||
void _verifyCounters() const {
|
|
||||||
size_t t=0,c=0;
|
|
||||||
for(const auto b: _buffers) {
|
|
||||||
t+=b->length();
|
|
||||||
c++;
|
|
||||||
}
|
|
||||||
if (AsyncWebSocketMessageBufferLinkedList::_totalSize!=t || AsyncWebSocketMessageBufferLinkedList::_totalCount!=c) {
|
|
||||||
::printf(PSTR("AsyncWebSocketMessageBufferLinkedList size %u=%u cnt %u=%u\n"), AsyncWebSocketMessageBufferLinkedList::_totalSize, t,AsyncWebSocketMessageBufferLinkedList::_totalCount, c);
|
|
||||||
panic();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
size_t getQueuedMessageCount() const {
|
|
||||||
return _getQueuedMessageCount();
|
|
||||||
}
|
|
||||||
size_t getQueuedMessageSize() const {
|
|
||||||
return _getQueuedMessageSize();
|
|
||||||
}
|
|
||||||
|
|
||||||
static size_t _getQueuedMessageCount() {
|
|
||||||
return AsyncWebSocketMessageBufferLinkedList::_totalCount;
|
|
||||||
}
|
|
||||||
static size_t _getQueuedMessageSize() {
|
|
||||||
return AsyncWebSocketMessageBufferLinkedList::_totalSize;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
//WebServer response to authenticate the socket and detach the tcp client from the web server request
|
|
||||||
class AsyncWebSocketResponse: public AsyncWebServerResponse {
|
|
||||||
private:
|
|
||||||
String _content;
|
|
||||||
AsyncWebSocket *_server;
|
|
||||||
public:
|
|
||||||
AsyncWebSocketResponse(const String& key, AsyncWebSocket *server);
|
|
||||||
void _respond(AsyncWebServerRequest *request);
|
|
||||||
size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time);
|
|
||||||
bool _sourceValid() const { return true; }
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
#endif /* ASYNCWEBSOCKET_H_ */
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
#ifndef ASYNCWEBSYNCHRONIZATION_H_
|
|
||||||
#define ASYNCWEBSYNCHRONIZATION_H_
|
|
||||||
|
|
||||||
// Synchronisation is only available on ESP32, as the ESP8266 isn't using FreeRTOS by default
|
|
||||||
|
|
||||||
#include <ESPAsyncWebServer.h>
|
|
||||||
|
|
||||||
#ifdef ESP32
|
|
||||||
|
|
||||||
// This is the ESP32 version of the Sync Lock, using the FreeRTOS Semaphore
|
|
||||||
class AsyncWebLock
|
|
||||||
{
|
|
||||||
private:
|
|
||||||
SemaphoreHandle_t _lock;
|
|
||||||
mutable void *_lockedBy;
|
|
||||||
|
|
||||||
public:
|
|
||||||
AsyncWebLock() {
|
|
||||||
_lock = xSemaphoreCreateBinary();
|
|
||||||
_lockedBy = NULL;
|
|
||||||
xSemaphoreGive(_lock);
|
|
||||||
}
|
|
||||||
|
|
||||||
~AsyncWebLock() {
|
|
||||||
vSemaphoreDelete(_lock);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool lock() const {
|
|
||||||
extern void *pxCurrentTCB;
|
|
||||||
if (_lockedBy != pxCurrentTCB) {
|
|
||||||
xSemaphoreTake(_lock, portMAX_DELAY);
|
|
||||||
_lockedBy = pxCurrentTCB;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void unlock() const {
|
|
||||||
_lockedBy = NULL;
|
|
||||||
xSemaphoreGive(_lock);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
#else
|
|
||||||
|
|
||||||
// This is the 8266 version of the Sync Lock which is currently unimplemented
|
|
||||||
class AsyncWebLock
|
|
||||||
{
|
|
||||||
|
|
||||||
public:
|
|
||||||
AsyncWebLock() {
|
|
||||||
}
|
|
||||||
|
|
||||||
~AsyncWebLock() {
|
|
||||||
}
|
|
||||||
|
|
||||||
bool lock() const {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void unlock() const {
|
|
||||||
}
|
|
||||||
};
|
|
||||||
#endif
|
|
||||||
|
|
||||||
class AsyncWebLockGuard
|
|
||||||
{
|
|
||||||
private:
|
|
||||||
const AsyncWebLock *_lock;
|
|
||||||
|
|
||||||
public:
|
|
||||||
AsyncWebLockGuard(const AsyncWebLock &l) {
|
|
||||||
if (l.lock()) {
|
|
||||||
_lock = &l;
|
|
||||||
} else {
|
|
||||||
_lock = NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
~AsyncWebLockGuard() {
|
|
||||||
if (_lock) {
|
|
||||||
_lock->unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // ASYNCWEBSYNCHRONIZATION_H_
|
|
||||||
@@ -1,487 +0,0 @@
|
|||||||
/*
|
|
||||||
Asynchronous WebServer library for Espressif MCUs
|
|
||||||
|
|
||||||
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
|
|
||||||
This file is part of the esp8266 core for Arduino environment.
|
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
|
||||||
modify it under the terms of the GNU Lesser General Public
|
|
||||||
License as published by the Free Software Foundation; either
|
|
||||||
version 2.1 of the License, or (at your option) any later version.
|
|
||||||
|
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
Lesser General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU Lesser General Public
|
|
||||||
License along with this library; if not, write to the Free Software
|
|
||||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
||||||
*/
|
|
||||||
#ifndef _ESPAsyncWebServer_H_
|
|
||||||
#define _ESPAsyncWebServer_H_
|
|
||||||
|
|
||||||
#include "Arduino.h"
|
|
||||||
|
|
||||||
#include <functional>
|
|
||||||
#include "FS.h"
|
|
||||||
|
|
||||||
#include "StringArray.h"
|
|
||||||
|
|
||||||
#ifdef ESP32
|
|
||||||
#include <WiFi.h>
|
|
||||||
#include <AsyncTCP.h>
|
|
||||||
#elif defined(ESP8266)
|
|
||||||
#include <ESP8266WiFi.h>
|
|
||||||
#include <ESPAsyncTCP.h>
|
|
||||||
#else
|
|
||||||
#error Platform not supported
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef ASYNCWEBSERVER_REGEX
|
|
||||||
#define ASYNCWEBSERVER_REGEX_ATTRIBUTE
|
|
||||||
#else
|
|
||||||
#define ASYNCWEBSERVER_REGEX_ATTRIBUTE __attribute__((warning("ASYNCWEBSERVER_REGEX not defined")))
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define DEBUGF(...) //Serial.printf(__VA_ARGS__)
|
|
||||||
|
|
||||||
class AsyncWebServer;
|
|
||||||
class AsyncWebServerRequest;
|
|
||||||
class AsyncWebServerResponse;
|
|
||||||
class AsyncWebHeader;
|
|
||||||
class AsyncWebParameter;
|
|
||||||
class AsyncWebRewrite;
|
|
||||||
class AsyncWebHandler;
|
|
||||||
class AsyncStaticWebHandler;
|
|
||||||
class AsyncCallbackWebHandler;
|
|
||||||
class AsyncResponseStream;
|
|
||||||
|
|
||||||
#ifndef WEBSERVER_H
|
|
||||||
typedef enum {
|
|
||||||
HTTP_GET = 0b00000001,
|
|
||||||
HTTP_POST = 0b00000010,
|
|
||||||
HTTP_DELETE = 0b00000100,
|
|
||||||
HTTP_PUT = 0b00001000,
|
|
||||||
HTTP_PATCH = 0b00010000,
|
|
||||||
HTTP_HEAD = 0b00100000,
|
|
||||||
HTTP_OPTIONS = 0b01000000,
|
|
||||||
HTTP_ANY = 0b01111111,
|
|
||||||
} WebRequestMethod;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef HAVE_FS_FILE_OPEN_MODE
|
|
||||||
namespace fs {
|
|
||||||
class FileOpenMode {
|
|
||||||
public:
|
|
||||||
static const char *read;
|
|
||||||
static const char *write;
|
|
||||||
static const char *append;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
#else
|
|
||||||
#include "FileOpenMode.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
//if this value is returned when asked for data, packet will not be sent and you will be asked for data again
|
|
||||||
#define RESPONSE_TRY_AGAIN 0xFFFFFFFF
|
|
||||||
|
|
||||||
typedef uint8_t WebRequestMethodComposite;
|
|
||||||
typedef std::function<void(void)> ArDisconnectHandler;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* PARAMETER :: Chainable object to hold GET/POST and FILE parameters
|
|
||||||
* */
|
|
||||||
|
|
||||||
class AsyncWebParameter {
|
|
||||||
private:
|
|
||||||
String _name;
|
|
||||||
String _value;
|
|
||||||
size_t _size;
|
|
||||||
bool _isForm;
|
|
||||||
bool _isFile;
|
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
AsyncWebParameter(const String& name, const String& value, bool form=false, bool file=false, size_t size=0): _name(name), _value(value), _size(size), _isForm(form), _isFile(file){}
|
|
||||||
const String& name() const { return _name; }
|
|
||||||
const String& value() const { return _value; }
|
|
||||||
size_t size() const { return _size; }
|
|
||||||
bool isPost() const { return _isForm; }
|
|
||||||
bool isFile() const { return _isFile; }
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
* HEADER :: Chainable object to hold the headers
|
|
||||||
* */
|
|
||||||
|
|
||||||
class AsyncWebHeader {
|
|
||||||
private:
|
|
||||||
String _name;
|
|
||||||
String _value;
|
|
||||||
|
|
||||||
public:
|
|
||||||
AsyncWebHeader(const String& name, const String& value): _name(name), _value(value){}
|
|
||||||
AsyncWebHeader(const String& data): _name(), _value(){
|
|
||||||
if(!data) return;
|
|
||||||
int index = data.indexOf(':');
|
|
||||||
if (index < 0) return;
|
|
||||||
_name = data.substring(0, index);
|
|
||||||
_value = data.substring(index + 2);
|
|
||||||
}
|
|
||||||
~AsyncWebHeader(){}
|
|
||||||
const String& name() const { return _name; }
|
|
||||||
const String& value() const { return _value; }
|
|
||||||
String toString() const { return String(_name + F(": ") + _value + F("\r\n")); }
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
* REQUEST :: Each incoming Client is wrapped inside a Request and both live together until disconnect
|
|
||||||
* */
|
|
||||||
|
|
||||||
typedef enum { RCT_NOT_USED = -1, RCT_DEFAULT = 0, RCT_HTTP, RCT_WS, RCT_EVENT, RCT_MAX } RequestedConnectionType;
|
|
||||||
|
|
||||||
typedef std::function<size_t(uint8_t*, size_t, size_t)> AwsResponseFiller;
|
|
||||||
typedef std::function<String(const String&)> AwsTemplateProcessor;
|
|
||||||
|
|
||||||
class AsyncWebServerRequest {
|
|
||||||
using File = fs::File;
|
|
||||||
using FS = fs::FS;
|
|
||||||
friend class AsyncWebServer;
|
|
||||||
friend class AsyncCallbackWebHandler;
|
|
||||||
friend class HttpCookieHeader;
|
|
||||||
private:
|
|
||||||
AsyncClient* _client;
|
|
||||||
AsyncWebServer* _server;
|
|
||||||
AsyncWebHandler* _handler;
|
|
||||||
AsyncWebServerResponse* _response;
|
|
||||||
StringArray _interestingHeaders;
|
|
||||||
ArDisconnectHandler _onDisconnectfn;
|
|
||||||
|
|
||||||
String _temp;
|
|
||||||
uint8_t _parseState;
|
|
||||||
|
|
||||||
uint8_t _version;
|
|
||||||
WebRequestMethodComposite _method;
|
|
||||||
String _url;
|
|
||||||
String _host;
|
|
||||||
String _contentType;
|
|
||||||
String _boundary;
|
|
||||||
String _authorization;
|
|
||||||
RequestedConnectionType _reqconntype;
|
|
||||||
void _removeNotInterestingHeaders();
|
|
||||||
bool _isDigest;
|
|
||||||
bool _isMultipart;
|
|
||||||
bool _isPlainPost;
|
|
||||||
bool _expectingContinue;
|
|
||||||
size_t _contentLength;
|
|
||||||
size_t _parsedLength;
|
|
||||||
|
|
||||||
LinkedList<AsyncWebHeader *> _headers;
|
|
||||||
LinkedList<AsyncWebParameter *> _params;
|
|
||||||
LinkedList<String *> _pathParams;
|
|
||||||
|
|
||||||
uint8_t _multiParseState;
|
|
||||||
uint8_t _boundaryPosition;
|
|
||||||
size_t _itemStartIndex;
|
|
||||||
size_t _itemSize;
|
|
||||||
String _itemName;
|
|
||||||
String _itemFilename;
|
|
||||||
String _itemType;
|
|
||||||
String _itemValue;
|
|
||||||
uint8_t *_itemBuffer;
|
|
||||||
size_t _itemBufferIndex;
|
|
||||||
bool _itemIsFile;
|
|
||||||
|
|
||||||
void _onPoll();
|
|
||||||
void _onAck(size_t len, uint32_t time);
|
|
||||||
void _onError(int8_t error);
|
|
||||||
void _onTimeout(uint32_t time);
|
|
||||||
void _onDisconnect();
|
|
||||||
void _onData(void *buf, size_t len);
|
|
||||||
|
|
||||||
void _addParam(AsyncWebParameter*);
|
|
||||||
void _addPathParam(const char *param);
|
|
||||||
|
|
||||||
bool _parseReqHead();
|
|
||||||
bool _parseReqHeader();
|
|
||||||
void _parseLine();
|
|
||||||
void _parsePlainPostChar(uint8_t data);
|
|
||||||
void _parseMultipartPostByte(uint8_t data, bool last);
|
|
||||||
void _addGetParams(const String& params);
|
|
||||||
|
|
||||||
void _handleUploadStart();
|
|
||||||
void _handleUploadByte(uint8_t data, bool last);
|
|
||||||
void _handleUploadEnd();
|
|
||||||
|
|
||||||
public:
|
|
||||||
File _tempFile;
|
|
||||||
void *_tempObject;
|
|
||||||
|
|
||||||
AsyncWebServerRequest(AsyncWebServer*, AsyncClient*);
|
|
||||||
~AsyncWebServerRequest();
|
|
||||||
|
|
||||||
AsyncClient* client(){ return _client; }
|
|
||||||
uint8_t version() const { return _version; }
|
|
||||||
WebRequestMethodComposite method() const { return _method; }
|
|
||||||
const String& url() const { return _url; }
|
|
||||||
const String& host() const { return _host; }
|
|
||||||
const String& contentType() const { return _contentType; }
|
|
||||||
size_t contentLength() const { return _contentLength; }
|
|
||||||
bool multipart() const { return _isMultipart; }
|
|
||||||
const char *methodToString() const;
|
|
||||||
const char *requestedConnTypeToString() const;
|
|
||||||
RequestedConnectionType requestedConnType() const { return _reqconntype; }
|
|
||||||
bool isExpectedRequestedConnType(RequestedConnectionType erct1, RequestedConnectionType erct2 = RCT_NOT_USED, RequestedConnectionType erct3 = RCT_NOT_USED);
|
|
||||||
void onDisconnect (ArDisconnectHandler fn);
|
|
||||||
|
|
||||||
//hash is the string representation of:
|
|
||||||
// base64(user:pass) for basic or
|
|
||||||
// user:realm:md5(user:realm:pass) for digest
|
|
||||||
bool authenticate(const char * hash);
|
|
||||||
bool authenticate(const char * username, const char * password, const char * realm = NULL, bool passwordIsHash = false);
|
|
||||||
void requestAuthentication(const char * realm = NULL, bool isDigest = true);
|
|
||||||
|
|
||||||
void setHandler(AsyncWebHandler *handler){ _handler = handler; }
|
|
||||||
void addInterestingHeader(const String& name);
|
|
||||||
|
|
||||||
void redirect(const String& url);
|
|
||||||
|
|
||||||
void send(AsyncWebServerResponse *response);
|
|
||||||
void send(int code, const String& contentType=String(), const String& content=String());
|
|
||||||
void send(FS &fs, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr);
|
|
||||||
void send(File content, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr);
|
|
||||||
void send(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback=nullptr);
|
|
||||||
void send(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr);
|
|
||||||
void sendChunked(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr);
|
|
||||||
void send_P(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback=nullptr);
|
|
||||||
void send_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback=nullptr);
|
|
||||||
|
|
||||||
AsyncWebServerResponse *beginResponse(int code, const String& contentType=String(), const String& content=String());
|
|
||||||
AsyncWebServerResponse *beginResponse(FS &fs, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr);
|
|
||||||
AsyncWebServerResponse *beginResponse(File content, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr);
|
|
||||||
AsyncWebServerResponse *beginResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback=nullptr);
|
|
||||||
AsyncWebServerResponse *beginResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr);
|
|
||||||
AsyncWebServerResponse *beginChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr);
|
|
||||||
AsyncResponseStream *beginResponseStream(const String& contentType, size_t bufferSize=1460);
|
|
||||||
AsyncWebServerResponse *beginResponse_P(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback=nullptr);
|
|
||||||
AsyncWebServerResponse *beginResponse_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback=nullptr);
|
|
||||||
|
|
||||||
size_t headers() const; // get header count
|
|
||||||
bool hasHeader(const String& name) const; // check if header exists
|
|
||||||
bool hasHeader(const __FlashStringHelper * data) const; // check if header exists
|
|
||||||
|
|
||||||
AsyncWebHeader* getHeader(const String& name) const;
|
|
||||||
AsyncWebHeader* getHeader(const __FlashStringHelper * data) const;
|
|
||||||
AsyncWebHeader* getHeader(size_t num) const;
|
|
||||||
|
|
||||||
size_t params() const; // get arguments count
|
|
||||||
bool hasParam(const String& name, bool post=false, bool file=false) const;
|
|
||||||
bool hasParam(const __FlashStringHelper * data, bool post=false, bool file=false) const;
|
|
||||||
|
|
||||||
AsyncWebParameter* getParam(const String& name, bool post=false, bool file=false) const;
|
|
||||||
AsyncWebParameter* getParam(const __FlashStringHelper * data, bool post, bool file) const;
|
|
||||||
AsyncWebParameter* getParam(size_t num) const;
|
|
||||||
|
|
||||||
size_t args() const { return params(); } // get arguments count
|
|
||||||
const String& arg(const String& name) const; // get request argument value by name
|
|
||||||
const String& arg(const __FlashStringHelper * data) const; // get request argument value by F(name)
|
|
||||||
const String& arg(size_t i) const; // get request argument value by number
|
|
||||||
const String& argName(size_t i) const; // get request argument name by number
|
|
||||||
bool hasArg(const char* name) const; // check if argument exists
|
|
||||||
bool hasArg(const __FlashStringHelper * data) const; // check if F(argument) exists
|
|
||||||
|
|
||||||
const String& ASYNCWEBSERVER_REGEX_ATTRIBUTE pathArg(size_t i) const;
|
|
||||||
|
|
||||||
const String& header(const char* name) const;// get request header value by name
|
|
||||||
const String& header(const __FlashStringHelper * data) const;// get request header value by F(name)
|
|
||||||
const String& header(size_t i) const; // get request header value by number
|
|
||||||
const String& headerName(size_t i) const; // get request header name by number
|
|
||||||
String urlDecode(const String& text) const;
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
* FILTER :: Callback to filter AsyncWebRewrite and AsyncWebHandler (done by the Server)
|
|
||||||
* */
|
|
||||||
|
|
||||||
typedef std::function<bool(AsyncWebServerRequest *request)> ArRequestFilterFunction;
|
|
||||||
|
|
||||||
bool ON_STA_FILTER(AsyncWebServerRequest *request);
|
|
||||||
|
|
||||||
bool ON_AP_FILTER(AsyncWebServerRequest *request);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* REWRITE :: One instance can be handle any Request (done by the Server)
|
|
||||||
* */
|
|
||||||
|
|
||||||
class AsyncWebRewrite {
|
|
||||||
protected:
|
|
||||||
String _from;
|
|
||||||
String _toUrl;
|
|
||||||
String _params;
|
|
||||||
ArRequestFilterFunction _filter;
|
|
||||||
public:
|
|
||||||
AsyncWebRewrite(const char* from, const char* to): _from(from), _toUrl(to), _params(String()), _filter(NULL){
|
|
||||||
int index = _toUrl.indexOf('?');
|
|
||||||
if (index > 0) {
|
|
||||||
_params = _toUrl.substring(index +1);
|
|
||||||
_toUrl = _toUrl.substring(0, index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
virtual ~AsyncWebRewrite(){}
|
|
||||||
AsyncWebRewrite& setFilter(ArRequestFilterFunction fn) { _filter = fn; return *this; }
|
|
||||||
bool filter(AsyncWebServerRequest *request) const { return _filter == NULL || _filter(request); }
|
|
||||||
const String& from(void) const { return _from; }
|
|
||||||
const String& toUrl(void) const { return _toUrl; }
|
|
||||||
const String& params(void) const { return _params; }
|
|
||||||
virtual bool match(AsyncWebServerRequest *request) { return from() == request->url() && filter(request); }
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
* HANDLER :: One instance can be attached to any Request (done by the Server)
|
|
||||||
* */
|
|
||||||
|
|
||||||
class AsyncWebHandler {
|
|
||||||
protected:
|
|
||||||
ArRequestFilterFunction _filter;
|
|
||||||
String _username;
|
|
||||||
String _password;
|
|
||||||
public:
|
|
||||||
AsyncWebHandler():_username(""), _password(""){}
|
|
||||||
AsyncWebHandler& setFilter(ArRequestFilterFunction fn) { _filter = fn; return *this; }
|
|
||||||
AsyncWebHandler& setAuthentication(const char *username, const char *password){ _username = String(username);_password = String(password); return *this; };
|
|
||||||
bool filter(AsyncWebServerRequest *request){ return _filter == NULL || _filter(request); }
|
|
||||||
virtual ~AsyncWebHandler(){}
|
|
||||||
virtual bool canHandle(AsyncWebServerRequest *request __attribute__((unused))){
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
virtual void handleRequest(AsyncWebServerRequest *request __attribute__((unused))){}
|
|
||||||
virtual void handleUpload(AsyncWebServerRequest *request __attribute__((unused)), const String& filename __attribute__((unused)), size_t index __attribute__((unused)), uint8_t *data __attribute__((unused)), size_t len __attribute__((unused)), bool final __attribute__((unused))){}
|
|
||||||
virtual void handleBody(AsyncWebServerRequest *request __attribute__((unused)), uint8_t *data __attribute__((unused)), size_t len __attribute__((unused)), size_t index __attribute__((unused)), size_t total __attribute__((unused))){}
|
|
||||||
virtual bool isRequestHandlerTrivial(){return true;}
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
* RESPONSE :: One instance is created for each Request (attached by the Handler)
|
|
||||||
* */
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
RESPONSE_SETUP, RESPONSE_HEADERS, RESPONSE_CONTENT, RESPONSE_WAIT_ACK, RESPONSE_END, RESPONSE_FAILED
|
|
||||||
} WebResponseState;
|
|
||||||
|
|
||||||
class AsyncWebServerResponse {
|
|
||||||
protected:
|
|
||||||
int _code;
|
|
||||||
LinkedList<AsyncWebHeader *> _headers;
|
|
||||||
String _contentType;
|
|
||||||
size_t _contentLength;
|
|
||||||
bool _sendContentLength;
|
|
||||||
bool _chunked;
|
|
||||||
size_t _headLength;
|
|
||||||
size_t _sentLength;
|
|
||||||
size_t _ackedLength;
|
|
||||||
size_t _writtenLength;
|
|
||||||
WebResponseState _state;
|
|
||||||
const char* _responseCodeToString(int code);
|
|
||||||
public:
|
|
||||||
static const __FlashStringHelper *responseCodeToString(int code);
|
|
||||||
|
|
||||||
public:
|
|
||||||
AsyncWebServerResponse();
|
|
||||||
virtual ~AsyncWebServerResponse();
|
|
||||||
virtual void setCode(int code);
|
|
||||||
virtual void setContentLength(size_t len);
|
|
||||||
virtual void setContentType(const String& type);
|
|
||||||
virtual void addHeader(const String& name, const String& value);
|
|
||||||
virtual String _assembleHead(uint8_t version);
|
|
||||||
virtual bool _started() const;
|
|
||||||
virtual bool _finished() const;
|
|
||||||
virtual bool _failed() const;
|
|
||||||
virtual bool _sourceValid() const;
|
|
||||||
virtual void _respond(AsyncWebServerRequest *request);
|
|
||||||
virtual size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time);
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
* SERVER :: One instance
|
|
||||||
* */
|
|
||||||
|
|
||||||
typedef std::function<void(AsyncWebServerRequest *request)> ArRequestHandlerFunction;
|
|
||||||
typedef std::function<void(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final)> ArUploadHandlerFunction;
|
|
||||||
typedef std::function<void(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total)> ArBodyHandlerFunction;
|
|
||||||
|
|
||||||
class AsyncWebServer {
|
|
||||||
protected:
|
|
||||||
AsyncServer _server;
|
|
||||||
LinkedList<AsyncWebRewrite*> _rewrites;
|
|
||||||
LinkedList<AsyncWebHandler*> _handlers;
|
|
||||||
AsyncCallbackWebHandler* _catchAllHandler;
|
|
||||||
|
|
||||||
public:
|
|
||||||
AsyncWebServer(uint16_t port);
|
|
||||||
~AsyncWebServer();
|
|
||||||
|
|
||||||
void begin();
|
|
||||||
void end();
|
|
||||||
|
|
||||||
#if ASYNC_TCP_SSL_ENABLED
|
|
||||||
void onSslFileRequest(AcSSlFileHandler cb, void* arg);
|
|
||||||
void beginSecure(const char *cert, const char *private_key_file, const char *password);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
AsyncWebRewrite& addRewrite(AsyncWebRewrite* rewrite);
|
|
||||||
bool removeRewrite(AsyncWebRewrite* rewrite);
|
|
||||||
AsyncWebRewrite& rewrite(const char* from, const char* to);
|
|
||||||
|
|
||||||
AsyncWebHandler& addHandler(AsyncWebHandler* handler);
|
|
||||||
bool removeHandler(AsyncWebHandler* handler);
|
|
||||||
|
|
||||||
AsyncCallbackWebHandler& on(const char* uri, ArRequestHandlerFunction onRequest);
|
|
||||||
AsyncCallbackWebHandler& on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest);
|
|
||||||
AsyncCallbackWebHandler& on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload);
|
|
||||||
AsyncCallbackWebHandler& on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload, ArBodyHandlerFunction onBody);
|
|
||||||
|
|
||||||
AsyncStaticWebHandler& serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_control = NULL);
|
|
||||||
|
|
||||||
void onNotFound(ArRequestHandlerFunction fn); //called when handler is not assigned
|
|
||||||
void onFileUpload(ArUploadHandlerFunction fn); //handle file uploads
|
|
||||||
void onRequestBody(ArBodyHandlerFunction fn); //handle posts with plain body content (JSON often transmitted this way as a request)
|
|
||||||
|
|
||||||
void reset(); //remove all writers and handlers, with onNotFound/onFileUpload/onRequestBody
|
|
||||||
|
|
||||||
void _handleDisconnect(AsyncWebServerRequest *request);
|
|
||||||
void _attachHandler(AsyncWebServerRequest *request);
|
|
||||||
void _rewriteRequest(AsyncWebServerRequest *request);
|
|
||||||
};
|
|
||||||
|
|
||||||
class DefaultHeaders {
|
|
||||||
using headers_t = LinkedList<AsyncWebHeader *>;
|
|
||||||
headers_t _headers;
|
|
||||||
|
|
||||||
DefaultHeaders()
|
|
||||||
:_headers(headers_t([](AsyncWebHeader *h){ delete h; }))
|
|
||||||
{}
|
|
||||||
public:
|
|
||||||
using ConstIterator = headers_t::ConstIterator;
|
|
||||||
|
|
||||||
void addHeader(const String& name, const String& value){
|
|
||||||
_headers.add(new AsyncWebHeader(name, value));
|
|
||||||
}
|
|
||||||
|
|
||||||
ConstIterator begin() const { return _headers.begin(); }
|
|
||||||
ConstIterator end() const { return _headers.end(); }
|
|
||||||
|
|
||||||
DefaultHeaders(DefaultHeaders const &) = delete;
|
|
||||||
DefaultHeaders &operator=(DefaultHeaders const &) = delete;
|
|
||||||
static DefaultHeaders &Instance() {
|
|
||||||
static DefaultHeaders instance;
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
#include "WebResponseImpl.h"
|
|
||||||
#include "WebHandlerImpl.h"
|
|
||||||
#include "AsyncWebSocket.h"
|
|
||||||
#include "AsyncEventSource.h"
|
|
||||||
|
|
||||||
#endif /* _AsyncWebServer_H_ */
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,544 +0,0 @@
|
|||||||
#include "SPIFFSEditor.h"
|
|
||||||
#include <FS.h>
|
|
||||||
|
|
||||||
//File: edit.htm.gz, Size: 4151
|
|
||||||
#define edit_htm_gz_len 4151
|
|
||||||
const uint8_t edit_htm_gz[] PROGMEM = {
|
|
||||||
0x1F, 0x8B, 0x08, 0x08, 0xB8, 0x94, 0xB1, 0x59, 0x00, 0x03, 0x65, 0x64, 0x69, 0x74, 0x2E, 0x68,
|
|
||||||
0x74, 0x6D, 0x00, 0xB5, 0x3A, 0x0B, 0x7B, 0xDA, 0xB8, 0xB2, 0x7F, 0xC5, 0x71, 0xCF, 0x66, 0xED,
|
|
||||||
0x83, 0x31, 0x90, 0xA4, 0xD9, 0xD6, 0xC4, 0xC9, 0x42, 0x92, 0x36, 0x6D, 0xF3, 0x6A, 0x80, 0xB6,
|
|
||||||
0x69, 0x4F, 0xEE, 0x7E, 0xC2, 0x16, 0xA0, 0xC6, 0x96, 0x5D, 0x5B, 0x0E, 0x49, 0x59, 0xFE, 0xFB,
|
|
||||||
0x9D, 0x91, 0x6C, 0xB0, 0x09, 0x69, 0x77, 0xCF, 0xBD, 0xBB, 0xDD, 0x2D, 0x92, 0x46, 0x33, 0x9A,
|
|
||||||
0x19, 0xCD, 0x53, 0xDE, 0xBD, 0x8D, 0xA3, 0x8B, 0xC3, 0xFE, 0xF5, 0xE5, 0xB1, 0x36, 0x11, 0x61,
|
|
||||||
0xB0, 0xBF, 0x87, 0x7F, 0x6B, 0x01, 0xE1, 0x63, 0x97, 0xF2, 0xFD, 0x3D, 0xC1, 0x44, 0x40, 0xF7,
|
|
||||||
0x8F, 0x7B, 0x97, 0xDA, 0xB1, 0xCF, 0x44, 0x94, 0xEC, 0x35, 0xD4, 0xCA, 0x5E, 0x2A, 0x1E, 0x02,
|
|
||||||
0xAA, 0x85, 0xD4, 0x67, 0xC4, 0x4D, 0xBD, 0x84, 0xC2, 0x66, 0xDB, 0x0B, 0x67, 0xDF, 0xEB, 0x8C,
|
|
||||||
0xFB, 0xF4, 0xDE, 0xD9, 0x6E, 0x36, 0xDB, 0x71, 0x94, 0x32, 0xC1, 0x22, 0xEE, 0x90, 0x61, 0x1A,
|
|
||||||
0x05, 0x99, 0xA0, 0xED, 0x80, 0x8E, 0x84, 0xF3, 0x3C, 0xBE, 0x6F, 0x0F, 0xA3, 0xC4, 0xA7, 0x89,
|
|
||||||
0xD3, 0x8A, 0xEF, 0x35, 0x00, 0x31, 0x5F, 0x7B, 0xB6, 0xB3, 0xB3, 0xD3, 0x1E, 0x12, 0xEF, 0x76,
|
|
||||||
0x9C, 0x44, 0x19, 0xF7, 0xEB, 0x5E, 0x14, 0x44, 0x89, 0xF3, 0x6C, 0xF4, 0x1C, 0xFF, 0xB4, 0x7D,
|
|
||||||
0x96, 0xC6, 0x01, 0x79, 0x70, 0x78, 0xC4, 0x29, 0xE0, 0xDE, 0xD7, 0xD3, 0x09, 0xF1, 0xA3, 0xA9,
|
|
||||||
0xD3, 0xD4, 0x9A, 0x5A, 0xAB, 0x09, 0x44, 0x92, 0xF1, 0x90, 0x18, 0x4D, 0x0B, 0xFF, 0xD8, 0x3B,
|
|
||||||
0x66, 0x7B, 0x14, 0x71, 0x51, 0x4F, 0xD9, 0x77, 0xEA, 0xB4, 0xB6, 0xE0, 0x34, 0x39, 0x1D, 0x91,
|
|
||||||
0x90, 0x05, 0x0F, 0x4E, 0x4A, 0x78, 0x5A, 0x4F, 0x69, 0xC2, 0x46, 0x6A, 0x79, 0x4A, 0xD9, 0x78,
|
|
||||||
0x22, 0x9C, 0xDF, 0x9A, 0xCD, 0x39, 0xF0, 0xAF, 0x65, 0xC1, 0x2C, 0x60, 0x29, 0x20, 0xA3, 0x78,
|
|
||||||
0xEA, 0x3C, 0x11, 0xC5, 0x4E, 0x53, 0xB1, 0xDE, 0x6C, 0x87, 0x24, 0x19, 0x33, 0x0E, 0x83, 0x98,
|
|
||||||
0xF8, 0x3E, 0xE3, 0x63, 0x47, 0xA1, 0x05, 0x6C, 0xB6, 0x90, 0x36, 0xA1, 0x01, 0x11, 0xEC, 0x8E,
|
|
||||||
0xB6, 0x43, 0xC6, 0xEB, 0x53, 0xE6, 0x8B, 0x89, 0xB3, 0x0B, 0x3C, 0xB6, 0xBD, 0x2C, 0x49, 0x41,
|
|
||||||
0xA6, 0x38, 0x62, 0x5C, 0xD0, 0x44, 0xA2, 0xA5, 0x31, 0xE1, 0xB3, 0x5C, 0x54, 0x54, 0x40, 0x21,
|
|
||||||
0x27, 0xE3, 0x01, 0xE3, 0xB4, 0x3E, 0x0C, 0x22, 0xEF, 0x76, 0x71, 0xD2, 0x6E, 0x7C, 0x9F, 0x9F,
|
|
||||||
0xE5, 0x4C, 0xA2, 0x3B, 0x9A, 0xCC, 0x96, 0xEA, 0x92, 0xD8, 0x15, 0x60, 0x85, 0x34, 0xA5, 0x74,
|
|
||||||
0x6E, 0x8B, 0xBB, 0x0C, 0xA0, 0x96, 0xFC, 0x05, 0x29, 0x17, 0xFC, 0x2F, 0x45, 0x5A, 0x11, 0x5C,
|
|
||||||
0xA1, 0x30, 0x1E, 0x67, 0x62, 0xF6, 0xF8, 0x2A, 0xA3, 0x98, 0x78, 0x4C, 0x3C, 0xA0, 0xFC, 0xB0,
|
|
||||||
0x6D, 0x86, 0xBA, 0x04, 0xAC, 0x24, 0x24, 0x81, 0x86, 0x3A, 0xD7, 0x3E, 0xD0, 0xC4, 0x27, 0x9C,
|
|
||||||
0x58, 0x9D, 0x84, 0x91, 0xC0, 0xEA, 0x2D, 0xB5, 0x5E, 0x0F, 0xA3, 0xEF, 0xF5, 0x0C, 0xC6, 0x30,
|
|
||||||
0x0F, 0xA8, 0x27, 0x94, 0x92, 0xE1, 0x1E, 0x86, 0xB7, 0x4C, 0x3C, 0x06, 0x3C, 0x5A, 0x28, 0xA9,
|
|
||||||
0x4B, 0x2A, 0x69, 0xA2, 0x2E, 0xB0, 0x25, 0xD5, 0x83, 0x1C, 0x4B, 0xC9, 0x95, 0x50, 0xF5, 0x61,
|
|
||||||
0x24, 0x44, 0x14, 0x4A, 0x93, 0x5B, 0x08, 0xAC, 0x49, 0xAB, 0x79, 0xF1, 0xE8, 0x46, 0xD6, 0x6B,
|
|
||||||
0xBF, 0x44, 0xBE, 0x0D, 0x7A, 0x15, 0xCC, 0x23, 0x41, 0x9D, 0x04, 0x6C, 0xCC, 0x9D, 0x90, 0xF9,
|
|
||||||
0x7E, 0x40, 0x4B, 0x56, 0xEB, 0x64, 0x49, 0x60, 0xF8, 0x44, 0x10, 0x87, 0x85, 0x64, 0x4C, 0x1B,
|
|
||||||
0x31, 0x1F, 0x03, 0x34, 0xA5, 0xBB, 0x3B, 0x16, 0xFB, 0xD0, 0xBD, 0xB8, 0x9A, 0x36, 0xDF, 0xBD,
|
|
||||||
0x1E, 0x47, 0x1D, 0xF8, 0xE7, 0xBC, 0x37, 0x98, 0x1C, 0x0F, 0xC6, 0x30, 0xEA, 0xE2, 0xB4, 0xF3,
|
|
||||||
0xFE, 0xB0, 0xF3, 0x1E, 0x7E, 0x0E, 0x5B, 0xB5, 0xAF, 0xA3, 0x6F, 0xB8, 0xD0, 0x7D, 0xED, 0x77,
|
|
||||||
0xFB, 0x83, 0xE3, 0x4E, 0xE7, 0x5D, 0xE3, 0xCD, 0xF9, 0xF4, 0xE3, 0xBB, 0x5D, 0x04, 0x77, 0x83,
|
|
||||||
0xE6, 0xD5, 0x87, 0x49, 0x73, 0xB0, 0xF5, 0x32, 0xF4, 0x4F, 0xFC, 0x89, 0x17, 0x0E, 0x3A, 0xEF,
|
|
||||||
0x3F, 0x5E, 0xDD, 0x5D, 0x87, 0x83, 0x71, 0xEF, 0x63, 0x6B, 0xF2, 0x79, 0xEB, 0x43, 0xEF, 0xF3,
|
|
||||||
0xC7, 0x57, 0xB7, 0xF4, 0xD3, 0xC9, 0xDB, 0xCF, 0xFD, 0x29, 0x20, 0x1C, 0x45, 0xBD, 0xC1, 0x55,
|
|
||||||
0xF7, 0x43, 0x77, 0xFC, 0xB9, 0xEB, 0x1D, 0xDF, 0x0F, 0x83, 0xF3, 0xEE, 0xEB, 0xCE, 0xB0, 0xB3,
|
|
||||||
0xE5, 0x51, 0x3A, 0xEE, 0x5F, 0x75, 0xB3, 0x37, 0xEF, 0x2E, 0xC6, 0x8C, 0x4D, 0x7A, 0x9F, 0xCF,
|
|
||||||
0xFB, 0xDE, 0xE1, 0xF3, 0xD3, 0xC1, 0x49, 0x87, 0x4D, 0xCE, 0xDF, 0x5E, 0x35, 0x6F, 0x5F, 0xBF,
|
|
||||||
0x3B, 0x3C, 0xF2, 0xAE, 0xDF, 0x5E, 0xEF, 0x1E, 0x6D, 0x37, 0x7E, 0xFB, 0xED, 0xCC, 0xBF, 0x60,
|
|
||||||
0xBC, 0x7F, 0xF7, 0xBD, 0x33, 0x3E, 0x9C, 0xBE, 0x78, 0x48, 0xFB, 0x93, 0x37, 0x77, 0xBC, 0xF1,
|
|
||||||
0x21, 0xFA, 0xFA, 0xE6, 0xE1, 0x0C, 0xFE, 0xBB, 0xBC, 0xAC, 0x0D, 0x7B, 0xAD, 0x74, 0xF0, 0xFE,
|
|
||||||
0xCD, 0x87, 0xAD, 0xF4, 0xE5, 0xF3, 0xB8, 0x7B, 0x74, 0x74, 0x17, 0x0E, 0x2F, 0x1B, 0xA1, 0x7F,
|
|
||||||
0x3B, 0x12, 0x2F, 0xB6, 0x45, 0x7C, 0x3D, 0xCE, 0x3E, 0x7F, 0x7B, 0xFE, 0x76, 0xD2, 0xB8, 0xA0,
|
|
||||||
0xE4, 0x7A, 0x52, 0x7B, 0xF8, 0xFE, 0xF0, 0x62, 0xD2, 0x3F, 0xB9, 0x3B, 0x0F, 0xC8, 0xFD, 0xF9,
|
|
||||||
0xB9, 0xF7, 0x3D, 0xAC, 0x05, 0xE4, 0xE5, 0x45, 0x3F, 0x20, 0x49, 0x6B, 0xE0, 0x77, 0x1A, 0xB5,
|
|
||||||
0xC3, 0xAD, 0xCE, 0x8E, 0x48, 0xAE, 0x0E, 0xF9, 0xD1, 0xF6, 0xD7, 0xDE, 0x8B, 0x6E, 0xB7, 0x15,
|
|
||||||
0x0D, 0xBF, 0x6D, 0xBD, 0xBE, 0xDD, 0x7D, 0x3D, 0xD8, 0x7D, 0x3F, 0x7C, 0xDF, 0xE9, 0xED, 0x74,
|
|
||||||
0x07, 0xE4, 0xBA, 0xF7, 0xBE, 0x33, 0xDA, 0x19, 0x4E, 0x26, 0xEF, 0xDE, 0xF5, 0x5F, 0xF9, 0x9D,
|
|
||||||
0xEF, 0x49, 0xE7, 0x62, 0xDA, 0xB9, 0x3F, 0x1E, 0x74, 0x4E, 0x6A, 0xEF, 0x8E, 0xCF, 0x9A, 0xAD,
|
|
||||||
0xDE, 0xF5, 0xF6, 0xF8, 0x6C, 0x77, 0xDA, 0x4D, 0x8F, 0x3B, 0xEF, 0xBB, 0xCD, 0xF1, 0xDB, 0x5A,
|
|
||||||
0x48, 0x3E, 0x47, 0x87, 0xDB, 0xE3, 0x37, 0xBB, 0xEC, 0xF2, 0x9A, 0x74, 0xDE, 0x74, 0xDF, 0xA6,
|
|
||||||
0xEC, 0x2A, 0x3C, 0x19, 0x34, 0x3B, 0x9D, 0xD3, 0x0B, 0xFA, 0xEA, 0x70, 0x9B, 0xBC, 0xDB, 0xF2,
|
|
||||||
0x3E, 0x82, 0xFE, 0x07, 0x9F, 0xE8, 0x6F, 0xB5, 0xCE, 0xF4, 0xA2, 0x19, 0x78, 0x2F, 0x69, 0xFF,
|
|
||||||
0xE4, 0xBA, 0x2F, 0x6F, 0xE7, 0x38, 0x78, 0xD5, 0xBF, 0xED, 0x65, 0xEF, 0xC3, 0xC3, 0x43, 0x53,
|
|
||||||
0xE3, 0x51, 0x3D, 0xA1, 0x31, 0x25, 0xA2, 0x1C, 0xAE, 0x16, 0xFE, 0x01, 0xB6, 0xB5, 0xB4, 0xC2,
|
|
||||||
0xDC, 0x4F, 0x05, 0xBD, 0x17, 0x75, 0x9F, 0x7A, 0x51, 0x42, 0xE4, 0x1E, 0x40, 0xA0, 0x09, 0x9A,
|
|
||||||
0xD8, 0xFC, 0x77, 0x19, 0x3F, 0x35, 0x15, 0x3F, 0x35, 0xC2, 0x7D, 0xCD, 0x28, 0x1C, 0x01, 0x83,
|
|
||||||
0x87, 0x4F, 0xEF, 0x98, 0x47, 0xEB, 0x31, 0xBB, 0xA7, 0x41, 0x5D, 0x22, 0x3B, 0x4D, 0x73, 0x26,
|
|
||||||
0xFD, 0xAD, 0xD8, 0x46, 0x38, 0x98, 0x9A, 0xA4, 0x5A, 0x2C, 0xF8, 0x5F, 0x89, 0x47, 0x21, 0xB0,
|
|
||||||
0x81, 0xCB, 0x84, 0xF8, 0xAB, 0x7C, 0x27, 0x4A, 0xEA, 0xC3, 0x6C, 0x3C, 0x62, 0xF7, 0xE0, 0xD0,
|
|
||||||
0x23, 0xC6, 0x99, 0xA0, 0x5A, 0x2B, 0x9D, 0xFF, 0x5E, 0x90, 0xB9, 0xA5, 0x0F, 0xA3, 0x84, 0x84,
|
|
||||||
0x34, 0xD5, 0xFE, 0x22, 0x99, 0xD9, 0x28, 0x89, 0xC2, 0x65, 0x10, 0x99, 0x8B, 0xA8, 0x34, 0x99,
|
|
||||||
0xCF, 0x9F, 0x65, 0x71, 0x10, 0x11, 0x10, 0x73, 0x4D, 0xE4, 0x50, 0xF1, 0x34, 0x91, 0x6E, 0xB5,
|
|
||||||
0x88, 0xAB, 0xB9, 0x9B, 0x6D, 0xA1, 0x5B, 0x96, 0xDD, 0x7A, 0x6B, 0x67, 0xE9, 0xBA, 0x75, 0xB9,
|
|
||||||
0x17, 0xE3, 0xFD, 0x9A, 0x4C, 0x81, 0xF1, 0xA0, 0x14, 0xEE, 0x9E, 0x09, 0x50, 0xE9, 0x13, 0x87,
|
|
||||||
0xCB, 0x43, 0xF2, 0xC8, 0xB0, 0x60, 0x40, 0x05, 0xEA, 0x96, 0x8C, 0xD4, 0x85, 0x24, 0xB0, 0x6F,
|
|
||||||
0xFE, 0x8C, 0xCA, 0xBC, 0x67, 0x3D, 0x8B, 0x13, 0xB8, 0x0D, 0x3A, 0xFD, 0x11, 0xCD, 0x42, 0xA6,
|
|
||||||
0x2A, 0x6D, 0x45, 0x53, 0x65, 0xBC, 0x5C, 0x84, 0x65, 0xDA, 0x93, 0xBC, 0x16, 0xA4, 0x1F, 0x4B,
|
|
||||||
0x05, 0xE0, 0x05, 0x37, 0xCF, 0x91, 0x9B, 0x1F, 0x6A, 0x75, 0x7B, 0xF7, 0x97, 0x9C, 0x87, 0x9D,
|
|
||||||
0xE6, 0x2F, 0x73, 0x3B, 0xDF, 0x5B, 0xA4, 0xE4, 0x56, 0x13, 0xFE, 0x29, 0x32, 0xEF, 0x8B, 0x25,
|
|
||||||
0x0B, 0xC3, 0xE7, 0xF8, 0xA7, 0x60, 0x10, 0xE9, 0x94, 0x80, 0xDB, 0x3B, 0x2F, 0x5F, 0xF8, 0xC3,
|
|
||||||
0x02, 0x98, 0x0B, 0xF6, 0x24, 0x3C, 0x21, 0x3E, 0xCB, 0x52, 0xE7, 0x79, 0xF3, 0x97, 0x5C, 0x9F,
|
|
||||||
0x5B, 0x3B, 0x28, 0xFB, 0xE2, 0x2E, 0x71, 0xB2, 0xB4, 0xD8, 0x34, 0x66, 0x5C, 0xDB, 0x4A, 0x35,
|
|
||||||
0xBC, 0x6F, 0x92, 0x2C, 0x0C, 0xB3, 0x92, 0xED, 0xE7, 0xBF, 0x2F, 0x4D, 0x13, 0xF7, 0xCF, 0x9A,
|
|
||||||
0xBF, 0xCC, 0x44, 0x02, 0xD9, 0x64, 0x04, 0xB9, 0xC6, 0x49, 0x22, 0x41, 0x04, 0x35, 0x9A, 0xE6,
|
|
||||||
0x1C, 0x84, 0x5B, 0x03, 0xD8, 0xDE, 0x6D, 0xFA, 0x74, 0x6C, 0xCE, 0xE7, 0x7B, 0x0D, 0x99, 0xD7,
|
|
||||||
0xA0, 0x6C, 0xF1, 0x12, 0x16, 0x8B, 0xFD, 0x51, 0xC6, 0x3D, 0xE4, 0x41, 0x1B, 0x53, 0x83, 0x9A,
|
|
||||||
0xB3, 0x84, 0x8A, 0x2C, 0xE1, 0x9A, 0x1F, 0x79, 0x19, 0x1A, 0xBB, 0x3D, 0xA6, 0xE2, 0x58, 0xD9,
|
|
||||||
0x7D, 0xF7, 0xE1, 0x8D, 0x0F, 0x3B, 0xE6, 0x0B, 0x04, 0x6F, 0x2D, 0x02, 0x38, 0x30, 0x9C, 0x97,
|
|
||||||
0xE3, 0x54, 0xF6, 0x43, 0x82, 0x01, 0x22, 0xEF, 0xE8, 0x83, 0x41, 0x2D, 0xB1, 0x40, 0xA4, 0x36,
|
|
||||||
0xAE, 0x1B, 0xC5, 0x2E, 0x80, 0x71, 0x73, 0x76, 0x07, 0x4A, 0x20, 0x2E, 0xFD, 0x22, 0x6E, 0x2C,
|
|
||||||
0xE6, 0x72, 0xF8, 0x69, 0xE7, 0xBB, 0xC9, 0x1E, 0x3B, 0xA8, 0xB7, 0x1C, 0xB2, 0xCF, 0x0E, 0x5A,
|
|
||||||
0xE0, 0x5E, 0x65, 0x6E, 0xE4, 0xB9, 0xAF, 0x58, 0x40, 0x07, 0xB9, 0xC3, 0xE1, 0x31, 0x48, 0x6C,
|
|
||||||
0xB1, 0x85, 0x28, 0xE2, 0x5B, 0xCD, 0xE6, 0x86, 0x4B, 0x0F, 0x48, 0x00, 0x39, 0xCC, 0xD0, 0x8F,
|
|
||||||
0xAF, 0xAE, 0x2E, 0xAE, 0xBE, 0xE8, 0x35, 0x5A, 0xD3, 0x6F, 0x1C, 0x4D, 0xAF, 0x71, 0xD3, 0x11,
|
|
||||||
0x76, 0x42, 0x47, 0x09, 0x4D, 0x27, 0x97, 0x44, 0x4C, 0x8C, 0xD4, 0xBE, 0x23, 0x41, 0x56, 0x16,
|
|
||||||
0x84, 0xA1, 0xDC, 0xC8, 0xA2, 0x70, 0x39, 0x9D, 0x6A, 0xAF, 0x40, 0xCD, 0x47, 0x90, 0xEA, 0xDA,
|
|
||||||
0xC2, 0x26, 0x71, 0x4C, 0xB9, 0x6F, 0xE8, 0x31, 0x20, 0xEA, 0x16, 0x35, 0xAD, 0x84, 0x7E, 0xCB,
|
|
||||||
0x68, 0x2A, 0x52, 0x1B, 0x2C, 0xD7, 0xD0, 0x2F, 0x07, 0x7D, 0xDD, 0xD2, 0x1B, 0xE8, 0x47, 0x3A,
|
|
||||||
0xF0, 0x46, 0xCC, 0x39, 0x52, 0x89, 0x5C, 0xD0, 0xA4, 0x3E, 0xCC, 0xC0, 0xA0, 0xB8, 0x6E, 0xB6,
|
|
||||||
0x23, 0x9B, 0x71, 0x4E, 0x93, 0x93, 0xFE, 0xD9, 0xA9, 0xAB, 0x5F, 0x29, 0x46, 0xB4, 0x53, 0x28,
|
|
||||||
0x48, 0x74, 0x4B, 0x5E, 0x51, 0x7E, 0xC8, 0xE1, 0x84, 0x05, 0xBE, 0x11, 0x99, 0x6D, 0x24, 0xE1,
|
|
||||||
0x49, 0x12, 0xB2, 0x40, 0x01, 0x0A, 0x9E, 0x2D, 0x1E, 0x62, 0xEA, 0xEA, 0x23, 0x50, 0x86, 0x6E,
|
|
||||||
0x79, 0x76, 0x98, 0x05, 0x82, 0xC5, 0x01, 0x75, 0x37, 0x5A, 0x30, 0xE3, 0x60, 0x41, 0xAE, 0x8E,
|
|
||||||
0xB9, 0x19, 0x61, 0xCC, 0x77, 0x75, 0x15, 0xA1, 0xF2, 0xB8, 0xB6, 0xEE, 0x14, 0x4F, 0x9D, 0x92,
|
|
||||||
0x56, 0x4E, 0x49, 0xCB, 0xB8, 0x4A, 0xE0, 0x34, 0x3F, 0x18, 0xC3, 0x3C, 0xCE, 0xD4, 0x51, 0x05,
|
|
||||||
0xCC, 0xA7, 0x23, 0x02, 0x9C, 0x7C, 0x40, 0x6D, 0xBA, 0x7A, 0x63, 0xDD, 0x41, 0xA9, 0x3A, 0xC8,
|
|
||||||
0xAF, 0x6A, 0xC4, 0x2F, 0x6B, 0x44, 0xDD, 0xEE, 0x3A, 0x64, 0x5F, 0x21, 0x07, 0x55, 0xE4, 0xA0,
|
|
||||||
0x8C, 0x7C, 0x28, 0x8D, 0x64, 0x1D, 0x72, 0xA0, 0x90, 0x93, 0x8A, 0x88, 0x89, 0x14, 0x51, 0x85,
|
|
||||||
0xBD, 0x3A, 0x6A, 0x13, 0x05, 0xD2, 0xAD, 0xA4, 0x22, 0x66, 0x62, 0x83, 0x97, 0x92, 0x61, 0x40,
|
|
||||||
0x7D, 0x77, 0xA3, 0x09, 0x33, 0x2C, 0xB6, 0xDD, 0xAD, 0xE6, 0x9A, 0x33, 0x12, 0x75, 0x46, 0x56,
|
|
||||||
0x65, 0x30, 0x2B, 0x33, 0xA8, 0xF5, 0xC8, 0x1D, 0xD5, 0xD6, 0x31, 0x98, 0x99, 0x56, 0x60, 0x47,
|
|
||||||
0xDC, 0x0B, 0x98, 0x77, 0xEB, 0x2E, 0xBD, 0xC5, 0x9C, 0xB1, 0x85, 0x85, 0x5A, 0x5C, 0x06, 0xBA,
|
|
||||||
0x01, 0x94, 0x5E, 0x8B, 0xA5, 0x7C, 0x80, 0xFA, 0x9E, 0x5B, 0xD9, 0x5A, 0x02, 0xDC, 0xA6, 0xF7,
|
|
||||||
0xD4, 0x3B, 0x8C, 0xC2, 0x90, 0xA0, 0xED, 0xA6, 0xC0, 0x41, 0x3E, 0xD1, 0xCD, 0xB9, 0x15, 0xAD,
|
|
||||||
0xC5, 0x79, 0xC2, 0x45, 0x2C, 0x7F, 0x3D, 0x8B, 0x23, 0x03, 0x5C, 0xCE, 0xF5, 0x6C, 0xD4, 0x61,
|
|
||||||
0x6A, 0x83, 0x1E, 0xC7, 0x62, 0xF2, 0x13, 0x17, 0x2A, 0x0C, 0x54, 0xA2, 0x7C, 0x69, 0xDE, 0x58,
|
|
||||||
0x0B, 0x91, 0x56, 0x7C, 0xEA, 0xA2, 0xB7, 0xE2, 0x54, 0xA8, 0xBC, 0x8A, 0x5D, 0x9A, 0x4B, 0x1D,
|
|
||||||
0x94, 0x61, 0xB9, 0xBD, 0x2F, 0xA0, 0xFA, 0x7C, 0x0E, 0xE7, 0x01, 0xFF, 0x13, 0x68, 0xF9, 0xE8,
|
|
||||||
0x5F, 0x17, 0x60, 0xC9, 0xA3, 0x34, 0x78, 0x8B, 0xBB, 0x0D, 0xE3, 0xC0, 0xF9, 0x8F, 0x6D, 0x7C,
|
|
||||||
0xF9, 0x1F, 0xFB, 0xA6, 0x66, 0x9A, 0x07, 0xFF, 0x6A, 0x48, 0x0D, 0x1B, 0xC2, 0xFC, 0xD2, 0xBA,
|
|
||||||
0xB1, 0x08, 0x80, 0xED, 0x7F, 0x9B, 0xFF, 0xB1, 0x25, 0xB8, 0x02, 0x6B, 0xDF, 0x45, 0x90, 0x49,
|
|
||||||
0xF0, 0x24, 0x34, 0xB0, 0x68, 0xA4, 0x91, 0xCD, 0x4D, 0x43, 0xB8, 0xA4, 0x72, 0x8D, 0x35, 0x51,
|
|
||||||
0xD3, 0x6D, 0x88, 0x53, 0x50, 0x5B, 0xAC, 0x04, 0xBF, 0x3E, 0x24, 0x7A, 0x15, 0x5B, 0x17, 0x00,
|
|
||||||
0xC9, 0x3D, 0xCA, 0x0C, 0x3D, 0x22, 0x97, 0x52, 0xCB, 0x0C, 0x02, 0x42, 0xA7, 0x89, 0xE7, 0x2A,
|
|
||||||
0xAD, 0x1D, 0x14, 0x30, 0x17, 0xA2, 0xE0, 0xBC, 0x1C, 0x2D, 0x15, 0xEA, 0xAA, 0xFD, 0x17, 0x0A,
|
|
||||||
0xA3, 0xD6, 0x12, 0x8A, 0x04, 0x31, 0xAD, 0xD8, 0x79, 0xC6, 0x72, 0x75, 0x4C, 0x59, 0xBA, 0x35,
|
|
||||||
0x59, 0x5D, 0x96, 0xAD, 0x04, 0xAE, 0x2F, 0x8D, 0xFE, 0xD7, 0x3D, 0x16, 0x8E, 0xB5, 0x12, 0x3F,
|
|
||||||
0xF8, 0x97, 0xFB, 0x2B, 0x46, 0xE4, 0xCD, 0x3F, 0xBC, 0x21, 0x70, 0x05, 0xA6, 0x41, 0x6D, 0x1E,
|
|
||||||
0x4D, 0x0D, 0xB3, 0xF6, 0xAB, 0xAE, 0x49, 0x8A, 0xAE, 0x1E, 0x92, 0xFB, 0xBC, 0xA7, 0xC4, 0x8C,
|
|
||||||
0xD7, 0xD6, 0x70, 0x5E, 0xB4, 0x28, 0xF9, 0x82, 0xEC, 0xE6, 0x48, 0x26, 0xA2, 0xB6, 0x56, 0x64,
|
|
||||||
0x52, 0xD5, 0xCA, 0xE8, 0x5A, 0x63, 0xFF, 0xD7, 0x4A, 0x40, 0xB7, 0x98, 0xBA, 0x4E, 0x15, 0x8C,
|
|
||||||
0xB3, 0x00, 0x1C, 0x93, 0x3E, 0x1D, 0x69, 0x03, 0x26, 0x03, 0x75, 0x35, 0x46, 0x5A, 0x81, 0xC1,
|
|
||||||
0xCC, 0x03, 0xC3, 0x2B, 0xFB, 0xF3, 0x1E, 0x16, 0xBF, 0xFB, 0x97, 0xAA, 0xAA, 0x81, 0xD4, 0x8B,
|
|
||||||
0x33, 0x5D, 0x59, 0x59, 0xD5, 0x4B, 0xE0, 0xD2, 0x08, 0xA0, 0x5B, 0x8B, 0x3C, 0x3A, 0x8C, 0xFC,
|
|
||||||
0x87, 0x52, 0xF6, 0x4D, 0xBB, 0x0F, 0x87, 0x01, 0x49, 0xD3, 0x73, 0xB8, 0x01, 0x43, 0xF7, 0x42,
|
|
||||||
0x50, 0xB8, 0xB2, 0xC2, 0xFD, 0xE6, 0xE6, 0x66, 0x15, 0x29, 0xA1, 0x21, 0x14, 0xDB, 0x8A, 0x2B,
|
|
||||||
0xF0, 0x49, 0xD3, 0xF1, 0x81, 0x30, 0x18, 0xD2, 0x1A, 0xC6, 0xF0, 0x25, 0xE3, 0x47, 0x5C, 0x71,
|
|
||||||
0xF4, 0xF4, 0x22, 0xA6, 0xFC, 0x33, 0xDC, 0x95, 0x32, 0xCB, 0x1A, 0xAD, 0xA6, 0x68, 0xFA, 0x8F,
|
|
||||||
0xD8, 0x3E, 0xCA, 0x0D, 0x76, 0xC1, 0x7A, 0xBA, 0x56, 0xA1, 0xFC, 0x9F, 0x61, 0xB9, 0x94, 0x28,
|
|
||||||
0xD6, 0x70, 0x9C, 0x40, 0x80, 0x5A, 0xC3, 0x31, 0xC4, 0x1A, 0x41, 0x17, 0xFC, 0x26, 0x6B, 0xF9,
|
|
||||||
0xCD, 0xFE, 0x19, 0x7E, 0x97, 0x76, 0x1E, 0x15, 0x25, 0x91, 0xAA, 0xAF, 0x50, 0x02, 0x9F, 0xDD,
|
|
||||||
0xE9, 0xA6, 0x15, 0xB9, 0x55, 0x0A, 0x50, 0x1B, 0x46, 0x41, 0xD0, 0x8F, 0xE2, 0x83, 0x27, 0xD6,
|
|
||||||
0x9D, 0xC5, 0x7A, 0x31, 0xC8, 0xD9, 0x5C, 0x6E, 0xB1, 0xBC, 0xB5, 0x44, 0x4F, 0xA1, 0xEC, 0x5F,
|
|
||||||
0x4B, 0x15, 0x01, 0x3F, 0x23, 0x8B, 0x7B, 0xAC, 0xD4, 0xA5, 0x36, 0x28, 0x0F, 0x56, 0x3F, 0xD5,
|
|
||||||
0x3C, 0xCB, 0x5F, 0xCC, 0xAE, 0x6B, 0x51, 0x9B, 0xC0, 0x38, 0x57, 0x92, 0x8B, 0x4A, 0xB2, 0xC8,
|
|
||||||
0x13, 0x01, 0xA8, 0x58, 0xC7, 0x2E, 0xC4, 0x4D, 0x6B, 0x7A, 0x7C, 0xBF, 0x5C, 0x83, 0xC2, 0xDF,
|
|
||||||
0xF5, 0xD5, 0x12, 0x33, 0x08, 0xC4, 0xD3, 0x95, 0x4B, 0x29, 0x5F, 0x37, 0x29, 0x8A, 0x0E, 0x62,
|
|
||||||
0x47, 0xA3, 0x51, 0x4A, 0xC5, 0x47, 0x0C, 0x49, 0x56, 0xB2, 0x98, 0x9F, 0xC8, 0x90, 0x04, 0x8C,
|
|
||||||
0x45, 0x3C, 0x8C, 0xB2, 0x94, 0x46, 0x99, 0xA8, 0xA4, 0x16, 0x63, 0x21, 0xCC, 0x5E, 0xFA, 0xE7,
|
|
||||||
0x9F, 0x8B, 0xC9, 0x7E, 0x5A, 0x0B, 0x96, 0xD3, 0xEB, 0x3D, 0xBF, 0x34, 0xD9, 0xF7, 0x6B, 0x89,
|
|
||||||
0xB9, 0x7A, 0xE9, 0xFF, 0x67, 0x4B, 0x21, 0x65, 0x4B, 0xF1, 0xB0, 0x54, 0x2E, 0x62, 0x62, 0x29,
|
|
||||||
0xE6, 0xC9, 0x82, 0x91, 0x97, 0x7C, 0x16, 0x0D, 0x1A, 0x2B, 0x25, 0x55, 0x9E, 0x97, 0x7D, 0x95,
|
|
||||||
0x43, 0x40, 0x59, 0x71, 0xE5, 0x35, 0x11, 0x06, 0x34, 0xE0, 0x63, 0x64, 0xF2, 0x41, 0xEB, 0xA7,
|
|
||||||
0xD1, 0x94, 0x26, 0x87, 0x24, 0xA5, 0x06, 0x24, 0xCD, 0x65, 0xDC, 0x41, 0xA8, 0xE9, 0x04, 0xEB,
|
|
||||||
0x76, 0x6D, 0x6E, 0x12, 0x05, 0xCE, 0x33, 0x77, 0xC4, 0xB1, 0x26, 0x03, 0xF9, 0xB2, 0xCA, 0x09,
|
|
||||||
0xD4, 0xC6, 0xBE, 0x12, 0xA4, 0x3E, 0x52, 0x25, 0xA8, 0x61, 0x5A, 0xD0, 0x76, 0xC0, 0x35, 0x5F,
|
|
||||||
0x26, 0x51, 0x4C, 0xC6, 0xB2, 0x07, 0x83, 0x35, 0x74, 0x0F, 0xA4, 0x66, 0x6D, 0x34, 0x91, 0x60,
|
|
||||||
0xA9, 0x73, 0x29, 0xFC, 0x66, 0xD9, 0xC2, 0x70, 0x4B, 0x57, 0xC9, 0xB0, 0xBD, 0xF4, 0xA5, 0x35,
|
|
||||||
0x59, 0x83, 0xE0, 0x0B, 0x6C, 0x62, 0xE0, 0x1E, 0x68, 0x64, 0xF2, 0x7B, 0x00, 0x77, 0x6B, 0xB6,
|
|
||||||
0xA3, 0x3D, 0xD6, 0x8E, 0x6A, 0x35, 0x53, 0x55, 0xE9, 0xAE, 0x0B, 0x6D, 0x4E, 0x74, 0x23, 0x0B,
|
|
||||||
0x4B, 0x10, 0xAA, 0x9A, 0x59, 0x0C, 0x38, 0x1B, 0x81, 0xAA, 0xBA, 0xC0, 0x11, 0xD6, 0x98, 0x66,
|
|
||||||
0xA9, 0x23, 0xF1, 0x97, 0x1D, 0xC9, 0x13, 0xB5, 0x07, 0x95, 0xF5, 0x05, 0xD4, 0x31, 0xAB, 0x25,
|
|
||||||
0x86, 0x30, 0xD3, 0x29, 0x13, 0xDE, 0x04, 0x03, 0x90, 0x07, 0x5A, 0xD5, 0x05, 0x14, 0xB5, 0x8E,
|
|
||||||
0x1C, 0x4D, 0x44, 0xB8, 0x1C, 0x05, 0xF9, 0xF0, 0x6B, 0x9A, 0x0F, 0xBC, 0xB4, 0x18, 0xDD, 0x97,
|
|
||||||
0x80, 0x50, 0xD2, 0xE6, 0xE0, 0x88, 0x8F, 0xF2, 0x21, 0xF4, 0xB2, 0x05, 0x9D, 0x02, 0x58, 0xFC,
|
|
||||||
0xC6, 0x71, 0x3E, 0x8A, 0x27, 0xC5, 0x68, 0x42, 0xEF, 0x17, 0x78, 0x51, 0x01, 0xF5, 0xA9, 0xEE,
|
|
||||||
0x28, 0x1B, 0xDB, 0x68, 0xCE, 0xF3, 0x41, 0x6B, 0x29, 0x7F, 0xF0, 0xFF, 0x28, 0x7F, 0xCC, 0xC7,
|
|
||||||
0x85, 0x34, 0x71, 0x31, 0x1A, 0xB3, 0x42, 0x96, 0x61, 0x18, 0xFF, 0x90, 0x93, 0xA4, 0xD4, 0x13,
|
|
||||||
0x97, 0x7A, 0x5A, 0xF1, 0xB3, 0xB6, 0x53, 0x98, 0x8E, 0x31, 0xAA, 0xF8, 0xE3, 0xC8, 0xF6, 0xF0,
|
|
||||||
0xF7, 0x3C, 0xF2, 0x65, 0x6D, 0x69, 0x5A, 0xA1, 0x31, 0x82, 0x3A, 0x57, 0x37, 0xCB, 0x7E, 0x9A,
|
|
||||||
0xFD, 0xB7, 0xAD, 0xE8, 0xD1, 0xF1, 0xE9, 0x71, 0xFF, 0xB8, 0x5C, 0x38, 0x23, 0xE7, 0x25, 0x93,
|
|
||||||
0x8A, 0x2B, 0x5D, 0xFA, 0xB2, 0x22, 0x80, 0x02, 0x1B, 0x45, 0x01, 0x7B, 0xDD, 0xDC, 0x54, 0x7E,
|
|
||||||
0xF1, 0xB6, 0x77, 0x71, 0x6E, 0xC7, 0x24, 0x01, 0x8F, 0x24, 0x15, 0xE6, 0xC2, 0x82, 0x44, 0xF9,
|
|
||||||
0xE0, 0xD7, 0xC7, 0xA5, 0x72, 0x5D, 0x7E, 0x61, 0x70, 0xC4, 0xDC, 0x52, 0xA7, 0xA9, 0x7E, 0x78,
|
|
||||||
0xE2, 0x62, 0x5D, 0x99, 0xBF, 0x04, 0x41, 0x72, 0x1A, 0x2D, 0x13, 0x55, 0x11, 0x67, 0x46, 0xE5,
|
|
||||||
0x30, 0x2F, 0xEE, 0xB2, 0x75, 0x0D, 0xD3, 0xC8, 0xB4, 0xC4, 0x84, 0xA5, 0xE5, 0x46, 0xA5, 0x12,
|
|
||||||
0x14, 0xFE, 0xA2, 0xB6, 0xE7, 0x8B, 0x91, 0x24, 0xB7, 0x5A, 0x73, 0xAB, 0x6F, 0x41, 0x2A, 0x3E,
|
|
||||||
0x58, 0x04, 0x23, 0x66, 0x39, 0xDB, 0x16, 0x77, 0xA3, 0x43, 0xEE, 0x61, 0x5C, 0x7F, 0xBA, 0x35,
|
|
||||||
0x78, 0xD2, 0x3C, 0x79, 0x61, 0x9E, 0xFC, 0xB1, 0x7B, 0x2E, 0x1C, 0x45, 0xF9, 0xDA, 0xE2, 0x98,
|
|
||||||
0xF6, 0x10, 0x58, 0xBB, 0x6D, 0x2F, 0x7D, 0x18, 0x20, 0xD2, 0x83, 0xCB, 0x00, 0xF4, 0x63, 0x58,
|
|
||||||
0xFF, 0x4A, 0xEE, 0x88, 0x7A, 0x09, 0xAA, 0xA2, 0xAD, 0x73, 0x54, 0xD8, 0xEE, 0xFD, 0x81, 0xA3,
|
|
||||||
0xF2, 0xCE, 0x65, 0x18, 0x48, 0x97, 0xC3, 0x92, 0x37, 0x8B, 0x75, 0xC1, 0x61, 0x19, 0x31, 0x64,
|
|
||||||
0x6C, 0x00, 0xE3, 0xCD, 0x5D, 0x49, 0x13, 0xD5, 0x1C, 0xB4, 0xF0, 0x1B, 0x08, 0x8A, 0x4F, 0x39,
|
|
||||||
0xCE, 0x9A, 0x38, 0xAD, 0x62, 0x72, 0xC5, 0x23, 0xC8, 0x4A, 0x67, 0x89, 0xC0, 0x6E, 0x10, 0x0D,
|
|
||||||
0x0D, 0x7C, 0x64, 0x9A, 0xA1, 0xB6, 0x1D, 0x3E, 0x37, 0xD7, 0xBC, 0xD9, 0x54, 0xFA, 0x4B, 0x62,
|
|
||||||
0x79, 0xD5, 0xB0, 0x8B, 0x1C, 0x56, 0xCC, 0x75, 0x7D, 0x1F, 0xF4, 0xA3, 0x4E, 0x29, 0xAF, 0x48,
|
|
||||||
0xA4, 0x53, 0xD1, 0x83, 0xC4, 0x86, 0xA2, 0x41, 0xBE, 0x91, 0x40, 0x44, 0x72, 0x4A, 0x33, 0x5D,
|
|
||||||
0xC7, 0xCA, 0xD2, 0x0B, 0x28, 0x49, 0x7A, 0xB2, 0x73, 0x95, 0x49, 0x6B, 0x25, 0x06, 0xFE, 0xC8,
|
|
||||||
0xD7, 0xF0, 0xC7, 0xA1, 0xD0, 0xA3, 0x83, 0x9B, 0x49, 0x2B, 0x83, 0xA4, 0x23, 0x64, 0x83, 0xA9,
|
|
||||||
0x37, 0xE4, 0xBB, 0xA8, 0x2D, 0x2F, 0xCB, 0xB4, 0x16, 0x50, 0x70, 0x71, 0x83, 0xBB, 0x11, 0x30,
|
|
||||||
0x52, 0x5A, 0xC4, 0x9E, 0x94, 0xA8, 0xC7, 0x8F, 0x10, 0x1F, 0x53, 0x4A, 0x20, 0x06, 0x20, 0xA6,
|
|
||||||
0x40, 0xD0, 0xA7, 0x42, 0x8A, 0x54, 0xE6, 0x92, 0x53, 0x2A, 0x20, 0xCA, 0x48, 0xCD, 0xE2, 0xC1,
|
|
||||||
0x85, 0x78, 0xD4, 0x46, 0xD6, 0x80, 0xFD, 0xDC, 0xBD, 0x73, 0x33, 0xDE, 0x90, 0x68, 0x09, 0x56,
|
|
||||||
0x36, 0x3D, 0x9A, 0xA6, 0x52, 0x5C, 0x54, 0xC7, 0x19, 0xF8, 0xA8, 0xA1, 0x03, 0x5A, 0x23, 0x84,
|
|
||||||
0x11, 0x1E, 0x84, 0x8A, 0x01, 0x40, 0x7F, 0x42, 0xC3, 0x1C, 0x22, 0x70, 0x08, 0x20, 0x82, 0xA0,
|
|
||||||
0x7F, 0x49, 0x0D, 0xF7, 0x64, 0x05, 0xC9, 0xF8, 0xD8, 0x6D, 0x35, 0xF0, 0x9D, 0x66, 0x95, 0xEC,
|
|
||||||
0x20, 0xA5, 0xBD, 0x68, 0x24, 0xFA, 0x64, 0x98, 0x1A, 0x50, 0x00, 0xAC, 0xD9, 0x01, 0xA0, 0x1E,
|
|
||||||
0x24, 0x5E, 0x63, 0x2B, 0x3F, 0xEF, 0x04, 0x2A, 0xBB, 0x00, 0xAB, 0xBB, 0x8E, 0x87, 0x5F, 0x39,
|
|
||||||
0x4F, 0x19, 0xA7, 0x39, 0x26, 0x00, 0x7B, 0x93, 0x68, 0x7A, 0x99, 0x30, 0x2E, 0xCE, 0x64, 0x1B,
|
|
||||||
0x6A, 0x6C, 0xB4, 0xE4, 0xF5, 0xA9, 0x87, 0x15, 0x79, 0x3F, 0xC5, 0x8B, 0xCB, 0x0C, 0xF3, 0xBA,
|
|
||||||
0x53, 0x79, 0x77, 0xB1, 0x86, 0x70, 0x21, 0x50, 0x66, 0x38, 0xB3, 0x29, 0x74, 0xB0, 0xFA, 0xA1,
|
|
||||||
0x48, 0x82, 0x7A, 0x4F, 0xB7, 0x42, 0xE2, 0xC1, 0x44, 0xED, 0x81, 0xF9, 0xDC, 0xC2, 0xD8, 0xE1,
|
|
||||||
0x94, 0x83, 0x5A, 0x0A, 0xB5, 0x02, 0x45, 0xC6, 0x95, 0xCD, 0x98, 0x35, 0x1D, 0x6A, 0x58, 0x88,
|
|
||||||
0x61, 0xE0, 0xAF, 0xFE, 0x05, 0x0F, 0x1E, 0x1C, 0xC8, 0x55, 0x3F, 0xE1, 0x23, 0xE3, 0x7E, 0xF4,
|
|
||||||
0x23, 0x3E, 0x3E, 0xAF, 0xF0, 0xF1, 0x79, 0x1D, 0x1F, 0xB4, 0xAA, 0x3C, 0x98, 0x0C, 0x80, 0xEC,
|
|
||||||
0x19, 0xE1, 0x64, 0x4C, 0x13, 0x58, 0xC0, 0x43, 0x50, 0x25, 0x7F, 0x8B, 0xB3, 0x84, 0xFE, 0x98,
|
|
||||||
0xB3, 0xDE, 0x84, 0x8D, 0xC4, 0x23, 0xFE, 0x8A, 0xD5, 0xFF, 0x82, 0x4B, 0x3C, 0x70, 0x3D, 0x97,
|
|
||||||
0x79, 0x6D, 0x5A, 0x49, 0x28, 0x3F, 0x7E, 0x2B, 0x91, 0x7E, 0xE4, 0x42, 0x78, 0xA9, 0x38, 0xC8,
|
|
||||||
0xDF, 0xB7, 0xF4, 0x00, 0xBC, 0x11, 0xF8, 0x29, 0x35, 0x75, 0xBC, 0x0B, 0xA5, 0xFC, 0x29, 0x30,
|
|
||||||
0x64, 0xA8, 0xC0, 0x47, 0xDD, 0xD9, 0xDC, 0x12, 0xAE, 0x01, 0x8A, 0xF1, 0xA3, 0x29, 0xB0, 0xEA,
|
|
||||||
0xC9, 0x02, 0xD7, 0x9E, 0x40, 0x26, 0x04, 0x91, 0xE0, 0x48, 0xC8, 0xA7, 0x8D, 0x2F, 0x07, 0x9B,
|
|
||||||
0x37, 0x35, 0xC8, 0x43, 0x2E, 0xFC, 0x98, 0x2E, 0x0C, 0x36, 0x6F, 0xFE, 0x6D, 0x36, 0xC6, 0xCC,
|
|
||||||
0x5A, 0x76, 0xA4, 0x96, 0x4C, 0xF6, 0xF4, 0x0B, 0xBF, 0x71, 0x09, 0x48, 0x5D, 0x49, 0x78, 0x45,
|
|
||||||
0x34, 0x03, 0x6B, 0x43, 0x61, 0xE1, 0x07, 0xFF, 0x47, 0x09, 0xF8, 0x91, 0x9E, 0x07, 0xCE, 0xBD,
|
|
||||||
0xE6, 0x3D, 0x5E, 0x2F, 0x3E, 0x85, 0xE9, 0x56, 0xE9, 0xC1, 0x4A, 0xC7, 0xEF, 0x53, 0x3A, 0x76,
|
|
||||||
0x59, 0xA2, 0x14, 0x4A, 0x14, 0x59, 0x88, 0x1A, 0x6A, 0x50, 0x0E, 0x51, 0x98, 0x89, 0x17, 0xCD,
|
|
||||||
0x81, 0x02, 0x9B, 0x73, 0x34, 0x5B, 0x3A, 0x02, 0x0F, 0xF4, 0xF5, 0x45, 0xEE, 0xFC, 0x74, 0x76,
|
|
||||||
0x7A, 0x22, 0x44, 0x7C, 0xA5, 0x62, 0x22, 0xD0, 0xAA, 0x2E, 0x2C, 0x2F, 0xCF, 0x9C, 0x89, 0xE4,
|
|
||||||
0xA1, 0x28, 0x75, 0x30, 0x31, 0x28, 0x87, 0xFE, 0x74, 0x31, 0xFC, 0x0A, 0x71, 0xD6, 0xD0, 0xCF,
|
|
||||||
0x52, 0x48, 0x58, 0x5B, 0x36, 0xA2, 0xF7, 0xFB, 0x97, 0xF6, 0xAE, 0xDD, 0x84, 0xBA, 0x00, 0xB4,
|
|
||||||
0x0A, 0x69, 0x19, 0xEE, 0x7D, 0xFE, 0xB7, 0x90, 0xB7, 0xFF, 0x1E, 0x32, 0x83, 0xA8, 0x95, 0x42,
|
|
||||||
0x58, 0x2A, 0xF0, 0xAB, 0xB8, 0x93, 0x24, 0x9A, 0x4A, 0xB4, 0xE3, 0x24, 0xC1, 0x4B, 0xE9, 0x43,
|
|
||||||
0x85, 0xA2, 0x0D, 0x61, 0x31, 0xA5, 0x89, 0xE6, 0x47, 0x34, 0xD5, 0x78, 0x24, 0xB4, 0x34, 0x8B,
|
|
||||||
0x63, 0x68, 0x5C, 0x56, 0xF4, 0x61, 0xEB, 0xC5, 0xEB, 0xCB, 0xFB, 0x8C, 0x66, 0xD4, 0xCF, 0x97,
|
|
||||||
0x69, 0x52, 0xD1, 0x0B, 0x56, 0x50, 0xDF, 0x10, 0xEE, 0x7E, 0xB9, 0xC9, 0xEB, 0xA9, 0x8C, 0x73,
|
|
||||||
0x8C, 0xA2, 0x1B, 0x2D, 0x35, 0x07, 0xE9, 0x26, 0x40, 0xD5, 0xE5, 0x59, 0x10, 0xCC, 0xDB, 0x2B,
|
|
||||||
0xB4, 0xA0, 0xF1, 0x8A, 0x44, 0x24, 0x9F, 0xCB, 0x67, 0x7F, 0xE4, 0xC9, 0xA9, 0xE2, 0x82, 0x50,
|
|
||||||
0xF2, 0x54, 0xA9, 0x36, 0xAD, 0x0D, 0x63, 0x83, 0x6A, 0x8C, 0xA7, 0x82, 0x70, 0x0F, 0xAF, 0x51,
|
|
||||||
0xE9, 0xC2, 0x2C, 0x6A, 0x29, 0xDC, 0xDE, 0x46, 0x5F, 0xCB, 0x6D, 0xE9, 0x89, 0x7C, 0x2A, 0x25,
|
|
||||||
0xE3, 0xAE, 0xAE, 0x63, 0x55, 0x45, 0xB1, 0x3E, 0x25, 0x61, 0x5A, 0x26, 0x5B, 0x54, 0x06, 0x26,
|
|
||||||
0x77, 0x0B, 0x70, 0x9B, 0x06, 0x29, 0x1C, 0xBD, 0x7E, 0x7F, 0xCE, 0x46, 0xD1, 0xCE, 0x11, 0x80,
|
|
||||||
0x69, 0xC5, 0x3E, 0x93, 0xD7, 0xE0, 0x24, 0xCC, 0x73, 0x07, 0x32, 0xE9, 0x4A, 0x03, 0x0E, 0xA9,
|
|
||||||
0x98, 0x44, 0xFE, 0x81, 0x7E, 0xA0, 0x3B, 0x3A, 0xFC, 0xBB, 0x09, 0x35, 0x47, 0xCD, 0xA5, 0xD0,
|
|
||||||
0xA4, 0xFA, 0x74, 0x70, 0xF5, 0x06, 0xC2, 0x53, 0x0C, 0xA5, 0x01, 0x17, 0x50, 0x34, 0xD7, 0x74,
|
|
||||||
0x7C, 0x7A, 0x7D, 0x0C, 0x29, 0xC8, 0x7F, 0x21, 0x37, 0x66, 0xBB, 0xAA, 0x6C, 0xB8, 0xF3, 0xEA,
|
|
||||||
0x75, 0x56, 0x2E, 0x03, 0x7A, 0x61, 0x8C, 0x58, 0x0F, 0x29, 0x7E, 0xFB, 0x7B, 0xF4, 0x9E, 0x8D,
|
|
||||||
0x15, 0xD2, 0x6A, 0x5D, 0x6F, 0xCE, 0x76, 0x90, 0x67, 0x89, 0xD5, 0x43, 0x2C, 0x70, 0x97, 0x1F,
|
|
||||||
0x29, 0x59, 0x95, 0x35, 0xDC, 0xF6, 0x48, 0x10, 0xE0, 0xC7, 0x5A, 0x03, 0x1B, 0x6A, 0x22, 0xB2,
|
|
||||||
0xD4, 0x42, 0x22, 0x29, 0x08, 0x90, 0xD2, 0x3E, 0x84, 0x39, 0xD3, 0x92, 0x65, 0x86, 0xB2, 0xA1,
|
|
||||||
0xBC, 0xFF, 0xC5, 0x9A, 0xA3, 0x64, 0x46, 0xE8, 0xCE, 0xF9, 0x6C, 0x73, 0x53, 0xD8, 0x85, 0x99,
|
|
||||||
0x18, 0x05, 0x52, 0x8A, 0x01, 0x1C, 0x9A, 0x7D, 0x68, 0x2D, 0x8C, 0xB2, 0x90, 0x58, 0xAB, 0x3D,
|
|
||||||
0xD2, 0xB6, 0x51, 0x55, 0x03, 0x54, 0x7C, 0x46, 0x01, 0x03, 0xCE, 0xB2, 0x24, 0x80, 0xA8, 0x8B,
|
|
||||||
0x39, 0xBA, 0xB2, 0x2D, 0xC5, 0xBA, 0xD0, 0x84, 0x0E, 0xEC, 0x67, 0xC8, 0x12, 0x95, 0x97, 0xAD,
|
|
||||||
0xA2, 0x27, 0x12, 0xC5, 0x77, 0x95, 0x9E, 0xC8, 0x6F, 0xE5, 0x84, 0xAA, 0xC8, 0x77, 0x88, 0x2F,
|
|
||||||
0x13, 0x5C, 0xD4, 0xD1, 0x13, 0xA0, 0x24, 0x83, 0x52, 0x34, 0x60, 0x2A, 0x2C, 0x37, 0xEE, 0xEB,
|
|
||||||
0xD3, 0xE9, 0xB4, 0x8E, 0xDF, 0x6A, 0xEB, 0x70, 0x82, 0xB2, 0x02, 0x5F, 0x5F, 0xC7, 0x21, 0x47,
|
|
||||||
0x15, 0x58, 0xF8, 0x6E, 0xE1, 0xAC, 0xBA, 0xE8, 0x42, 0x7F, 0x2B, 0xDE, 0xD4, 0xAA, 0xD2, 0x59,
|
|
||||||
0xE1, 0x73, 0x79, 0xDB, 0x7B, 0x3B, 0x2B, 0x20, 0x32, 0xC4, 0xAF, 0xB2, 0x90, 0x69, 0x20, 0x0D,
|
|
||||||
0x3B, 0xE5, 0x46, 0x56, 0x25, 0x85, 0x65, 0x5C, 0xB0, 0xE3, 0x2C, 0x9D, 0x18, 0x33, 0x60, 0xDD,
|
|
||||||
0x11, 0x96, 0xD2, 0x95, 0x43, 0x2D, 0x65, 0xB7, 0x0E, 0xB7, 0x0A, 0xFB, 0x70, 0x30, 0x83, 0x94,
|
|
||||||
0x79, 0xFB, 0xF3, 0x4F, 0x39, 0x5B, 0xDE, 0xF6, 0x92, 0x62, 0x71, 0xE1, 0xF3, 0xFC, 0xA9, 0x35,
|
|
||||||
0xAF, 0x69, 0xA5, 0xD1, 0xAF, 0xC4, 0x97, 0xBD, 0x46, 0xFE, 0x19, 0x3B, 0xFF, 0x9C, 0xAD, 0x81,
|
|
||||||
0xB1, 0x43, 0x23, 0x2A, 0xDC, 0x4C, 0x8C, 0xEA, 0x2F, 0x34, 0xE6, 0x63, 0x79, 0x29, 0xBF, 0x2D,
|
|
||||||
0xA0, 0x54, 0xA9, 0xD3, 0x68, 0x78, 0x3E, 0xFF, 0x9A, 0x42, 0x19, 0x1D, 0x65, 0xFE, 0x28, 0x20,
|
|
||||||
0x09, 0xC5, 0x82, 0xA3, 0x41, 0xBE, 0x92, 0xFB, 0x46, 0xC0, 0x86, 0x69, 0x03, 0x93, 0x6D, 0xCB,
|
|
||||||
0xDE, 0xB2, 0x77, 0x71, 0x64, 0x7F, 0x4D, 0xF7, 0x57, 0x4F, 0xD8, 0x5F, 0x34, 0x69, 0x58, 0x0B,
|
|
||||||
0xE7, 0xB5, 0xAB, 0x8A, 0x4D, 0x6A, 0x83, 0xFB, 0xC4, 0xA7, 0x70, 0x3D, 0x6F, 0xB3, 0xCC, 0xB6,
|
|
||||||
0x1A, 0xE4, 0x5F, 0x60, 0xD4, 0x31, 0xBA, 0x95, 0x2F, 0x92, 0xF4, 0x81, 0x7B, 0x18, 0x5B, 0x17,
|
|
||||||
0x54, 0x26, 0x70, 0x49, 0xD5, 0x87, 0x34, 0xB9, 0xD3, 0x9C, 0x2F, 0x39, 0xC3, 0xB7, 0x3C, 0xA8,
|
|
||||||
0x03, 0xE4, 0x37, 0x9C, 0x72, 0x39, 0xB0, 0xBF, 0x07, 0x5D, 0x33, 0x2A, 0x41, 0x79, 0xB1, 0x26,
|
|
||||||
0x9B, 0xE6, 0x7C, 0x02, 0x82, 0x01, 0x70, 0xB1, 0xA3, 0x48, 0xCD, 0x2B, 0xCB, 0x98, 0x9B, 0x57,
|
|
||||||
0x96, 0x54, 0xE2, 0x5F, 0x59, 0xCC, 0xDB, 0x9F, 0xFC, 0xDB, 0x4C, 0xF9, 0x7F, 0x5B, 0x28, 0x36,
|
|
||||||
0x32, 0xF9, 0xE1, 0x09, 0xF7, 0x56, 0x3F, 0x45, 0xAD, 0x47, 0x51, 0xBB, 0xF7, 0xFF, 0x17, 0x53,
|
|
||||||
0xE8, 0x9D, 0x36, 0x92, 0x29, 0x00, 0x00
|
|
||||||
};
|
|
||||||
|
|
||||||
#define SPIFFS_MAXLENGTH_FILEPATH 32
|
|
||||||
const char *excludeListFile = "/.exclude.files";
|
|
||||||
|
|
||||||
typedef struct ExcludeListS {
|
|
||||||
char *item;
|
|
||||||
ExcludeListS *next;
|
|
||||||
} ExcludeList;
|
|
||||||
|
|
||||||
static ExcludeList *excludes = NULL;
|
|
||||||
|
|
||||||
static bool matchWild(const char *pattern, const char *testee) {
|
|
||||||
const char *nxPat = NULL, *nxTst = NULL;
|
|
||||||
|
|
||||||
while (*testee) {
|
|
||||||
if (( *pattern == '?' ) || (*pattern == *testee)){
|
|
||||||
pattern++;testee++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (*pattern=='*'){
|
|
||||||
nxPat=pattern++; nxTst=testee;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (nxPat){
|
|
||||||
pattern = nxPat+1; testee=++nxTst;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
while (*pattern=='*'){pattern++;}
|
|
||||||
return (*pattern == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool addExclude(const char *item){
|
|
||||||
size_t len = strlen(item);
|
|
||||||
if(!len){
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
ExcludeList *e = (ExcludeList *)malloc(sizeof(ExcludeList));
|
|
||||||
if(!e){
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
e->item = (char *)malloc(len+1);
|
|
||||||
if(!e->item){
|
|
||||||
free(e);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
memcpy(e->item, item, len+1);
|
|
||||||
e->next = excludes;
|
|
||||||
excludes = e;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void loadExcludeList(fs::FS &_fs, const char *filename){
|
|
||||||
static char linebuf[SPIFFS_MAXLENGTH_FILEPATH];
|
|
||||||
fs::File excludeFile=_fs.open(filename, "r");
|
|
||||||
if(!excludeFile){
|
|
||||||
//addExclude("/*.js.gz");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
#ifdef ESP32
|
|
||||||
if(excludeFile.isDirectory()){
|
|
||||||
excludeFile.close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
if (excludeFile.size() > 0){
|
|
||||||
uint8_t idx;
|
|
||||||
bool isOverflowed = false;
|
|
||||||
while (excludeFile.available()){
|
|
||||||
linebuf[0] = '\0';
|
|
||||||
idx = 0;
|
|
||||||
int lastChar;
|
|
||||||
do {
|
|
||||||
lastChar = excludeFile.read();
|
|
||||||
if(lastChar != '\r'){
|
|
||||||
linebuf[idx++] = (char) lastChar;
|
|
||||||
}
|
|
||||||
} while ((lastChar >= 0) && (lastChar != '\n') && (idx < SPIFFS_MAXLENGTH_FILEPATH));
|
|
||||||
|
|
||||||
if(isOverflowed){
|
|
||||||
isOverflowed = (lastChar != '\n');
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
isOverflowed = (idx >= SPIFFS_MAXLENGTH_FILEPATH);
|
|
||||||
linebuf[idx-1] = '\0';
|
|
||||||
if(!addExclude(linebuf)){
|
|
||||||
excludeFile.close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
excludeFile.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool isExcluded(fs::FS &_fs, const char *filename) {
|
|
||||||
if(excludes == NULL){
|
|
||||||
loadExcludeList(_fs, excludeListFile);
|
|
||||||
}
|
|
||||||
ExcludeList *e = excludes;
|
|
||||||
while(e){
|
|
||||||
if (matchWild(e->item, filename)){
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
e = e->next;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// WEB HANDLER IMPLEMENTATION
|
|
||||||
|
|
||||||
#ifdef ESP32
|
|
||||||
SPIFFSEditor::SPIFFSEditor(const fs::FS& fs, const String& username, const String& password)
|
|
||||||
#else
|
|
||||||
SPIFFSEditor::SPIFFSEditor(const String& username, const String& password, const fs::FS& fs)
|
|
||||||
#endif
|
|
||||||
:_fs(fs)
|
|
||||||
,_username(username)
|
|
||||||
,_password(password)
|
|
||||||
,_authenticated(false)
|
|
||||||
,_startTime(0)
|
|
||||||
{}
|
|
||||||
|
|
||||||
bool SPIFFSEditor::canHandle(AsyncWebServerRequest *request){
|
|
||||||
if(request->url().equalsIgnoreCase("/edit")){
|
|
||||||
if(request->method() == HTTP_GET){
|
|
||||||
if(request->hasParam("list"))
|
|
||||||
return true;
|
|
||||||
if(request->hasParam("edit")){
|
|
||||||
request->_tempFile = _fs.open(request->arg("edit"), "r");
|
|
||||||
if(!request->_tempFile){
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
#ifdef ESP32
|
|
||||||
if(request->_tempFile.isDirectory()){
|
|
||||||
request->_tempFile.close();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
if(request->hasParam("download")){
|
|
||||||
request->_tempFile = _fs.open(request->arg("download"), "r");
|
|
||||||
if(!request->_tempFile){
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
#ifdef ESP32
|
|
||||||
if(request->_tempFile.isDirectory()){
|
|
||||||
request->_tempFile.close();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
request->addInterestingHeader("If-Modified-Since");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else if(request->method() == HTTP_POST)
|
|
||||||
return true;
|
|
||||||
else if(request->method() == HTTP_DELETE)
|
|
||||||
return true;
|
|
||||||
else if(request->method() == HTTP_PUT)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void SPIFFSEditor::handleRequest(AsyncWebServerRequest *request){
|
|
||||||
if(_username.length() && _password.length() && !request->authenticate(_username.c_str(), _password.c_str()))
|
|
||||||
return request->requestAuthentication();
|
|
||||||
|
|
||||||
if(request->method() == HTTP_GET){
|
|
||||||
if(request->hasParam("list")){
|
|
||||||
String path = request->getParam("list")->value();
|
|
||||||
#ifdef ESP32
|
|
||||||
File dir = _fs.open(path);
|
|
||||||
#else
|
|
||||||
Dir dir = _fs.openDir(path);
|
|
||||||
#endif
|
|
||||||
path = String();
|
|
||||||
String output = "[";
|
|
||||||
#ifdef ESP32
|
|
||||||
File entry = dir.openNextFile();
|
|
||||||
while(entry){
|
|
||||||
#else
|
|
||||||
while(dir.next()){
|
|
||||||
fs::File entry = dir.openFile("r");
|
|
||||||
#endif
|
|
||||||
if (isExcluded(_fs, entry.name())) {
|
|
||||||
#ifdef ESP32
|
|
||||||
entry = dir.openNextFile();
|
|
||||||
#endif
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (output != "[") output += ',';
|
|
||||||
output += "{\"type\":\"";
|
|
||||||
output += "file";
|
|
||||||
output += "\",\"name\":\"";
|
|
||||||
output += String(entry.name());
|
|
||||||
output += "\",\"size\":";
|
|
||||||
output += String(entry.size());
|
|
||||||
output += "}";
|
|
||||||
#ifdef ESP32
|
|
||||||
entry = dir.openNextFile();
|
|
||||||
#else
|
|
||||||
entry.close();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
#ifdef ESP32
|
|
||||||
dir.close();
|
|
||||||
#endif
|
|
||||||
output += "]";
|
|
||||||
request->send(200, "application/json", output);
|
|
||||||
output = String();
|
|
||||||
}
|
|
||||||
else if(request->hasParam("edit") || request->hasParam("download")){
|
|
||||||
request->send(request->_tempFile, request->_tempFile.name(), String(), request->hasParam("download"));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
const char * buildTime = __DATE__ " " __TIME__ " GMT";
|
|
||||||
if (request->header("If-Modified-Since").equals(buildTime)) {
|
|
||||||
request->send(304);
|
|
||||||
} else {
|
|
||||||
AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", edit_htm_gz, edit_htm_gz_len);
|
|
||||||
response->addHeader("Content-Encoding", "gzip");
|
|
||||||
response->addHeader("Last-Modified", buildTime);
|
|
||||||
request->send(response);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if(request->method() == HTTP_DELETE){
|
|
||||||
if(request->hasParam("path", true)){
|
|
||||||
_fs.remove(request->getParam("path", true)->value());
|
|
||||||
request->send(200, "", "DELETE: "+request->getParam("path", true)->value());
|
|
||||||
} else
|
|
||||||
request->send(404);
|
|
||||||
} else if(request->method() == HTTP_POST){
|
|
||||||
if(request->hasParam("data", true, true) && _fs.exists(request->getParam("data", true, true)->value()))
|
|
||||||
request->send(200, "", "UPLOADED: "+request->getParam("data", true, true)->value());
|
|
||||||
else
|
|
||||||
request->send(500);
|
|
||||||
} else if(request->method() == HTTP_PUT){
|
|
||||||
if(request->hasParam("path", true)){
|
|
||||||
String filename = request->getParam("path", true)->value();
|
|
||||||
if(_fs.exists(filename)){
|
|
||||||
request->send(200);
|
|
||||||
} else {
|
|
||||||
fs::File f = _fs.open(filename, "w");
|
|
||||||
if(f){
|
|
||||||
f.write((uint8_t)0x00);
|
|
||||||
f.close();
|
|
||||||
request->send(200, "", "CREATE: "+filename);
|
|
||||||
} else {
|
|
||||||
request->send(500);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else
|
|
||||||
request->send(400);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SPIFFSEditor::handleUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final){
|
|
||||||
if(!index){
|
|
||||||
if(!_username.length() || request->authenticate(_username.c_str(),_password.c_str())){
|
|
||||||
_authenticated = true;
|
|
||||||
request->_tempFile = _fs.open(filename, "w");
|
|
||||||
_startTime = millis();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(_authenticated && request->_tempFile){
|
|
||||||
if(len){
|
|
||||||
request->_tempFile.write(data,len);
|
|
||||||
}
|
|
||||||
if(final){
|
|
||||||
request->_tempFile.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
#ifndef SPIFFSEditor_H_
|
|
||||||
#define SPIFFSEditor_H_
|
|
||||||
#include <ESPAsyncWebServer.h>
|
|
||||||
|
|
||||||
class SPIFFSEditor : public AsyncWebHandler {
|
|
||||||
private:
|
|
||||||
fs::FS _fs;
|
|
||||||
String _username;
|
|
||||||
String _password;
|
|
||||||
bool _authenticated;
|
|
||||||
uint32_t _startTime;
|
|
||||||
|
|
||||||
public:
|
|
||||||
#ifdef ESP32
|
|
||||||
SPIFFSEditor(const fs::FS & fs, const String & username = String(), const String & password = String());
|
|
||||||
#else
|
|
||||||
#pragma GCC diagnostic push
|
|
||||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
|
||||||
SPIFFSEditor(const String & username = String(), const String & password = String(), const fs::FS & fs = SPIFFS);
|
|
||||||
#pragma GCC diagnostic pop
|
|
||||||
#endif
|
|
||||||
virtual bool canHandle(AsyncWebServerRequest * request) override final;
|
|
||||||
virtual void handleRequest(AsyncWebServerRequest * request) override final;
|
|
||||||
virtual void handleUpload(AsyncWebServerRequest * request, const String & filename, size_t index, uint8_t * data, size_t len, bool final) override final;
|
|
||||||
virtual bool isRequestHandlerTrivial() override final {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,193 +0,0 @@
|
|||||||
/*
|
|
||||||
Asynchronous WebServer library for Espressif MCUs
|
|
||||||
|
|
||||||
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
|
|
||||||
This file is part of the esp8266 core for Arduino environment.
|
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
|
||||||
modify it under the terms of the GNU Lesser General Public
|
|
||||||
License as published by the Free Software Foundation; either
|
|
||||||
version 2.1 of the License, or (at your option) any later version.
|
|
||||||
|
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
Lesser General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU Lesser General Public
|
|
||||||
License along with this library; if not, write to the Free Software
|
|
||||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
||||||
*/
|
|
||||||
#ifndef STRINGARRAY_H_
|
|
||||||
#define STRINGARRAY_H_
|
|
||||||
|
|
||||||
#include "stddef.h"
|
|
||||||
#include "WString.h"
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
class LinkedListNode {
|
|
||||||
T _value;
|
|
||||||
public:
|
|
||||||
LinkedListNode<T>* next;
|
|
||||||
LinkedListNode(const T val): _value(val), next(nullptr) {}
|
|
||||||
~LinkedListNode(){}
|
|
||||||
const T& value() const { return _value; };
|
|
||||||
T& value(){ return _value; }
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename T, template<typename> class Item = LinkedListNode>
|
|
||||||
class LinkedList {
|
|
||||||
public:
|
|
||||||
typedef Item<T> ItemType;
|
|
||||||
typedef std::function<void(const T&)> OnRemove;
|
|
||||||
typedef std::function<bool(const T&)> Predicate;
|
|
||||||
private:
|
|
||||||
ItemType* _root;
|
|
||||||
OnRemove _onRemove;
|
|
||||||
|
|
||||||
class Iterator {
|
|
||||||
ItemType* _node;
|
|
||||||
public:
|
|
||||||
Iterator(ItemType* current = nullptr) : _node(current) {}
|
|
||||||
Iterator(const Iterator& i) : _node(i._node) {}
|
|
||||||
Iterator& operator ++() { _node = _node->next; return *this; }
|
|
||||||
bool operator != (const Iterator& i) const { return _node != i._node; }
|
|
||||||
const T& operator * () const { return _node->value(); }
|
|
||||||
const T* operator -> () const { return &_node->value(); }
|
|
||||||
};
|
|
||||||
|
|
||||||
public:
|
|
||||||
typedef const Iterator ConstIterator;
|
|
||||||
ConstIterator begin() const { return ConstIterator(_root); }
|
|
||||||
ConstIterator end() const { return ConstIterator(nullptr); }
|
|
||||||
|
|
||||||
LinkedList(OnRemove onRemove) : _root(nullptr), _onRemove(onRemove) {}
|
|
||||||
~LinkedList(){}
|
|
||||||
void add(const T& t){
|
|
||||||
auto it = new ItemType(t);
|
|
||||||
if(!_root){
|
|
||||||
_root = it;
|
|
||||||
} else {
|
|
||||||
auto i = _root;
|
|
||||||
while(i->next) i = i->next;
|
|
||||||
i->next = it;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
T& front() const {
|
|
||||||
return _root->value();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isEmpty() const {
|
|
||||||
return _root == nullptr;
|
|
||||||
}
|
|
||||||
size_t length() const {
|
|
||||||
size_t i = 0;
|
|
||||||
auto it = _root;
|
|
||||||
while(it){
|
|
||||||
i++;
|
|
||||||
it = it->next;
|
|
||||||
}
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
size_t count_if(Predicate predicate) const {
|
|
||||||
size_t i = 0;
|
|
||||||
auto it = _root;
|
|
||||||
while(it){
|
|
||||||
if (!predicate){
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
else if (predicate(it->value())) {
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
it = it->next;
|
|
||||||
}
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
const T* nth(size_t N) const {
|
|
||||||
size_t i = 0;
|
|
||||||
auto it = _root;
|
|
||||||
while(it){
|
|
||||||
if(i++ == N)
|
|
||||||
return &(it->value());
|
|
||||||
it = it->next;
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
bool remove(const T& t){
|
|
||||||
auto it = _root;
|
|
||||||
auto pit = _root;
|
|
||||||
while(it){
|
|
||||||
if(it->value() == t){
|
|
||||||
if(it == _root){
|
|
||||||
_root = _root->next;
|
|
||||||
} else {
|
|
||||||
pit->next = it->next;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_onRemove) {
|
|
||||||
_onRemove(it->value());
|
|
||||||
}
|
|
||||||
|
|
||||||
delete it;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
pit = it;
|
|
||||||
it = it->next;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
bool remove_first(Predicate predicate){
|
|
||||||
auto it = _root;
|
|
||||||
auto pit = _root;
|
|
||||||
while(it){
|
|
||||||
if(predicate(it->value())){
|
|
||||||
if(it == _root){
|
|
||||||
_root = _root->next;
|
|
||||||
} else {
|
|
||||||
pit->next = it->next;
|
|
||||||
}
|
|
||||||
if (_onRemove) {
|
|
||||||
_onRemove(it->value());
|
|
||||||
}
|
|
||||||
delete it;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
pit = it;
|
|
||||||
it = it->next;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void free(){
|
|
||||||
while(_root != nullptr){
|
|
||||||
auto it = _root;
|
|
||||||
_root = _root->next;
|
|
||||||
if (_onRemove) {
|
|
||||||
_onRemove(it->value());
|
|
||||||
}
|
|
||||||
delete it;
|
|
||||||
}
|
|
||||||
_root = nullptr;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
class StringArray : public LinkedList<String> {
|
|
||||||
public:
|
|
||||||
|
|
||||||
StringArray() : LinkedList(nullptr) {}
|
|
||||||
|
|
||||||
bool containsIgnoreCase(const String& str){
|
|
||||||
for (const auto& s : *this) {
|
|
||||||
if (str.equalsIgnoreCase(s)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#endif /* STRINGARRAY_H_ */
|
|
||||||
@@ -1,235 +0,0 @@
|
|||||||
/*
|
|
||||||
Asynchronous WebServer library for Espressif MCUs
|
|
||||||
|
|
||||||
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
|
|
||||||
This file is part of the esp8266 core for Arduino environment.
|
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
|
||||||
modify it under the terms of the GNU Lesser General Public
|
|
||||||
License as published by the Free Software Foundation; either
|
|
||||||
version 2.1 of the License, or (at your option) any later version.
|
|
||||||
|
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
Lesser General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU Lesser General Public
|
|
||||||
License along with this library; if not, write to the Free Software
|
|
||||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
||||||
*/
|
|
||||||
#include "WebAuthentication.h"
|
|
||||||
#include <libb64/cencode.h>
|
|
||||||
#ifdef ESP32
|
|
||||||
#include "mbedtls/md5.h"
|
|
||||||
#else
|
|
||||||
#include "md5.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
// Basic Auth hash = base64("username:password")
|
|
||||||
|
|
||||||
bool checkBasicAuthentication(const char * hash, const char * username, const char * password){
|
|
||||||
if(username == NULL || password == NULL || hash == NULL)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
size_t toencodeLen = strlen(username)+strlen(password)+1;
|
|
||||||
size_t encodedLen = base64_encode_expected_len(toencodeLen);
|
|
||||||
if(strlen(hash) != encodedLen)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
char *toencode = new char[toencodeLen+1];
|
|
||||||
if(toencode == NULL){
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
char *encoded = new char[base64_encode_expected_len(toencodeLen)+1];
|
|
||||||
if(encoded == NULL){
|
|
||||||
delete[] toencode;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
sprintf_P(toencode, PSTR("%s:%s"), username, password);
|
|
||||||
if(base64_encode_chars(toencode, toencodeLen, encoded) > 0 && memcmp(hash, encoded, encodedLen) == 0){
|
|
||||||
delete[] toencode;
|
|
||||||
delete[] encoded;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
delete[] toencode;
|
|
||||||
delete[] encoded;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool getMD5(uint8_t * data, uint16_t len, char * output){//33 bytes or more
|
|
||||||
#ifdef ESP32
|
|
||||||
mbedtls_md5_context _ctx;
|
|
||||||
#else
|
|
||||||
md5_context_t _ctx;
|
|
||||||
#endif
|
|
||||||
uint8_t i;
|
|
||||||
uint8_t * _buf = (uint8_t*)malloc(16);
|
|
||||||
if(_buf == NULL)
|
|
||||||
return false;
|
|
||||||
memset(_buf, 0x00, 16);
|
|
||||||
#ifdef ESP32
|
|
||||||
mbedtls_md5_init(&_ctx);
|
|
||||||
mbedtls_md5_update (&_ctx,data,len);
|
|
||||||
mbedtls_md5_finish(&_ctx,data);
|
|
||||||
mbedtls_internal_md5_process( &_ctx ,data);
|
|
||||||
#else
|
|
||||||
MD5Init(&_ctx);
|
|
||||||
MD5Update(&_ctx, data, len);
|
|
||||||
MD5Final(_buf, &_ctx);
|
|
||||||
#endif
|
|
||||||
for(i = 0; i < 16; i++) {
|
|
||||||
sprintf_P(output + (i * 2), PSTR("%02x"), _buf[i]);
|
|
||||||
}
|
|
||||||
free(_buf);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static String genRandomMD5(){
|
|
||||||
#ifdef ESP8266
|
|
||||||
uint32_t r = RANDOM_REG32;
|
|
||||||
#else
|
|
||||||
uint32_t r = rand();
|
|
||||||
#endif
|
|
||||||
char * out = (char*)malloc(33);
|
|
||||||
if(out == NULL || !getMD5((uint8_t*)(&r), 4, out))
|
|
||||||
return emptyString;
|
|
||||||
String res = String(out);
|
|
||||||
free(out);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
static String stringMD5(const String& in){
|
|
||||||
char * out = (char*)malloc(33);
|
|
||||||
if(out == NULL || !getMD5((uint8_t*)(in.c_str()), in.length(), out))
|
|
||||||
return emptyString;
|
|
||||||
String res = String(out);
|
|
||||||
free(out);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
String generateDigestHash(const char * username, const char * password, const char * realm){
|
|
||||||
if(username == NULL || password == NULL || realm == NULL){
|
|
||||||
return emptyString;
|
|
||||||
}
|
|
||||||
char * out = (char*)malloc(33);
|
|
||||||
String res = String(username);
|
|
||||||
res += ':';
|
|
||||||
res.concat(realm);
|
|
||||||
res += ':';
|
|
||||||
String in = res;
|
|
||||||
in.concat(password);
|
|
||||||
if(out == NULL || !getMD5((uint8_t*)(in.c_str()), in.length(), out))
|
|
||||||
return emptyString;
|
|
||||||
res.concat(out);
|
|
||||||
free(out);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
String requestDigestAuthentication(const char * realm){
|
|
||||||
String header = F("realm=\"");
|
|
||||||
if(realm == NULL)
|
|
||||||
header.concat(F("asyncesp"));
|
|
||||||
else
|
|
||||||
header.concat(realm);
|
|
||||||
header.concat(F("\", qop=\"auth\", nonce=\""));
|
|
||||||
header.concat(genRandomMD5());
|
|
||||||
header.concat(F("\", opaque=\""));
|
|
||||||
header.concat(genRandomMD5());
|
|
||||||
header += '"';
|
|
||||||
return header;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool checkDigestAuthentication(const char * header, const char *method, const char * username, const char * password, const char * realm, bool passwordIsHash, const char * nonce, const char * opaque, const char * uri){
|
|
||||||
if(username == NULL || password == NULL || header == NULL || method == NULL){
|
|
||||||
//os_printf("AUTH FAIL: missing requred fields\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
String myHeader = String(header);
|
|
||||||
int nextBreak = myHeader.indexOf(',');
|
|
||||||
if(nextBreak < 0){
|
|
||||||
//os_printf("AUTH FAIL: no variables\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
String myUsername = String();
|
|
||||||
String myRealm = String();
|
|
||||||
String myNonce = String();
|
|
||||||
String myUri = String();
|
|
||||||
String myResponse = String();
|
|
||||||
String myQop = String();
|
|
||||||
String myNc = String();
|
|
||||||
String myCnonce = String();
|
|
||||||
|
|
||||||
myHeader += F(", ");
|
|
||||||
do {
|
|
||||||
String avLine = myHeader.substring(0, nextBreak);
|
|
||||||
avLine.trim();
|
|
||||||
myHeader = myHeader.substring(nextBreak+1);
|
|
||||||
nextBreak = myHeader.indexOf(',');
|
|
||||||
|
|
||||||
int eqSign = avLine.indexOf('=');
|
|
||||||
if(eqSign < 0){
|
|
||||||
//os_printf("AUTH FAIL: no = sign\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
String varName = avLine.substring(0, eqSign);
|
|
||||||
avLine = avLine.substring(eqSign + 1);
|
|
||||||
if(avLine.startsWith(String('"'))){
|
|
||||||
avLine = avLine.substring(1, avLine.length() - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(varName.equals(F("username"))){
|
|
||||||
if(!avLine.equals(username)){
|
|
||||||
//os_printf("AUTH FAIL: username\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
myUsername = avLine;
|
|
||||||
} else if(varName.equals(F("realm"))){
|
|
||||||
if(realm != NULL && !avLine.equals(realm)){
|
|
||||||
//os_printf("AUTH FAIL: realm\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
myRealm = avLine;
|
|
||||||
} else if(varName.equals(F("nonce"))){
|
|
||||||
if(nonce != NULL && !avLine.equals(nonce)){
|
|
||||||
//os_printf("AUTH FAIL: nonce\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
myNonce = avLine;
|
|
||||||
} else if(varName.equals(F("opaque"))){
|
|
||||||
if(opaque != NULL && !avLine.equals(opaque)){
|
|
||||||
//os_printf("AUTH FAIL: opaque\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if(varName.equals(F("uri"))){
|
|
||||||
if(uri != NULL && !avLine.equals(uri)){
|
|
||||||
//os_printf("AUTH FAIL: uri\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
myUri = avLine;
|
|
||||||
} else if(varName.equals(F("response"))){
|
|
||||||
myResponse = avLine;
|
|
||||||
} else if(varName.equals(F("qop"))){
|
|
||||||
myQop = avLine;
|
|
||||||
} else if(varName.equals(F("nc"))){
|
|
||||||
myNc = avLine;
|
|
||||||
} else if(varName.equals(F("cnonce"))){
|
|
||||||
myCnonce = avLine;
|
|
||||||
}
|
|
||||||
} while(nextBreak > 0);
|
|
||||||
|
|
||||||
String ha1 = (passwordIsHash) ? String(password) : stringMD5(myUsername + ':' + myRealm + ':' + String(password));
|
|
||||||
String ha2 = String(method) + ':' + myUri;
|
|
||||||
String response = ha1 + ':' + myNonce + ':' + myNc + ':' + myCnonce + ':' + myQop + ':' + stringMD5(ha2);
|
|
||||||
|
|
||||||
if(myResponse.equals(stringMD5(response))){
|
|
||||||
//os_printf("AUTH SUCCESS\n");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
//os_printf("AUTH FAIL: password\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
/*
|
|
||||||
Asynchronous WebServer library for Espressif MCUs
|
|
||||||
|
|
||||||
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
|
|
||||||
This file is part of the esp8266 core for Arduino environment.
|
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
|
||||||
modify it under the terms of the GNU Lesser General Public
|
|
||||||
License as published by the Free Software Foundation; either
|
|
||||||
version 2.1 of the License, or (at your option) any later version.
|
|
||||||
|
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
Lesser General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU Lesser General Public
|
|
||||||
License along with this library; if not, write to the Free Software
|
|
||||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef WEB_AUTHENTICATION_H_
|
|
||||||
#define WEB_AUTHENTICATION_H_
|
|
||||||
|
|
||||||
#include "Arduino.h"
|
|
||||||
|
|
||||||
bool checkBasicAuthentication(const char * header, const char * username, const char * password);
|
|
||||||
String requestDigestAuthentication(const char * realm);
|
|
||||||
bool checkDigestAuthentication(const char * header, const char *method, const char * username, const char * password, const char * realm, bool passwordIsHash, const char * nonce, const char * opaque, const char * uri);
|
|
||||||
|
|
||||||
//for storing hashed versions on the device that can be authenticated against
|
|
||||||
String generateDigestHash(const char * username, const char * password, const char * realm);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,138 +0,0 @@
|
|||||||
/*
|
|
||||||
Asynchronous WebServer library for Espressif MCUs
|
|
||||||
|
|
||||||
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
|
|
||||||
This file is part of the esp8266 core for Arduino environment.
|
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
|
||||||
modify it under the terms of the GNU Lesser General Public
|
|
||||||
License as published by the Free Software Foundation; either
|
|
||||||
version 2.1 of the License, or (at your option) any later version.
|
|
||||||
|
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
Lesser General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU Lesser General Public
|
|
||||||
License along with this library; if not, write to the Free Software
|
|
||||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
||||||
*/
|
|
||||||
#ifndef ASYNCWEBSERVERHANDLERIMPL_H_
|
|
||||||
#define ASYNCWEBSERVERHANDLERIMPL_H_
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#ifdef ASYNCWEBSERVER_REGEX
|
|
||||||
#include <regex>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "stddef.h"
|
|
||||||
#include <time.h>
|
|
||||||
|
|
||||||
class AsyncStaticWebHandler: public AsyncWebHandler {
|
|
||||||
using File = fs::File;
|
|
||||||
using FS = fs::FS;
|
|
||||||
private:
|
|
||||||
bool _getFile(AsyncWebServerRequest *request);
|
|
||||||
bool _fileExists(AsyncWebServerRequest *request, const String& path);
|
|
||||||
uint8_t _countBits(const uint8_t value) const;
|
|
||||||
protected:
|
|
||||||
FS _fs;
|
|
||||||
String _uri;
|
|
||||||
String _path;
|
|
||||||
String _default_file;
|
|
||||||
String _cache_control;
|
|
||||||
String _last_modified;
|
|
||||||
AwsTemplateProcessor _callback;
|
|
||||||
bool _isDir;
|
|
||||||
bool _gzipFirst;
|
|
||||||
uint8_t _gzipStats;
|
|
||||||
public:
|
|
||||||
AsyncStaticWebHandler(const char* uri, FS& fs, const char* path, const char* cache_control);
|
|
||||||
virtual bool canHandle(AsyncWebServerRequest *request) override final;
|
|
||||||
virtual void handleRequest(AsyncWebServerRequest *request) override final;
|
|
||||||
AsyncStaticWebHandler& setIsDir(bool isDir);
|
|
||||||
AsyncStaticWebHandler& setDefaultFile(const char* filename);
|
|
||||||
AsyncStaticWebHandler& setCacheControl(const char* cache_control);
|
|
||||||
AsyncStaticWebHandler& setLastModified(const char* last_modified);
|
|
||||||
AsyncStaticWebHandler& setLastModified(struct tm* last_modified);
|
|
||||||
#ifdef ESP8266
|
|
||||||
AsyncStaticWebHandler& setLastModified(time_t last_modified);
|
|
||||||
AsyncStaticWebHandler& setLastModified(); //sets to current time. Make sure sntp is runing and time is updated
|
|
||||||
#endif
|
|
||||||
AsyncStaticWebHandler& setTemplateProcessor(AwsTemplateProcessor newCallback) {_callback = newCallback; return *this;}
|
|
||||||
};
|
|
||||||
|
|
||||||
class AsyncCallbackWebHandler: public AsyncWebHandler {
|
|
||||||
private:
|
|
||||||
protected:
|
|
||||||
String _uri;
|
|
||||||
WebRequestMethodComposite _method;
|
|
||||||
ArRequestHandlerFunction _onRequest;
|
|
||||||
ArUploadHandlerFunction _onUpload;
|
|
||||||
ArBodyHandlerFunction _onBody;
|
|
||||||
bool _isRegex;
|
|
||||||
public:
|
|
||||||
AsyncCallbackWebHandler() : _uri(), _method(HTTP_ANY), _onRequest(NULL), _onUpload(NULL), _onBody(NULL), _isRegex(false) {}
|
|
||||||
void setUri(const String& uri){
|
|
||||||
_uri = uri;
|
|
||||||
_isRegex = uri.startsWith("^") && uri.endsWith("$");
|
|
||||||
}
|
|
||||||
void setMethod(WebRequestMethodComposite method){ _method = method; }
|
|
||||||
void onRequest(ArRequestHandlerFunction fn){ _onRequest = fn; }
|
|
||||||
void onUpload(ArUploadHandlerFunction fn){ _onUpload = fn; }
|
|
||||||
void onBody(ArBodyHandlerFunction fn){ _onBody = fn; }
|
|
||||||
|
|
||||||
virtual bool canHandle(AsyncWebServerRequest *request) override final{
|
|
||||||
|
|
||||||
if(!_onRequest)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if(!(_method & request->method()))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
#ifdef ASYNCWEBSERVER_REGEX
|
|
||||||
if (_isRegex) {
|
|
||||||
std::regex pattern(_uri.c_str());
|
|
||||||
std::smatch matches;
|
|
||||||
std::string s(request->url().c_str());
|
|
||||||
if(std::regex_search(s, matches, pattern)) {
|
|
||||||
for (size_t i = 1; i < matches.size(); ++i) { // start from 1
|
|
||||||
request->_addPathParam(matches[i].str().c_str());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else
|
|
||||||
#endif
|
|
||||||
if (_uri.length() && _uri.endsWith("*")) {
|
|
||||||
String uriTemplate = String(_uri);
|
|
||||||
uriTemplate = uriTemplate.substring(0, uriTemplate.length() - 1);
|
|
||||||
if (!request->url().startsWith(uriTemplate))
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else if(_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri+"/")))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
request->addInterestingHeader("ANY");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual void handleRequest(AsyncWebServerRequest *request) override final {
|
|
||||||
if(_onRequest)
|
|
||||||
_onRequest(request);
|
|
||||||
else
|
|
||||||
request->send(500);
|
|
||||||
}
|
|
||||||
virtual void handleUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final) override final {
|
|
||||||
if(_onUpload)
|
|
||||||
_onUpload(request, filename, index, data, len, final);
|
|
||||||
}
|
|
||||||
virtual void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) override final {
|
|
||||||
if(_onBody)
|
|
||||||
_onBody(request, data, len, index, total);
|
|
||||||
}
|
|
||||||
virtual bool isRequestHandlerTrivial() override final {return _onRequest ? false : true;}
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif /* ASYNCWEBSERVERHANDLERIMPL_H_ */
|
|
||||||
@@ -1,224 +0,0 @@
|
|||||||
/*
|
|
||||||
Asynchronous WebServer library for Espressif MCUs
|
|
||||||
|
|
||||||
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
|
|
||||||
This file is part of the esp8266 core for Arduino environment.
|
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
|
||||||
modify it under the terms of the GNU Lesser General Public
|
|
||||||
License as published by the Free Software Foundation; either
|
|
||||||
version 2.1 of the License, or (at your option) any later version.
|
|
||||||
|
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
Lesser General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU Lesser General Public
|
|
||||||
License along with this library; if not, write to the Free Software
|
|
||||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
||||||
*/
|
|
||||||
#include "ESPAsyncWebServer.h"
|
|
||||||
#include "WebHandlerImpl.h"
|
|
||||||
|
|
||||||
AsyncStaticWebHandler::AsyncStaticWebHandler(const char* uri, FS& fs, const char* path, const char* cache_control)
|
|
||||||
: _fs(fs), _uri(uri), _path(path), _default_file(F("index.htm")), _cache_control(cache_control), _last_modified(), _callback(nullptr)
|
|
||||||
{
|
|
||||||
// Ensure leading '/'
|
|
||||||
if (_uri.length() == 0 || _uri[0] != '/') _uri = String('/') + _uri;
|
|
||||||
if (_path.length() == 0 || _path[0] != '/') _path = String('/') + _path;
|
|
||||||
|
|
||||||
// If path ends with '/' we assume a hint that this is a directory to improve performance.
|
|
||||||
// However - if it does not end with '/' we, can't assume a file, path can still be a directory.
|
|
||||||
_isDir = _path[_path.length()-1] == '/';
|
|
||||||
|
|
||||||
// Remove the trailing '/' so we can handle default file
|
|
||||||
// Notice that root will be "" not "/"
|
|
||||||
if (_uri[_uri.length()-1] == '/') _uri = _uri.substring(0, _uri.length()-1);
|
|
||||||
if (_path[_path.length()-1] == '/') _path = _path.substring(0, _path.length()-1);
|
|
||||||
|
|
||||||
// Reset stats
|
|
||||||
_gzipFirst = false;
|
|
||||||
_gzipStats = 0xF8;
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncStaticWebHandler& AsyncStaticWebHandler::setIsDir(bool isDir){
|
|
||||||
_isDir = isDir;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncStaticWebHandler& AsyncStaticWebHandler::setDefaultFile(const char* filename){
|
|
||||||
_default_file = String(filename);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncStaticWebHandler& AsyncStaticWebHandler::setCacheControl(const char* cache_control){
|
|
||||||
_cache_control = String(cache_control);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(const char* last_modified){
|
|
||||||
_last_modified = String(last_modified);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(struct tm* last_modified){
|
|
||||||
auto formatP = PSTR("%a, %d %b %Y %H:%M:%S %Z");
|
|
||||||
char format[strlen_P(formatP) + 1];
|
|
||||||
strcpy_P(format, formatP);
|
|
||||||
|
|
||||||
char result[30];
|
|
||||||
strftime(result, sizeof(result), format, last_modified);
|
|
||||||
return setLastModified((const char *)result);
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef ESP8266
|
|
||||||
AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(time_t last_modified){
|
|
||||||
return setLastModified((struct tm *)gmtime(&last_modified));
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(){
|
|
||||||
time_t last_modified;
|
|
||||||
if(time(&last_modified) == 0) //time is not yet set
|
|
||||||
return *this;
|
|
||||||
return setLastModified(last_modified);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
bool AsyncStaticWebHandler::canHandle(AsyncWebServerRequest *request){
|
|
||||||
if(request->method() != HTTP_GET
|
|
||||||
|| !request->url().startsWith(_uri)
|
|
||||||
|| !request->isExpectedRequestedConnType(RCT_DEFAULT, RCT_HTTP)
|
|
||||||
){
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (_getFile(request)) {
|
|
||||||
// We interested in "If-Modified-Since" header to check if file was modified
|
|
||||||
if (_last_modified.length())
|
|
||||||
request->addInterestingHeader(F("If-Modified-Since"));
|
|
||||||
|
|
||||||
if(_cache_control.length())
|
|
||||||
request->addInterestingHeader(F("If-None-Match"));
|
|
||||||
|
|
||||||
DEBUGF("[AsyncStaticWebHandler::canHandle] TRUE\n");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AsyncStaticWebHandler::_getFile(AsyncWebServerRequest *request)
|
|
||||||
{
|
|
||||||
// Remove the found uri
|
|
||||||
String path = request->url().substring(_uri.length());
|
|
||||||
|
|
||||||
// We can skip the file check and look for default if request is to the root of a directory or that request path ends with '/'
|
|
||||||
bool canSkipFileCheck = (_isDir && path.length() == 0) || (path.length() && path[path.length()-1] == '/');
|
|
||||||
|
|
||||||
path = _path + path;
|
|
||||||
|
|
||||||
// Do we have a file or .gz file
|
|
||||||
if (!canSkipFileCheck && _fileExists(request, path))
|
|
||||||
return true;
|
|
||||||
|
|
||||||
// Can't handle if not default file
|
|
||||||
if (_default_file.length() == 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Try to add default file, ensure there is a trailing '/' ot the path.
|
|
||||||
if (path.length() == 0 || path[path.length()-1] != '/')
|
|
||||||
path += String('/');
|
|
||||||
path += _default_file;
|
|
||||||
|
|
||||||
return _fileExists(request, path);
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef ESP32
|
|
||||||
#define FILE_IS_REAL(f) (f == true && !f.isDirectory())
|
|
||||||
#else
|
|
||||||
#define FILE_IS_REAL(f) (f == true)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
bool AsyncStaticWebHandler::_fileExists(AsyncWebServerRequest *request, const String& path)
|
|
||||||
{
|
|
||||||
bool fileFound = false;
|
|
||||||
bool gzipFound = false;
|
|
||||||
|
|
||||||
String gzip = path + F(".gz");
|
|
||||||
|
|
||||||
if (_gzipFirst) {
|
|
||||||
request->_tempFile = _fs.open(gzip, fs::FileOpenMode::read);
|
|
||||||
gzipFound = FILE_IS_REAL(request->_tempFile);
|
|
||||||
if (!gzipFound){
|
|
||||||
request->_tempFile = _fs.open(path, fs::FileOpenMode::read);
|
|
||||||
fileFound = FILE_IS_REAL(request->_tempFile);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
request->_tempFile = _fs.open(path, fs::FileOpenMode::read);
|
|
||||||
fileFound = FILE_IS_REAL(request->_tempFile);
|
|
||||||
if (!fileFound){
|
|
||||||
request->_tempFile = _fs.open(gzip, fs::FileOpenMode::read);
|
|
||||||
gzipFound = FILE_IS_REAL(request->_tempFile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool found = fileFound || gzipFound;
|
|
||||||
|
|
||||||
if (found) {
|
|
||||||
// Extract the file name from the path and keep it in _tempObject
|
|
||||||
size_t pathLen = path.length();
|
|
||||||
char * _tempPath = (char*)malloc(pathLen+1);
|
|
||||||
snprintf_P(_tempPath, pathLen+1, PSTR("%s"), path.c_str());
|
|
||||||
request->_tempObject = (void*)_tempPath;
|
|
||||||
|
|
||||||
// Calculate gzip statistic
|
|
||||||
_gzipStats = (_gzipStats << 1) + (gzipFound ? 1 : 0);
|
|
||||||
if (_gzipStats == 0x00) _gzipFirst = false; // All files are not gzip
|
|
||||||
else if (_gzipStats == 0xFF) _gzipFirst = true; // All files are gzip
|
|
||||||
else _gzipFirst = _countBits(_gzipStats) > 4; // IF we have more gzip files - try gzip first
|
|
||||||
}
|
|
||||||
|
|
||||||
return found;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t AsyncStaticWebHandler::_countBits(const uint8_t value) const
|
|
||||||
{
|
|
||||||
uint8_t w = value;
|
|
||||||
uint8_t n;
|
|
||||||
for (n=0; w!=0; n++) w&=w-1;
|
|
||||||
return n;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncStaticWebHandler::handleRequest(AsyncWebServerRequest *request)
|
|
||||||
{
|
|
||||||
// Get the filename from request->_tempObject and free it
|
|
||||||
String filename = String((char*)request->_tempObject);
|
|
||||||
free(request->_tempObject);
|
|
||||||
request->_tempObject = NULL;
|
|
||||||
if((_username.length() && _password.length()) && !request->authenticate(_username.c_str(), _password.c_str()))
|
|
||||||
return request->requestAuthentication();
|
|
||||||
|
|
||||||
if (request->_tempFile == true) {
|
|
||||||
String etag = String(request->_tempFile.size());
|
|
||||||
if (_last_modified.length() && _last_modified == request->header(F("If-Modified-Since"))) {
|
|
||||||
request->_tempFile.close();
|
|
||||||
request->send(304); // Not modified
|
|
||||||
} else if (_cache_control.length() && request->hasHeader(F("If-None-Match")) && request->header(F("If-None-Match")).equals(etag)) {
|
|
||||||
request->_tempFile.close();
|
|
||||||
AsyncWebServerResponse * response = new AsyncBasicResponse(304); // Not modified
|
|
||||||
response->addHeader(F("Cache-Control"), _cache_control);
|
|
||||||
response->addHeader(F("ETag"), etag);
|
|
||||||
request->send(response);
|
|
||||||
} else {
|
|
||||||
AsyncWebServerResponse * response = new AsyncFileResponse(request->_tempFile, filename, String(), false, _callback);
|
|
||||||
if (_last_modified.length())
|
|
||||||
response->addHeader(F("Last-Modified"), _last_modified);
|
|
||||||
if (_cache_control.length()){
|
|
||||||
response->addHeader(F("Cache-Control"), _cache_control);
|
|
||||||
response->addHeader(F("ETag"), etag);
|
|
||||||
}
|
|
||||||
request->send(response);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
request->send(404);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,930 +0,0 @@
|
|||||||
/*
|
|
||||||
Asynchronous WebServer library for Espressif MCUs
|
|
||||||
|
|
||||||
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
|
|
||||||
This file is part of the esp8266 core for Arduino environment.
|
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
|
||||||
modify it under the terms of the GNU Lesser General Public
|
|
||||||
License as published by the Free Software Foundation; either
|
|
||||||
version 2.1 of the License, or (at your option) any later version.
|
|
||||||
|
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
Lesser General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU Lesser General Public
|
|
||||||
License along with this library; if not, write to the Free Software
|
|
||||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
||||||
*/
|
|
||||||
#include "ESPAsyncWebServer.h"
|
|
||||||
#include "WebResponseImpl.h"
|
|
||||||
#include "WebAuthentication.h"
|
|
||||||
|
|
||||||
#ifndef ESP8266
|
|
||||||
#define os_strlen strlen
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define __is_param_char(c) ((c) && ((c) != '{') && ((c) != '[') && ((c) != '&') && ((c) != '='))
|
|
||||||
|
|
||||||
enum { PARSE_REQ_START, PARSE_REQ_HEADERS, PARSE_REQ_BODY, PARSE_REQ_END, PARSE_REQ_FAIL };
|
|
||||||
|
|
||||||
AsyncWebServerRequest::AsyncWebServerRequest(AsyncWebServer* s, AsyncClient* c)
|
|
||||||
: _client(c)
|
|
||||||
, _server(s)
|
|
||||||
, _handler(NULL)
|
|
||||||
, _response(NULL)
|
|
||||||
, _temp()
|
|
||||||
, _parseState(0)
|
|
||||||
, _version(0)
|
|
||||||
, _method(HTTP_ANY)
|
|
||||||
, _url()
|
|
||||||
, _host()
|
|
||||||
, _contentType()
|
|
||||||
, _boundary()
|
|
||||||
, _authorization()
|
|
||||||
, _reqconntype(RCT_HTTP)
|
|
||||||
, _isDigest(false)
|
|
||||||
, _isMultipart(false)
|
|
||||||
, _isPlainPost(false)
|
|
||||||
, _expectingContinue(false)
|
|
||||||
, _contentLength(0)
|
|
||||||
, _parsedLength(0)
|
|
||||||
, _headers(LinkedList<AsyncWebHeader *>([](AsyncWebHeader *h){ delete h; }))
|
|
||||||
, _params(LinkedList<AsyncWebParameter *>([](AsyncWebParameter *p){ delete p; }))
|
|
||||||
, _pathParams(LinkedList<String *>([](String *p){ delete p; }))
|
|
||||||
, _multiParseState(0)
|
|
||||||
, _boundaryPosition(0)
|
|
||||||
, _itemStartIndex(0)
|
|
||||||
, _itemSize(0)
|
|
||||||
, _itemName()
|
|
||||||
, _itemFilename()
|
|
||||||
, _itemType()
|
|
||||||
, _itemValue()
|
|
||||||
, _itemBuffer(0)
|
|
||||||
, _itemBufferIndex(0)
|
|
||||||
, _itemIsFile(false)
|
|
||||||
, _tempObject(NULL)
|
|
||||||
{
|
|
||||||
c->onError([](void *r, AsyncClient* c, int8_t error){ (void)c; AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onError(error); }, this);
|
|
||||||
c->onAck([](void *r, AsyncClient* c, size_t len, uint32_t time){ (void)c; AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onAck(len, time); }, this);
|
|
||||||
c->onDisconnect([](void *r, AsyncClient* c){ AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onDisconnect(); delete c; }, this);
|
|
||||||
c->onTimeout([](void *r, AsyncClient* c, uint32_t time){ (void)c; AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onTimeout(time); }, this);
|
|
||||||
c->onData([](void *r, AsyncClient* c, void *buf, size_t len){ (void)c; AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onData(buf, len); }, this);
|
|
||||||
c->onPoll([](void *r, AsyncClient* c){ (void)c; AsyncWebServerRequest *req = ( AsyncWebServerRequest*)r; req->_onPoll(); }, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncWebServerRequest::~AsyncWebServerRequest(){
|
|
||||||
_headers.free();
|
|
||||||
|
|
||||||
_params.free();
|
|
||||||
_pathParams.free();
|
|
||||||
|
|
||||||
_interestingHeaders.free();
|
|
||||||
|
|
||||||
if(_response != NULL){
|
|
||||||
delete _response;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(_tempObject != NULL){
|
|
||||||
free(_tempObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(_tempFile){
|
|
||||||
_tempFile.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncWebServerRequest::_onData(void *buf, size_t len){
|
|
||||||
size_t i = 0;
|
|
||||||
while (true) {
|
|
||||||
|
|
||||||
if(_parseState < PARSE_REQ_BODY){
|
|
||||||
// Find new line in buf
|
|
||||||
char *str = (char*)buf;
|
|
||||||
for (i = 0; i < len; i++) {
|
|
||||||
if (str[i] == '\n') {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (i == len) { // No new line, just add the buffer in _temp
|
|
||||||
char ch = str[len-1];
|
|
||||||
str[len-1] = 0;
|
|
||||||
_temp.reserve(_temp.length()+len);
|
|
||||||
_temp.concat(str);
|
|
||||||
_temp.concat(ch);
|
|
||||||
} else { // Found new line - extract it and parse
|
|
||||||
str[i] = 0; // Terminate the string at the end of the line.
|
|
||||||
_temp.concat(str);
|
|
||||||
_temp.trim();
|
|
||||||
_parseLine();
|
|
||||||
if (++i < len) {
|
|
||||||
// Still have more buffer to process
|
|
||||||
buf = str+i;
|
|
||||||
len-= i;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if(_parseState == PARSE_REQ_BODY){
|
|
||||||
// A handler should be already attached at this point in _parseLine function.
|
|
||||||
// If handler does nothing (_onRequest is NULL), we don't need to really parse the body.
|
|
||||||
const bool needParse = _handler && !_handler->isRequestHandlerTrivial();
|
|
||||||
if(_isMultipart){
|
|
||||||
if(needParse){
|
|
||||||
size_t i;
|
|
||||||
for(i=0; i<len; i++){
|
|
||||||
_parseMultipartPostByte(((uint8_t*)buf)[i], i == len - 1);
|
|
||||||
_parsedLength++;
|
|
||||||
}
|
|
||||||
} else
|
|
||||||
_parsedLength += len;
|
|
||||||
} else {
|
|
||||||
if(_parsedLength == 0){
|
|
||||||
if(_contentType.startsWith(F("application/x-www-form-urlencoded"))){
|
|
||||||
_isPlainPost = true;
|
|
||||||
} else if(_contentType == F("text/plain") && __is_param_char(((char*)buf)[0])){
|
|
||||||
size_t i = 0;
|
|
||||||
while (i<len && __is_param_char(((char*)buf)[i++]));
|
|
||||||
if(i < len && ((char*)buf)[i-1] == '='){
|
|
||||||
_isPlainPost = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(!_isPlainPost) {
|
|
||||||
//check if authenticated before calling the body
|
|
||||||
if(_handler) _handler->handleBody(this, (uint8_t*)buf, len, _parsedLength, _contentLength);
|
|
||||||
_parsedLength += len;
|
|
||||||
} else if(needParse) {
|
|
||||||
size_t i;
|
|
||||||
for(i=0; i<len; i++){
|
|
||||||
_parsedLength++;
|
|
||||||
_parsePlainPostChar(((uint8_t*)buf)[i]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_parsedLength += len;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(_parsedLength == _contentLength){
|
|
||||||
_parseState = PARSE_REQ_END;
|
|
||||||
//check if authenticated before calling handleRequest and request auth instead
|
|
||||||
if(_handler) _handler->handleRequest(this);
|
|
||||||
else send(501);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncWebServerRequest::_removeNotInterestingHeaders(){
|
|
||||||
if (_interestingHeaders.containsIgnoreCase(F("ANY"))) return; // nothing to do
|
|
||||||
for(const auto& header: _headers){
|
|
||||||
if(!_interestingHeaders.containsIgnoreCase(header->name().c_str())){
|
|
||||||
_headers.remove(header);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncWebServerRequest::_onPoll(){
|
|
||||||
//os_printf("p\n");
|
|
||||||
if(_response != NULL && _client != NULL && _client->canSend() && !_response->_finished()){
|
|
||||||
_response->_ack(this, 0, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncWebServerRequest::_onAck(size_t len, uint32_t time){
|
|
||||||
//os_printf("a:%u:%u\n", len, time);
|
|
||||||
if(_response != NULL){
|
|
||||||
if(!_response->_finished()){
|
|
||||||
_response->_ack(this, len, time);
|
|
||||||
} else {
|
|
||||||
AsyncWebServerResponse* r = _response;
|
|
||||||
_response = NULL;
|
|
||||||
delete r;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncWebServerRequest::_onError(int8_t error){
|
|
||||||
(void)error;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncWebServerRequest::_onTimeout(uint32_t time){
|
|
||||||
(void)time;
|
|
||||||
//os_printf("TIMEOUT: %u, state: %s\n", time, _client->stateToString());
|
|
||||||
_client->close();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncWebServerRequest::onDisconnect (ArDisconnectHandler fn){
|
|
||||||
_onDisconnectfn=fn;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncWebServerRequest::_onDisconnect(){
|
|
||||||
//os_printf("d\n");
|
|
||||||
if(_onDisconnectfn) {
|
|
||||||
_onDisconnectfn();
|
|
||||||
}
|
|
||||||
_server->_handleDisconnect(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncWebServerRequest::_addParam(AsyncWebParameter *p){
|
|
||||||
_params.add(p);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncWebServerRequest::_addPathParam(const char *p){
|
|
||||||
_pathParams.add(new String(p));
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncWebServerRequest::_addGetParams(const String& params){
|
|
||||||
size_t start = 0;
|
|
||||||
while (start < params.length()){
|
|
||||||
int end = params.indexOf('&', start);
|
|
||||||
if (end < 0) end = params.length();
|
|
||||||
int equal = params.indexOf('=', start);
|
|
||||||
if (equal < 0 || equal > end) equal = end;
|
|
||||||
String name = params.substring(start, equal);
|
|
||||||
String value = equal + 1 < end ? params.substring(equal + 1, end) : String();
|
|
||||||
_addParam(new AsyncWebParameter(urlDecode(name), urlDecode(value)));
|
|
||||||
start = end + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AsyncWebServerRequest::_parseReqHead(){
|
|
||||||
// Split the head into method, url and version
|
|
||||||
int index = _temp.indexOf(' ');
|
|
||||||
String m = _temp.substring(0, index);
|
|
||||||
index = _temp.indexOf(' ', index+1);
|
|
||||||
String u = _temp.substring(m.length()+1, index);
|
|
||||||
_temp = _temp.substring(index+1);
|
|
||||||
|
|
||||||
if(m == F("GET")){
|
|
||||||
_method = HTTP_GET;
|
|
||||||
} else if(m == F("POST")){
|
|
||||||
_method = HTTP_POST;
|
|
||||||
} else if(m == F("DELETE")){
|
|
||||||
_method = HTTP_DELETE;
|
|
||||||
} else if(m == F("PUT")){
|
|
||||||
_method = HTTP_PUT;
|
|
||||||
} else if(m == F("PATCH")){
|
|
||||||
_method = HTTP_PATCH;
|
|
||||||
} else if(m == F("HEAD")){
|
|
||||||
_method = HTTP_HEAD;
|
|
||||||
} else if(m == F("OPTIONS")){
|
|
||||||
_method = HTTP_OPTIONS;
|
|
||||||
}
|
|
||||||
|
|
||||||
String g;
|
|
||||||
index = u.indexOf('?');
|
|
||||||
if(index > 0){
|
|
||||||
g = u.substring(index +1);
|
|
||||||
u = u.substring(0, index);
|
|
||||||
}
|
|
||||||
_url = urlDecode(u);
|
|
||||||
_addGetParams(g);
|
|
||||||
|
|
||||||
if(!_temp.startsWith(F("HTTP/1.0")))
|
|
||||||
_version = 1;
|
|
||||||
|
|
||||||
_temp = String();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool strContains(const String &src, const String &find, bool mindcase = true) {
|
|
||||||
int pos=0, i=0;
|
|
||||||
const int slen = src.length();
|
|
||||||
const int flen = find.length();
|
|
||||||
|
|
||||||
if (slen < flen) return false;
|
|
||||||
while (pos <= (slen - flen)) {
|
|
||||||
for (i=0; i < flen; i++) {
|
|
||||||
if (mindcase) {
|
|
||||||
if (src[pos+i] != find[i]) i = flen + 1; // no match
|
|
||||||
}
|
|
||||||
else if (tolower(src[pos+i]) != tolower(find[i])) {
|
|
||||||
i = flen + 1; // no match
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (i == flen) return true;
|
|
||||||
pos++;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AsyncWebServerRequest::_parseReqHeader(){
|
|
||||||
int index = _temp.indexOf(':');
|
|
||||||
if(index){
|
|
||||||
String name = _temp.substring(0, index);
|
|
||||||
String value = _temp.substring(index + 2);
|
|
||||||
if(name.equalsIgnoreCase("Host")){
|
|
||||||
_host = value;
|
|
||||||
} else if(name.equalsIgnoreCase(F("Content-Type"))){
|
|
||||||
_contentType = value.substring(0, value.indexOf(';'));
|
|
||||||
if (value.startsWith(F("multipart/"))){
|
|
||||||
_boundary = value.substring(value.indexOf('=')+1);
|
|
||||||
_boundary.replace(String('"'), String());
|
|
||||||
_isMultipart = true;
|
|
||||||
}
|
|
||||||
} else if(name.equalsIgnoreCase(F("Content-Length"))){
|
|
||||||
_contentLength = atoi(value.c_str());
|
|
||||||
} else if(name.equalsIgnoreCase(F("Expect")) && value == F("100-continue")){
|
|
||||||
_expectingContinue = true;
|
|
||||||
} else if(name.equalsIgnoreCase(F("Authorization"))){
|
|
||||||
if(value.length() > 5 && value.substring(0,5).equalsIgnoreCase(F("Basic"))){
|
|
||||||
_authorization = value.substring(6);
|
|
||||||
} else if(value.length() > 6 && value.substring(0,6).equalsIgnoreCase(F("Digest"))){
|
|
||||||
_isDigest = true;
|
|
||||||
_authorization = value.substring(7);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if(name.equalsIgnoreCase(F("Upgrade")) && value.equalsIgnoreCase(F("websocket"))){
|
|
||||||
// WebSocket request can be uniquely identified by header: [Upgrade: websocket]
|
|
||||||
_reqconntype = RCT_WS;
|
|
||||||
} else {
|
|
||||||
if(name.equalsIgnoreCase(F("Accept")) && strContains(value, F("text/event-stream"), false)){
|
|
||||||
// WebEvent request can be uniquely identified by header: [Accept: text/event-stream]
|
|
||||||
_reqconntype = RCT_EVENT;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_headers.add(new AsyncWebHeader(name, value));
|
|
||||||
}
|
|
||||||
_temp = String();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncWebServerRequest::_parsePlainPostChar(uint8_t data){
|
|
||||||
if(data && (char)data != '&')
|
|
||||||
_temp += (char)data;
|
|
||||||
if(!data || (char)data == '&' || _parsedLength == _contentLength){
|
|
||||||
String name = F("body");
|
|
||||||
String value = _temp;
|
|
||||||
if(!_temp.startsWith(String('{')) && !_temp.startsWith(String('[')) && _temp.indexOf('=') > 0){
|
|
||||||
name = _temp.substring(0, _temp.indexOf('='));
|
|
||||||
value = _temp.substring(_temp.indexOf('=') + 1);
|
|
||||||
}
|
|
||||||
_addParam(new AsyncWebParameter(urlDecode(name), urlDecode(value), true));
|
|
||||||
_temp = String();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncWebServerRequest::_handleUploadByte(uint8_t data, bool last){
|
|
||||||
_itemBuffer[_itemBufferIndex++] = data;
|
|
||||||
|
|
||||||
if(last || _itemBufferIndex == 1460){
|
|
||||||
//check if authenticated before calling the upload
|
|
||||||
if(_handler)
|
|
||||||
_handler->handleUpload(this, _itemFilename, _itemSize - _itemBufferIndex, _itemBuffer, _itemBufferIndex, false);
|
|
||||||
_itemBufferIndex = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum {
|
|
||||||
EXPECT_BOUNDARY,
|
|
||||||
PARSE_HEADERS,
|
|
||||||
WAIT_FOR_RETURN1,
|
|
||||||
EXPECT_FEED1,
|
|
||||||
EXPECT_DASH1,
|
|
||||||
EXPECT_DASH2,
|
|
||||||
BOUNDARY_OR_DATA,
|
|
||||||
DASH3_OR_RETURN2,
|
|
||||||
EXPECT_FEED2,
|
|
||||||
PARSING_FINISHED,
|
|
||||||
PARSE_ERROR
|
|
||||||
};
|
|
||||||
|
|
||||||
void AsyncWebServerRequest::_parseMultipartPostByte(uint8_t data, bool last){
|
|
||||||
#define itemWriteByte(b) do { _itemSize++; if(_itemIsFile) _handleUploadByte(b, last); else _itemValue+=(char)(b); } while(0)
|
|
||||||
|
|
||||||
if(!_parsedLength){
|
|
||||||
_multiParseState = EXPECT_BOUNDARY;
|
|
||||||
_temp = String();
|
|
||||||
_itemName = String();
|
|
||||||
_itemFilename = String();
|
|
||||||
_itemType = String();
|
|
||||||
}
|
|
||||||
|
|
||||||
if(_multiParseState == WAIT_FOR_RETURN1){
|
|
||||||
if(data != '\r'){
|
|
||||||
itemWriteByte(data);
|
|
||||||
} else {
|
|
||||||
_multiParseState = EXPECT_FEED1;
|
|
||||||
}
|
|
||||||
} else if(_multiParseState == EXPECT_BOUNDARY){
|
|
||||||
if(_parsedLength < 2 && data != '-'){
|
|
||||||
_multiParseState = PARSE_ERROR;
|
|
||||||
return;
|
|
||||||
} else if(_parsedLength - 2 < _boundary.length() && _boundary.c_str()[_parsedLength - 2] != data){
|
|
||||||
_multiParseState = PARSE_ERROR;
|
|
||||||
return;
|
|
||||||
} else if(_parsedLength - 2 == _boundary.length() && data != '\r'){
|
|
||||||
_multiParseState = PARSE_ERROR;
|
|
||||||
return;
|
|
||||||
} else if(_parsedLength - 3 == _boundary.length()){
|
|
||||||
if(data != '\n'){
|
|
||||||
_multiParseState = PARSE_ERROR;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_multiParseState = PARSE_HEADERS;
|
|
||||||
_itemIsFile = false;
|
|
||||||
}
|
|
||||||
} else if(_multiParseState == PARSE_HEADERS){
|
|
||||||
if((char)data != '\r' && (char)data != '\n')
|
|
||||||
_temp += (char)data;
|
|
||||||
if((char)data == '\n'){
|
|
||||||
if(_temp.length()){
|
|
||||||
if(_temp.length() > 12 && _temp.substring(0, 12).equalsIgnoreCase(F("Content-Type"))){
|
|
||||||
_itemType = _temp.substring(14);
|
|
||||||
_itemIsFile = true;
|
|
||||||
} else if(_temp.length() > 19 && _temp.substring(0, 19).equalsIgnoreCase(F("Content-Disposition"))){
|
|
||||||
_temp = _temp.substring(_temp.indexOf(';') + 2);
|
|
||||||
while(_temp.indexOf(';') > 0){
|
|
||||||
String name = _temp.substring(0, _temp.indexOf('='));
|
|
||||||
String nameVal = _temp.substring(_temp.indexOf('=') + 2, _temp.indexOf(';') - 1);
|
|
||||||
if(name == F("name")){
|
|
||||||
_itemName = nameVal;
|
|
||||||
} else if(name == F("filename")){
|
|
||||||
_itemFilename = nameVal;
|
|
||||||
_itemIsFile = true;
|
|
||||||
}
|
|
||||||
_temp = _temp.substring(_temp.indexOf(';') + 2);
|
|
||||||
}
|
|
||||||
String name = _temp.substring(0, _temp.indexOf('='));
|
|
||||||
String nameVal = _temp.substring(_temp.indexOf('=') + 2, _temp.length() - 1);
|
|
||||||
if(name == F("name")){
|
|
||||||
_itemName = nameVal;
|
|
||||||
} else if(name == F("filename")){
|
|
||||||
_itemFilename = nameVal;
|
|
||||||
_itemIsFile = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_temp = String();
|
|
||||||
} else {
|
|
||||||
_multiParseState = WAIT_FOR_RETURN1;
|
|
||||||
//value starts from here
|
|
||||||
_itemSize = 0;
|
|
||||||
_itemStartIndex = _parsedLength;
|
|
||||||
_itemValue = String();
|
|
||||||
if(_itemIsFile){
|
|
||||||
if(_itemBuffer)
|
|
||||||
free(_itemBuffer);
|
|
||||||
_itemBuffer = (uint8_t*)malloc(1460);
|
|
||||||
if(_itemBuffer == NULL){
|
|
||||||
_multiParseState = PARSE_ERROR;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_itemBufferIndex = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if(_multiParseState == EXPECT_FEED1){
|
|
||||||
if(data != '\n'){
|
|
||||||
_multiParseState = WAIT_FOR_RETURN1;
|
|
||||||
itemWriteByte('\r'); _parseMultipartPostByte(data, last);
|
|
||||||
} else {
|
|
||||||
_multiParseState = EXPECT_DASH1;
|
|
||||||
}
|
|
||||||
} else if(_multiParseState == EXPECT_DASH1){
|
|
||||||
if(data != '-'){
|
|
||||||
_multiParseState = WAIT_FOR_RETURN1;
|
|
||||||
itemWriteByte('\r'); itemWriteByte('\n'); _parseMultipartPostByte(data, last);
|
|
||||||
} else {
|
|
||||||
_multiParseState = EXPECT_DASH2;
|
|
||||||
}
|
|
||||||
} else if(_multiParseState == EXPECT_DASH2){
|
|
||||||
if(data != '-'){
|
|
||||||
_multiParseState = WAIT_FOR_RETURN1;
|
|
||||||
itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-'); _parseMultipartPostByte(data, last);
|
|
||||||
} else {
|
|
||||||
_multiParseState = BOUNDARY_OR_DATA;
|
|
||||||
_boundaryPosition = 0;
|
|
||||||
}
|
|
||||||
} else if(_multiParseState == BOUNDARY_OR_DATA){
|
|
||||||
if(_boundaryPosition < _boundary.length() && _boundary.c_str()[_boundaryPosition] != data){
|
|
||||||
_multiParseState = WAIT_FOR_RETURN1;
|
|
||||||
itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-'); itemWriteByte('-');
|
|
||||||
uint8_t i;
|
|
||||||
for(i=0; i<_boundaryPosition; i++)
|
|
||||||
itemWriteByte(_boundary.c_str()[i]);
|
|
||||||
_parseMultipartPostByte(data, last);
|
|
||||||
} else if(_boundaryPosition == _boundary.length() - 1){
|
|
||||||
_multiParseState = DASH3_OR_RETURN2;
|
|
||||||
if(!_itemIsFile){
|
|
||||||
_addParam(new AsyncWebParameter(_itemName, _itemValue, true));
|
|
||||||
} else {
|
|
||||||
if(_itemSize){
|
|
||||||
//check if authenticated before calling the upload
|
|
||||||
if(_handler) _handler->handleUpload(this, _itemFilename, _itemSize - _itemBufferIndex, _itemBuffer, _itemBufferIndex, true);
|
|
||||||
_itemBufferIndex = 0;
|
|
||||||
_addParam(new AsyncWebParameter(_itemName, _itemFilename, true, true, _itemSize));
|
|
||||||
}
|
|
||||||
free(_itemBuffer);
|
|
||||||
_itemBuffer = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
_boundaryPosition++;
|
|
||||||
}
|
|
||||||
} else if(_multiParseState == DASH3_OR_RETURN2){
|
|
||||||
if(data == '-' && (_contentLength - _parsedLength - 4) != 0){
|
|
||||||
//os_printf("ERROR: The parser got to the end of the POST but is expecting %u bytes more!\nDrop an issue so we can have more info on the matter!\n", _contentLength - _parsedLength - 4);
|
|
||||||
_contentLength = _parsedLength + 4;//lets close the request gracefully
|
|
||||||
}
|
|
||||||
if(data == '\r'){
|
|
||||||
_multiParseState = EXPECT_FEED2;
|
|
||||||
} else if(data == '-' && _contentLength == (_parsedLength + 4)){
|
|
||||||
_multiParseState = PARSING_FINISHED;
|
|
||||||
} else {
|
|
||||||
_multiParseState = WAIT_FOR_RETURN1;
|
|
||||||
itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-'); itemWriteByte('-');
|
|
||||||
uint8_t i; for(i=0; i<_boundary.length(); i++) itemWriteByte(_boundary.c_str()[i]);
|
|
||||||
_parseMultipartPostByte(data, last);
|
|
||||||
}
|
|
||||||
} else if(_multiParseState == EXPECT_FEED2){
|
|
||||||
if(data == '\n'){
|
|
||||||
_multiParseState = PARSE_HEADERS;
|
|
||||||
_itemIsFile = false;
|
|
||||||
} else {
|
|
||||||
_multiParseState = WAIT_FOR_RETURN1;
|
|
||||||
itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-'); itemWriteByte('-');
|
|
||||||
uint8_t i; for(i=0; i<_boundary.length(); i++) itemWriteByte(_boundary.c_str()[i]);
|
|
||||||
itemWriteByte('\r'); _parseMultipartPostByte(data, last);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncWebServerRequest::_parseLine(){
|
|
||||||
if(_parseState == PARSE_REQ_START){
|
|
||||||
if(!_temp.length()){
|
|
||||||
_parseState = PARSE_REQ_FAIL;
|
|
||||||
_client->close();
|
|
||||||
} else {
|
|
||||||
_parseReqHead();
|
|
||||||
_parseState = PARSE_REQ_HEADERS;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(_parseState == PARSE_REQ_HEADERS){
|
|
||||||
if(!_temp.length()){
|
|
||||||
//end of headers
|
|
||||||
_server->_rewriteRequest(this);
|
|
||||||
_server->_attachHandler(this);
|
|
||||||
_removeNotInterestingHeaders();
|
|
||||||
if(_expectingContinue){
|
|
||||||
String response = F("HTTP/1.1 100 Continue\r\n\r\n");
|
|
||||||
_client->write(response.c_str(), response.length());
|
|
||||||
}
|
|
||||||
//check handler for authentication
|
|
||||||
if(_contentLength){
|
|
||||||
_parseState = PARSE_REQ_BODY;
|
|
||||||
} else {
|
|
||||||
_parseState = PARSE_REQ_END;
|
|
||||||
if(_handler) _handler->handleRequest(this);
|
|
||||||
else send(501);
|
|
||||||
}
|
|
||||||
} else _parseReqHeader();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t AsyncWebServerRequest::headers() const{
|
|
||||||
return _headers.length();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AsyncWebServerRequest::hasHeader(const String& name) const {
|
|
||||||
for(const auto& h: _headers){
|
|
||||||
if(h->name().equalsIgnoreCase(name)){
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AsyncWebServerRequest::hasHeader(const __FlashStringHelper * data) const {
|
|
||||||
return hasHeader(String(data));
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncWebHeader* AsyncWebServerRequest::getHeader(const String& name) const {
|
|
||||||
for(const auto& h: _headers){
|
|
||||||
if(h->name().equalsIgnoreCase(name)){
|
|
||||||
return h;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncWebHeader* AsyncWebServerRequest::getHeader(const __FlashStringHelper * data) const {
|
|
||||||
return getHeader(String(data));
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncWebHeader* AsyncWebServerRequest::getHeader(size_t num) const {
|
|
||||||
auto header = _headers.nth(num);
|
|
||||||
return header ? *header : nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t AsyncWebServerRequest::params() const {
|
|
||||||
return _params.length();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AsyncWebServerRequest::hasParam(const String& name, bool post, bool file) const {
|
|
||||||
for(const auto& p: _params){
|
|
||||||
if(p->name() == name && p->isPost() == post && p->isFile() == file){
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AsyncWebServerRequest::hasParam(const __FlashStringHelper * data, bool post, bool file) const {
|
|
||||||
return hasParam(String(data).c_str(), post, file);
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncWebParameter* AsyncWebServerRequest::getParam(const String& name, bool post, bool file) const {
|
|
||||||
for(const auto& p: _params){
|
|
||||||
if(p->name() == name && p->isPost() == post && p->isFile() == file){
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncWebParameter* AsyncWebServerRequest::getParam(const __FlashStringHelper * data, bool post, bool file) const {
|
|
||||||
return getParam(String(data).c_str(), post, file);
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncWebParameter* AsyncWebServerRequest::getParam(size_t num) const {
|
|
||||||
auto param = _params.nth(num);
|
|
||||||
return param ? *param : nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncWebServerRequest::addInterestingHeader(const String& name){
|
|
||||||
if(!_interestingHeaders.containsIgnoreCase(name))
|
|
||||||
_interestingHeaders.add(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncWebServerRequest::send(AsyncWebServerResponse *response){
|
|
||||||
_response = response;
|
|
||||||
if(_response == NULL){
|
|
||||||
_client->close(true);
|
|
||||||
_onDisconnect();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(!_response->_sourceValid()){
|
|
||||||
delete response;
|
|
||||||
_response = NULL;
|
|
||||||
send(500);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
_client->setRxTimeout(0);
|
|
||||||
_response->_respond(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(int code, const String& contentType, const String& content){
|
|
||||||
return new AsyncBasicResponse(code, contentType, content);
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(FS &fs, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback){
|
|
||||||
if(fs.exists(path) || (!download && fs.exists(path+F(".gz"))))
|
|
||||||
return new AsyncFileResponse(fs, path, contentType, download, callback);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(File content, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback){
|
|
||||||
if(content == true)
|
|
||||||
return new AsyncFileResponse(content, path, contentType, download, callback);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback){
|
|
||||||
return new AsyncStreamResponse(stream, contentType, len, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback){
|
|
||||||
return new AsyncCallbackResponse(contentType, len, callback, templateCallback);
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncWebServerResponse * AsyncWebServerRequest::beginChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback){
|
|
||||||
if(_version)
|
|
||||||
return new AsyncChunkedResponse(contentType, callback, templateCallback);
|
|
||||||
return new AsyncCallbackResponse(contentType, 0, callback, templateCallback);
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncResponseStream * AsyncWebServerRequest::beginResponseStream(const String& contentType, size_t bufferSize){
|
|
||||||
return new AsyncResponseStream(contentType, bufferSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncWebServerResponse * AsyncWebServerRequest::beginResponse_P(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback){
|
|
||||||
return new AsyncProgmemResponse(code, contentType, content, len, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncWebServerResponse * AsyncWebServerRequest::beginResponse_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback){
|
|
||||||
return beginResponse_P(code, contentType, (const uint8_t *)content, strlen_P(content), callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncWebServerRequest::send(int code, const String& contentType, const String& content){
|
|
||||||
send(beginResponse(code, contentType, content));
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncWebServerRequest::send(FS &fs, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback){
|
|
||||||
if(fs.exists(path) || (!download && fs.exists(path+F(".gz")))){
|
|
||||||
send(beginResponse(fs, path, contentType, download, callback));
|
|
||||||
} else send(404);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncWebServerRequest::send(File content, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback){
|
|
||||||
if(content == true){
|
|
||||||
send(beginResponse(content, path, contentType, download, callback));
|
|
||||||
} else send(404);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncWebServerRequest::send(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback){
|
|
||||||
send(beginResponse(stream, contentType, len, callback));
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncWebServerRequest::send(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback){
|
|
||||||
send(beginResponse(contentType, len, callback, templateCallback));
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncWebServerRequest::sendChunked(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback){
|
|
||||||
send(beginChunkedResponse(contentType, callback, templateCallback));
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncWebServerRequest::send_P(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback){
|
|
||||||
send(beginResponse_P(code, contentType, content, len, callback));
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncWebServerRequest::send_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback){
|
|
||||||
send(beginResponse_P(code, contentType, content, callback));
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncWebServerRequest::redirect(const String& url){
|
|
||||||
AsyncWebServerResponse * response = beginResponse(302);
|
|
||||||
response->addHeader(F("Location"), url);
|
|
||||||
send(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AsyncWebServerRequest::authenticate(const char * username, const char * password, const char * realm, bool passwordIsHash){
|
|
||||||
if(_authorization.length()){
|
|
||||||
if(_isDigest)
|
|
||||||
return checkDigestAuthentication(_authorization.c_str(), methodToString(), username, password, realm, passwordIsHash, NULL, NULL, NULL);
|
|
||||||
else if(!passwordIsHash)
|
|
||||||
return checkBasicAuthentication(_authorization.c_str(), username, password);
|
|
||||||
else
|
|
||||||
return _authorization.equals(password);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AsyncWebServerRequest::authenticate(const char * hash){
|
|
||||||
if(!_authorization.length() || hash == NULL)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if(_isDigest){
|
|
||||||
String hStr = String(hash);
|
|
||||||
int separator = hStr.indexOf(':');
|
|
||||||
if(separator <= 0)
|
|
||||||
return false;
|
|
||||||
String username = hStr.substring(0, separator);
|
|
||||||
hStr = hStr.substring(separator + 1);
|
|
||||||
separator = hStr.indexOf(':');
|
|
||||||
if(separator <= 0)
|
|
||||||
return false;
|
|
||||||
String realm = hStr.substring(0, separator);
|
|
||||||
hStr = hStr.substring(separator + 1);
|
|
||||||
return checkDigestAuthentication(_authorization.c_str(), methodToString(), username.c_str(), hStr.c_str(), realm.c_str(), true, NULL, NULL, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (_authorization.equals(hash));
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncWebServerRequest::requestAuthentication(const char * realm, bool isDigest){
|
|
||||||
AsyncWebServerResponse * r = beginResponse(401);
|
|
||||||
if(!isDigest && realm == NULL){
|
|
||||||
r->addHeader(F("WWW-Authenticate"), F("Basic realm=\"Login Required\""));
|
|
||||||
} else if(!isDigest){
|
|
||||||
String header = F("Basic realm=\"");
|
|
||||||
header.concat(realm);
|
|
||||||
header += '"';
|
|
||||||
r->addHeader(F("WWW-Authenticate"), header);
|
|
||||||
} else {
|
|
||||||
String header = F("Digest ");
|
|
||||||
header.concat(requestDigestAuthentication(realm));
|
|
||||||
r->addHeader(F("WWW-Authenticate"), header);
|
|
||||||
}
|
|
||||||
send(r);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AsyncWebServerRequest::hasArg(const char* name) const {
|
|
||||||
for(const auto& arg: _params){
|
|
||||||
if(arg->name() == name){
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AsyncWebServerRequest::hasArg(const __FlashStringHelper * data) const {
|
|
||||||
return hasArg(String(data).c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const String& AsyncWebServerRequest::arg(const String& name) const {
|
|
||||||
for(const auto& arg: _params){
|
|
||||||
if(arg->name() == name){
|
|
||||||
return arg->value();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return emptyString;
|
|
||||||
}
|
|
||||||
|
|
||||||
const String& AsyncWebServerRequest::arg(const __FlashStringHelper * data) const {
|
|
||||||
return arg(String(data).c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
const String& AsyncWebServerRequest::arg(size_t i) const {
|
|
||||||
return getParam(i)->value();
|
|
||||||
}
|
|
||||||
|
|
||||||
const String& AsyncWebServerRequest::argName(size_t i) const {
|
|
||||||
return getParam(i)->name();
|
|
||||||
}
|
|
||||||
|
|
||||||
const String& AsyncWebServerRequest::pathArg(size_t i) const {
|
|
||||||
auto param = _pathParams.nth(i);
|
|
||||||
return param ? **param : emptyString;
|
|
||||||
}
|
|
||||||
|
|
||||||
const String& AsyncWebServerRequest::header(const char* name) const {
|
|
||||||
AsyncWebHeader* h = getHeader(String(name));
|
|
||||||
return h ? h->value() : emptyString;
|
|
||||||
}
|
|
||||||
|
|
||||||
const String& AsyncWebServerRequest::header(const __FlashStringHelper * data) const {
|
|
||||||
return header(String(data).c_str());
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
const String& AsyncWebServerRequest::header(size_t i) const {
|
|
||||||
AsyncWebHeader* h = getHeader(i);
|
|
||||||
return h ? h->value() : emptyString;
|
|
||||||
}
|
|
||||||
|
|
||||||
const String& AsyncWebServerRequest::headerName(size_t i) const {
|
|
||||||
AsyncWebHeader* h = getHeader(i);
|
|
||||||
return h ? h->name() : emptyString;
|
|
||||||
}
|
|
||||||
|
|
||||||
String AsyncWebServerRequest::urlDecode(const String& text) const {
|
|
||||||
char temp[] = "0x00";
|
|
||||||
unsigned int len = text.length();
|
|
||||||
unsigned int i = 0;
|
|
||||||
String decoded = String();
|
|
||||||
decoded.reserve(len); // Allocate the string internal buffer - never longer from source text
|
|
||||||
while (i < len){
|
|
||||||
char decodedChar;
|
|
||||||
char encodedChar = text.charAt(i++);
|
|
||||||
if ((encodedChar == '%') && (i + 1 < len)){
|
|
||||||
temp[2] = text.charAt(i++);
|
|
||||||
temp[3] = text.charAt(i++);
|
|
||||||
decodedChar = strtol(temp, NULL, 16);
|
|
||||||
} else if (encodedChar == '+') {
|
|
||||||
decodedChar = ' ';
|
|
||||||
} else {
|
|
||||||
decodedChar = encodedChar; // normal ascii char
|
|
||||||
}
|
|
||||||
decoded.concat(decodedChar);
|
|
||||||
}
|
|
||||||
return decoded;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const char *AsyncWebServerRequest::methodToString() const {
|
|
||||||
if(_method == HTTP_ANY) return ("ANY");
|
|
||||||
else if(_method & HTTP_GET) return ("GET");
|
|
||||||
else if(_method & HTTP_POST) return ("POST");
|
|
||||||
else if(_method & HTTP_DELETE) return ("DELETE");
|
|
||||||
else if(_method & HTTP_PUT) return ("PUT");
|
|
||||||
else if(_method & HTTP_PATCH) return ("PATCH");
|
|
||||||
else if(_method & HTTP_HEAD) return ("HEAD");
|
|
||||||
else if(_method & HTTP_OPTIONS) return ("OPTIONS");
|
|
||||||
return ("UNKNOWN");
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *AsyncWebServerRequest::requestedConnTypeToString() const {
|
|
||||||
switch (_reqconntype) {
|
|
||||||
case RCT_NOT_USED: return ("RCT_NOT_USED");
|
|
||||||
case RCT_DEFAULT: return ("RCT_DEFAULT");
|
|
||||||
case RCT_HTTP: return ("RCT_HTTP");
|
|
||||||
case RCT_WS: return ("RCT_WS");
|
|
||||||
case RCT_EVENT: return ("RCT_EVENT");
|
|
||||||
default: return ("ERROR");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AsyncWebServerRequest::isExpectedRequestedConnType(RequestedConnectionType erct1, RequestedConnectionType erct2, RequestedConnectionType erct3) {
|
|
||||||
bool res = false;
|
|
||||||
if ((erct1 != RCT_NOT_USED) && (erct1 == _reqconntype)) res = true;
|
|
||||||
if ((erct2 != RCT_NOT_USED) && (erct2 == _reqconntype)) res = true;
|
|
||||||
if ((erct3 != RCT_NOT_USED) && (erct3 == _reqconntype)) res = true;
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
@@ -1,136 +0,0 @@
|
|||||||
/*
|
|
||||||
Asynchronous WebServer library for Espressif MCUs
|
|
||||||
|
|
||||||
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
|
|
||||||
This file is part of the esp8266 core for Arduino environment.
|
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
|
||||||
modify it under the terms of the GNU Lesser General Public
|
|
||||||
License as published by the Free Software Foundation; either
|
|
||||||
version 2.1 of the License, or (at your option) any later version.
|
|
||||||
|
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
Lesser General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU Lesser General Public
|
|
||||||
License along with this library; if not, write to the Free Software
|
|
||||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
||||||
*/
|
|
||||||
#ifndef ASYNCWEBSERVERRESPONSEIMPL_H_
|
|
||||||
#define ASYNCWEBSERVERRESPONSEIMPL_H_
|
|
||||||
|
|
||||||
#ifdef Arduino_h
|
|
||||||
// arduino is not compatible with std::vector
|
|
||||||
#undef min
|
|
||||||
#undef max
|
|
||||||
#endif
|
|
||||||
#include <vector>
|
|
||||||
// It is possible to restore these defines, but one can use _min and _max instead. Or std::min, std::max.
|
|
||||||
|
|
||||||
class AsyncBasicResponse: public AsyncWebServerResponse {
|
|
||||||
private:
|
|
||||||
String _content;
|
|
||||||
public:
|
|
||||||
AsyncBasicResponse(int code, const String& contentType=String(), const String& content=String());
|
|
||||||
void _respond(AsyncWebServerRequest *request);
|
|
||||||
size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time);
|
|
||||||
bool _sourceValid() const { return true; }
|
|
||||||
};
|
|
||||||
|
|
||||||
class AsyncAbstractResponse: public AsyncWebServerResponse {
|
|
||||||
private:
|
|
||||||
String _head;
|
|
||||||
// Data is inserted into cache at begin().
|
|
||||||
// This is inefficient with vector, but if we use some other container,
|
|
||||||
// we won't be able to access it as contiguous array of bytes when reading from it,
|
|
||||||
// so by gaining performance in one place, we'll lose it in another.
|
|
||||||
std::vector<uint8_t> _cache;
|
|
||||||
size_t _readDataFromCacheOrContent(uint8_t* data, const size_t len);
|
|
||||||
size_t _fillBufferAndProcessTemplates(uint8_t* buf, size_t maxLen);
|
|
||||||
protected:
|
|
||||||
AwsTemplateProcessor _callback;
|
|
||||||
public:
|
|
||||||
AsyncAbstractResponse(AwsTemplateProcessor callback=nullptr);
|
|
||||||
void _respond(AsyncWebServerRequest *request);
|
|
||||||
size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time);
|
|
||||||
bool _sourceValid() const { return false; }
|
|
||||||
virtual size_t _fillBuffer(uint8_t *buf __attribute__((unused)), size_t maxLen __attribute__((unused))) { return 0; }
|
|
||||||
};
|
|
||||||
|
|
||||||
#ifndef TEMPLATE_PLACEHOLDER
|
|
||||||
#define TEMPLATE_PLACEHOLDER '%'
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define TEMPLATE_PARAM_NAME_LENGTH 32
|
|
||||||
class AsyncFileResponse: public AsyncAbstractResponse {
|
|
||||||
using File = fs::File;
|
|
||||||
using FS = fs::FS;
|
|
||||||
private:
|
|
||||||
File _content;
|
|
||||||
String _path;
|
|
||||||
void _setContentType(const String& path);
|
|
||||||
public:
|
|
||||||
AsyncFileResponse(FS &fs, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr);
|
|
||||||
AsyncFileResponse(File content, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr);
|
|
||||||
~AsyncFileResponse();
|
|
||||||
bool _sourceValid() const { return !!(_content); }
|
|
||||||
virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override;
|
|
||||||
};
|
|
||||||
|
|
||||||
class AsyncStreamResponse: public AsyncAbstractResponse {
|
|
||||||
private:
|
|
||||||
Stream *_content;
|
|
||||||
public:
|
|
||||||
AsyncStreamResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback=nullptr);
|
|
||||||
bool _sourceValid() const { return !!(_content); }
|
|
||||||
virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override;
|
|
||||||
};
|
|
||||||
|
|
||||||
class AsyncCallbackResponse: public AsyncAbstractResponse {
|
|
||||||
private:
|
|
||||||
AwsResponseFiller _content;
|
|
||||||
size_t _filledLength;
|
|
||||||
public:
|
|
||||||
AsyncCallbackResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr);
|
|
||||||
bool _sourceValid() const { return !!(_content); }
|
|
||||||
virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override;
|
|
||||||
};
|
|
||||||
|
|
||||||
class AsyncChunkedResponse: public AsyncAbstractResponse {
|
|
||||||
private:
|
|
||||||
AwsResponseFiller _content;
|
|
||||||
size_t _filledLength;
|
|
||||||
public:
|
|
||||||
AsyncChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr);
|
|
||||||
bool _sourceValid() const { return !!(_content); }
|
|
||||||
virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override;
|
|
||||||
};
|
|
||||||
|
|
||||||
class AsyncProgmemResponse: public AsyncAbstractResponse {
|
|
||||||
private:
|
|
||||||
const uint8_t * _content;
|
|
||||||
size_t _readLength;
|
|
||||||
public:
|
|
||||||
AsyncProgmemResponse(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback=nullptr);
|
|
||||||
bool _sourceValid() const { return true; }
|
|
||||||
virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override;
|
|
||||||
};
|
|
||||||
|
|
||||||
class cbuf;
|
|
||||||
|
|
||||||
class AsyncResponseStream: public AsyncAbstractResponse, public Print {
|
|
||||||
private:
|
|
||||||
cbuf *_content;
|
|
||||||
public:
|
|
||||||
AsyncResponseStream(const String& contentType, size_t bufferSize);
|
|
||||||
~AsyncResponseStream();
|
|
||||||
bool _sourceValid() const { return (_state < RESPONSE_END); }
|
|
||||||
virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override;
|
|
||||||
size_t write(const uint8_t *data, size_t len);
|
|
||||||
size_t write(uint8_t data);
|
|
||||||
using Print::write;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif /* ASYNCWEBSERVERRESPONSEIMPL_H_ */
|
|
||||||
@@ -1,789 +0,0 @@
|
|||||||
/*
|
|
||||||
Asynchronous WebServer library for Espressif MCUs
|
|
||||||
|
|
||||||
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
|
|
||||||
This file is part of the esp8266 core for Arduino environment.
|
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
|
||||||
modify it under the terms of the GNU Lesser General Public
|
|
||||||
License as published by the Free Software Foundation; either
|
|
||||||
version 2.1 of the License, or (at your option) any later version.
|
|
||||||
|
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
Lesser General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU Lesser General Public
|
|
||||||
License along with this library; if not, write to the Free Software
|
|
||||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
||||||
*/
|
|
||||||
#include "ESPAsyncWebServer.h"
|
|
||||||
#include "WebResponseImpl.h"
|
|
||||||
#include "cbuf.h"
|
|
||||||
|
|
||||||
// Since ESP8266 does not link memchr by default, here's its implementation.
|
|
||||||
void * memchr(void * ptr, int ch, size_t count) {
|
|
||||||
unsigned char * p = static_cast<unsigned char *>(ptr);
|
|
||||||
while (count--)
|
|
||||||
if (*p++ == static_cast<unsigned char>(ch))
|
|
||||||
return --p;
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Abstract Response
|
|
||||||
* */
|
|
||||||
const char * AsyncWebServerResponse::_responseCodeToString(int code) {
|
|
||||||
switch (code) {
|
|
||||||
case 100:
|
|
||||||
return ("Continue");
|
|
||||||
case 101:
|
|
||||||
return ("Switching Protocols");
|
|
||||||
case 200:
|
|
||||||
return ("OK");
|
|
||||||
case 201:
|
|
||||||
return ("Created");
|
|
||||||
case 202:
|
|
||||||
return ("Accepted"); // proddy: used in wifi
|
|
||||||
case 203:
|
|
||||||
return ("Non-Authoritative Information");
|
|
||||||
case 204:
|
|
||||||
return ("No Content");
|
|
||||||
case 205:
|
|
||||||
return ("Reset Content"); // proddy: reboot required
|
|
||||||
case 206:
|
|
||||||
return ("Partial Content");
|
|
||||||
case 300:
|
|
||||||
return ("Multiple Choices");
|
|
||||||
case 301:
|
|
||||||
return ("Moved Permanently");
|
|
||||||
case 302:
|
|
||||||
return ("Found");
|
|
||||||
case 303:
|
|
||||||
return ("See Other");
|
|
||||||
case 304:
|
|
||||||
return ("Not Modified");
|
|
||||||
case 305:
|
|
||||||
return ("Use Proxy");
|
|
||||||
case 307:
|
|
||||||
return ("Temporary Redirect");
|
|
||||||
case 400:
|
|
||||||
return ("Bad Request");
|
|
||||||
case 401:
|
|
||||||
return ("Unauthorized");
|
|
||||||
case 402:
|
|
||||||
return ("Payment Required");
|
|
||||||
case 403:
|
|
||||||
return ("Forbidden");
|
|
||||||
case 404:
|
|
||||||
return ("Not Found");
|
|
||||||
case 405:
|
|
||||||
return ("Method Not Allowed");
|
|
||||||
case 406:
|
|
||||||
return ("Not Acceptable");
|
|
||||||
case 407:
|
|
||||||
return ("Proxy Authentication Required");
|
|
||||||
case 408:
|
|
||||||
return ("Request Time-out");
|
|
||||||
case 409:
|
|
||||||
return ("Conflict");
|
|
||||||
case 410:
|
|
||||||
return ("Gone");
|
|
||||||
case 411:
|
|
||||||
return ("Length Required");
|
|
||||||
case 412:
|
|
||||||
return ("Precondition Failed");
|
|
||||||
case 413:
|
|
||||||
return ("Request Entity Too Large");
|
|
||||||
case 414:
|
|
||||||
return ("Request-URI Too Large");
|
|
||||||
case 415:
|
|
||||||
return ("Unsupported Media Type");
|
|
||||||
case 416:
|
|
||||||
return ("Requested range not satisfiable");
|
|
||||||
case 417:
|
|
||||||
return ("Expectation Failed");
|
|
||||||
case 500:
|
|
||||||
return ("Internal Server Error");
|
|
||||||
case 501:
|
|
||||||
return ("Not Implemented");
|
|
||||||
case 502:
|
|
||||||
return ("Bad Gateway");
|
|
||||||
case 503:
|
|
||||||
return ("Service Unavailable");
|
|
||||||
case 504:
|
|
||||||
return ("Gateway Time-out");
|
|
||||||
case 505:
|
|
||||||
return ("HTTP Version not supported");
|
|
||||||
case 507:
|
|
||||||
return ("Insufficient Storage");
|
|
||||||
default:
|
|
||||||
return ("");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const __FlashStringHelper * AsyncWebServerResponse::responseCodeToString(int code) {
|
|
||||||
return reinterpret_cast<const __FlashStringHelper *>(responseCodeToString(code));
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncWebServerResponse::AsyncWebServerResponse()
|
|
||||||
: _code(0)
|
|
||||||
, _headers(LinkedList<AsyncWebHeader *>([](AsyncWebHeader * h) { delete h; }))
|
|
||||||
, _contentType()
|
|
||||||
, _contentLength(0)
|
|
||||||
, _sendContentLength(true)
|
|
||||||
, _chunked(false)
|
|
||||||
, _headLength(0)
|
|
||||||
, _sentLength(0)
|
|
||||||
, _ackedLength(0)
|
|
||||||
, _writtenLength(0)
|
|
||||||
, _state(RESPONSE_SETUP) {
|
|
||||||
for (auto header : DefaultHeaders::Instance()) {
|
|
||||||
_headers.add(new AsyncWebHeader(header->name(), header->value()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncWebServerResponse::~AsyncWebServerResponse() {
|
|
||||||
_headers.free();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncWebServerResponse::setCode(int code) {
|
|
||||||
if (_state == RESPONSE_SETUP)
|
|
||||||
_code = code;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncWebServerResponse::setContentLength(size_t len) {
|
|
||||||
if (_state == RESPONSE_SETUP)
|
|
||||||
_contentLength = len;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncWebServerResponse::setContentType(const String & type) {
|
|
||||||
if (_state == RESPONSE_SETUP)
|
|
||||||
_contentType = type;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncWebServerResponse::addHeader(const String & name, const String & value) {
|
|
||||||
_headers.add(new AsyncWebHeader(name, value));
|
|
||||||
}
|
|
||||||
|
|
||||||
String AsyncWebServerResponse::_assembleHead(uint8_t version) {
|
|
||||||
if (version) {
|
|
||||||
addHeader(F("Accept-Ranges"), F("none"));
|
|
||||||
if (_chunked)
|
|
||||||
addHeader(F("Transfer-Encoding"), F("chunked"));
|
|
||||||
}
|
|
||||||
String out = String();
|
|
||||||
int bufSize = 300;
|
|
||||||
char buf[bufSize];
|
|
||||||
|
|
||||||
snprintf_P(buf, bufSize, PSTR("HTTP/1.%d %d %s\r\n"), version, _code, _responseCodeToString(_code));
|
|
||||||
out.concat(buf);
|
|
||||||
|
|
||||||
if (_sendContentLength) {
|
|
||||||
snprintf_P(buf, bufSize, PSTR("Content-Length: %d\r\n"), _contentLength);
|
|
||||||
out.concat(buf);
|
|
||||||
}
|
|
||||||
if (_contentType.length()) {
|
|
||||||
snprintf_P(buf, bufSize, PSTR("Content-Type: %s\r\n"), _contentType.c_str());
|
|
||||||
out.concat(buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const auto & header : _headers) {
|
|
||||||
snprintf_P(buf, bufSize, PSTR("%s: %s\r\n"), header->name().c_str(), header->value().c_str());
|
|
||||||
out.concat(buf);
|
|
||||||
}
|
|
||||||
_headers.free();
|
|
||||||
|
|
||||||
out.concat(F("\r\n"));
|
|
||||||
_headLength = out.length();
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AsyncWebServerResponse::_started() const {
|
|
||||||
return _state > RESPONSE_SETUP;
|
|
||||||
}
|
|
||||||
bool AsyncWebServerResponse::_finished() const {
|
|
||||||
return _state > RESPONSE_WAIT_ACK;
|
|
||||||
}
|
|
||||||
bool AsyncWebServerResponse::_failed() const {
|
|
||||||
return _state == RESPONSE_FAILED;
|
|
||||||
}
|
|
||||||
bool AsyncWebServerResponse::_sourceValid() const {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
void AsyncWebServerResponse::_respond(AsyncWebServerRequest * request) {
|
|
||||||
_state = RESPONSE_END;
|
|
||||||
request->client()->close();
|
|
||||||
}
|
|
||||||
size_t AsyncWebServerResponse::_ack(AsyncWebServerRequest * request, size_t len, uint32_t time) {
|
|
||||||
(void)request;
|
|
||||||
(void)len;
|
|
||||||
(void)time;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* String/Code Response
|
|
||||||
* */
|
|
||||||
AsyncBasicResponse::AsyncBasicResponse(int code, const String & contentType, const String & content) {
|
|
||||||
_code = code;
|
|
||||||
_content = content;
|
|
||||||
_contentType = contentType;
|
|
||||||
if (_content.length()) {
|
|
||||||
_contentLength = _content.length();
|
|
||||||
if (!_contentType.length())
|
|
||||||
_contentType = F("text/plain");
|
|
||||||
}
|
|
||||||
addHeader(F("Connection"), F("close"));
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncBasicResponse::_respond(AsyncWebServerRequest * request) {
|
|
||||||
_state = RESPONSE_HEADERS;
|
|
||||||
String out = _assembleHead(request->version());
|
|
||||||
size_t outLen = out.length();
|
|
||||||
size_t space = request->client()->space();
|
|
||||||
if (!_contentLength && space >= outLen) {
|
|
||||||
_writtenLength += request->client()->write(out.c_str(), outLen);
|
|
||||||
_state = RESPONSE_WAIT_ACK;
|
|
||||||
} else if (_contentLength && space >= outLen + _contentLength) {
|
|
||||||
out += _content;
|
|
||||||
outLen += _contentLength;
|
|
||||||
_writtenLength += request->client()->write(out.c_str(), outLen);
|
|
||||||
_state = RESPONSE_WAIT_ACK;
|
|
||||||
} else if (space && space < outLen) {
|
|
||||||
String partial = out.substring(0, space);
|
|
||||||
_content = out.substring(space) + _content;
|
|
||||||
_contentLength += outLen - space;
|
|
||||||
_writtenLength += request->client()->write(partial.c_str(), partial.length());
|
|
||||||
_state = RESPONSE_CONTENT;
|
|
||||||
} else if (space > outLen && space < (outLen + _contentLength)) {
|
|
||||||
size_t shift = space - outLen;
|
|
||||||
outLen += shift;
|
|
||||||
_sentLength += shift;
|
|
||||||
out += _content.substring(0, shift);
|
|
||||||
_content = _content.substring(shift);
|
|
||||||
_writtenLength += request->client()->write(out.c_str(), outLen);
|
|
||||||
_state = RESPONSE_CONTENT;
|
|
||||||
} else {
|
|
||||||
_content = out + _content;
|
|
||||||
_contentLength += outLen;
|
|
||||||
_state = RESPONSE_CONTENT;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t AsyncBasicResponse::_ack(AsyncWebServerRequest * request, size_t len, uint32_t time) {
|
|
||||||
(void)time;
|
|
||||||
_ackedLength += len;
|
|
||||||
if (_state == RESPONSE_CONTENT) {
|
|
||||||
size_t available = _contentLength - _sentLength;
|
|
||||||
size_t space = request->client()->space();
|
|
||||||
//we can fit in this packet
|
|
||||||
if (space > available) {
|
|
||||||
_writtenLength += request->client()->write(_content.c_str(), available);
|
|
||||||
_content = String();
|
|
||||||
_state = RESPONSE_WAIT_ACK;
|
|
||||||
return available;
|
|
||||||
}
|
|
||||||
//send some data, the rest on ack
|
|
||||||
String out = _content.substring(0, space);
|
|
||||||
_content = _content.substring(space);
|
|
||||||
_sentLength += space;
|
|
||||||
_writtenLength += request->client()->write(out.c_str(), space);
|
|
||||||
return space;
|
|
||||||
} else if (_state == RESPONSE_WAIT_ACK) {
|
|
||||||
if (_ackedLength >= _writtenLength) {
|
|
||||||
_state = RESPONSE_END;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Abstract Response
|
|
||||||
* */
|
|
||||||
|
|
||||||
AsyncAbstractResponse::AsyncAbstractResponse(AwsTemplateProcessor callback)
|
|
||||||
: _callback(callback) {
|
|
||||||
// In case of template processing, we're unable to determine real response size
|
|
||||||
if (callback) {
|
|
||||||
_contentLength = 0;
|
|
||||||
_sendContentLength = false;
|
|
||||||
_chunked = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncAbstractResponse::_respond(AsyncWebServerRequest * request) {
|
|
||||||
addHeader(F("Connection"), F("close"));
|
|
||||||
_head = _assembleHead(request->version());
|
|
||||||
_state = RESPONSE_HEADERS;
|
|
||||||
_ack(request, 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest * request, size_t len, uint32_t time) {
|
|
||||||
(void)time;
|
|
||||||
if (!_sourceValid()) {
|
|
||||||
_state = RESPONSE_FAILED;
|
|
||||||
request->client()->close();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
_ackedLength += len;
|
|
||||||
size_t space = request->client()->space();
|
|
||||||
|
|
||||||
size_t headLen = _head.length();
|
|
||||||
if (_state == RESPONSE_HEADERS) {
|
|
||||||
if (space >= headLen) {
|
|
||||||
_state = RESPONSE_CONTENT;
|
|
||||||
space -= headLen;
|
|
||||||
} else {
|
|
||||||
String out = _head.substring(0, space);
|
|
||||||
_head = _head.substring(space);
|
|
||||||
_writtenLength += request->client()->write(out.c_str(), out.length());
|
|
||||||
return out.length();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_state == RESPONSE_CONTENT) {
|
|
||||||
size_t outLen;
|
|
||||||
if (_chunked) {
|
|
||||||
if (space <= 8) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
outLen = space;
|
|
||||||
} else if (!_sendContentLength) {
|
|
||||||
outLen = space;
|
|
||||||
} else {
|
|
||||||
outLen = ((_contentLength - _sentLength) > space) ? space : (_contentLength - _sentLength);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t * buf = (uint8_t *)malloc(outLen + headLen);
|
|
||||||
if (!buf) {
|
|
||||||
// os_printf("_ack malloc %d failed\n", outLen+headLen);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (headLen) {
|
|
||||||
memcpy(buf, _head.c_str(), _head.length());
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t readLen = 0;
|
|
||||||
|
|
||||||
if (_chunked) {
|
|
||||||
// HTTP 1.1 allows leading zeros in chunk length. Or spaces may be added.
|
|
||||||
// See RFC2616 sections 2, 3.6.1.
|
|
||||||
readLen = _fillBufferAndProcessTemplates(buf + headLen + 6, outLen - 8);
|
|
||||||
if (readLen == RESPONSE_TRY_AGAIN) {
|
|
||||||
free(buf);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
outLen = snprintf_P((char *)buf + headLen, sizeof(buf) - headLen - 2, PSTR("%x"), readLen) + headLen;
|
|
||||||
while (outLen < headLen + 4)
|
|
||||||
buf[outLen++] = ' ';
|
|
||||||
buf[outLen++] = '\r';
|
|
||||||
buf[outLen++] = '\n';
|
|
||||||
outLen += readLen;
|
|
||||||
buf[outLen++] = '\r';
|
|
||||||
buf[outLen++] = '\n';
|
|
||||||
} else {
|
|
||||||
readLen = _fillBufferAndProcessTemplates(buf + headLen, outLen);
|
|
||||||
if (readLen == RESPONSE_TRY_AGAIN) {
|
|
||||||
free(buf);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
outLen = readLen + headLen;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (headLen) {
|
|
||||||
_head = String();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (outLen) {
|
|
||||||
_writtenLength += request->client()->write((const char *)buf, outLen);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_chunked) {
|
|
||||||
_sentLength += readLen;
|
|
||||||
} else {
|
|
||||||
_sentLength += outLen - headLen;
|
|
||||||
}
|
|
||||||
|
|
||||||
free(buf);
|
|
||||||
|
|
||||||
if ((_chunked && readLen == 0) || (!_sendContentLength && outLen == 0) || (!_chunked && _sentLength == _contentLength)) {
|
|
||||||
_state = RESPONSE_WAIT_ACK;
|
|
||||||
}
|
|
||||||
return outLen;
|
|
||||||
|
|
||||||
} else if (_state == RESPONSE_WAIT_ACK) {
|
|
||||||
if (!_sendContentLength || _ackedLength >= _writtenLength) {
|
|
||||||
_state = RESPONSE_END;
|
|
||||||
if (!_chunked && !_sendContentLength)
|
|
||||||
request->client()->close(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t AsyncAbstractResponse::_readDataFromCacheOrContent(uint8_t * data, const size_t len) {
|
|
||||||
// If we have something in cache, copy it to buffer
|
|
||||||
const size_t readFromCache = std::min(len, _cache.size());
|
|
||||||
if (readFromCache) {
|
|
||||||
memcpy(data, _cache.data(), readFromCache);
|
|
||||||
_cache.erase(_cache.begin(), _cache.begin() + readFromCache);
|
|
||||||
}
|
|
||||||
// If we need to read more...
|
|
||||||
const size_t needFromFile = len - readFromCache;
|
|
||||||
const size_t readFromContent = _fillBuffer(data + readFromCache, needFromFile);
|
|
||||||
return readFromCache + readFromContent;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t * data, size_t len) {
|
|
||||||
if (!_callback)
|
|
||||||
return _fillBuffer(data, len);
|
|
||||||
|
|
||||||
const size_t originalLen = len;
|
|
||||||
len = _readDataFromCacheOrContent(data, len);
|
|
||||||
// Now we've read 'len' bytes, either from cache or from file
|
|
||||||
// Search for template placeholders
|
|
||||||
uint8_t * pTemplateStart = data;
|
|
||||||
while ((pTemplateStart < &data[len])
|
|
||||||
&& (pTemplateStart = (uint8_t *)memchr(pTemplateStart, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart + 1))) { // data[0] ... data[len - 1]
|
|
||||||
uint8_t * pTemplateEnd =
|
|
||||||
(pTemplateStart < &data[len - 1]) ? (uint8_t *)memchr(pTemplateStart + 1, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart) : nullptr;
|
|
||||||
// temporary buffer to hold parameter name
|
|
||||||
uint8_t buf[TEMPLATE_PARAM_NAME_LENGTH + 1];
|
|
||||||
String paramName;
|
|
||||||
// If closing placeholder is found:
|
|
||||||
if (pTemplateEnd) {
|
|
||||||
// prepare argument to callback
|
|
||||||
const size_t paramNameLength = std::min((size_t)sizeof(buf) - 1, (size_t)(pTemplateEnd - pTemplateStart - 1));
|
|
||||||
if (paramNameLength) {
|
|
||||||
memcpy(buf, pTemplateStart + 1, paramNameLength);
|
|
||||||
buf[paramNameLength] = 0;
|
|
||||||
paramName = String(reinterpret_cast<char *>(buf));
|
|
||||||
} else { // double percent sign encountered, this is single percent sign escaped.
|
|
||||||
// remove the 2nd percent sign
|
|
||||||
memmove(pTemplateEnd, pTemplateEnd + 1, &data[len] - pTemplateEnd - 1);
|
|
||||||
len += _readDataFromCacheOrContent(&data[len - 1], 1) - 1;
|
|
||||||
++pTemplateStart;
|
|
||||||
}
|
|
||||||
} else if (&data[len - 1] - pTemplateStart + 1 < TEMPLATE_PARAM_NAME_LENGTH + 2) { // closing placeholder not found, check if it's in the remaining file data
|
|
||||||
memcpy(buf, pTemplateStart + 1, &data[len - 1] - pTemplateStart);
|
|
||||||
const size_t readFromCacheOrContent =
|
|
||||||
_readDataFromCacheOrContent(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PARAM_NAME_LENGTH + 2 - (&data[len - 1] - pTemplateStart + 1));
|
|
||||||
if (readFromCacheOrContent) {
|
|
||||||
pTemplateEnd = (uint8_t *)memchr(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PLACEHOLDER, readFromCacheOrContent);
|
|
||||||
if (pTemplateEnd) {
|
|
||||||
// prepare argument to callback
|
|
||||||
*pTemplateEnd = 0;
|
|
||||||
paramName = String(reinterpret_cast<char *>(buf));
|
|
||||||
// Copy remaining read-ahead data into cache
|
|
||||||
_cache.insert(_cache.begin(), pTemplateEnd + 1, buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent);
|
|
||||||
pTemplateEnd = &data[len - 1];
|
|
||||||
} else // closing placeholder not found in file data, store found percent symbol as is and advance to the next position
|
|
||||||
{
|
|
||||||
// but first, store read file data in cache
|
|
||||||
_cache.insert(_cache.begin(), buf + (&data[len - 1] - pTemplateStart), buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent);
|
|
||||||
++pTemplateStart;
|
|
||||||
}
|
|
||||||
} else // closing placeholder not found in content data, store found percent symbol as is and advance to the next position
|
|
||||||
++pTemplateStart;
|
|
||||||
} else // closing placeholder not found in content data, store found percent symbol as is and advance to the next position
|
|
||||||
++pTemplateStart;
|
|
||||||
if (paramName.length()) {
|
|
||||||
// call callback and replace with result.
|
|
||||||
// Everything in range [pTemplateStart, pTemplateEnd] can be safely replaced with parameter value.
|
|
||||||
// Data after pTemplateEnd may need to be moved.
|
|
||||||
// The first byte of data after placeholder is located at pTemplateEnd + 1.
|
|
||||||
// It should be located at pTemplateStart + numBytesCopied (to begin right after inserted parameter value).
|
|
||||||
const String paramValue(_callback(paramName));
|
|
||||||
const char * pvstr = paramValue.c_str();
|
|
||||||
const unsigned int pvlen = paramValue.length();
|
|
||||||
const size_t numBytesCopied = std::min(pvlen, static_cast<unsigned int>(&data[originalLen - 1] - pTemplateStart + 1));
|
|
||||||
// make room for param value
|
|
||||||
// 1. move extra data to cache if parameter value is longer than placeholder AND if there is no room to store
|
|
||||||
if ((pTemplateEnd + 1 < pTemplateStart + numBytesCopied) && (originalLen - (pTemplateStart + numBytesCopied - pTemplateEnd - 1) < len)) {
|
|
||||||
_cache.insert(_cache.begin(), &data[originalLen - (pTemplateStart + numBytesCopied - pTemplateEnd - 1)], &data[len]);
|
|
||||||
//2. parameter value is longer than placeholder text, push the data after placeholder which not saved into cache further to the end
|
|
||||||
memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[originalLen] - pTemplateStart - numBytesCopied);
|
|
||||||
len = originalLen; // fix issue with truncated data, not sure if it has any side effects
|
|
||||||
} else if (pTemplateEnd + 1 != pTemplateStart + numBytesCopied)
|
|
||||||
//2. Either parameter value is shorter than placeholder text OR there is enough free space in buffer to fit.
|
|
||||||
// Move the entire data after the placeholder
|
|
||||||
memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[len] - pTemplateEnd - 1);
|
|
||||||
// 3. replace placeholder with actual value
|
|
||||||
memcpy(pTemplateStart, pvstr, numBytesCopied);
|
|
||||||
// If result is longer than buffer, copy the remainder into cache (this could happen only if placeholder text itself did not fit entirely in buffer)
|
|
||||||
if (numBytesCopied < pvlen) {
|
|
||||||
_cache.insert(_cache.begin(), pvstr + numBytesCopied, pvstr + pvlen);
|
|
||||||
} else if (pTemplateStart + numBytesCopied < pTemplateEnd + 1) { // result is copied fully; if result is shorter than placeholder text...
|
|
||||||
// there is some free room, fill it from cache
|
|
||||||
const size_t roomFreed = pTemplateEnd + 1 - pTemplateStart - numBytesCopied;
|
|
||||||
const size_t totalFreeRoom = originalLen - len + roomFreed;
|
|
||||||
len += _readDataFromCacheOrContent(&data[len - roomFreed], totalFreeRoom) - roomFreed;
|
|
||||||
} else { // result is copied fully; it is longer than placeholder text
|
|
||||||
const size_t roomTaken = pTemplateStart + numBytesCopied - pTemplateEnd - 1;
|
|
||||||
len = std::min(len + roomTaken, originalLen);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // while(pTemplateStart)
|
|
||||||
return len;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* File Response
|
|
||||||
* */
|
|
||||||
|
|
||||||
AsyncFileResponse::~AsyncFileResponse() {
|
|
||||||
if (_content)
|
|
||||||
_content.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncFileResponse::_setContentType(const String & path) {
|
|
||||||
#if HAVE_EXTERN_GET_CONTENT_TYPE_FUNCTION
|
|
||||||
extern const __FlashStringHelper * getContentType(const String & path);
|
|
||||||
_contentType = getContentType(path);
|
|
||||||
#else
|
|
||||||
if (path.endsWith(F(".html")))
|
|
||||||
_contentType = F("text/html");
|
|
||||||
else if (path.endsWith(F(".htm")))
|
|
||||||
_contentType = F("text/html");
|
|
||||||
else if (path.endsWith(F(".css")))
|
|
||||||
_contentType = F("text/css");
|
|
||||||
else if (path.endsWith(F(".json")))
|
|
||||||
_contentType = F("application/json");
|
|
||||||
else if (path.endsWith(F(".js")))
|
|
||||||
_contentType = F("application/javascript");
|
|
||||||
else if (path.endsWith(F(".png")))
|
|
||||||
_contentType = F("image/png");
|
|
||||||
else if (path.endsWith(F(".gif")))
|
|
||||||
_contentType = F("image/gif");
|
|
||||||
else if (path.endsWith(F(".jpg")))
|
|
||||||
_contentType = F("image/jpeg");
|
|
||||||
else if (path.endsWith(F(".ico")))
|
|
||||||
_contentType = F("image/x-icon");
|
|
||||||
else if (path.endsWith(F(".svg")))
|
|
||||||
_contentType = F("image/svg+xml");
|
|
||||||
else if (path.endsWith(F(".eot")))
|
|
||||||
_contentType = F("font/eot");
|
|
||||||
else if (path.endsWith(F(".woff")))
|
|
||||||
_contentType = F("font/woff");
|
|
||||||
else if (path.endsWith(F(".woff2")))
|
|
||||||
_contentType = F("font/woff2");
|
|
||||||
else if (path.endsWith(F(".ttf")))
|
|
||||||
_contentType = F("font/ttf");
|
|
||||||
else if (path.endsWith(F(".xml")))
|
|
||||||
_contentType = F("text/xml");
|
|
||||||
else if (path.endsWith(F(".pdf")))
|
|
||||||
_contentType = F("application/pdf");
|
|
||||||
else if (path.endsWith(F(".zip")))
|
|
||||||
_contentType = F("application/zip");
|
|
||||||
else if (path.endsWith(F(".gz")))
|
|
||||||
_contentType = F("application/x-gzip");
|
|
||||||
else
|
|
||||||
_contentType = F("text/plain");
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncFileResponse::AsyncFileResponse(FS & fs, const String & path, const String & contentType, bool download, AwsTemplateProcessor callback)
|
|
||||||
: AsyncAbstractResponse(callback) {
|
|
||||||
_code = 200;
|
|
||||||
_path = path;
|
|
||||||
|
|
||||||
if (!download && !fs.exists(_path) && fs.exists(_path + F(".gz"))) {
|
|
||||||
_path = _path + F(".gz");
|
|
||||||
addHeader(F("Content-Encoding"), F("gzip"));
|
|
||||||
_callback = nullptr; // Unable to process zipped templates
|
|
||||||
_sendContentLength = true;
|
|
||||||
_chunked = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
_content = fs.open(_path, fs::FileOpenMode::read);
|
|
||||||
_contentLength = _content.size();
|
|
||||||
|
|
||||||
if (contentType.length() == 0)
|
|
||||||
_setContentType(path);
|
|
||||||
else
|
|
||||||
_contentType = contentType;
|
|
||||||
|
|
||||||
int filenameStart = path.lastIndexOf('/') + 1;
|
|
||||||
char buf[26 + path.length() - filenameStart];
|
|
||||||
char * filename = (char *)path.c_str() + filenameStart;
|
|
||||||
|
|
||||||
if (download) {
|
|
||||||
// set filename and force download
|
|
||||||
snprintf_P(buf, sizeof(buf), PSTR("attachment; filename=\"%s\""), filename);
|
|
||||||
} else {
|
|
||||||
// set filename and force rendering
|
|
||||||
snprintf_P(buf, sizeof(buf), PSTR("inline; filename=\"%s\""), filename);
|
|
||||||
}
|
|
||||||
addHeader(F("Content-Disposition"), buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncFileResponse::AsyncFileResponse(File content, const String & path, const String & contentType, bool download, AwsTemplateProcessor callback)
|
|
||||||
: AsyncAbstractResponse(callback) {
|
|
||||||
_code = 200;
|
|
||||||
_path = path;
|
|
||||||
|
|
||||||
if (!download && String(content.name()).endsWith(F(".gz")) && !path.endsWith(F(".gz"))) {
|
|
||||||
addHeader(F("Content-Encoding"), F("gzip"));
|
|
||||||
_callback = nullptr; // Unable to process gzipped templates
|
|
||||||
_sendContentLength = true;
|
|
||||||
_chunked = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
_content = content;
|
|
||||||
_contentLength = _content.size();
|
|
||||||
|
|
||||||
if (contentType.length() == 0)
|
|
||||||
_setContentType(path);
|
|
||||||
else
|
|
||||||
_contentType = contentType;
|
|
||||||
|
|
||||||
int filenameStart = path.lastIndexOf('/') + 1;
|
|
||||||
char buf[26 + path.length() - filenameStart];
|
|
||||||
char * filename = (char *)path.c_str() + filenameStart;
|
|
||||||
|
|
||||||
if (download) {
|
|
||||||
snprintf_P(buf, sizeof(buf), PSTR("attachment; filename=\"%s\""), filename);
|
|
||||||
} else {
|
|
||||||
snprintf_P(buf, sizeof(buf), PSTR("inline; filename=\"%s\""), filename);
|
|
||||||
}
|
|
||||||
addHeader(F("Content-Disposition"), buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t AsyncFileResponse::_fillBuffer(uint8_t * data, size_t len) {
|
|
||||||
return _content.read(data, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Stream Response
|
|
||||||
* */
|
|
||||||
|
|
||||||
AsyncStreamResponse::AsyncStreamResponse(Stream & stream, const String & contentType, size_t len, AwsTemplateProcessor callback)
|
|
||||||
: AsyncAbstractResponse(callback) {
|
|
||||||
_code = 200;
|
|
||||||
_content = &stream;
|
|
||||||
_contentLength = len;
|
|
||||||
_contentType = contentType;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t AsyncStreamResponse::_fillBuffer(uint8_t * data, size_t len) {
|
|
||||||
size_t available = _content->available();
|
|
||||||
size_t outLen = (available > len) ? len : available;
|
|
||||||
size_t i;
|
|
||||||
for (i = 0; i < outLen; i++)
|
|
||||||
data[i] = _content->read();
|
|
||||||
return outLen;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Callback Response
|
|
||||||
* */
|
|
||||||
|
|
||||||
AsyncCallbackResponse::AsyncCallbackResponse(const String & contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback)
|
|
||||||
: AsyncAbstractResponse(templateCallback) {
|
|
||||||
_code = 200;
|
|
||||||
_content = callback;
|
|
||||||
_contentLength = len;
|
|
||||||
if (!len)
|
|
||||||
_sendContentLength = false;
|
|
||||||
_contentType = contentType;
|
|
||||||
_filledLength = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t AsyncCallbackResponse::_fillBuffer(uint8_t * data, size_t len) {
|
|
||||||
size_t ret = _content(data, len, _filledLength);
|
|
||||||
if (ret != RESPONSE_TRY_AGAIN) {
|
|
||||||
_filledLength += ret;
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Chunked Response
|
|
||||||
* */
|
|
||||||
|
|
||||||
AsyncChunkedResponse::AsyncChunkedResponse(const String & contentType, AwsResponseFiller callback, AwsTemplateProcessor processorCallback)
|
|
||||||
: AsyncAbstractResponse(processorCallback) {
|
|
||||||
_code = 200;
|
|
||||||
_content = callback;
|
|
||||||
_contentLength = 0;
|
|
||||||
_contentType = contentType;
|
|
||||||
_sendContentLength = false;
|
|
||||||
_chunked = true;
|
|
||||||
_filledLength = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t AsyncChunkedResponse::_fillBuffer(uint8_t * data, size_t len) {
|
|
||||||
size_t ret = _content(data, len, _filledLength);
|
|
||||||
if (ret != RESPONSE_TRY_AGAIN) {
|
|
||||||
_filledLength += ret;
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Progmem Response
|
|
||||||
* */
|
|
||||||
|
|
||||||
AsyncProgmemResponse::AsyncProgmemResponse(int code, const String & contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback)
|
|
||||||
: AsyncAbstractResponse(callback) {
|
|
||||||
_code = code;
|
|
||||||
_content = content;
|
|
||||||
_contentType = contentType;
|
|
||||||
_contentLength = len;
|
|
||||||
_readLength = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t AsyncProgmemResponse::_fillBuffer(uint8_t * data, size_t len) {
|
|
||||||
size_t left = _contentLength - _readLength;
|
|
||||||
if (left > len) {
|
|
||||||
memcpy_P(data, _content + _readLength, len);
|
|
||||||
_readLength += len;
|
|
||||||
return len;
|
|
||||||
}
|
|
||||||
memcpy_P(data, _content + _readLength, left);
|
|
||||||
_readLength += left;
|
|
||||||
return left;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Response Stream (You can print/write/printf to it, up to the contentLen bytes)
|
|
||||||
* */
|
|
||||||
|
|
||||||
AsyncResponseStream::AsyncResponseStream(const String & contentType, size_t bufferSize) {
|
|
||||||
_code = 200;
|
|
||||||
_contentLength = 0;
|
|
||||||
_contentType = contentType;
|
|
||||||
_content = new cbuf(bufferSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncResponseStream::~AsyncResponseStream() {
|
|
||||||
delete _content;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t AsyncResponseStream::_fillBuffer(uint8_t * buf, size_t maxLen) {
|
|
||||||
return _content->read((char *)buf, maxLen);
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t AsyncResponseStream::write(const uint8_t * data, size_t len) {
|
|
||||||
if (_started())
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
if (len > _content->room()) {
|
|
||||||
size_t needed = len - _content->room();
|
|
||||||
_content->resizeAdd(needed);
|
|
||||||
}
|
|
||||||
size_t written = _content->write((const char *)data, len);
|
|
||||||
_contentLength += written;
|
|
||||||
return written;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t AsyncResponseStream::write(uint8_t data) {
|
|
||||||
return write(&data, 1);
|
|
||||||
}
|
|
||||||
@@ -1,198 +0,0 @@
|
|||||||
/*
|
|
||||||
Asynchronous WebServer library for Espressif MCUs
|
|
||||||
|
|
||||||
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
|
|
||||||
This file is part of the esp8266 core for Arduino environment.
|
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
|
||||||
modify it under the terms of the GNU Lesser General Public
|
|
||||||
License as published by the Free Software Foundation; either
|
|
||||||
version 2.1 of the License, or (at your option) any later version.
|
|
||||||
|
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
Lesser General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU Lesser General Public
|
|
||||||
License along with this library; if not, write to the Free Software
|
|
||||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
||||||
*/
|
|
||||||
#include "ESPAsyncWebServer.h"
|
|
||||||
#include "WebHandlerImpl.h"
|
|
||||||
|
|
||||||
bool ON_STA_FILTER(AsyncWebServerRequest *request) {
|
|
||||||
return WiFi.localIP() == request->client()->localIP();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ON_AP_FILTER(AsyncWebServerRequest *request) {
|
|
||||||
return WiFi.localIP() != request->client()->localIP();
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifndef HAVE_FS_FILE_OPEN_MODE
|
|
||||||
const char *fs::FileOpenMode::read = "r";
|
|
||||||
const char *fs::FileOpenMode::write = "w";
|
|
||||||
const char *fs::FileOpenMode::append = "a";
|
|
||||||
#endif
|
|
||||||
|
|
||||||
AsyncWebServer::AsyncWebServer(uint16_t port)
|
|
||||||
: _server(port)
|
|
||||||
, _rewrites(LinkedList<AsyncWebRewrite*>([](AsyncWebRewrite* r){ delete r; }))
|
|
||||||
, _handlers(LinkedList<AsyncWebHandler*>([](AsyncWebHandler* h){ delete h; }))
|
|
||||||
{
|
|
||||||
_catchAllHandler = new AsyncCallbackWebHandler();
|
|
||||||
if(_catchAllHandler == NULL)
|
|
||||||
return;
|
|
||||||
_server.onClient([](void *s, AsyncClient* c){
|
|
||||||
if(c == NULL)
|
|
||||||
return;
|
|
||||||
c->setRxTimeout(3);
|
|
||||||
AsyncWebServerRequest *r = new AsyncWebServerRequest((AsyncWebServer*)s, c);
|
|
||||||
if(r == NULL){
|
|
||||||
c->close(true);
|
|
||||||
c->free();
|
|
||||||
delete c;
|
|
||||||
}
|
|
||||||
}, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncWebServer::~AsyncWebServer(){
|
|
||||||
reset();
|
|
||||||
end();
|
|
||||||
if(_catchAllHandler) delete _catchAllHandler;
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncWebRewrite& AsyncWebServer::addRewrite(AsyncWebRewrite* rewrite){
|
|
||||||
_rewrites.add(rewrite);
|
|
||||||
return *rewrite;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AsyncWebServer::removeRewrite(AsyncWebRewrite *rewrite){
|
|
||||||
return _rewrites.remove(rewrite);
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncWebRewrite& AsyncWebServer::rewrite(const char* from, const char* to){
|
|
||||||
return addRewrite(new AsyncWebRewrite(from, to));
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncWebHandler& AsyncWebServer::addHandler(AsyncWebHandler* handler){
|
|
||||||
_handlers.add(handler);
|
|
||||||
return *handler;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AsyncWebServer::removeHandler(AsyncWebHandler *handler){
|
|
||||||
return _handlers.remove(handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncWebServer::begin(){
|
|
||||||
_server.setNoDelay(true);
|
|
||||||
_server.begin();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncWebServer::end(){
|
|
||||||
_server.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
#if ASYNC_TCP_SSL_ENABLED
|
|
||||||
void AsyncWebServer::onSslFileRequest(AcSSlFileHandler cb, void* arg){
|
|
||||||
_server.onSslFileRequest(cb, arg);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncWebServer::beginSecure(const char *cert, const char *key, const char *password){
|
|
||||||
_server.beginSecure(cert, key, password);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void AsyncWebServer::_handleDisconnect(AsyncWebServerRequest *request){
|
|
||||||
delete request;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncWebServer::_rewriteRequest(AsyncWebServerRequest *request){
|
|
||||||
for(const auto& r: _rewrites){
|
|
||||||
if (r->match(request)){
|
|
||||||
request->_url = r->toUrl();
|
|
||||||
request->_addGetParams(r->params());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncWebServer::_attachHandler(AsyncWebServerRequest *request){
|
|
||||||
for(const auto& h: _handlers){
|
|
||||||
if (h->filter(request) && h->canHandle(request)){
|
|
||||||
request->setHandler(h);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
request->addInterestingHeader(F("ANY"));
|
|
||||||
request->setHandler(_catchAllHandler);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload, ArBodyHandlerFunction onBody){
|
|
||||||
AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler();
|
|
||||||
handler->setUri(uri);
|
|
||||||
handler->setMethod(method);
|
|
||||||
handler->onRequest(onRequest);
|
|
||||||
handler->onUpload(onUpload);
|
|
||||||
handler->onBody(onBody);
|
|
||||||
addHandler(handler);
|
|
||||||
return *handler;
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload){
|
|
||||||
AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler();
|
|
||||||
handler->setUri(uri);
|
|
||||||
handler->setMethod(method);
|
|
||||||
handler->onRequest(onRequest);
|
|
||||||
handler->onUpload(onUpload);
|
|
||||||
addHandler(handler);
|
|
||||||
return *handler;
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest){
|
|
||||||
AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler();
|
|
||||||
handler->setUri(uri);
|
|
||||||
handler->setMethod(method);
|
|
||||||
handler->onRequest(onRequest);
|
|
||||||
addHandler(handler);
|
|
||||||
return *handler;
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, ArRequestHandlerFunction onRequest){
|
|
||||||
AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler();
|
|
||||||
handler->setUri(uri);
|
|
||||||
handler->onRequest(onRequest);
|
|
||||||
addHandler(handler);
|
|
||||||
return *handler;
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncStaticWebHandler& AsyncWebServer::serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_control){
|
|
||||||
AsyncStaticWebHandler* handler = new AsyncStaticWebHandler(uri, fs, path, cache_control);
|
|
||||||
addHandler(handler);
|
|
||||||
return *handler;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncWebServer::onNotFound(ArRequestHandlerFunction fn){
|
|
||||||
_catchAllHandler->onRequest(fn);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncWebServer::onFileUpload(ArUploadHandlerFunction fn){
|
|
||||||
_catchAllHandler->onUpload(fn);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncWebServer::onRequestBody(ArBodyHandlerFunction fn){
|
|
||||||
_catchAllHandler->onBody(fn);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AsyncWebServer::reset(){
|
|
||||||
_rewrites.free();
|
|
||||||
_handlers.free();
|
|
||||||
|
|
||||||
if (_catchAllHandler != NULL){
|
|
||||||
_catchAllHandler->onRequest(NULL);
|
|
||||||
_catchAllHandler->onUpload(NULL);
|
|
||||||
_catchAllHandler->onBody(NULL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -156,9 +156,7 @@
|
|||||||
#define IO_REG_BASE_ATTR
|
#define IO_REG_BASE_ATTR
|
||||||
#define IO_REG_MASK_ATTR
|
#define IO_REG_MASK_ATTR
|
||||||
|
|
||||||
static inline __attribute__((always_inline))
|
static inline __attribute__((always_inline)) IO_REG_TYPE directRead(IO_REG_TYPE pin) {
|
||||||
IO_REG_TYPE directRead(IO_REG_TYPE pin)
|
|
||||||
{
|
|
||||||
// return digitalRead(pin); // Works most of the time
|
// return digitalRead(pin); // Works most of the time
|
||||||
// return gpio_ll_get_level(&GPIO, pin); // The hal is not public api, don't use in application code
|
// return gpio_ll_get_level(&GPIO, pin); // The hal is not public api, don't use in application code
|
||||||
|
|
||||||
@@ -172,12 +170,9 @@ IO_REG_TYPE directRead(IO_REG_TYPE pin)
|
|||||||
return (GPIO.in1.val >> (pin - 32)) & 0x1;
|
return (GPIO.in1.val >> (pin - 32)) & 0x1;
|
||||||
#endif
|
#endif
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline __attribute__((always_inline))
|
static inline __attribute__((always_inline)) void directWriteLow(IO_REG_TYPE pin) {
|
||||||
void directWriteLow(IO_REG_TYPE pin)
|
|
||||||
{
|
|
||||||
// digitalWrite(pin, 0); // Works most of the time
|
// digitalWrite(pin, 0); // Works most of the time
|
||||||
// gpio_ll_set_level(&GPIO, pin, 0); // The hal is not public api, don't use in application code
|
// gpio_ll_set_level(&GPIO, pin, 0); // The hal is not public api, don't use in application code
|
||||||
|
|
||||||
@@ -192,9 +187,7 @@ void directWriteLow(IO_REG_TYPE pin)
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline __attribute__((always_inline))
|
static inline __attribute__((always_inline)) void directWriteHigh(IO_REG_TYPE pin) {
|
||||||
void directWriteHigh(IO_REG_TYPE pin)
|
|
||||||
{
|
|
||||||
// digitalWrite(pin, 1); // Works most of the time
|
// digitalWrite(pin, 1); // Works most of the time
|
||||||
// gpio_ll_set_level(&GPIO, pin, 1); // The hal is not public api, don't use in application code
|
// gpio_ll_set_level(&GPIO, pin, 1); // The hal is not public api, don't use in application code
|
||||||
|
|
||||||
@@ -207,17 +200,17 @@ void directWriteHigh(IO_REG_TYPE pin)
|
|||||||
else
|
else
|
||||||
GPIO.out1_w1ts.val = ((uint32_t)1 << (pin - 32));
|
GPIO.out1_w1ts.val = ((uint32_t)1 << (pin - 32));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline __attribute__((always_inline))
|
|
||||||
void directModeInput(IO_REG_TYPE pin)
|
#pragma GCC diagnostic push
|
||||||
{
|
#pragma GCC diagnostic ignored "-Wtype-limits"
|
||||||
|
|
||||||
|
static inline __attribute__((always_inline)) void directModeInput(IO_REG_TYPE pin) {
|
||||||
// pinMode(pin, INPUT); // Too slow - doesn't work
|
// pinMode(pin, INPUT); // Too slow - doesn't work
|
||||||
// gpio_ll_output_disable(&GPIO, pin); // The hal is not public api, don't use in application code
|
// gpio_ll_output_disable(&GPIO, pin); // The hal is not public api, don't use in application code
|
||||||
|
|
||||||
if ( digitalPinIsValid(pin) )
|
if (digitalPinIsValid(pin)) {
|
||||||
{
|
|
||||||
// Input
|
// Input
|
||||||
//#if CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C6
|
//#if CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C6
|
||||||
#if SOC_GPIO_PIN_COUNT <= 32
|
#if SOC_GPIO_PIN_COUNT <= 32
|
||||||
@@ -229,17 +222,13 @@ void directModeInput(IO_REG_TYPE pin)
|
|||||||
GPIO.enable1_w1tc.val = ((uint32_t)1 << (pin - 32));
|
GPIO.enable1_w1tc.val = ((uint32_t)1 << (pin - 32));
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline __attribute__((always_inline))
|
static inline __attribute__((always_inline)) void directModeOutput(IO_REG_TYPE pin) {
|
||||||
void directModeOutput(IO_REG_TYPE pin)
|
|
||||||
{
|
|
||||||
// pinMode(pin, OUTPUT); // Too slow - doesn't work
|
// pinMode(pin, OUTPUT); // Too slow - doesn't work
|
||||||
// gpio_ll_output_enable(&GPIO, pin); // The hal is not public api, don't use in application code
|
// gpio_ll_output_enable(&GPIO, pin); // The hal is not public api, don't use in application code
|
||||||
|
|
||||||
if ( digitalPinCanOutput(pin) )
|
if (digitalPinCanOutput(pin)) {
|
||||||
{
|
|
||||||
// Output
|
// Output
|
||||||
//#if CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C6
|
//#if CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C6
|
||||||
#if SOC_GPIO_PIN_COUNT <= 32
|
#if SOC_GPIO_PIN_COUNT <= 32
|
||||||
@@ -251,9 +240,11 @@ void directModeOutput(IO_REG_TYPE pin)
|
|||||||
GPIO.enable1_w1ts.val = ((uint32_t)1 << (pin - 32));
|
GPIO.enable1_w1ts.val = ((uint32_t)1 << (pin - 32));
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#pragma GCC diagnostic pop
|
||||||
|
|
||||||
|
|
||||||
#define DIRECT_READ(base, pin) directRead(pin)
|
#define DIRECT_READ(base, pin) directRead(pin)
|
||||||
#define DIRECT_WRITE_LOW(base, pin) directWriteLow(pin)
|
#define DIRECT_WRITE_LOW(base, pin) directWriteLow(pin)
|
||||||
#define DIRECT_WRITE_HIGH(base, pin) directWriteHigh(pin)
|
#define DIRECT_WRITE_HIGH(base, pin) directWriteHigh(pin)
|
||||||
@@ -305,9 +296,7 @@ void directModeOutput(IO_REG_TYPE pin)
|
|||||||
#define IO_REG_BASE_ATTR
|
#define IO_REG_BASE_ATTR
|
||||||
#define IO_REG_MASK_ATTR
|
#define IO_REG_MASK_ATTR
|
||||||
|
|
||||||
static inline __attribute__((always_inline))
|
static inline __attribute__((always_inline)) IO_REG_TYPE directRead(volatile IO_REG_TYPE * base, IO_REG_TYPE pin) {
|
||||||
IO_REG_TYPE directRead(volatile IO_REG_TYPE *base, IO_REG_TYPE pin)
|
|
||||||
{
|
|
||||||
IO_REG_TYPE ret;
|
IO_REG_TYPE ret;
|
||||||
if (SS_GPIO == GPIO_TYPE(pin)) {
|
if (SS_GPIO == GPIO_TYPE(pin)) {
|
||||||
ret = READ_ARC_REG(((IO_REG_TYPE)base + EXT_PORT_OFFSET_SS));
|
ret = READ_ARC_REG(((IO_REG_TYPE)base + EXT_PORT_OFFSET_SS));
|
||||||
@@ -317,31 +306,23 @@ IO_REG_TYPE directRead(volatile IO_REG_TYPE *base, IO_REG_TYPE pin)
|
|||||||
return ((ret >> GPIO_ID(pin)) & 0x01);
|
return ((ret >> GPIO_ID(pin)) & 0x01);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline __attribute__((always_inline))
|
static inline __attribute__((always_inline)) void directModeInput(volatile IO_REG_TYPE * base, IO_REG_TYPE pin) {
|
||||||
void directModeInput(volatile IO_REG_TYPE *base, IO_REG_TYPE pin)
|
|
||||||
{
|
|
||||||
if (SS_GPIO == GPIO_TYPE(pin)) {
|
if (SS_GPIO == GPIO_TYPE(pin)) {
|
||||||
WRITE_ARC_REG(READ_ARC_REG((((IO_REG_TYPE)base) + DIR_OFFSET_SS)) & ~(0x01 << GPIO_ID(pin)),
|
WRITE_ARC_REG(READ_ARC_REG((((IO_REG_TYPE)base) + DIR_OFFSET_SS)) & ~(0x01 << GPIO_ID(pin)), ((IO_REG_TYPE)(base) + DIR_OFFSET_SS));
|
||||||
((IO_REG_TYPE)(base) + DIR_OFFSET_SS));
|
|
||||||
} else {
|
} else {
|
||||||
MMIO_REG_VAL_FROM_BASE((IO_REG_TYPE)base, DIR_OFFSET_SOC) &= ~(0x01 << GPIO_ID(pin));
|
MMIO_REG_VAL_FROM_BASE((IO_REG_TYPE)base, DIR_OFFSET_SOC) &= ~(0x01 << GPIO_ID(pin));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline __attribute__((always_inline))
|
static inline __attribute__((always_inline)) void directModeOutput(volatile IO_REG_TYPE * base, IO_REG_TYPE pin) {
|
||||||
void directModeOutput(volatile IO_REG_TYPE *base, IO_REG_TYPE pin)
|
|
||||||
{
|
|
||||||
if (SS_GPIO == GPIO_TYPE(pin)) {
|
if (SS_GPIO == GPIO_TYPE(pin)) {
|
||||||
WRITE_ARC_REG(READ_ARC_REG(((IO_REG_TYPE)(base) + DIR_OFFSET_SS)) | (0x01 << GPIO_ID(pin)),
|
WRITE_ARC_REG(READ_ARC_REG(((IO_REG_TYPE)(base) + DIR_OFFSET_SS)) | (0x01 << GPIO_ID(pin)), ((IO_REG_TYPE)(base) + DIR_OFFSET_SS));
|
||||||
((IO_REG_TYPE)(base) + DIR_OFFSET_SS));
|
|
||||||
} else {
|
} else {
|
||||||
MMIO_REG_VAL_FROM_BASE((IO_REG_TYPE)base, DIR_OFFSET_SOC) |= (0x01 << GPIO_ID(pin));
|
MMIO_REG_VAL_FROM_BASE((IO_REG_TYPE)base, DIR_OFFSET_SOC) |= (0x01 << GPIO_ID(pin));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline __attribute__((always_inline))
|
static inline __attribute__((always_inline)) void directWriteLow(volatile IO_REG_TYPE * base, IO_REG_TYPE pin) {
|
||||||
void directWriteLow(volatile IO_REG_TYPE *base, IO_REG_TYPE pin)
|
|
||||||
{
|
|
||||||
if (SS_GPIO == GPIO_TYPE(pin)) {
|
if (SS_GPIO == GPIO_TYPE(pin)) {
|
||||||
WRITE_ARC_REG(READ_ARC_REG(base) & ~(0x01 << GPIO_ID(pin)), base);
|
WRITE_ARC_REG(READ_ARC_REG(base) & ~(0x01 << GPIO_ID(pin)), base);
|
||||||
} else {
|
} else {
|
||||||
@@ -349,9 +330,7 @@ void directWriteLow(volatile IO_REG_TYPE *base, IO_REG_TYPE pin)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline __attribute__((always_inline))
|
static inline __attribute__((always_inline)) void directWriteHigh(volatile IO_REG_TYPE * base, IO_REG_TYPE pin) {
|
||||||
void directWriteHigh(volatile IO_REG_TYPE *base, IO_REG_TYPE pin)
|
|
||||||
{
|
|
||||||
if (SS_GPIO == GPIO_TYPE(pin)) {
|
if (SS_GPIO == GPIO_TYPE(pin)) {
|
||||||
WRITE_ARC_REG(READ_ARC_REG(base) | (0x01 << GPIO_ID(pin)), base);
|
WRITE_ARC_REG(READ_ARC_REG(base) | (0x01 << GPIO_ID(pin)), base);
|
||||||
} else {
|
} else {
|
||||||
@@ -380,15 +359,11 @@ void directWriteHigh(volatile IO_REG_TYPE *base, IO_REG_TYPE pin)
|
|||||||
#define IO_REG_BASE_ATTR
|
#define IO_REG_BASE_ATTR
|
||||||
#define IO_REG_MASK_ATTR
|
#define IO_REG_MASK_ATTR
|
||||||
|
|
||||||
static inline __attribute__((always_inline))
|
static inline __attribute__((always_inline)) IO_REG_TYPE directRead(IO_REG_TYPE mask) {
|
||||||
IO_REG_TYPE directRead(IO_REG_TYPE mask)
|
|
||||||
{
|
|
||||||
return ((GPIO_REG(GPIO_INPUT_VAL) & mask) != 0) ? 1 : 0;
|
return ((GPIO_REG(GPIO_INPUT_VAL) & mask) != 0) ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline __attribute__((always_inline))
|
static inline __attribute__((always_inline)) void directModeInput(IO_REG_TYPE mask) {
|
||||||
void directModeInput(IO_REG_TYPE mask)
|
|
||||||
{
|
|
||||||
GPIO_REG(GPIO_OUTPUT_XOR) &= ~mask;
|
GPIO_REG(GPIO_OUTPUT_XOR) &= ~mask;
|
||||||
GPIO_REG(GPIO_IOF_EN) &= ~mask;
|
GPIO_REG(GPIO_IOF_EN) &= ~mask;
|
||||||
|
|
||||||
@@ -396,9 +371,7 @@ void directModeInput(IO_REG_TYPE mask)
|
|||||||
GPIO_REG(GPIO_OUTPUT_EN) &= ~mask;
|
GPIO_REG(GPIO_OUTPUT_EN) &= ~mask;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline __attribute__((always_inline))
|
static inline __attribute__((always_inline)) void directModeOutput(IO_REG_TYPE mask) {
|
||||||
void directModeOutput(IO_REG_TYPE mask)
|
|
||||||
{
|
|
||||||
GPIO_REG(GPIO_OUTPUT_XOR) &= ~mask;
|
GPIO_REG(GPIO_OUTPUT_XOR) &= ~mask;
|
||||||
GPIO_REG(GPIO_IOF_EN) &= ~mask;
|
GPIO_REG(GPIO_IOF_EN) &= ~mask;
|
||||||
|
|
||||||
@@ -406,15 +379,11 @@ void directModeOutput(IO_REG_TYPE mask)
|
|||||||
GPIO_REG(GPIO_OUTPUT_EN) |= mask;
|
GPIO_REG(GPIO_OUTPUT_EN) |= mask;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline __attribute__((always_inline))
|
static inline __attribute__((always_inline)) void directWriteLow(IO_REG_TYPE mask) {
|
||||||
void directWriteLow(IO_REG_TYPE mask)
|
|
||||||
{
|
|
||||||
GPIO_REG(GPIO_OUTPUT_VAL) &= ~mask;
|
GPIO_REG(GPIO_OUTPUT_VAL) &= ~mask;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline __attribute__((always_inline))
|
static inline __attribute__((always_inline)) void directWriteHigh(IO_REG_TYPE mask) {
|
||||||
void directWriteHigh(IO_REG_TYPE mask)
|
|
||||||
{
|
|
||||||
GPIO_REG(GPIO_OUTPUT_VAL) |= mask;
|
GPIO_REG(GPIO_OUTPUT_VAL) |= mask;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -440,8 +409,7 @@ void directWriteHigh(IO_REG_TYPE mask)
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
class OneWire
|
class OneWire {
|
||||||
{
|
|
||||||
private:
|
private:
|
||||||
IO_REG_TYPE bitmask;
|
IO_REG_TYPE bitmask;
|
||||||
volatile IO_REG_TYPE * baseReg;
|
volatile IO_REG_TYPE * baseReg;
|
||||||
@@ -455,8 +423,11 @@ class OneWire
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
public:
|
public:
|
||||||
OneWire() { }
|
OneWire() {
|
||||||
OneWire(uint8_t pin) { begin(pin); }
|
}
|
||||||
|
OneWire(uint8_t pin) {
|
||||||
|
begin(pin);
|
||||||
|
}
|
||||||
void begin(uint8_t pin);
|
void begin(uint8_t pin);
|
||||||
// OneWire( uint8_t pin);
|
// OneWire( uint8_t pin);
|
||||||
|
|
||||||
|
|||||||
16
lib/PsychicHttp/CHANGELOG.md
Normal file
16
lib/PsychicHttp/CHANGELOG.md
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# v1.1
|
||||||
|
|
||||||
|
* Changed the internal structure to support request handlers on endpoints and generic requests that do not match an endpoint
|
||||||
|
* websockets, uploads, etc should now create an appropriate handler and attach to an endpoint with the server.on() syntax
|
||||||
|
* Added PsychicClient to abstract away some of the internals of ESP-IDF sockets + add convenience
|
||||||
|
* onOpen and onClose callbacks have changed as a result
|
||||||
|
* Added support for EventSource / SSE
|
||||||
|
* Added support for multipart file uploads
|
||||||
|
* changed getParam() to return a PsychicWebParameter in line with ESPAsyncWebserver
|
||||||
|
* Renamed various classes / files:
|
||||||
|
* PsychicHttpFileResponse -> PsychicFileResponse
|
||||||
|
* PsychicHttpServerEndpoint -> PsychicEndpoint
|
||||||
|
* PsychicHttpServerRequest -> PsychicRequest
|
||||||
|
* PsychicHttpServerResponse -> PsychicResponse
|
||||||
|
* PsychicHttpWebsocket.h -> PsychicWebSocket.h
|
||||||
|
* Websocket => WebSocket
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
GNU LESSER GENERAL PUBLIC LICENSE
|
GNU LESSER GENERAL PUBLIC LICENSE
|
||||||
Version 3, 29 June 2007
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||||
Everyone is permitted to copy and distribute verbatim copies
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
of this license document, but changing it is not allowed.
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
44
lib/PsychicHttp/library.json
Normal file
44
lib/PsychicHttp/library.json
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
{
|
||||||
|
"name": "PsychicHttp",
|
||||||
|
"version": "1.0.1",
|
||||||
|
"description": "Arduino style wrapper around ESP-IDF HTTP library. HTTP server with SSL + websockets. Works on esp32 and probably esp8266",
|
||||||
|
"keywords": "network,http,https,tcp,ssl,tls,websocket,espasyncwebserver",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/hoeken/PsychicHttp"
|
||||||
|
},
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Zach Hoeken",
|
||||||
|
"email": "hoeken@gmail.com",
|
||||||
|
"maintainer": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "LGPL-3.0-or-later",
|
||||||
|
"examples": [
|
||||||
|
{
|
||||||
|
"name": "platformio",
|
||||||
|
"base": "examples/platformio",
|
||||||
|
"files": ["src/main.cpp"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"frameworks": "arduino",
|
||||||
|
"platforms": "espressif32",
|
||||||
|
"dependencies": [
|
||||||
|
{
|
||||||
|
"owner": "bblanchon",
|
||||||
|
"name": "ArduinoJson",
|
||||||
|
"version": "^6.21.4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"owner": "bblanchon",
|
||||||
|
"name": "ArduinoTrace",
|
||||||
|
"version": "^1.2.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"owner": "plageoj",
|
||||||
|
"name": "UrlEncode",
|
||||||
|
"version": "^1.0.1"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
11
lib/PsychicHttp/library.properties
Normal file
11
lib/PsychicHttp/library.properties
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
name=PsychicHttp
|
||||||
|
version=1.0.1
|
||||||
|
author=Zach Hoeken <hoeken@gmail.com>
|
||||||
|
maintainer=Zach Hoeken <hoeken@gmail.com>
|
||||||
|
sentence=PsychicHttp is a robust webserver that supports http/https + websockets.
|
||||||
|
paragraph=This library is based on the ESP-IDF HTTP Server library which is asynchronous, does http / https+ssl and supports websockets.
|
||||||
|
category=Communication
|
||||||
|
architectures=esp32
|
||||||
|
url=https://github.com/hoeken/PsychicHttp
|
||||||
|
includes=PsychicHttp.h
|
||||||
|
depends=ArduinoJson,ArduinoTrace,UrlEncode
|
||||||
56
lib/PsychicHttp/src/ChunkPrinter.h
Normal file
56
lib/PsychicHttp/src/ChunkPrinter.h
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
#ifndef ChunkPrinter_h
|
||||||
|
#define ChunkPrinter_h
|
||||||
|
|
||||||
|
// #include "PsychicCore.h"
|
||||||
|
#include "PsychicResponse.h"
|
||||||
|
#include <Print.h>
|
||||||
|
|
||||||
|
class ChunkPrinter : public Print
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
PsychicResponse *_response;
|
||||||
|
uint8_t *_buffer;
|
||||||
|
size_t _length;
|
||||||
|
size_t _pos;
|
||||||
|
|
||||||
|
public:
|
||||||
|
ChunkPrinter(PsychicResponse *response, uint8_t *buffer, size_t len) :
|
||||||
|
_response(response),
|
||||||
|
_buffer(buffer),
|
||||||
|
_length(len),
|
||||||
|
_pos(0)
|
||||||
|
{}
|
||||||
|
|
||||||
|
virtual ~ChunkPrinter() {}
|
||||||
|
|
||||||
|
size_t write(uint8_t c)
|
||||||
|
{
|
||||||
|
esp_err_t err;
|
||||||
|
|
||||||
|
_buffer[_pos] = c;
|
||||||
|
_pos++;
|
||||||
|
|
||||||
|
//if we're full, send a chunk
|
||||||
|
if (_pos == _length)
|
||||||
|
{
|
||||||
|
_pos = 0;
|
||||||
|
|
||||||
|
err = _response->sendChunk(_buffer, _length);
|
||||||
|
if (err != ESP_OK)
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void flush() override
|
||||||
|
{
|
||||||
|
if (_pos)
|
||||||
|
{
|
||||||
|
_response->sendChunk(_buffer, _pos);
|
||||||
|
_pos = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
72
lib/PsychicHttp/src/PsychicClient.cpp
Normal file
72
lib/PsychicHttp/src/PsychicClient.cpp
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
#include "PsychicClient.h"
|
||||||
|
#include "PsychicHttpServer.h"
|
||||||
|
#include <lwip/sockets.h>
|
||||||
|
|
||||||
|
PsychicClient::PsychicClient(httpd_handle_t server, int socket) :
|
||||||
|
_server(server),
|
||||||
|
_socket(socket),
|
||||||
|
_friend(NULL),
|
||||||
|
isNew(false)
|
||||||
|
{}
|
||||||
|
|
||||||
|
PsychicClient::~PsychicClient() {
|
||||||
|
}
|
||||||
|
|
||||||
|
httpd_handle_t PsychicClient::server() {
|
||||||
|
return _server;
|
||||||
|
}
|
||||||
|
|
||||||
|
int PsychicClient::socket() {
|
||||||
|
return _socket;
|
||||||
|
}
|
||||||
|
|
||||||
|
// I'm not sure this is entirely safe to call. I was having issues with race conditions when highly loaded using this.
|
||||||
|
esp_err_t PsychicClient::close()
|
||||||
|
{
|
||||||
|
esp_err_t err = httpd_sess_trigger_close(_server, _socket);
|
||||||
|
//PsychicHttpServer::closeCallback(_server, _socket); // call this immediately so the client is taken off the list.
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
IPAddress PsychicClient::localIP()
|
||||||
|
{
|
||||||
|
IPAddress address(0,0,0,0);
|
||||||
|
|
||||||
|
char ipstr[INET6_ADDRSTRLEN];
|
||||||
|
struct sockaddr_in6 addr; // esp_http_server uses IPv6 addressing
|
||||||
|
socklen_t addr_size = sizeof(addr);
|
||||||
|
|
||||||
|
if (getsockname(_socket, (struct sockaddr *)&addr, &addr_size) < 0) {
|
||||||
|
ESP_LOGE(PH_TAG, "Error getting client IP");
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to IPv4 string
|
||||||
|
inet_ntop(AF_INET, &addr.sin6_addr.un.u32_addr[3], ipstr, sizeof(ipstr));
|
||||||
|
ESP_LOGI(PH_TAG, "Client Local IP => %s", ipstr);
|
||||||
|
address.fromString(ipstr);
|
||||||
|
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
|
||||||
|
IPAddress PsychicClient::remoteIP()
|
||||||
|
{
|
||||||
|
IPAddress address(0,0,0,0);
|
||||||
|
|
||||||
|
char ipstr[INET6_ADDRSTRLEN];
|
||||||
|
struct sockaddr_in6 addr; // esp_http_server uses IPv6 addressing
|
||||||
|
socklen_t addr_size = sizeof(addr);
|
||||||
|
|
||||||
|
if (getpeername(_socket, (struct sockaddr *)&addr, &addr_size) < 0) {
|
||||||
|
ESP_LOGE(PH_TAG, "Error getting client IP");
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to IPv4 string
|
||||||
|
inet_ntop(AF_INET, &addr.sin6_addr.un.u32_addr[3], ipstr, sizeof(ipstr));
|
||||||
|
ESP_LOGI(PH_TAG, "Client Remote IP => %s", ipstr);
|
||||||
|
address.fromString(ipstr);
|
||||||
|
|
||||||
|
return address;
|
||||||
|
}
|
||||||
35
lib/PsychicHttp/src/PsychicClient.h
Normal file
35
lib/PsychicHttp/src/PsychicClient.h
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
#ifndef PsychicClient_h
|
||||||
|
#define PsychicClient_h
|
||||||
|
|
||||||
|
#include "PsychicCore.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* PsychicClient :: Generic wrapper around the ESP-IDF socket
|
||||||
|
*/
|
||||||
|
|
||||||
|
class PsychicClient {
|
||||||
|
protected:
|
||||||
|
httpd_handle_t _server;
|
||||||
|
int _socket;
|
||||||
|
|
||||||
|
public:
|
||||||
|
PsychicClient(httpd_handle_t server, int socket);
|
||||||
|
~PsychicClient();
|
||||||
|
|
||||||
|
//no idea if this is the right way to do it or not, but lets see.
|
||||||
|
//pointer to our derived class (eg. PsychicWebSocketConnection)
|
||||||
|
void *_friend;
|
||||||
|
|
||||||
|
bool isNew = false;
|
||||||
|
|
||||||
|
bool operator==(PsychicClient& rhs) const { return _socket == rhs.socket(); }
|
||||||
|
|
||||||
|
httpd_handle_t server();
|
||||||
|
int socket();
|
||||||
|
esp_err_t close();
|
||||||
|
|
||||||
|
IPAddress localIP();
|
||||||
|
IPAddress remoteIP();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
115
lib/PsychicHttp/src/PsychicCore.h
Normal file
115
lib/PsychicHttp/src/PsychicCore.h
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
#ifndef PsychicCore_h
|
||||||
|
#define PsychicCore_h
|
||||||
|
|
||||||
|
#define PH_TAG "psychic"
|
||||||
|
|
||||||
|
// version numbers
|
||||||
|
#define PSYCHIC_HTTP_VERSION_MAJOR 1
|
||||||
|
#define PSYCHIC_HTTP_VERSION_MINOR 1
|
||||||
|
#define PSYCHIC_HTTP_VERSION_PATCH 0
|
||||||
|
|
||||||
|
#ifndef MAX_COOKIE_SIZE
|
||||||
|
#define MAX_COOKIE_SIZE 512
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef FILE_CHUNK_SIZE
|
||||||
|
#define FILE_CHUNK_SIZE 8 * 1024
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef STREAM_CHUNK_SIZE
|
||||||
|
#define STREAM_CHUNK_SIZE 1024
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef MAX_UPLOAD_SIZE
|
||||||
|
#define MAX_UPLOAD_SIZE (2048 * 1024) // 2MB
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef MAX_REQUEST_BODY_SIZE
|
||||||
|
#define MAX_REQUEST_BODY_SIZE (16 * 1024) // 16K
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef ARDUINO
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <ArduinoTrace.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <esp_http_server.h>
|
||||||
|
#include <map>
|
||||||
|
#include <list>
|
||||||
|
#include <libb64/cencode.h>
|
||||||
|
#include "esp_random.h"
|
||||||
|
#include "MD5Builder.h"
|
||||||
|
#include <UrlEncode.h>
|
||||||
|
#include "FS.h"
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
|
||||||
|
enum HTTPAuthMethod
|
||||||
|
{
|
||||||
|
BASIC_AUTH,
|
||||||
|
DIGEST_AUTH
|
||||||
|
};
|
||||||
|
|
||||||
|
String urlDecode(const char *encoded);
|
||||||
|
|
||||||
|
class PsychicHttpServer;
|
||||||
|
class PsychicRequest;
|
||||||
|
class PsychicWebSocketRequest;
|
||||||
|
class PsychicClient;
|
||||||
|
|
||||||
|
// filter function definition
|
||||||
|
typedef std::function<bool(PsychicRequest *request)> PsychicRequestFilterFunction;
|
||||||
|
|
||||||
|
// client connect callback
|
||||||
|
typedef std::function<void(PsychicClient *client)> PsychicClientCallback;
|
||||||
|
|
||||||
|
// callback definitions
|
||||||
|
typedef std::function<esp_err_t(PsychicRequest *request)> PsychicHttpRequestCallback;
|
||||||
|
typedef std::function<esp_err_t(PsychicRequest *request, JsonVariant &json)> PsychicJsonRequestCallback;
|
||||||
|
|
||||||
|
struct HTTPHeader
|
||||||
|
{
|
||||||
|
char *field;
|
||||||
|
char *value;
|
||||||
|
};
|
||||||
|
|
||||||
|
class DefaultHeaders
|
||||||
|
{
|
||||||
|
std::list<HTTPHeader> _headers;
|
||||||
|
|
||||||
|
public:
|
||||||
|
DefaultHeaders() {}
|
||||||
|
|
||||||
|
void addHeader(const String &field, const String &value)
|
||||||
|
{
|
||||||
|
addHeader(field.c_str(), value.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
void addHeader(const char *field, const char *value)
|
||||||
|
{
|
||||||
|
HTTPHeader header;
|
||||||
|
|
||||||
|
// these are just going to stick around forever.
|
||||||
|
header.field = (char *)malloc(strlen(field) + 1);
|
||||||
|
header.value = (char *)malloc(strlen(value) + 1);
|
||||||
|
|
||||||
|
strlcpy(header.field, field, strlen(field) + 1);
|
||||||
|
strlcpy(header.value, value, strlen(value) + 1);
|
||||||
|
|
||||||
|
_headers.push_back(header);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::list<HTTPHeader> &getHeaders() { return _headers; }
|
||||||
|
|
||||||
|
// delete the copy constructor, singleton class
|
||||||
|
DefaultHeaders(DefaultHeaders const &) = delete;
|
||||||
|
DefaultHeaders &operator=(DefaultHeaders const &) = delete;
|
||||||
|
|
||||||
|
// single static class interface
|
||||||
|
static DefaultHeaders &Instance()
|
||||||
|
{
|
||||||
|
static DefaultHeaders instance;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // PsychicCore_h
|
||||||
90
lib/PsychicHttp/src/PsychicEndpoint.cpp
Normal file
90
lib/PsychicHttp/src/PsychicEndpoint.cpp
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
#include "PsychicEndpoint.h"
|
||||||
|
#include "PsychicHttpServer.h"
|
||||||
|
|
||||||
|
PsychicEndpoint::PsychicEndpoint() :
|
||||||
|
_server(NULL),
|
||||||
|
_uri(""),
|
||||||
|
_method(HTTP_GET),
|
||||||
|
_handler(NULL)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
PsychicEndpoint::PsychicEndpoint(PsychicHttpServer *server, http_method method, const char * uri) :
|
||||||
|
_server(server),
|
||||||
|
_uri(uri),
|
||||||
|
_method(method),
|
||||||
|
_handler(NULL)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
PsychicEndpoint * PsychicEndpoint::setHandler(PsychicHandler *handler)
|
||||||
|
{
|
||||||
|
//clean up old / default handler
|
||||||
|
if (_handler != NULL)
|
||||||
|
delete _handler;
|
||||||
|
|
||||||
|
//get our new pointer
|
||||||
|
_handler = handler;
|
||||||
|
|
||||||
|
//keep a pointer to the server
|
||||||
|
_handler->_server = _server;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
PsychicHandler * PsychicEndpoint::handler()
|
||||||
|
{
|
||||||
|
return _handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
String PsychicEndpoint::uri() {
|
||||||
|
return _uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t PsychicEndpoint::requestCallback(httpd_req_t *req)
|
||||||
|
{
|
||||||
|
#ifdef ENABLE_ASYNC
|
||||||
|
if (is_on_async_worker_thread() == false) {
|
||||||
|
if (submit_async_req(req, PsychicEndpoint::requestCallback) == ESP_OK) {
|
||||||
|
return ESP_OK;
|
||||||
|
} else {
|
||||||
|
httpd_resp_set_status(req, "503 Busy");
|
||||||
|
httpd_resp_sendstr(req, "No workers available. Server busy.</div>");
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
PsychicEndpoint *self = (PsychicEndpoint *)req->user_ctx;
|
||||||
|
PsychicHandler *handler = self->handler();
|
||||||
|
PsychicRequest request(self->_server, req);
|
||||||
|
|
||||||
|
//make sure we have a handler
|
||||||
|
if (handler != NULL)
|
||||||
|
{
|
||||||
|
if (handler->filter(&request) && handler->canHandle(&request))
|
||||||
|
{
|
||||||
|
//check our credentials
|
||||||
|
if (handler->needsAuthentication(&request))
|
||||||
|
return handler->authenticate(&request);
|
||||||
|
|
||||||
|
//pass it to our handler
|
||||||
|
return handler->handleRequest(&request);
|
||||||
|
}
|
||||||
|
//pass it to our generic handlers
|
||||||
|
else
|
||||||
|
return PsychicHttpServer::notFoundHandler(req, HTTPD_500_INTERNAL_SERVER_ERROR);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return request.reply(500, "text/html", "No handler registered.");
|
||||||
|
}
|
||||||
|
|
||||||
|
PsychicEndpoint* PsychicEndpoint::setFilter(PsychicRequestFilterFunction fn) {
|
||||||
|
_handler->setFilter(fn);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
PsychicEndpoint* PsychicEndpoint::setAuthentication(const char *username, const char *password, HTTPAuthMethod method, const char *realm, const char *authFailMsg) {
|
||||||
|
_handler->setAuthentication(username, password, method, realm, authFailMsg);
|
||||||
|
return this;
|
||||||
|
};
|
||||||
37
lib/PsychicHttp/src/PsychicEndpoint.h
Normal file
37
lib/PsychicHttp/src/PsychicEndpoint.h
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
#ifndef PsychicEndpoint_h
|
||||||
|
#define PsychicEndpoint_h
|
||||||
|
|
||||||
|
#include "PsychicCore.h"
|
||||||
|
|
||||||
|
class PsychicHandler;
|
||||||
|
|
||||||
|
#ifdef ENABLE_ASYNC
|
||||||
|
#include "async_worker.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class PsychicEndpoint
|
||||||
|
{
|
||||||
|
friend PsychicHttpServer;
|
||||||
|
|
||||||
|
private:
|
||||||
|
PsychicHttpServer *_server;
|
||||||
|
String _uri;
|
||||||
|
http_method _method;
|
||||||
|
PsychicHandler *_handler;
|
||||||
|
|
||||||
|
public:
|
||||||
|
PsychicEndpoint();
|
||||||
|
PsychicEndpoint(PsychicHttpServer *server, http_method method, const char * uri);
|
||||||
|
|
||||||
|
PsychicEndpoint *setHandler(PsychicHandler *handler);
|
||||||
|
PsychicHandler *handler();
|
||||||
|
|
||||||
|
PsychicEndpoint* setFilter(PsychicRequestFilterFunction fn);
|
||||||
|
PsychicEndpoint* setAuthentication(const char *username, const char *password, HTTPAuthMethod method = BASIC_AUTH, const char *realm = "", const char *authFailMsg = "");
|
||||||
|
|
||||||
|
String uri();
|
||||||
|
|
||||||
|
static esp_err_t requestCallback(httpd_req_t *req);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // PsychicEndpoint_h
|
||||||
227
lib/PsychicHttp/src/PsychicEventSource.cpp
Normal file
227
lib/PsychicHttp/src/PsychicEventSource.cpp
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
/*
|
||||||
|
Asynchronous WebServer library for Espressif MCUs
|
||||||
|
|
||||||
|
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with this library; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "PsychicEventSource.h"
|
||||||
|
|
||||||
|
/*****************************************/
|
||||||
|
// PsychicEventSource - Handler
|
||||||
|
/*****************************************/
|
||||||
|
|
||||||
|
PsychicEventSource::PsychicEventSource() :
|
||||||
|
PsychicHandler(),
|
||||||
|
_onOpen(NULL),
|
||||||
|
_onClose(NULL)
|
||||||
|
{}
|
||||||
|
|
||||||
|
PsychicEventSource::~PsychicEventSource() {
|
||||||
|
}
|
||||||
|
|
||||||
|
PsychicEventSourceClient * PsychicEventSource::getClient(int socket)
|
||||||
|
{
|
||||||
|
PsychicClient *client = PsychicHandler::getClient(socket);
|
||||||
|
|
||||||
|
if (client == NULL)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
return (PsychicEventSourceClient *)client->_friend;
|
||||||
|
}
|
||||||
|
|
||||||
|
PsychicEventSourceClient * PsychicEventSource::getClient(PsychicClient *client) {
|
||||||
|
return getClient(client->socket());
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t PsychicEventSource::handleRequest(PsychicRequest *request)
|
||||||
|
{
|
||||||
|
//start our open ended HTTP response
|
||||||
|
PsychicEventSourceResponse response(request);
|
||||||
|
esp_err_t err = response.send();
|
||||||
|
|
||||||
|
//lookup our client
|
||||||
|
PsychicClient *client = checkForNewClient(request->client());
|
||||||
|
if (client->isNew)
|
||||||
|
{
|
||||||
|
//did we get our last id?
|
||||||
|
if(request->hasHeader("Last-Event-ID"))
|
||||||
|
{
|
||||||
|
PsychicEventSourceClient *buddy = getClient(client);
|
||||||
|
buddy->_lastId = atoi(request->header("Last-Event-ID").c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
//let our handler know.
|
||||||
|
openCallback(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
PsychicEventSource * PsychicEventSource::onOpen(PsychicEventSourceClientCallback fn) {
|
||||||
|
_onOpen = fn;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
PsychicEventSource * PsychicEventSource::onClose(PsychicEventSourceClientCallback fn) {
|
||||||
|
_onClose = fn;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PsychicEventSource::addClient(PsychicClient *client) {
|
||||||
|
client->_friend = new PsychicEventSourceClient(client);
|
||||||
|
PsychicHandler::addClient(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PsychicEventSource::removeClient(PsychicClient *client) {
|
||||||
|
PsychicHandler::removeClient(client);
|
||||||
|
delete (PsychicEventSourceClient*)client->_friend;
|
||||||
|
client->_friend = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PsychicEventSource::openCallback(PsychicClient *client) {
|
||||||
|
PsychicEventSourceClient *buddy = getClient(client);
|
||||||
|
if (buddy == NULL)
|
||||||
|
{
|
||||||
|
TRACE();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_onOpen != NULL)
|
||||||
|
_onOpen(buddy);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PsychicEventSource::closeCallback(PsychicClient *client) {
|
||||||
|
PsychicEventSourceClient *buddy = getClient(client);
|
||||||
|
if (buddy == NULL)
|
||||||
|
{
|
||||||
|
TRACE();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_onClose != NULL)
|
||||||
|
_onClose(getClient(buddy));
|
||||||
|
}
|
||||||
|
|
||||||
|
void PsychicEventSource::send(const char *message, const char *event, uint32_t id, uint32_t reconnect)
|
||||||
|
{
|
||||||
|
String ev = generateEventMessage(message, event, id, reconnect);
|
||||||
|
for(PsychicClient *c : _clients) {
|
||||||
|
((PsychicEventSourceClient*)c->_friend)->sendEvent(ev.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*****************************************/
|
||||||
|
// PsychicEventSourceClient
|
||||||
|
/*****************************************/
|
||||||
|
|
||||||
|
PsychicEventSourceClient::PsychicEventSourceClient(PsychicClient *client) :
|
||||||
|
PsychicClient(client->server(), client->socket()),
|
||||||
|
_lastId(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
PsychicEventSourceClient::~PsychicEventSourceClient(){
|
||||||
|
}
|
||||||
|
|
||||||
|
void PsychicEventSourceClient::send(const char *message, const char *event, uint32_t id, uint32_t reconnect){
|
||||||
|
String ev = generateEventMessage(message, event, id, reconnect);
|
||||||
|
sendEvent(ev.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
void PsychicEventSourceClient::sendEvent(const char *event) {
|
||||||
|
int result;
|
||||||
|
do {
|
||||||
|
result = httpd_socket_send(this->server(), this->socket(), event, strlen(event), 0);
|
||||||
|
} while (result == HTTPD_SOCK_ERR_TIMEOUT);
|
||||||
|
|
||||||
|
//if (result < 0)
|
||||||
|
//error log here
|
||||||
|
}
|
||||||
|
|
||||||
|
/*****************************************/
|
||||||
|
// PsychicEventSourceResponse
|
||||||
|
/*****************************************/
|
||||||
|
|
||||||
|
PsychicEventSourceResponse::PsychicEventSourceResponse(PsychicRequest *request)
|
||||||
|
: PsychicResponse(request)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t PsychicEventSourceResponse::send() {
|
||||||
|
|
||||||
|
//build our main header
|
||||||
|
String out = String();
|
||||||
|
out.concat("HTTP/1.1 200 OK\r\n");
|
||||||
|
out.concat("Content-Type: text/event-stream\r\n");
|
||||||
|
out.concat("Cache-Control: no-cache\r\n");
|
||||||
|
out.concat("Connection: keep-alive\r\n");
|
||||||
|
|
||||||
|
//get our global headers out of the way first
|
||||||
|
for (HTTPHeader header : DefaultHeaders::Instance().getHeaders())
|
||||||
|
out.concat(String(header.field) + ": " + String(header.value) + "\r\n");
|
||||||
|
|
||||||
|
//separator
|
||||||
|
out.concat("\r\n");
|
||||||
|
|
||||||
|
int result;
|
||||||
|
do {
|
||||||
|
result = httpd_send(_request->request(), out.c_str(), out.length());
|
||||||
|
} while (result == HTTPD_SOCK_ERR_TIMEOUT);
|
||||||
|
|
||||||
|
if (result < 0)
|
||||||
|
ESP_LOGE(PH_TAG, "EventSource send failed with %s", esp_err_to_name(result));
|
||||||
|
|
||||||
|
if (result > 0)
|
||||||
|
return ESP_OK;
|
||||||
|
else
|
||||||
|
return ESP_ERR_HTTPD_RESP_SEND;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*****************************************/
|
||||||
|
// Event Message Generator
|
||||||
|
/*****************************************/
|
||||||
|
|
||||||
|
String generateEventMessage(const char *message, const char *event, uint32_t id, uint32_t reconnect) {
|
||||||
|
String ev = "";
|
||||||
|
|
||||||
|
if(reconnect){
|
||||||
|
ev += "retry: ";
|
||||||
|
ev += String(reconnect);
|
||||||
|
ev += "\r\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
if(id){
|
||||||
|
ev += "id: ";
|
||||||
|
ev += String(id);
|
||||||
|
ev += "\r\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
if(event != NULL){
|
||||||
|
ev += "event: ";
|
||||||
|
ev += String(event);
|
||||||
|
ev += "\r\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
if(message != NULL){
|
||||||
|
ev += "data: ";
|
||||||
|
ev += String(message);
|
||||||
|
ev += "\r\n";
|
||||||
|
}
|
||||||
|
ev += "\r\n";
|
||||||
|
|
||||||
|
return ev;
|
||||||
|
}
|
||||||
82
lib/PsychicHttp/src/PsychicEventSource.h
Normal file
82
lib/PsychicHttp/src/PsychicEventSource.h
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
/*
|
||||||
|
Asynchronous WebServer library for Espressif MCUs
|
||||||
|
|
||||||
|
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with this library; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
#ifndef PsychicEventSource_H_
|
||||||
|
#define PsychicEventSource_H_
|
||||||
|
|
||||||
|
#include "PsychicCore.h"
|
||||||
|
#include "PsychicHandler.h"
|
||||||
|
#include "PsychicClient.h"
|
||||||
|
#include "PsychicResponse.h"
|
||||||
|
|
||||||
|
class PsychicEventSource;
|
||||||
|
class PsychicEventSourceResponse;
|
||||||
|
class PsychicEventSourceClient;
|
||||||
|
class PsychicResponse;
|
||||||
|
|
||||||
|
typedef std::function<void(PsychicEventSourceClient *client)> PsychicEventSourceClientCallback;
|
||||||
|
|
||||||
|
class PsychicEventSourceClient : public PsychicClient {
|
||||||
|
friend PsychicEventSource;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
uint32_t _lastId;
|
||||||
|
|
||||||
|
public:
|
||||||
|
PsychicEventSourceClient(PsychicClient *client);
|
||||||
|
~PsychicEventSourceClient();
|
||||||
|
|
||||||
|
uint32_t lastId() const { return _lastId; }
|
||||||
|
void send(const char *message, const char *event=NULL, uint32_t id=0, uint32_t reconnect=0);
|
||||||
|
void sendEvent(const char *event);
|
||||||
|
};
|
||||||
|
|
||||||
|
class PsychicEventSource : public PsychicHandler {
|
||||||
|
private:
|
||||||
|
PsychicEventSourceClientCallback _onOpen;
|
||||||
|
PsychicEventSourceClientCallback _onClose;
|
||||||
|
|
||||||
|
public:
|
||||||
|
PsychicEventSource();
|
||||||
|
~PsychicEventSource();
|
||||||
|
|
||||||
|
PsychicEventSourceClient * getClient(int socket) override;
|
||||||
|
PsychicEventSourceClient * getClient(PsychicClient *client) override;
|
||||||
|
void addClient(PsychicClient *client) override;
|
||||||
|
void removeClient(PsychicClient *client) override;
|
||||||
|
void openCallback(PsychicClient *client) override;
|
||||||
|
void closeCallback(PsychicClient *client) override;
|
||||||
|
|
||||||
|
PsychicEventSource *onOpen(PsychicEventSourceClientCallback fn);
|
||||||
|
PsychicEventSource *onClose(PsychicEventSourceClientCallback fn);
|
||||||
|
|
||||||
|
esp_err_t handleRequest(PsychicRequest *request) override final;
|
||||||
|
|
||||||
|
void send(const char *message, const char *event=NULL, uint32_t id=0, uint32_t reconnect=0);
|
||||||
|
};
|
||||||
|
|
||||||
|
class PsychicEventSourceResponse: public PsychicResponse {
|
||||||
|
public:
|
||||||
|
PsychicEventSourceResponse(PsychicRequest *request);
|
||||||
|
virtual esp_err_t send() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
String generateEventMessage(const char *message, const char *event, uint32_t id, uint32_t reconnect);
|
||||||
|
|
||||||
|
#endif /* PsychicEventSource_H_ */
|
||||||
158
lib/PsychicHttp/src/PsychicFileResponse.cpp
Normal file
158
lib/PsychicHttp/src/PsychicFileResponse.cpp
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
#include "PsychicFileResponse.h"
|
||||||
|
#include "PsychicResponse.h"
|
||||||
|
#include "PsychicRequest.h"
|
||||||
|
|
||||||
|
|
||||||
|
PsychicFileResponse::PsychicFileResponse(PsychicRequest *request, FS &fs, const String& path, const String& contentType, bool download)
|
||||||
|
: PsychicResponse(request) {
|
||||||
|
//_code = 200;
|
||||||
|
_path = path;
|
||||||
|
|
||||||
|
if(!download && !fs.exists(_path) && fs.exists(_path+".gz")){
|
||||||
|
_path = _path+".gz";
|
||||||
|
addHeader("Content-Encoding", "gzip");
|
||||||
|
_sendContentLength = true;
|
||||||
|
_chunked = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_content = fs.open(_path, "r");
|
||||||
|
_contentLength = _content.size();
|
||||||
|
|
||||||
|
if(contentType == "")
|
||||||
|
_setContentType(path);
|
||||||
|
else
|
||||||
|
_contentType = contentType;
|
||||||
|
setContentType(_contentType.c_str());
|
||||||
|
|
||||||
|
int filenameStart = path.lastIndexOf('/') + 1;
|
||||||
|
char buf[26+path.length()-filenameStart];
|
||||||
|
char* filename = (char*)path.c_str() + filenameStart;
|
||||||
|
|
||||||
|
if(download) {
|
||||||
|
// set filename and force download
|
||||||
|
snprintf(buf, sizeof (buf), "attachment; filename=\"%s\"", filename);
|
||||||
|
} else {
|
||||||
|
// set filename and force rendering
|
||||||
|
snprintf(buf, sizeof (buf), "inline; filename=\"%s\"", filename);
|
||||||
|
}
|
||||||
|
addHeader("Content-Disposition", buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
PsychicFileResponse::PsychicFileResponse(PsychicRequest *request, File content, const String& path, const String& contentType, bool download)
|
||||||
|
: PsychicResponse(request) {
|
||||||
|
_path = path;
|
||||||
|
|
||||||
|
if(!download && String(content.name()).endsWith(".gz") && !path.endsWith(".gz")){
|
||||||
|
addHeader("Content-Encoding", "gzip");
|
||||||
|
_sendContentLength = true;
|
||||||
|
_chunked = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_content = content;
|
||||||
|
_contentLength = _content.size();
|
||||||
|
setContentLength(_contentLength);
|
||||||
|
|
||||||
|
if(contentType == "")
|
||||||
|
_setContentType(path);
|
||||||
|
else
|
||||||
|
_contentType = contentType;
|
||||||
|
setContentType(_contentType.c_str());
|
||||||
|
|
||||||
|
int filenameStart = path.lastIndexOf('/') + 1;
|
||||||
|
char buf[26+path.length()-filenameStart];
|
||||||
|
char* filename = (char*)path.c_str() + filenameStart;
|
||||||
|
|
||||||
|
if(download) {
|
||||||
|
snprintf(buf, sizeof (buf), "attachment; filename=\"%s\"", filename);
|
||||||
|
} else {
|
||||||
|
snprintf(buf, sizeof (buf), "inline; filename=\"%s\"", filename);
|
||||||
|
}
|
||||||
|
addHeader("Content-Disposition", buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
PsychicFileResponse::~PsychicFileResponse()
|
||||||
|
{
|
||||||
|
if(_content)
|
||||||
|
_content.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PsychicFileResponse::_setContentType(const String& path){
|
||||||
|
if (path.endsWith(".html")) _contentType = "text/html";
|
||||||
|
else if (path.endsWith(".htm")) _contentType = "text/html";
|
||||||
|
else if (path.endsWith(".css")) _contentType = "text/css";
|
||||||
|
else if (path.endsWith(".json")) _contentType = "application/json";
|
||||||
|
else if (path.endsWith(".js")) _contentType = "application/javascript";
|
||||||
|
else if (path.endsWith(".png")) _contentType = "image/png";
|
||||||
|
else if (path.endsWith(".gif")) _contentType = "image/gif";
|
||||||
|
else if (path.endsWith(".jpg")) _contentType = "image/jpeg";
|
||||||
|
else if (path.endsWith(".ico")) _contentType = "image/x-icon";
|
||||||
|
else if (path.endsWith(".svg")) _contentType = "image/svg+xml";
|
||||||
|
else if (path.endsWith(".eot")) _contentType = "font/eot";
|
||||||
|
else if (path.endsWith(".woff")) _contentType = "font/woff";
|
||||||
|
else if (path.endsWith(".woff2")) _contentType = "font/woff2";
|
||||||
|
else if (path.endsWith(".ttf")) _contentType = "font/ttf";
|
||||||
|
else if (path.endsWith(".xml")) _contentType = "text/xml";
|
||||||
|
else if (path.endsWith(".pdf")) _contentType = "application/pdf";
|
||||||
|
else if (path.endsWith(".zip")) _contentType = "application/zip";
|
||||||
|
else if(path.endsWith(".gz")) _contentType = "application/x-gzip";
|
||||||
|
else _contentType = "text/plain";
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t PsychicFileResponse::send()
|
||||||
|
{
|
||||||
|
esp_err_t err = ESP_OK;
|
||||||
|
|
||||||
|
//just send small files directly
|
||||||
|
size_t size = getContentLength();
|
||||||
|
if (size < FILE_CHUNK_SIZE)
|
||||||
|
{
|
||||||
|
uint8_t *buffer = (uint8_t *)malloc(size);
|
||||||
|
int readSize = _content.readBytes((char *)buffer, size);
|
||||||
|
|
||||||
|
this->setContent(buffer, size);
|
||||||
|
err = PsychicResponse::send();
|
||||||
|
|
||||||
|
free(buffer);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* Retrieve the pointer to scratch buffer for temporary storage */
|
||||||
|
char *chunk = (char *)malloc(FILE_CHUNK_SIZE);
|
||||||
|
if (chunk == NULL)
|
||||||
|
{
|
||||||
|
/* Respond with 500 Internal Server Error */
|
||||||
|
httpd_resp_send_err(this->_request->request(), HTTPD_500_INTERNAL_SERVER_ERROR, "Unable to allocate memory.");
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->sendHeaders();
|
||||||
|
|
||||||
|
size_t chunksize;
|
||||||
|
do {
|
||||||
|
/* Read file in chunks into the scratch buffer */
|
||||||
|
chunksize = _content.readBytes(chunk, FILE_CHUNK_SIZE);
|
||||||
|
if (chunksize > 0)
|
||||||
|
{
|
||||||
|
err = this->sendChunk((uint8_t *)chunk, chunksize);
|
||||||
|
if (err != ESP_OK)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Keep looping till the whole file is sent */
|
||||||
|
} while (chunksize != 0);
|
||||||
|
|
||||||
|
//keep track of our memory
|
||||||
|
free(chunk);
|
||||||
|
|
||||||
|
if (err == ESP_OK)
|
||||||
|
{
|
||||||
|
ESP_LOGI(PH_TAG, "File sending complete");
|
||||||
|
this->finishChunking();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Close file after sending complete */
|
||||||
|
_content.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
27
lib/PsychicHttp/src/PsychicFileResponse.h
Normal file
27
lib/PsychicHttp/src/PsychicFileResponse.h
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
#ifndef PsychicFileResponse_h
|
||||||
|
#define PsychicFileResponse_h
|
||||||
|
|
||||||
|
#include "PsychicCore.h"
|
||||||
|
#include "PsychicResponse.h"
|
||||||
|
|
||||||
|
class PsychicRequest;
|
||||||
|
|
||||||
|
class PsychicFileResponse: public PsychicResponse
|
||||||
|
{
|
||||||
|
using File = fs::File;
|
||||||
|
using FS = fs::FS;
|
||||||
|
private:
|
||||||
|
File _content;
|
||||||
|
String _path;
|
||||||
|
bool _sendContentLength;
|
||||||
|
bool _chunked;
|
||||||
|
String _contentType;
|
||||||
|
void _setContentType(const String& path);
|
||||||
|
public:
|
||||||
|
PsychicFileResponse(PsychicRequest *request, FS &fs, const String& path, const String& contentType=String(), bool download=false);
|
||||||
|
PsychicFileResponse(PsychicRequest *request, File content, const String& path, const String& contentType=String(), bool download=false);
|
||||||
|
~PsychicFileResponse();
|
||||||
|
esp_err_t send();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // PsychicFileResponse_h
|
||||||
103
lib/PsychicHttp/src/PsychicHandler.cpp
Normal file
103
lib/PsychicHttp/src/PsychicHandler.cpp
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
#include "PsychicHandler.h"
|
||||||
|
|
||||||
|
PsychicHandler::PsychicHandler() :
|
||||||
|
_filter(NULL),
|
||||||
|
_server(NULL),
|
||||||
|
_username(""),
|
||||||
|
_password(""),
|
||||||
|
_method(DIGEST_AUTH),
|
||||||
|
_realm(""),
|
||||||
|
_authFailMsg("")
|
||||||
|
{}
|
||||||
|
|
||||||
|
PsychicHandler::~PsychicHandler() {
|
||||||
|
// actual PsychicClient deletion handled by PsychicServer
|
||||||
|
// for (PsychicClient *client : _clients)
|
||||||
|
// delete(client);
|
||||||
|
_clients.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
PsychicHandler* PsychicHandler::setFilter(PsychicRequestFilterFunction fn) {
|
||||||
|
_filter = fn;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PsychicHandler::filter(PsychicRequest *request){
|
||||||
|
return _filter == NULL || _filter(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
PsychicHandler* PsychicHandler::setAuthentication(const char *username, const char *password, HTTPAuthMethod method, const char *realm, const char *authFailMsg) {
|
||||||
|
_username = String(username);
|
||||||
|
_password = String(password);
|
||||||
|
_method = method;
|
||||||
|
_realm = String(realm);
|
||||||
|
_authFailMsg = String(authFailMsg);
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool PsychicHandler::needsAuthentication(PsychicRequest *request) {
|
||||||
|
return (_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t PsychicHandler::authenticate(PsychicRequest *request) {
|
||||||
|
return request->requestAuthentication(_method, _realm.c_str(), _authFailMsg.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
PsychicClient * PsychicHandler::checkForNewClient(PsychicClient *client)
|
||||||
|
{
|
||||||
|
PsychicClient *c = PsychicHandler::getClient(client);
|
||||||
|
if (c == NULL)
|
||||||
|
{
|
||||||
|
c = client;
|
||||||
|
addClient(c);
|
||||||
|
c->isNew = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
c->isNew = false;
|
||||||
|
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PsychicHandler::checkForClosedClient(PsychicClient *client)
|
||||||
|
{
|
||||||
|
if (hasClient(client))
|
||||||
|
{
|
||||||
|
closeCallback(client);
|
||||||
|
removeClient(client);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PsychicHandler::addClient(PsychicClient *client) {
|
||||||
|
_clients.push_back(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PsychicHandler::removeClient(PsychicClient *client) {
|
||||||
|
_clients.remove(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
PsychicClient * PsychicHandler::getClient(int socket)
|
||||||
|
{
|
||||||
|
//make sure the server has it too.
|
||||||
|
if (!_server->hasClient(socket))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
//what about us?
|
||||||
|
for (PsychicClient *client : _clients)
|
||||||
|
if (client->socket() == socket)
|
||||||
|
return client;
|
||||||
|
|
||||||
|
//nothing found.
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
PsychicClient * PsychicHandler::getClient(PsychicClient *client) {
|
||||||
|
return PsychicHandler::getClient(client->socket());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PsychicHandler::hasClient(PsychicClient *socket) {
|
||||||
|
return PsychicHandler::getClient(socket) != NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::list<PsychicClient*>& PsychicHandler::getClientList() {
|
||||||
|
return _clients;
|
||||||
|
}
|
||||||
61
lib/PsychicHttp/src/PsychicHandler.h
Normal file
61
lib/PsychicHttp/src/PsychicHandler.h
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
#ifndef PsychicHandler_h
|
||||||
|
#define PsychicHandler_h
|
||||||
|
|
||||||
|
#include "PsychicCore.h"
|
||||||
|
#include "PsychicRequest.h"
|
||||||
|
|
||||||
|
class PsychicEndpoint;
|
||||||
|
class PsychicHttpServer;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* HANDLER :: Can be attached to any endpoint or as a generic request handler.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class PsychicHandler {
|
||||||
|
friend PsychicEndpoint;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
PsychicRequestFilterFunction _filter;
|
||||||
|
PsychicHttpServer *_server;
|
||||||
|
|
||||||
|
String _username;
|
||||||
|
String _password;
|
||||||
|
HTTPAuthMethod _method;
|
||||||
|
String _realm;
|
||||||
|
String _authFailMsg;
|
||||||
|
|
||||||
|
std::list<PsychicClient*> _clients;
|
||||||
|
|
||||||
|
public:
|
||||||
|
PsychicHandler();
|
||||||
|
~PsychicHandler();
|
||||||
|
|
||||||
|
PsychicHandler* setFilter(PsychicRequestFilterFunction fn);
|
||||||
|
bool filter(PsychicRequest *request);
|
||||||
|
|
||||||
|
PsychicHandler* setAuthentication(const char *username, const char *password, HTTPAuthMethod method = BASIC_AUTH, const char *realm = "", const char *authFailMsg = "");
|
||||||
|
bool needsAuthentication(PsychicRequest *request);
|
||||||
|
esp_err_t authenticate(PsychicRequest *request);
|
||||||
|
|
||||||
|
virtual bool isWebSocket() { return false; };
|
||||||
|
|
||||||
|
PsychicClient * checkForNewClient(PsychicClient *client);
|
||||||
|
void checkForClosedClient(PsychicClient *client);
|
||||||
|
|
||||||
|
virtual void addClient(PsychicClient *client);
|
||||||
|
virtual void removeClient(PsychicClient *client);
|
||||||
|
virtual PsychicClient * getClient(int socket);
|
||||||
|
virtual PsychicClient * getClient(PsychicClient *client);
|
||||||
|
virtual void openCallback(PsychicClient *client) {};
|
||||||
|
virtual void closeCallback(PsychicClient *client) {};
|
||||||
|
|
||||||
|
bool hasClient(PsychicClient *client);
|
||||||
|
int count() { return _clients.size(); };
|
||||||
|
const std::list<PsychicClient*>& getClientList();
|
||||||
|
|
||||||
|
//derived classes must implement these functions
|
||||||
|
virtual bool canHandle(PsychicRequest *request) { return true; };
|
||||||
|
virtual esp_err_t handleRequest(PsychicRequest *request) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
24
lib/PsychicHttp/src/PsychicHttp.h
Normal file
24
lib/PsychicHttp/src/PsychicHttp.h
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#ifndef PsychicHttp_h
|
||||||
|
#define PsychicHttp_h
|
||||||
|
|
||||||
|
// #define ENABLE_ASYNC // This is something added in ESP-IDF 5.1.x where each request can be handled in its own thread
|
||||||
|
|
||||||
|
#include <http_status.h>
|
||||||
|
#include "PsychicHttpServer.h"
|
||||||
|
#include "PsychicRequest.h"
|
||||||
|
#include "PsychicResponse.h"
|
||||||
|
#include "PsychicEndpoint.h"
|
||||||
|
#include "PsychicHandler.h"
|
||||||
|
#include "PsychicStaticFileHandler.h"
|
||||||
|
#include "PsychicFileResponse.h"
|
||||||
|
#include "PsychicStreamResponse.h"
|
||||||
|
#include "PsychicUploadHandler.h"
|
||||||
|
#include "PsychicWebSocket.h"
|
||||||
|
#include "PsychicEventSource.h"
|
||||||
|
#include "PsychicJson.h"
|
||||||
|
|
||||||
|
#ifdef ENABLE_ASYNC
|
||||||
|
#include "async_worker.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* PsychicHttp_h */
|
||||||
366
lib/PsychicHttp/src/PsychicHttpServer.cpp
Normal file
366
lib/PsychicHttp/src/PsychicHttpServer.cpp
Normal file
@@ -0,0 +1,366 @@
|
|||||||
|
#include "PsychicHttpServer.h"
|
||||||
|
#include "PsychicEndpoint.h"
|
||||||
|
#include "PsychicHandler.h"
|
||||||
|
#include "PsychicWebHandler.h"
|
||||||
|
#include "PsychicStaticFileHandler.h"
|
||||||
|
#include "PsychicWebSocket.h"
|
||||||
|
#include "PsychicJson.h"
|
||||||
|
#include "WiFi.h"
|
||||||
|
|
||||||
|
PsychicHttpServer::PsychicHttpServer() :
|
||||||
|
_onOpen(NULL),
|
||||||
|
_onClose(NULL)
|
||||||
|
{
|
||||||
|
maxRequestBodySize = MAX_REQUEST_BODY_SIZE;
|
||||||
|
maxUploadSize = MAX_UPLOAD_SIZE;
|
||||||
|
|
||||||
|
defaultEndpoint = new PsychicEndpoint(this, HTTP_GET, "");
|
||||||
|
onNotFound(PsychicHttpServer::defaultNotFoundHandler);
|
||||||
|
|
||||||
|
//for a regular server
|
||||||
|
config = HTTPD_DEFAULT_CONFIG();
|
||||||
|
config.open_fn = PsychicHttpServer::openCallback;
|
||||||
|
config.close_fn = PsychicHttpServer::closeCallback;
|
||||||
|
config.uri_match_fn = httpd_uri_match_wildcard;
|
||||||
|
config.global_user_ctx = this;
|
||||||
|
config.global_user_ctx_free_fn = destroy;
|
||||||
|
config.max_uri_handlers = 20;
|
||||||
|
|
||||||
|
#ifdef ENABLE_ASYNC
|
||||||
|
// It is advisable that httpd_config_t->max_open_sockets > MAX_ASYNC_REQUESTS
|
||||||
|
// Why? This leaves at least one socket still available to handle
|
||||||
|
// quick synchronous requests. Otherwise, all the sockets will
|
||||||
|
// get taken by the long async handlers, and your server will no
|
||||||
|
// longer be responsive.
|
||||||
|
config.max_open_sockets = ASYNC_WORKER_COUNT + 1;
|
||||||
|
config.lru_purge_enable = true;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
PsychicHttpServer::~PsychicHttpServer()
|
||||||
|
{
|
||||||
|
for (auto *client : _clients)
|
||||||
|
delete(client);
|
||||||
|
_clients.clear();
|
||||||
|
|
||||||
|
for (auto *endpoint : _endpoints)
|
||||||
|
delete(endpoint);
|
||||||
|
_endpoints.clear();
|
||||||
|
|
||||||
|
for (auto *handler : _handlers)
|
||||||
|
delete(handler);
|
||||||
|
_handlers.clear();
|
||||||
|
|
||||||
|
delete defaultEndpoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PsychicHttpServer::destroy(void *ctx)
|
||||||
|
{
|
||||||
|
PsychicHttpServer *temp = (PsychicHttpServer *)ctx;
|
||||||
|
delete temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t PsychicHttpServer::listen(uint16_t port)
|
||||||
|
{
|
||||||
|
this->_use_ssl = false;
|
||||||
|
this->config.server_port = port;
|
||||||
|
|
||||||
|
return this->_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t PsychicHttpServer::_start()
|
||||||
|
{
|
||||||
|
esp_err_t ret;
|
||||||
|
|
||||||
|
#ifdef ENABLE_ASYNC
|
||||||
|
// start workers
|
||||||
|
start_async_req_workers();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//fire it up.
|
||||||
|
ret = _startServer();
|
||||||
|
if (ret != ESP_OK)
|
||||||
|
{
|
||||||
|
ESP_LOGE(PH_TAG, "Server start failed (%s)", esp_err_to_name(ret));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register handler
|
||||||
|
ret = httpd_register_err_handler(server, HTTPD_404_NOT_FOUND, PsychicHttpServer::notFoundHandler);
|
||||||
|
if (ret != ESP_OK)
|
||||||
|
ESP_LOGE(PH_TAG, "Add 404 handler failed (%s)", esp_err_to_name(ret));
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t PsychicHttpServer::_startServer() {
|
||||||
|
return httpd_start(&this->server, &this->config);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PsychicHttpServer::stop()
|
||||||
|
{
|
||||||
|
httpd_stop(this->server);
|
||||||
|
}
|
||||||
|
|
||||||
|
PsychicHandler& PsychicHttpServer::addHandler(PsychicHandler* handler){
|
||||||
|
_handlers.push_back(handler);
|
||||||
|
return *handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PsychicHttpServer::removeHandler(PsychicHandler *handler){
|
||||||
|
_handlers.remove(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
PsychicEndpoint* PsychicHttpServer::on(const char* uri) {
|
||||||
|
return on(uri, HTTP_GET);
|
||||||
|
}
|
||||||
|
|
||||||
|
PsychicEndpoint* PsychicHttpServer::on(const char* uri, http_method method)
|
||||||
|
{
|
||||||
|
PsychicWebHandler *handler = new PsychicWebHandler();
|
||||||
|
|
||||||
|
return on(uri, method, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
PsychicEndpoint* PsychicHttpServer::on(const char* uri, PsychicHandler *handler)
|
||||||
|
{
|
||||||
|
return on(uri, HTTP_GET, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
PsychicEndpoint* PsychicHttpServer::on(const char* uri, http_method method, PsychicHandler *handler)
|
||||||
|
{
|
||||||
|
//make our endpoint
|
||||||
|
PsychicEndpoint *endpoint = new PsychicEndpoint(this, method, uri);
|
||||||
|
|
||||||
|
//set our handler
|
||||||
|
endpoint->setHandler(handler);
|
||||||
|
|
||||||
|
// URI handler structure
|
||||||
|
httpd_uri_t my_uri {
|
||||||
|
.uri = uri,
|
||||||
|
.method = method,
|
||||||
|
.handler = PsychicEndpoint::requestCallback,
|
||||||
|
.user_ctx = endpoint,
|
||||||
|
.is_websocket = handler->isWebSocket()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Register endpoint with ESP-IDF server
|
||||||
|
esp_err_t ret = httpd_register_uri_handler(this->server, &my_uri);
|
||||||
|
if (ret != ESP_OK)
|
||||||
|
ESP_LOGE(PH_TAG, "Add endpoint failed (%s)", esp_err_to_name(ret));
|
||||||
|
|
||||||
|
//save it for later
|
||||||
|
_endpoints.push_back(endpoint);
|
||||||
|
|
||||||
|
return endpoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
PsychicEndpoint* PsychicHttpServer::on(const char* uri, PsychicHttpRequestCallback fn)
|
||||||
|
{
|
||||||
|
return on(uri, HTTP_GET, fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
PsychicEndpoint* PsychicHttpServer::on(const char* uri, http_method method, PsychicHttpRequestCallback fn)
|
||||||
|
{
|
||||||
|
//these basic requests need a basic web handler
|
||||||
|
PsychicWebHandler *handler = new PsychicWebHandler();
|
||||||
|
handler->onRequest(fn);
|
||||||
|
|
||||||
|
return on(uri, method, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
PsychicEndpoint* PsychicHttpServer::on(const char* uri, PsychicJsonRequestCallback fn)
|
||||||
|
{
|
||||||
|
return on(uri, HTTP_GET, fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
PsychicEndpoint* PsychicHttpServer::on(const char* uri, http_method method, PsychicJsonRequestCallback fn)
|
||||||
|
{
|
||||||
|
//these basic requests need a basic web handler
|
||||||
|
PsychicJsonHandler *handler = new PsychicJsonHandler();
|
||||||
|
handler->onRequest(fn);
|
||||||
|
|
||||||
|
return on(uri, method, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PsychicHttpServer::onNotFound(PsychicHttpRequestCallback fn)
|
||||||
|
{
|
||||||
|
PsychicWebHandler *handler = new PsychicWebHandler();
|
||||||
|
handler->onRequest(fn);
|
||||||
|
|
||||||
|
this->defaultEndpoint->setHandler(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t PsychicHttpServer::notFoundHandler(httpd_req_t *req, httpd_err_code_t err)
|
||||||
|
{
|
||||||
|
PsychicHttpServer *server = (PsychicHttpServer*)httpd_get_global_user_ctx(req->handle);
|
||||||
|
PsychicRequest request(server, req);
|
||||||
|
|
||||||
|
//loop through our global handlers and see if anyone wants it
|
||||||
|
for(auto *handler: server->_handlers)
|
||||||
|
{
|
||||||
|
//are we capable of handling this?
|
||||||
|
if (handler->filter(&request) && handler->canHandle(&request))
|
||||||
|
{
|
||||||
|
//check our credentials
|
||||||
|
if (handler->needsAuthentication(&request))
|
||||||
|
return handler->authenticate(&request);
|
||||||
|
else
|
||||||
|
return handler->handleRequest(&request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//nothing found, give it to our defaultEndpoint
|
||||||
|
PsychicHandler *handler = server->defaultEndpoint->handler();
|
||||||
|
if (handler->filter(&request) && handler->canHandle(&request))
|
||||||
|
return handler->handleRequest(&request);
|
||||||
|
|
||||||
|
//not sure how we got this far.
|
||||||
|
return ESP_ERR_HTTPD_INVALID_REQ;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t PsychicHttpServer::defaultNotFoundHandler(PsychicRequest *request)
|
||||||
|
{
|
||||||
|
request->reply(404, "text/html", "That URI does not exist.");
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PsychicHttpServer::onOpen(PsychicClientCallback handler) {
|
||||||
|
this->_onOpen = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t PsychicHttpServer::openCallback(httpd_handle_t hd, int sockfd)
|
||||||
|
{
|
||||||
|
ESP_LOGI(PH_TAG, "New client connected %d", sockfd);
|
||||||
|
|
||||||
|
//get our global server reference
|
||||||
|
PsychicHttpServer *server = (PsychicHttpServer*)httpd_get_global_user_ctx(hd);
|
||||||
|
|
||||||
|
//lookup our client
|
||||||
|
PsychicClient *client = server->getClient(sockfd);
|
||||||
|
if (client == NULL)
|
||||||
|
{
|
||||||
|
client = new PsychicClient(hd, sockfd);
|
||||||
|
server->addClient(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
//user callback
|
||||||
|
if (server->_onOpen != NULL)
|
||||||
|
server->_onOpen(client);
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PsychicHttpServer::onClose(PsychicClientCallback handler) {
|
||||||
|
this->_onClose = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PsychicHttpServer::closeCallback(httpd_handle_t hd, int sockfd)
|
||||||
|
{
|
||||||
|
ESP_LOGI(PH_TAG, "Client disconnected %d", sockfd);
|
||||||
|
|
||||||
|
PsychicHttpServer *server = (PsychicHttpServer*)httpd_get_global_user_ctx(hd);
|
||||||
|
|
||||||
|
//lookup our client
|
||||||
|
PsychicClient *client = server->getClient(sockfd);
|
||||||
|
if (client != NULL)
|
||||||
|
{
|
||||||
|
//give our handlers a chance to handle a disconnect first
|
||||||
|
for (PsychicEndpoint * endpoint : server->_endpoints)
|
||||||
|
{
|
||||||
|
PsychicHandler *handler = endpoint->handler();
|
||||||
|
handler->checkForClosedClient(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
//do we have a callback attached?
|
||||||
|
if (server->_onClose != NULL)
|
||||||
|
server->_onClose(client);
|
||||||
|
|
||||||
|
//remove it from our list
|
||||||
|
server->removeClient(client);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
ESP_LOGE(PH_TAG, "No client record %d", sockfd);
|
||||||
|
|
||||||
|
//finally close it out.
|
||||||
|
close(sockfd);
|
||||||
|
}
|
||||||
|
|
||||||
|
PsychicStaticFileHandler* PsychicHttpServer::serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_control)
|
||||||
|
{
|
||||||
|
PsychicStaticFileHandler* handler = new PsychicStaticFileHandler(uri, fs, path, cache_control);
|
||||||
|
this->addHandler(handler);
|
||||||
|
|
||||||
|
return handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PsychicHttpServer::addClient(PsychicClient *client) {
|
||||||
|
_clients.push_back(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PsychicHttpServer::removeClient(PsychicClient *client) {
|
||||||
|
_clients.remove(client);
|
||||||
|
delete client;
|
||||||
|
}
|
||||||
|
|
||||||
|
PsychicClient * PsychicHttpServer::getClient(int socket) {
|
||||||
|
for (PsychicClient * client : _clients)
|
||||||
|
if (client->socket() == socket)
|
||||||
|
return client;
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
PsychicClient * PsychicHttpServer::getClient(httpd_req_t *req) {
|
||||||
|
return getClient(httpd_req_to_sockfd(req));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PsychicHttpServer::hasClient(int socket) {
|
||||||
|
return getClient(socket) != NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::list<PsychicClient*>& PsychicHttpServer::getClientList() {
|
||||||
|
return _clients;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ON_STA_FILTER(PsychicRequest *request) {
|
||||||
|
return WiFi.localIP() == request->client()->localIP();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ON_AP_FILTER(PsychicRequest *request) {
|
||||||
|
return WiFi.softAPIP() == request->client()->localIP();
|
||||||
|
}
|
||||||
|
|
||||||
|
String urlDecode(const char* encoded)
|
||||||
|
{
|
||||||
|
size_t length = strlen(encoded);
|
||||||
|
char* decoded = (char*)malloc(length + 1);
|
||||||
|
if (!decoded) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t i, j = 0;
|
||||||
|
for (i = 0; i < length; ++i) {
|
||||||
|
if (encoded[i] == '%' && isxdigit(encoded[i + 1]) && isxdigit(encoded[i + 2])) {
|
||||||
|
// Valid percent-encoded sequence
|
||||||
|
int hex;
|
||||||
|
sscanf(encoded + i + 1, "%2x", &hex);
|
||||||
|
decoded[j++] = (char)hex;
|
||||||
|
i += 2; // Skip the two hexadecimal characters
|
||||||
|
} else if (encoded[i] == '+') {
|
||||||
|
// Convert '+' to space
|
||||||
|
decoded[j++] = ' ';
|
||||||
|
} else {
|
||||||
|
// Copy other characters as they are
|
||||||
|
decoded[j++] = encoded[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
decoded[j] = '\0'; // Null-terminate the decoded string
|
||||||
|
|
||||||
|
String output(decoded);
|
||||||
|
free(decoded);
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
81
lib/PsychicHttp/src/PsychicHttpServer.h
Normal file
81
lib/PsychicHttp/src/PsychicHttpServer.h
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
#ifndef PsychicHttpServer_h
|
||||||
|
#define PsychicHttpServer_h
|
||||||
|
|
||||||
|
#include "PsychicCore.h"
|
||||||
|
#include "PsychicClient.h"
|
||||||
|
#include "PsychicHandler.h"
|
||||||
|
|
||||||
|
class PsychicEndpoint;
|
||||||
|
class PsychicHandler;
|
||||||
|
class PsychicStaticFileHandler;
|
||||||
|
|
||||||
|
class PsychicHttpServer
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
bool _use_ssl = false;
|
||||||
|
std::list<PsychicEndpoint*> _endpoints;
|
||||||
|
std::list<PsychicHandler*> _handlers;
|
||||||
|
std::list<PsychicClient*> _clients;
|
||||||
|
|
||||||
|
PsychicClientCallback _onOpen;
|
||||||
|
PsychicClientCallback _onClose;
|
||||||
|
|
||||||
|
esp_err_t _start();
|
||||||
|
virtual esp_err_t _startServer();
|
||||||
|
|
||||||
|
public:
|
||||||
|
PsychicHttpServer();
|
||||||
|
~PsychicHttpServer();
|
||||||
|
|
||||||
|
//esp-idf specific stuff
|
||||||
|
httpd_handle_t server;
|
||||||
|
httpd_config_t config;
|
||||||
|
|
||||||
|
//some limits on what we will accept
|
||||||
|
unsigned long maxUploadSize;
|
||||||
|
unsigned long maxRequestBodySize;
|
||||||
|
|
||||||
|
PsychicEndpoint *defaultEndpoint;
|
||||||
|
|
||||||
|
static void destroy(void *ctx);
|
||||||
|
|
||||||
|
esp_err_t listen(uint16_t port);
|
||||||
|
|
||||||
|
virtual void stop();
|
||||||
|
|
||||||
|
PsychicHandler& addHandler(PsychicHandler* handler);
|
||||||
|
void removeHandler(PsychicHandler* handler);
|
||||||
|
|
||||||
|
void addClient(PsychicClient *client);
|
||||||
|
void removeClient(PsychicClient *client);
|
||||||
|
PsychicClient* getClient(int socket);
|
||||||
|
PsychicClient* getClient(httpd_req_t *req);
|
||||||
|
bool hasClient(int socket);
|
||||||
|
int count() { return _clients.size(); };
|
||||||
|
const std::list<PsychicClient*>& getClientList();
|
||||||
|
|
||||||
|
PsychicEndpoint* on(const char* uri);
|
||||||
|
PsychicEndpoint* on(const char* uri, http_method method);
|
||||||
|
PsychicEndpoint* on(const char* uri, PsychicHandler *handler);
|
||||||
|
PsychicEndpoint* on(const char* uri, http_method method, PsychicHandler *handler);
|
||||||
|
PsychicEndpoint* on(const char* uri, PsychicHttpRequestCallback onRequest);
|
||||||
|
PsychicEndpoint* on(const char* uri, http_method method, PsychicHttpRequestCallback onRequest);
|
||||||
|
PsychicEndpoint* on(const char* uri, PsychicJsonRequestCallback onRequest);
|
||||||
|
PsychicEndpoint* on(const char* uri, http_method method, PsychicJsonRequestCallback onRequest);
|
||||||
|
|
||||||
|
static esp_err_t notFoundHandler(httpd_req_t *req, httpd_err_code_t err);
|
||||||
|
static esp_err_t defaultNotFoundHandler(PsychicRequest *request);
|
||||||
|
void onNotFound(PsychicHttpRequestCallback fn);
|
||||||
|
|
||||||
|
void onOpen(PsychicClientCallback handler);
|
||||||
|
void onClose(PsychicClientCallback handler);
|
||||||
|
static esp_err_t openCallback(httpd_handle_t hd, int sockfd);
|
||||||
|
static void closeCallback(httpd_handle_t hd, int sockfd);
|
||||||
|
|
||||||
|
PsychicStaticFileHandler* serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_control = NULL);
|
||||||
|
};
|
||||||
|
|
||||||
|
bool ON_STA_FILTER(PsychicRequest *request);
|
||||||
|
bool ON_AP_FILTER(PsychicRequest *request);
|
||||||
|
|
||||||
|
#endif // PsychicHttpServer_h
|
||||||
50
lib/PsychicHttp/src/PsychicHttpsServer.cpp
Normal file
50
lib/PsychicHttp/src/PsychicHttpsServer.cpp
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
#include "PsychicHttpsServer.h"
|
||||||
|
|
||||||
|
PsychicHttpsServer::PsychicHttpsServer() : PsychicHttpServer()
|
||||||
|
{
|
||||||
|
//for a SSL server
|
||||||
|
ssl_config = HTTPD_SSL_CONFIG_DEFAULT();
|
||||||
|
ssl_config.httpd.open_fn = PsychicHttpServer::openCallback;
|
||||||
|
ssl_config.httpd.close_fn = PsychicHttpServer::closeCallback;
|
||||||
|
ssl_config.httpd.uri_match_fn = httpd_uri_match_wildcard;
|
||||||
|
ssl_config.httpd.global_user_ctx = this;
|
||||||
|
ssl_config.httpd.global_user_ctx_free_fn = destroy;
|
||||||
|
ssl_config.httpd.max_uri_handlers = 20;
|
||||||
|
|
||||||
|
// each SSL connection takes about 45kb of heap
|
||||||
|
// a barebones sketch with PsychicHttp has ~150kb of heap available
|
||||||
|
// if we set it higher than 2 and use all the connections, we get lots of memory errors.
|
||||||
|
// not to mention there is no heap left over for the program itself.
|
||||||
|
ssl_config.httpd.max_open_sockets = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
PsychicHttpsServer::~PsychicHttpsServer() {}
|
||||||
|
|
||||||
|
esp_err_t PsychicHttpsServer::listen(uint16_t port, const char *cert, const char *private_key)
|
||||||
|
{
|
||||||
|
this->_use_ssl = true;
|
||||||
|
|
||||||
|
this->ssl_config.port_secure = port;
|
||||||
|
this->ssl_config.cacert_pem = (uint8_t *)cert;
|
||||||
|
this->ssl_config.cacert_len = strlen(cert)+1;
|
||||||
|
this->ssl_config.prvtkey_pem = (uint8_t *)private_key;
|
||||||
|
this->ssl_config.prvtkey_len = strlen(private_key)+1;
|
||||||
|
|
||||||
|
return this->_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t PsychicHttpsServer::_startServer()
|
||||||
|
{
|
||||||
|
if (this->_use_ssl)
|
||||||
|
return httpd_ssl_start(&this->server, &this->ssl_config);
|
||||||
|
else
|
||||||
|
return httpd_start(&this->server, &this->config);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PsychicHttpsServer::stop()
|
||||||
|
{
|
||||||
|
if (this->_use_ssl)
|
||||||
|
httpd_ssl_stop(this->server);
|
||||||
|
else
|
||||||
|
httpd_stop(this->server);
|
||||||
|
}
|
||||||
32
lib/PsychicHttp/src/PsychicHttpsServer.h
Normal file
32
lib/PsychicHttp/src/PsychicHttpsServer.h
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
#ifndef PsychicHttpsServer_h
|
||||||
|
#define PsychicHttpsServer_h
|
||||||
|
|
||||||
|
#include "PsychicCore.h"
|
||||||
|
#include "PsychicHttpServer.h"
|
||||||
|
#include <esp_https_server.h>
|
||||||
|
|
||||||
|
#if !CONFIG_HTTPD_WS_SUPPORT
|
||||||
|
#error PsychicHttpsServer cannot be used unless HTTPD_WS_SUPPORT is enabled in esp-http-server component configuration
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define PSY_ENABLE_SSL //you can use this define in your code to enable/disable these features
|
||||||
|
|
||||||
|
class PsychicHttpsServer : public PsychicHttpServer
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
bool _use_ssl = false;
|
||||||
|
|
||||||
|
public:
|
||||||
|
PsychicHttpsServer();
|
||||||
|
~PsychicHttpsServer();
|
||||||
|
|
||||||
|
httpd_ssl_config_t ssl_config;
|
||||||
|
|
||||||
|
using PsychicHttpServer::listen; //keep the regular version
|
||||||
|
esp_err_t listen(uint16_t port, const char *cert, const char *private_key);
|
||||||
|
|
||||||
|
virtual esp_err_t _startServer() override final;
|
||||||
|
virtual void stop() override final;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // PsychicHttpsServer_h
|
||||||
150
lib/PsychicHttp/src/PsychicJson.DELETEME
Normal file
150
lib/PsychicHttp/src/PsychicJson.DELETEME
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
#include "PsychicJson.h"
|
||||||
|
|
||||||
|
#ifdef ARDUINOJSON_5_COMPATIBILITY
|
||||||
|
PsychicJsonResponse::PsychicJsonResponse(PsychicRequest *request, bool isArray) : PsychicResponse(request)
|
||||||
|
{
|
||||||
|
setContentType(JSON_MIMETYPE);
|
||||||
|
if (isArray)
|
||||||
|
_root = _jsonBuffer.createArray();
|
||||||
|
else
|
||||||
|
_root = _jsonBuffer.createObject();
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
PsychicJsonResponse::PsychicJsonResponse(PsychicRequest *request, bool isArray, size_t maxJsonBufferSize) :
|
||||||
|
PsychicResponse(request),
|
||||||
|
_jsonBuffer(maxJsonBufferSize)
|
||||||
|
{
|
||||||
|
setContentType(JSON_MIMETYPE);
|
||||||
|
if (isArray)
|
||||||
|
_root = _jsonBuffer.createNestedArray();
|
||||||
|
else
|
||||||
|
_root = _jsonBuffer.createNestedObject();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
JsonVariant &PsychicJsonResponse::getRoot() { return _root; }
|
||||||
|
|
||||||
|
size_t PsychicJsonResponse::getLength()
|
||||||
|
{
|
||||||
|
#ifdef ARDUINOJSON_5_COMPATIBILITY
|
||||||
|
return _root.measureLength();
|
||||||
|
#else
|
||||||
|
return measureJson(_root);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t PsychicJsonResponse::send()
|
||||||
|
{
|
||||||
|
esp_err_t err = ESP_OK;
|
||||||
|
size_t length = getLength();
|
||||||
|
size_t buffer_size;
|
||||||
|
char *buffer;
|
||||||
|
|
||||||
|
DUMP(length);
|
||||||
|
|
||||||
|
//how big of a buffer do we want?
|
||||||
|
if (length < JSON_BUFFER_SIZE)
|
||||||
|
buffer_size = length+1;
|
||||||
|
else
|
||||||
|
buffer_size = JSON_BUFFER_SIZE;
|
||||||
|
|
||||||
|
DUMP(buffer_size);
|
||||||
|
|
||||||
|
buffer = (char *)malloc(buffer_size);
|
||||||
|
if (buffer == NULL) {
|
||||||
|
httpd_resp_send_err(this->_request->request(), HTTPD_500_INTERNAL_SERVER_ERROR, "Unable to allocate memory.");
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
//send it in one shot or no?
|
||||||
|
if (length < JSON_BUFFER_SIZE)
|
||||||
|
{
|
||||||
|
TRACE();
|
||||||
|
|
||||||
|
#ifdef ARDUINOJSON_5_COMPATIBILITY
|
||||||
|
_root.printTo(buffer, buffer_size);
|
||||||
|
#else
|
||||||
|
serializeJson(_root, buffer, buffer_size);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
this->setContent((uint8_t *)buffer, length);
|
||||||
|
this->setContentType(JSON_MIMETYPE);
|
||||||
|
|
||||||
|
err = PsychicResponse::send();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//helper class that acts as a stream to print chunked responses
|
||||||
|
ChunkPrinter dest(this, (uint8_t *)buffer, buffer_size);
|
||||||
|
|
||||||
|
//keep our headers
|
||||||
|
this->sendHeaders();
|
||||||
|
|
||||||
|
//these commands write to the ChunkPrinter which does the sending
|
||||||
|
#ifdef ARDUINOJSON_5_COMPATIBILITY
|
||||||
|
_root.printTo(dest);
|
||||||
|
#else
|
||||||
|
serializeJson(_root, dest);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//send the last bits
|
||||||
|
dest.flush();
|
||||||
|
|
||||||
|
//done with our chunked response too
|
||||||
|
err = this->finishChunking();
|
||||||
|
}
|
||||||
|
|
||||||
|
//let the buffer go
|
||||||
|
free(buffer);
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef ARDUINOJSON_5_COMPATIBILITY
|
||||||
|
PsychicJsonHandler::PsychicJsonHandler() :
|
||||||
|
_onRequest(NULL)
|
||||||
|
{};
|
||||||
|
|
||||||
|
PsychicJsonHandler::PsychicJsonHandler(PsychicJsonRequestCallback onRequest) :
|
||||||
|
_onRequest(onRequest)
|
||||||
|
{}
|
||||||
|
#else
|
||||||
|
PsychicJsonHandler::PsychicJsonHandler(size_t maxJsonBufferSize) :
|
||||||
|
_onRequest(NULL),
|
||||||
|
_maxJsonBufferSize(maxJsonBufferSize)
|
||||||
|
{};
|
||||||
|
|
||||||
|
PsychicJsonHandler::PsychicJsonHandler(PsychicJsonRequestCallback onRequest, size_t maxJsonBufferSize) :
|
||||||
|
_onRequest(onRequest),
|
||||||
|
_maxJsonBufferSize(maxJsonBufferSize)
|
||||||
|
{}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void PsychicJsonHandler::onRequest(PsychicJsonRequestCallback fn) { _onRequest = fn; }
|
||||||
|
|
||||||
|
esp_err_t PsychicJsonHandler::handleRequest(PsychicRequest *request)
|
||||||
|
{
|
||||||
|
//process basic stuff
|
||||||
|
PsychicWebHandler::handleRequest(request);
|
||||||
|
|
||||||
|
if (_onRequest)
|
||||||
|
{
|
||||||
|
#ifdef ARDUINOJSON_5_COMPATIBILITY
|
||||||
|
DynamicJsonBuffer jsonBuffer;
|
||||||
|
JsonVariant json = jsonBuffer.parse();
|
||||||
|
if (!json.success())
|
||||||
|
return request->reply(400);
|
||||||
|
#else
|
||||||
|
DynamicJsonDocument jsonBuffer(this->_maxJsonBufferSize);
|
||||||
|
DeserializationError error = deserializeJson(jsonBuffer, request->body());
|
||||||
|
if (error)
|
||||||
|
return request->reply(400);
|
||||||
|
|
||||||
|
JsonVariant json = jsonBuffer.as<JsonVariant>();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return _onRequest(request, json);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return request->reply(500);
|
||||||
|
}
|
||||||
249
lib/PsychicHttp/src/PsychicJson.h
Normal file
249
lib/PsychicHttp/src/PsychicJson.h
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
// PsychicJson.h
|
||||||
|
/*
|
||||||
|
Async Response to use with ArduinoJson and AsyncWebServer
|
||||||
|
Written by Andrew Melvin (SticilFace) with help from me-no-dev and BBlanchon.
|
||||||
|
Ported to PsychicHttp by Zach Hoeken
|
||||||
|
|
||||||
|
*/
|
||||||
|
#ifndef PSYCHIC_JSON_H_
|
||||||
|
#define PSYCHIC_JSON_H_
|
||||||
|
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
#include "PsychicCore.h"
|
||||||
|
#include "PsychicResponse.h"
|
||||||
|
#include "ChunkPrinter.h"
|
||||||
|
|
||||||
|
#if ARDUINOJSON_VERSION_MAJOR == 5
|
||||||
|
#define ARDUINOJSON_5_COMPATIBILITY
|
||||||
|
#else
|
||||||
|
#ifndef DYNAMIC_JSON_DOCUMENT_SIZE
|
||||||
|
#define DYNAMIC_JSON_DOCUMENT_SIZE 4096
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef JSON_BUFFER_SIZE
|
||||||
|
//#define JSON_BUFFER_SIZE 256
|
||||||
|
#define JSON_BUFFER_SIZE 4 * 1024
|
||||||
|
#endif
|
||||||
|
|
||||||
|
constexpr const char * JSON_MIMETYPE = "application/json";
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Json Response
|
||||||
|
* */
|
||||||
|
|
||||||
|
class PsychicJsonResponse : public PsychicResponse {
|
||||||
|
protected:
|
||||||
|
#ifdef ARDUINOJSON_5_COMPATIBILITY
|
||||||
|
DynamicJsonBuffer _jsonBuffer;
|
||||||
|
#else
|
||||||
|
DynamicJsonDocument _jsonBuffer;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
JsonVariant _root;
|
||||||
|
size_t _contentLength;
|
||||||
|
bool _msgPack; // added by proddy
|
||||||
|
|
||||||
|
public:
|
||||||
|
#ifdef ARDUINOJSON_5_COMPATIBILITY
|
||||||
|
PsychicJsonResponse(PsychicRequest * request, bool isArray = false)
|
||||||
|
: PsychicResponse(request) {
|
||||||
|
setContentType(JSON_MIMETYPE);
|
||||||
|
if (isArray)
|
||||||
|
_root = _jsonBuffer.createArray();
|
||||||
|
else
|
||||||
|
_root = _jsonBuffer.createObject();
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
PsychicJsonResponse(PsychicRequest * request, bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE, bool msgPack = false) // added by proddy
|
||||||
|
: PsychicResponse(request)
|
||||||
|
, _jsonBuffer(maxJsonBufferSize)
|
||||||
|
, _msgPack(msgPack) // added by proddy
|
||||||
|
{
|
||||||
|
setContentType(JSON_MIMETYPE);
|
||||||
|
if (isArray)
|
||||||
|
_root = _jsonBuffer.createNestedArray();
|
||||||
|
else
|
||||||
|
_root = _jsonBuffer.createNestedObject();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
~PsychicJsonResponse() {
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonVariant & getRoot() {
|
||||||
|
return _root;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t getLength() {
|
||||||
|
#ifdef ARDUINOJSON_5_COMPATIBILITY
|
||||||
|
return _root.measureLength();
|
||||||
|
#else
|
||||||
|
// return measureJson(_root);
|
||||||
|
return (_msgPack) ? measureMsgPack(_root) : measureJson(_root); // added by proddy
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// size_t _fillBuffer(uint8_t *data, size_t len)
|
||||||
|
// {
|
||||||
|
// //TODO: fix
|
||||||
|
// //ChunkPrint dest(data, _sentLength, len);
|
||||||
|
|
||||||
|
// #ifdef ARDUINOJSON_5_COMPATIBILITY
|
||||||
|
// _root.printTo(dest);
|
||||||
|
// #else
|
||||||
|
// //serializeJson(_root, dest);
|
||||||
|
// #endif
|
||||||
|
// return len;
|
||||||
|
// }
|
||||||
|
|
||||||
|
virtual esp_err_t send() override {
|
||||||
|
esp_err_t err = ESP_OK;
|
||||||
|
// size_t length = getLength();
|
||||||
|
size_t length = JSON_BUFFER_SIZE; // TODO force chunking
|
||||||
|
|
||||||
|
size_t buffer_size;
|
||||||
|
char * buffer;
|
||||||
|
|
||||||
|
DUMP(length);
|
||||||
|
|
||||||
|
//how big of a buffer do we want?
|
||||||
|
if (length < JSON_BUFFER_SIZE)
|
||||||
|
buffer_size = length + 1;
|
||||||
|
else
|
||||||
|
buffer_size = JSON_BUFFER_SIZE;
|
||||||
|
|
||||||
|
DUMP(buffer_size);
|
||||||
|
|
||||||
|
buffer = (char *)malloc(buffer_size);
|
||||||
|
if (buffer == NULL) {
|
||||||
|
httpd_resp_send_err(this->_request->request(), HTTPD_500_INTERNAL_SERVER_ERROR, "Unable to allocate memory.");
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
//send it in one shot or no?
|
||||||
|
if (length < JSON_BUFFER_SIZE) {
|
||||||
|
TRACE();
|
||||||
|
|
||||||
|
#ifdef ARDUINOJSON_5_COMPATIBILITY
|
||||||
|
_root.printTo(buffer, buffer_size);
|
||||||
|
#else
|
||||||
|
// serializeJson(_root, buffer, buffer_size)
|
||||||
|
(_msgPack) ? serializeMsgPack(_root, buffer, buffer_size) : serializeJson(_root, buffer, buffer_size); // added by proddy
|
||||||
|
#endif
|
||||||
|
|
||||||
|
this->setContent((uint8_t *)buffer, length);
|
||||||
|
this->setContentType(JSON_MIMETYPE);
|
||||||
|
|
||||||
|
err = PsychicResponse::send();
|
||||||
|
} else {
|
||||||
|
//helper class that acts as a stream to print chunked responses
|
||||||
|
ChunkPrinter dest(this, (uint8_t *)buffer, buffer_size);
|
||||||
|
|
||||||
|
//keep our headers
|
||||||
|
this->sendHeaders();
|
||||||
|
|
||||||
|
//these commands write to the ChunkPrinter which does the sending
|
||||||
|
#ifdef ARDUINOJSON_5_COMPATIBILITY
|
||||||
|
_root.printTo(dest);
|
||||||
|
#else
|
||||||
|
// serializeJson(_root, dest); // added by proddy
|
||||||
|
(_msgPack) ? serializeMsgPack(_root, dest) : serializeJson(_root, dest); // added by proddy
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//send the last bits
|
||||||
|
dest.flush();
|
||||||
|
|
||||||
|
//done with our chunked response too
|
||||||
|
err = this->finishChunking();
|
||||||
|
}
|
||||||
|
|
||||||
|
//let the buffer go
|
||||||
|
free(buffer);
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// class PrettyPsychicJsonResponse : public PsychicJsonResponse
|
||||||
|
// {
|
||||||
|
// public:
|
||||||
|
// #ifdef ARDUINOJSON_5_COMPATIBILITY
|
||||||
|
// PrettyPsychicJsonResponse(PsychicRequest *request, bool isArray = false) : PsychicJsonResponse(request, isArray) {}
|
||||||
|
// #else
|
||||||
|
// PrettyPsychicJsonResponse(
|
||||||
|
// PsychicRequest *request,
|
||||||
|
// bool isArray = false,
|
||||||
|
// size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE)
|
||||||
|
// : PsychicJsonResponse{request, isArray, maxJsonBufferSize} {}
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// size_t setLength()
|
||||||
|
// {
|
||||||
|
// #ifdef ARDUINOJSON_5_COMPATIBILITY
|
||||||
|
// _contentLength = _root.measurePrettyLength();
|
||||||
|
// #else
|
||||||
|
// _contentLength = measureJsonPretty(_root);
|
||||||
|
// #endif
|
||||||
|
// return _contentLength;
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
typedef std::function<esp_err_t(PsychicRequest * request, JsonVariant & json)> PsychicJsonRequestCallback;
|
||||||
|
|
||||||
|
class PsychicJsonHandler : public PsychicWebHandler {
|
||||||
|
protected:
|
||||||
|
PsychicJsonRequestCallback _onRequest;
|
||||||
|
#ifndef ARDUINOJSON_5_COMPATIBILITY
|
||||||
|
const size_t _maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
public:
|
||||||
|
#ifdef ARDUINOJSON_5_COMPATIBILITY
|
||||||
|
PsychicJsonHandler()
|
||||||
|
: _onRequest(NULL){};
|
||||||
|
|
||||||
|
PsychicJsonHandler(PsychicJsonRequestCallback onRequest)
|
||||||
|
: _onRequest(onRequest) {
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
PsychicJsonHandler(size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE)
|
||||||
|
: _onRequest(NULL)
|
||||||
|
, _maxJsonBufferSize(maxJsonBufferSize){};
|
||||||
|
|
||||||
|
PsychicJsonHandler(PsychicJsonRequestCallback onRequest, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE)
|
||||||
|
: _onRequest(onRequest)
|
||||||
|
, _maxJsonBufferSize(maxJsonBufferSize) {
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void onRequest(PsychicJsonRequestCallback fn) {
|
||||||
|
_onRequest = fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual esp_err_t handleRequest(PsychicRequest * request) override {
|
||||||
|
//process basic stuff
|
||||||
|
PsychicWebHandler::handleRequest(request);
|
||||||
|
|
||||||
|
if (_onRequest) {
|
||||||
|
#ifdef ARDUINOJSON_5_COMPATIBILITY
|
||||||
|
DynamicJsonBuffer jsonBuffer;
|
||||||
|
JsonVariant json = jsonBuffer.parse();
|
||||||
|
if (!json.success())
|
||||||
|
return request->reply(400);
|
||||||
|
#else
|
||||||
|
DynamicJsonDocument jsonBuffer(this->_maxJsonBufferSize);
|
||||||
|
DeserializationError error = deserializeJson(jsonBuffer, request->body());
|
||||||
|
if (error)
|
||||||
|
return request->reply(400);
|
||||||
|
|
||||||
|
JsonVariant json = jsonBuffer.as<JsonVariant>();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return _onRequest(request, json);
|
||||||
|
} else
|
||||||
|
return request->reply(500);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
#endif
|
||||||
549
lib/PsychicHttp/src/PsychicRequest.cpp
Normal file
549
lib/PsychicHttp/src/PsychicRequest.cpp
Normal file
@@ -0,0 +1,549 @@
|
|||||||
|
#include "PsychicRequest.h"
|
||||||
|
#include "http_status.h"
|
||||||
|
#include "PsychicHttpServer.h"
|
||||||
|
|
||||||
|
|
||||||
|
PsychicRequest::PsychicRequest(PsychicHttpServer *server, httpd_req_t *req) :
|
||||||
|
_server(server),
|
||||||
|
_req(req),
|
||||||
|
_method(HTTP_GET),
|
||||||
|
_query(""),
|
||||||
|
_body(""),
|
||||||
|
_tempObject(NULL)
|
||||||
|
{
|
||||||
|
//load up our client.
|
||||||
|
this->_client = server->getClient(req);
|
||||||
|
|
||||||
|
//handle our session data
|
||||||
|
if (req->sess_ctx != NULL)
|
||||||
|
this->_session = (SessionData *)req->sess_ctx;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this->_session = new SessionData();
|
||||||
|
req->sess_ctx = this->_session;
|
||||||
|
}
|
||||||
|
|
||||||
|
//callback for freeing the session later
|
||||||
|
req->free_ctx = this->freeSession;
|
||||||
|
|
||||||
|
//load up some data
|
||||||
|
this->_uri = String(this->_req->uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
PsychicRequest::~PsychicRequest()
|
||||||
|
{
|
||||||
|
//temorary user object
|
||||||
|
if (_tempObject != NULL)
|
||||||
|
free(_tempObject);
|
||||||
|
|
||||||
|
//our web parameters
|
||||||
|
for (auto *param : _params)
|
||||||
|
delete(param);
|
||||||
|
_params.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PsychicRequest::freeSession(void *ctx)
|
||||||
|
{
|
||||||
|
if (ctx != NULL)
|
||||||
|
{
|
||||||
|
SessionData *session = (SessionData*)ctx;
|
||||||
|
delete session;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PsychicHttpServer * PsychicRequest::server() {
|
||||||
|
return _server;
|
||||||
|
}
|
||||||
|
|
||||||
|
httpd_req_t * PsychicRequest::request() {
|
||||||
|
return _req;
|
||||||
|
}
|
||||||
|
|
||||||
|
PsychicClient * PsychicRequest::client() {
|
||||||
|
return _client;
|
||||||
|
}
|
||||||
|
|
||||||
|
const String PsychicRequest::getFilename()
|
||||||
|
{
|
||||||
|
//parse the content-disposition header
|
||||||
|
if (this->hasHeader("Content-Disposition"))
|
||||||
|
{
|
||||||
|
ContentDisposition cd = this->getContentDisposition();
|
||||||
|
if (cd.filename != "")
|
||||||
|
return cd.filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
//fall back to passed in query string
|
||||||
|
PsychicWebParameter *param = getParam("_filename");
|
||||||
|
if (param != NULL)
|
||||||
|
return param->name();
|
||||||
|
|
||||||
|
//fall back to parsing it from url (useful for wildcard uploads)
|
||||||
|
String uri = this->uri();
|
||||||
|
int filenameStart = uri.lastIndexOf('/') + 1;
|
||||||
|
String filename = uri.substring(filenameStart);
|
||||||
|
if (filename != "")
|
||||||
|
return filename;
|
||||||
|
|
||||||
|
//finally, unknown.
|
||||||
|
ESP_LOGE(PH_TAG, "Did not get a valid filename from the upload.");
|
||||||
|
return "unknown.txt";
|
||||||
|
}
|
||||||
|
|
||||||
|
const ContentDisposition PsychicRequest::getContentDisposition()
|
||||||
|
{
|
||||||
|
ContentDisposition cd;
|
||||||
|
String header = this->header("Content-Disposition");
|
||||||
|
int start;
|
||||||
|
int end;
|
||||||
|
|
||||||
|
if (header.indexOf("form-data") == 0)
|
||||||
|
cd.disposition = FORM_DATA;
|
||||||
|
else if (header.indexOf("attachment") == 0)
|
||||||
|
cd.disposition = ATTACHMENT;
|
||||||
|
else if (header.indexOf("inline") == 0)
|
||||||
|
cd.disposition = INLINE;
|
||||||
|
else
|
||||||
|
cd.disposition = NONE;
|
||||||
|
|
||||||
|
start = header.indexOf("filename=");
|
||||||
|
if (start)
|
||||||
|
{
|
||||||
|
end = header.indexOf('"', start+10);
|
||||||
|
cd.filename = header.substring(start+10, end-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
start = header.indexOf("name=");
|
||||||
|
if (start)
|
||||||
|
{
|
||||||
|
end = header.indexOf('"', start+6);
|
||||||
|
cd.name = header.substring(start+6, end-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return cd;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t PsychicRequest::loadBody()
|
||||||
|
{
|
||||||
|
esp_err_t err = ESP_OK;
|
||||||
|
|
||||||
|
this->_body = String();
|
||||||
|
|
||||||
|
//Get header value string length and allocate memory for length + 1, extra byte for null termination
|
||||||
|
size_t remaining = this->_req->content_len;
|
||||||
|
char *buf = (char *)malloc(remaining+1);
|
||||||
|
|
||||||
|
//while loop for retries
|
||||||
|
while (remaining > 0)
|
||||||
|
{
|
||||||
|
//read our data from the socket
|
||||||
|
int received = httpd_req_recv(this->_req, buf, this->_req->content_len);
|
||||||
|
|
||||||
|
//Retry if timeout occurred
|
||||||
|
if (received == HTTPD_SOCK_ERR_TIMEOUT)
|
||||||
|
continue;
|
||||||
|
//bail if we got an error
|
||||||
|
else if (received == HTTPD_SOCK_ERR_FAIL)
|
||||||
|
{
|
||||||
|
err = ESP_FAIL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
//keep track of our
|
||||||
|
remaining -= received;
|
||||||
|
}
|
||||||
|
|
||||||
|
//null terminate and make our string
|
||||||
|
buf[this->_req->content_len] = '\0';
|
||||||
|
this->_body = String(buf);
|
||||||
|
|
||||||
|
//keep track of that pesky memory
|
||||||
|
free(buf);
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
http_method PsychicRequest::method() {
|
||||||
|
return (http_method)this->_req->method;
|
||||||
|
}
|
||||||
|
|
||||||
|
const String PsychicRequest::methodStr() {
|
||||||
|
return String(http_method_str((http_method)this->_req->method));
|
||||||
|
}
|
||||||
|
|
||||||
|
const String PsychicRequest::path() {
|
||||||
|
int index = _uri.indexOf("?");
|
||||||
|
if(index == -1)
|
||||||
|
return _uri;
|
||||||
|
else
|
||||||
|
return _uri.substring(0, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
const String& PsychicRequest::uri() {
|
||||||
|
return this->_uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
const String& PsychicRequest::query() {
|
||||||
|
return this->_query;
|
||||||
|
}
|
||||||
|
|
||||||
|
// no way to get list of headers yet....
|
||||||
|
// int PsychicRequest::headers()
|
||||||
|
// {
|
||||||
|
// }
|
||||||
|
|
||||||
|
const String PsychicRequest::header(const char *name)
|
||||||
|
{
|
||||||
|
size_t header_len = httpd_req_get_hdr_value_len(this->_req, name);
|
||||||
|
|
||||||
|
//if we've got one, allocated it and load it
|
||||||
|
if (header_len)
|
||||||
|
{
|
||||||
|
char header[header_len+1];
|
||||||
|
httpd_req_get_hdr_value_str(this->_req, name, header, sizeof(header));
|
||||||
|
return String(header);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PsychicRequest::hasHeader(const char *name)
|
||||||
|
{
|
||||||
|
return httpd_req_get_hdr_value_len(this->_req, name) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const String PsychicRequest::host() {
|
||||||
|
return this->header("Host");
|
||||||
|
}
|
||||||
|
|
||||||
|
const String PsychicRequest::contentType() {
|
||||||
|
return header("Content-Type");
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t PsychicRequest::contentLength() {
|
||||||
|
return this->_req->content_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
const String& PsychicRequest::body()
|
||||||
|
{
|
||||||
|
return this->_body;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PsychicRequest::isMultipart()
|
||||||
|
{
|
||||||
|
const String& type = this->contentType();
|
||||||
|
|
||||||
|
return (this->contentType().indexOf("multipart/form-data") >= 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t PsychicRequest::redirect(const char *url)
|
||||||
|
{
|
||||||
|
PsychicResponse response(this);
|
||||||
|
response.setCode(301);
|
||||||
|
response.addHeader("Location", url);
|
||||||
|
|
||||||
|
return response.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PsychicRequest::hasCookie(const char *key)
|
||||||
|
{
|
||||||
|
char cookie[MAX_COOKIE_SIZE];
|
||||||
|
size_t cookieSize = MAX_COOKIE_SIZE;
|
||||||
|
esp_err_t err = httpd_req_get_cookie_val(this->_req, key, cookie, &cookieSize);
|
||||||
|
|
||||||
|
//did we get anything?
|
||||||
|
if (err == ESP_OK)
|
||||||
|
return true;
|
||||||
|
else if (err == ESP_ERR_HTTPD_RESULT_TRUNC)
|
||||||
|
ESP_LOGE(PH_TAG, "cookie too large (%d bytes).\n", cookieSize);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const String PsychicRequest::getCookie(const char *key)
|
||||||
|
{
|
||||||
|
char cookie[MAX_COOKIE_SIZE];
|
||||||
|
size_t cookieSize = MAX_COOKIE_SIZE;
|
||||||
|
esp_err_t err = httpd_req_get_cookie_val(this->_req, key, cookie, &cookieSize);
|
||||||
|
|
||||||
|
//did we get anything?
|
||||||
|
if (err == ESP_OK)
|
||||||
|
return String(cookie);
|
||||||
|
else
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
void PsychicRequest::loadParams()
|
||||||
|
{
|
||||||
|
//did we get a query string?
|
||||||
|
size_t query_len = httpd_req_get_url_query_len(_req);
|
||||||
|
if (query_len)
|
||||||
|
{
|
||||||
|
char query[query_len+1];
|
||||||
|
httpd_req_get_url_query_str(_req, query, sizeof(query));
|
||||||
|
_query.concat(query);
|
||||||
|
|
||||||
|
//parse them.
|
||||||
|
_addParams(_query);
|
||||||
|
}
|
||||||
|
|
||||||
|
//did we get form data as body?
|
||||||
|
if (this->method() == HTTP_POST && this->contentType() == "application/x-www-form-urlencoded")
|
||||||
|
{
|
||||||
|
_addParams(_body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PsychicRequest::_addParams(const String& params){
|
||||||
|
size_t start = 0;
|
||||||
|
while (start < params.length()){
|
||||||
|
int end = params.indexOf('&', start);
|
||||||
|
if (end < 0) end = params.length();
|
||||||
|
int equal = params.indexOf('=', start);
|
||||||
|
if (equal < 0 || equal > end) equal = end;
|
||||||
|
String name = params.substring(start, equal);
|
||||||
|
String value = equal + 1 < end ? params.substring(equal + 1, end) : String();
|
||||||
|
addParam(name, value);
|
||||||
|
start = end + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PsychicWebParameter * PsychicRequest::addParam(const String &name, const String &value, bool decode)
|
||||||
|
{
|
||||||
|
if (decode)
|
||||||
|
return addParam(new PsychicWebParameter(urlDecode(name.c_str()), urlDecode(value.c_str())));
|
||||||
|
else
|
||||||
|
return addParam(new PsychicWebParameter(name, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
PsychicWebParameter * PsychicRequest::addParam(PsychicWebParameter *param) {
|
||||||
|
_params.push_back(param);
|
||||||
|
return param;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PsychicRequest::hasParam(const char *key)
|
||||||
|
{
|
||||||
|
return getParam(key) != NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
PsychicWebParameter * PsychicRequest::getParam(const char *key)
|
||||||
|
{
|
||||||
|
for (auto *param : _params)
|
||||||
|
if (param->name().equals(key))
|
||||||
|
return param;
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PsychicRequest::hasSessionKey(const String& key)
|
||||||
|
{
|
||||||
|
return this->_session->find(key) != this->_session->end();
|
||||||
|
}
|
||||||
|
|
||||||
|
const String PsychicRequest::getSessionKey(const String& key)
|
||||||
|
{
|
||||||
|
auto it = this->_session->find(key);
|
||||||
|
if (it != this->_session->end())
|
||||||
|
return it->second;
|
||||||
|
else
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
void PsychicRequest::setSessionKey(const String& key, const String& value)
|
||||||
|
{
|
||||||
|
this->_session->insert(std::pair<String, String>(key, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
static const String md5str(const String &in){
|
||||||
|
MD5Builder md5 = MD5Builder();
|
||||||
|
md5.begin();
|
||||||
|
md5.add(in);
|
||||||
|
md5.calculate();
|
||||||
|
return md5.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PsychicRequest::authenticate(const char * username, const char * password)
|
||||||
|
{
|
||||||
|
if(hasHeader("Authorization"))
|
||||||
|
{
|
||||||
|
String authReq = header("Authorization");
|
||||||
|
if(authReq.startsWith("Basic")){
|
||||||
|
authReq = authReq.substring(6);
|
||||||
|
authReq.trim();
|
||||||
|
char toencodeLen = strlen(username)+strlen(password)+1;
|
||||||
|
char *toencode = new char[toencodeLen + 1];
|
||||||
|
if(toencode == NULL){
|
||||||
|
authReq = "";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
char *encoded = new char[base64_encode_expected_len(toencodeLen)+1];
|
||||||
|
if(encoded == NULL){
|
||||||
|
authReq = "";
|
||||||
|
delete[] toencode;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
sprintf(toencode, "%s:%s", username, password);
|
||||||
|
if(base64_encode_chars(toencode, toencodeLen, encoded) > 0 && authReq.equalsConstantTime(encoded)) {
|
||||||
|
authReq = "";
|
||||||
|
delete[] toencode;
|
||||||
|
delete[] encoded;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
delete[] toencode;
|
||||||
|
delete[] encoded;
|
||||||
|
}
|
||||||
|
else if(authReq.startsWith(F("Digest")))
|
||||||
|
{
|
||||||
|
authReq = authReq.substring(7);
|
||||||
|
String _username = _extractParam(authReq,F("username=\""),'\"');
|
||||||
|
if(!_username.length() || _username != String(username)) {
|
||||||
|
authReq = "";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// extracting required parameters for RFC 2069 simpler Digest
|
||||||
|
String _realm = _extractParam(authReq, F("realm=\""),'\"');
|
||||||
|
String _nonce = _extractParam(authReq, F("nonce=\""),'\"');
|
||||||
|
String _uri = _extractParam(authReq, F("uri=\""),'\"');
|
||||||
|
String _resp = _extractParam(authReq, F("response=\""),'\"');
|
||||||
|
String _opaque = _extractParam(authReq, F("opaque=\""),'\"');
|
||||||
|
|
||||||
|
if((!_realm.length()) || (!_nonce.length()) || (!_uri.length()) || (!_resp.length()) || (!_opaque.length())) {
|
||||||
|
authReq = "";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if((_opaque != this->getSessionKey("opaque")) || (_nonce != this->getSessionKey("nonce")) || (_realm != this->getSessionKey("realm")))
|
||||||
|
{
|
||||||
|
// DUMP(_opaque);
|
||||||
|
// DUMP(this->getSessionKey("opaque"));
|
||||||
|
// DUMP(_nonce);
|
||||||
|
// DUMP(this->getSessionKey("nonce"));
|
||||||
|
// DUMP(_realm);
|
||||||
|
// DUMP(this->getSessionKey("realm"));
|
||||||
|
authReq = "";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// parameters for the RFC 2617 newer Digest
|
||||||
|
String _nc,_cnonce;
|
||||||
|
if(authReq.indexOf("qop=auth") != -1 || authReq.indexOf("qop=\"auth\"") != -1) {
|
||||||
|
_nc = _extractParam(authReq, F("nc="), ',');
|
||||||
|
_cnonce = _extractParam(authReq, F("cnonce=\""),'\"');
|
||||||
|
}
|
||||||
|
String _H1 = md5str(String(username) + ':' + _realm + ':' + String(password));
|
||||||
|
ESP_LOGD(PH_TAG, "Hash of user:realm:pass=%s", _H1);
|
||||||
|
String _H2 = "";
|
||||||
|
if(_method == HTTP_GET){
|
||||||
|
_H2 = md5str(String(F("GET:")) + _uri);
|
||||||
|
}else if(_method == HTTP_POST){
|
||||||
|
_H2 = md5str(String(F("POST:")) + _uri);
|
||||||
|
}else if(_method == HTTP_PUT){
|
||||||
|
_H2 = md5str(String(F("PUT:")) + _uri);
|
||||||
|
}else if(_method == HTTP_DELETE){
|
||||||
|
_H2 = md5str(String(F("DELETE:")) + _uri);
|
||||||
|
}else{
|
||||||
|
_H2 = md5str(String(F("GET:")) + _uri);
|
||||||
|
}
|
||||||
|
ESP_LOGD(PH_TAG, "Hash of GET:uri=%s", _H2);
|
||||||
|
String _responsecheck = "";
|
||||||
|
if(authReq.indexOf("qop=auth") != -1 || authReq.indexOf("qop=\"auth\"") != -1) {
|
||||||
|
_responsecheck = md5str(_H1 + ':' + _nonce + ':' + _nc + ':' + _cnonce + F(":auth:") + _H2);
|
||||||
|
} else {
|
||||||
|
_responsecheck = md5str(_H1 + ':' + _nonce + ':' + _H2);
|
||||||
|
}
|
||||||
|
ESP_LOGD(PH_TAG, "The Proper response=%s", _responsecheck);
|
||||||
|
if(_resp == _responsecheck){
|
||||||
|
authReq = "";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
authReq = "";
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const String PsychicRequest::_extractParam(const String& authReq, const String& param, const char delimit)
|
||||||
|
{
|
||||||
|
int _begin = authReq.indexOf(param);
|
||||||
|
if (_begin == -1)
|
||||||
|
return "";
|
||||||
|
return authReq.substring(_begin+param.length(),authReq.indexOf(delimit,_begin+param.length()));
|
||||||
|
}
|
||||||
|
|
||||||
|
const String PsychicRequest::_getRandomHexString()
|
||||||
|
{
|
||||||
|
char buffer[33]; // buffer to hold 32 Hex Digit + /0
|
||||||
|
int i;
|
||||||
|
for(i = 0; i < 4; i++) {
|
||||||
|
sprintf (buffer + (i*8), "%08lx", esp_random());
|
||||||
|
}
|
||||||
|
return String(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t PsychicRequest::requestAuthentication(HTTPAuthMethod mode, const char* realm, const char* authFailMsg)
|
||||||
|
{
|
||||||
|
//what is thy realm, sire?
|
||||||
|
if(!strcmp(realm, ""))
|
||||||
|
this->setSessionKey("realm", "Login Required");
|
||||||
|
else
|
||||||
|
this->setSessionKey("realm", realm);
|
||||||
|
|
||||||
|
PsychicResponse response(this);
|
||||||
|
String authStr;
|
||||||
|
|
||||||
|
//what kind of auth?
|
||||||
|
if(mode == BASIC_AUTH)
|
||||||
|
{
|
||||||
|
authStr = "Basic realm=\"" + this->getSessionKey("realm") + "\"";
|
||||||
|
response.addHeader("WWW-Authenticate", authStr.c_str());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//only make new ones if we havent sent them yet
|
||||||
|
if (this->getSessionKey("nonce").isEmpty())
|
||||||
|
this->setSessionKey("nonce", _getRandomHexString());
|
||||||
|
if (this->getSessionKey("opaque").isEmpty())
|
||||||
|
this->setSessionKey("opaque", _getRandomHexString());
|
||||||
|
|
||||||
|
authStr = "Digest realm=\"" + this->getSessionKey("realm") + "\", qop=\"auth\", nonce=\"" + this->getSessionKey("nonce") + "\", opaque=\"" + this->getSessionKey("opaque") + "\"";
|
||||||
|
response.addHeader("WWW-Authenticate", authStr.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
//DUMP(authStr);
|
||||||
|
|
||||||
|
response.setCode(401);
|
||||||
|
response.setContentType("text/html");
|
||||||
|
response.setContent(authStr.c_str());
|
||||||
|
return response.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t PsychicRequest::reply(int code)
|
||||||
|
{
|
||||||
|
PsychicResponse response(this);
|
||||||
|
|
||||||
|
response.setCode(code);
|
||||||
|
response.setContentType("text/plain");
|
||||||
|
response.setContent(http_status_reason(code));
|
||||||
|
|
||||||
|
return response.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t PsychicRequest::reply(const char *content)
|
||||||
|
{
|
||||||
|
PsychicResponse response(this);
|
||||||
|
|
||||||
|
response.setCode(200);
|
||||||
|
response.setContentType("text/html");
|
||||||
|
response.setContent(content);
|
||||||
|
|
||||||
|
return response.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t PsychicRequest::reply(int code, const char *contentType, const char *content)
|
||||||
|
{
|
||||||
|
PsychicResponse response(this);
|
||||||
|
|
||||||
|
response.setCode(code);
|
||||||
|
response.setContentType(contentType);
|
||||||
|
response.setContent(content);
|
||||||
|
|
||||||
|
return response.send();
|
||||||
|
}
|
||||||
98
lib/PsychicHttp/src/PsychicRequest.h
Normal file
98
lib/PsychicHttp/src/PsychicRequest.h
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
#ifndef PsychicRequest_h
|
||||||
|
#define PsychicRequest_h
|
||||||
|
|
||||||
|
#include "PsychicCore.h"
|
||||||
|
#include "PsychicHttpServer.h"
|
||||||
|
#include "PsychicClient.h"
|
||||||
|
#include "PsychicWebParameter.h"
|
||||||
|
#include "PsychicResponse.h"
|
||||||
|
|
||||||
|
typedef std::map<String, String> SessionData;
|
||||||
|
|
||||||
|
enum Disposition { NONE, INLINE, ATTACHMENT, FORM_DATA};
|
||||||
|
|
||||||
|
struct ContentDisposition {
|
||||||
|
Disposition disposition;
|
||||||
|
String filename;
|
||||||
|
String name;
|
||||||
|
};
|
||||||
|
|
||||||
|
class PsychicRequest {
|
||||||
|
friend PsychicHttpServer;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
PsychicHttpServer *_server;
|
||||||
|
httpd_req_t *_req;
|
||||||
|
SessionData *_session;
|
||||||
|
PsychicClient *_client;
|
||||||
|
|
||||||
|
http_method _method;
|
||||||
|
String _uri;
|
||||||
|
String _query;
|
||||||
|
String _body;
|
||||||
|
|
||||||
|
std::list<PsychicWebParameter*> _params;
|
||||||
|
|
||||||
|
void _addParams(const String& params);
|
||||||
|
void _parseGETParams();
|
||||||
|
void _parsePOSTParams();
|
||||||
|
|
||||||
|
const String _extractParam(const String& authReq, const String& param, const char delimit);
|
||||||
|
const String _getRandomHexString();
|
||||||
|
|
||||||
|
public:
|
||||||
|
PsychicRequest(PsychicHttpServer *server, httpd_req_t *req);
|
||||||
|
virtual ~PsychicRequest();
|
||||||
|
|
||||||
|
void *_tempObject;
|
||||||
|
|
||||||
|
PsychicHttpServer * server();
|
||||||
|
httpd_req_t * request();
|
||||||
|
virtual PsychicClient * client();
|
||||||
|
|
||||||
|
bool isMultipart();
|
||||||
|
esp_err_t loadBody();
|
||||||
|
|
||||||
|
const String header(const char *name);
|
||||||
|
bool hasHeader(const char *name);
|
||||||
|
|
||||||
|
static void freeSession(void *ctx);
|
||||||
|
bool hasSessionKey(const String& key);
|
||||||
|
const String getSessionKey(const String& key);
|
||||||
|
void setSessionKey(const String& key, const String& value);
|
||||||
|
|
||||||
|
bool hasCookie(const char * key);
|
||||||
|
const String getCookie(const char * key);
|
||||||
|
|
||||||
|
http_method method(); // returns the HTTP method used as enum value (eg. HTTP_GET)
|
||||||
|
const String methodStr(); // returns the HTTP method used as a string (eg. "GET")
|
||||||
|
const String path(); // returns the request path (eg /page?foo=bar returns "/page")
|
||||||
|
const String& uri(); // returns the full request uri (eg /page?foo=bar)
|
||||||
|
const String& query(); // returns the request query data (eg /page?foo=bar returns "foo=bar")
|
||||||
|
const String host(); // returns the requested host (request to http://psychic.local/foo will return "psychic.local")
|
||||||
|
const String contentType(); // returns the Content-Type header value
|
||||||
|
size_t contentLength(); // returns the Content-Length header value
|
||||||
|
const String& body(); // returns the body of the request
|
||||||
|
const ContentDisposition getContentDisposition();
|
||||||
|
|
||||||
|
const String& queryString() { return query(); } //compatability function. same as query()
|
||||||
|
const String& url() { return uri(); } //compatability function. same as uri()
|
||||||
|
|
||||||
|
void loadParams();
|
||||||
|
PsychicWebParameter * addParam(PsychicWebParameter *param);
|
||||||
|
PsychicWebParameter * addParam(const String &name, const String &value, bool decode = true);
|
||||||
|
bool hasParam(const char *key);
|
||||||
|
PsychicWebParameter * getParam(const char *name);
|
||||||
|
|
||||||
|
const String getFilename();
|
||||||
|
|
||||||
|
bool authenticate(const char * username, const char * password);
|
||||||
|
esp_err_t requestAuthentication(HTTPAuthMethod mode, const char* realm, const char* authFailMsg);
|
||||||
|
|
||||||
|
esp_err_t redirect(const char *url);
|
||||||
|
esp_err_t reply(int code);
|
||||||
|
esp_err_t reply(const char *content);
|
||||||
|
esp_err_t reply(int code, const char *contentType, const char *content);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // PsychicRequest_h
|
||||||
155
lib/PsychicHttp/src/PsychicResponse.cpp
Normal file
155
lib/PsychicHttp/src/PsychicResponse.cpp
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
#include "PsychicResponse.h"
|
||||||
|
#include "PsychicRequest.h"
|
||||||
|
#include <http_status.h>
|
||||||
|
|
||||||
|
PsychicResponse::PsychicResponse(PsychicRequest *request) :
|
||||||
|
_request(request),
|
||||||
|
_code(200),
|
||||||
|
_status(""),
|
||||||
|
_contentLength(0),
|
||||||
|
_body("")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
PsychicResponse::~PsychicResponse()
|
||||||
|
{
|
||||||
|
//clean up our header variables. we have to do this since httpd_resp_send doesn't store copies
|
||||||
|
for (HTTPHeader header : _headers)
|
||||||
|
{
|
||||||
|
free(header.field);
|
||||||
|
free(header.value);
|
||||||
|
}
|
||||||
|
_headers.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PsychicResponse::addHeader(const char *field, const char *value)
|
||||||
|
{
|
||||||
|
//these get freed during send()
|
||||||
|
HTTPHeader header;
|
||||||
|
header.field =(char *)malloc(strlen(field)+1);
|
||||||
|
header.value = (char *)malloc(strlen(value)+1);
|
||||||
|
|
||||||
|
strlcpy(header.field, field, strlen(field)+1);
|
||||||
|
strlcpy(header.value, value, strlen(value)+1);
|
||||||
|
|
||||||
|
_headers.push_back(header);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PsychicResponse::setCookie(const char *name, const char *value, unsigned long secondsFromNow, const char *extras)
|
||||||
|
{
|
||||||
|
time_t now = time(nullptr);
|
||||||
|
|
||||||
|
String output;
|
||||||
|
output = urlEncode(name) + "=" + urlEncode(value);
|
||||||
|
|
||||||
|
//if current time isn't modern, default to using max age
|
||||||
|
if (now < 1700000000)
|
||||||
|
output += "; Max-Age=" + String(secondsFromNow);
|
||||||
|
//otherwise, set an expiration date
|
||||||
|
else
|
||||||
|
{
|
||||||
|
time_t expirationTimestamp = now + secondsFromNow;
|
||||||
|
|
||||||
|
// Convert the expiration timestamp to a formatted string for the "expires" attribute
|
||||||
|
struct tm* tmInfo = gmtime(&expirationTimestamp);
|
||||||
|
char expires[30];
|
||||||
|
strftime(expires, sizeof(expires), "%a, %d %b %Y %H:%M:%S GMT", tmInfo);
|
||||||
|
output += "; Expires=" + String(expires);
|
||||||
|
}
|
||||||
|
|
||||||
|
//did we get any extras?
|
||||||
|
if (strlen(extras))
|
||||||
|
output += "; " + String(extras);
|
||||||
|
|
||||||
|
//okay, add it in.
|
||||||
|
addHeader("Set-Cookie", output.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
// time_t now = time(nullptr);
|
||||||
|
// // Set the cookie with the "expires" attribute
|
||||||
|
|
||||||
|
void PsychicResponse::setCode(int code)
|
||||||
|
{
|
||||||
|
_code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PsychicResponse::setContentType(const char *contentType)
|
||||||
|
{
|
||||||
|
httpd_resp_set_type(_request->request(), contentType);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PsychicResponse::setContent(const char *content)
|
||||||
|
{
|
||||||
|
_body = content;
|
||||||
|
setContentLength(strlen(content));
|
||||||
|
}
|
||||||
|
|
||||||
|
void PsychicResponse::setContent(const uint8_t *content, size_t len)
|
||||||
|
{
|
||||||
|
_body = (char *)content;
|
||||||
|
setContentLength(len);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char * PsychicResponse::getContent()
|
||||||
|
{
|
||||||
|
return _body;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t PsychicResponse::getContentLength()
|
||||||
|
{
|
||||||
|
return _contentLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t PsychicResponse::send()
|
||||||
|
{
|
||||||
|
//esp-idf makes you set the whole status.
|
||||||
|
sprintf(_status, "%u %s", _code, http_status_reason(_code));
|
||||||
|
httpd_resp_set_status(_request->request(), _status);
|
||||||
|
|
||||||
|
//our headers too
|
||||||
|
this->sendHeaders();
|
||||||
|
|
||||||
|
//now send it off
|
||||||
|
esp_err_t err = httpd_resp_send(_request->request(), getContent(), getContentLength());
|
||||||
|
|
||||||
|
//did something happen?
|
||||||
|
if (err != ESP_OK)
|
||||||
|
ESP_LOGE(PH_TAG, "Send response failed (%s)", esp_err_to_name(err));
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PsychicResponse::sendHeaders()
|
||||||
|
{
|
||||||
|
//get our global headers out of the way first
|
||||||
|
for (HTTPHeader header : DefaultHeaders::Instance().getHeaders())
|
||||||
|
httpd_resp_set_hdr(_request->request(), header.field, header.value);
|
||||||
|
|
||||||
|
//now do our individual headers
|
||||||
|
for (HTTPHeader header : _headers)
|
||||||
|
httpd_resp_set_hdr(this->_request->request(), header.field, header.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t PsychicResponse::sendChunk(uint8_t *chunk, size_t chunksize)
|
||||||
|
{
|
||||||
|
/* Send the buffer contents as HTTP response chunk */
|
||||||
|
esp_err_t err = httpd_resp_send_chunk(this->_request->request(), (char *)chunk, chunksize);
|
||||||
|
if (err != ESP_OK)
|
||||||
|
{
|
||||||
|
ESP_LOGE(PH_TAG, "File sending failed (%s)", esp_err_to_name(err));
|
||||||
|
|
||||||
|
/* Abort sending file */
|
||||||
|
httpd_resp_sendstr_chunk(this->_request->request(), NULL);
|
||||||
|
|
||||||
|
/* Respond with 500 Internal Server Error */
|
||||||
|
httpd_resp_send_err(this->_request->request(), HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to send file");
|
||||||
|
}
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t PsychicResponse::finishChunking()
|
||||||
|
{
|
||||||
|
/* Respond with an empty chunk to signal HTTP response completion */
|
||||||
|
return httpd_resp_send_chunk(this->_request->request(), NULL, 0);
|
||||||
|
}
|
||||||
46
lib/PsychicHttp/src/PsychicResponse.h
Normal file
46
lib/PsychicHttp/src/PsychicResponse.h
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
#ifndef PsychicResponse_h
|
||||||
|
#define PsychicResponse_h
|
||||||
|
|
||||||
|
#include "PsychicCore.h"
|
||||||
|
#include "time.h"
|
||||||
|
|
||||||
|
class PsychicRequest;
|
||||||
|
|
||||||
|
class PsychicResponse
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
PsychicRequest *_request;
|
||||||
|
|
||||||
|
int _code;
|
||||||
|
char _status[60];
|
||||||
|
std::list<HTTPHeader> _headers;
|
||||||
|
int64_t _contentLength;
|
||||||
|
const char * _body;
|
||||||
|
|
||||||
|
public:
|
||||||
|
PsychicResponse(PsychicRequest *request);
|
||||||
|
virtual ~PsychicResponse();
|
||||||
|
|
||||||
|
void setCode(int code);
|
||||||
|
|
||||||
|
void setContentType(const char *contentType);
|
||||||
|
void setContentLength(int64_t contentLength) { _contentLength = contentLength; }
|
||||||
|
int64_t getContentLength(int64_t contentLength) { return _contentLength; }
|
||||||
|
|
||||||
|
void addHeader(const char *field, const char *value);
|
||||||
|
|
||||||
|
void setCookie(const char *key, const char *value, unsigned long max_age = 60*60*24*30, const char *extras = "");
|
||||||
|
|
||||||
|
void setContent(const char *content);
|
||||||
|
void setContent(const uint8_t *content, size_t len);
|
||||||
|
|
||||||
|
const char * getContent();
|
||||||
|
size_t getContentLength();
|
||||||
|
|
||||||
|
virtual esp_err_t send();
|
||||||
|
void sendHeaders();
|
||||||
|
esp_err_t sendChunk(uint8_t *chunk, size_t chunksize);
|
||||||
|
esp_err_t finishChunking();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // PsychicResponse_h
|
||||||
193
lib/PsychicHttp/src/PsychicStaticFileHander.cpp
Normal file
193
lib/PsychicHttp/src/PsychicStaticFileHander.cpp
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
#include "PsychicStaticFileHandler.h"
|
||||||
|
|
||||||
|
/*************************************/
|
||||||
|
/* PsychicStaticFileHandler */
|
||||||
|
/*************************************/
|
||||||
|
|
||||||
|
PsychicStaticFileHandler::PsychicStaticFileHandler(const char* uri, FS& fs, const char* path, const char* cache_control)
|
||||||
|
: _fs(fs), _uri(uri), _path(path), _default_file("index.html"), _cache_control(cache_control), _last_modified("")
|
||||||
|
{
|
||||||
|
// Ensure leading '/'
|
||||||
|
if (_uri.length() == 0 || _uri[0] != '/') _uri = "/" + _uri;
|
||||||
|
if (_path.length() == 0 || _path[0] != '/') _path = "/" + _path;
|
||||||
|
|
||||||
|
// If path ends with '/' we assume a hint that this is a directory to improve performance.
|
||||||
|
// However - if it does not end with '/' we, can't assume a file, path can still be a directory.
|
||||||
|
_isDir = _path[_path.length()-1] == '/';
|
||||||
|
|
||||||
|
// Remove the trailing '/' so we can handle default file
|
||||||
|
// Notice that root will be "" not "/"
|
||||||
|
if (_uri[_uri.length()-1] == '/') _uri = _uri.substring(0, _uri.length()-1);
|
||||||
|
if (_path[_path.length()-1] == '/') _path = _path.substring(0, _path.length()-1);
|
||||||
|
|
||||||
|
// Reset stats
|
||||||
|
_gzipFirst = false;
|
||||||
|
_gzipStats = 0xF8;
|
||||||
|
}
|
||||||
|
|
||||||
|
PsychicStaticFileHandler& PsychicStaticFileHandler::setIsDir(bool isDir){
|
||||||
|
_isDir = isDir;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
PsychicStaticFileHandler& PsychicStaticFileHandler::setDefaultFile(const char* filename){
|
||||||
|
_default_file = String(filename);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
PsychicStaticFileHandler& PsychicStaticFileHandler::setCacheControl(const char* cache_control){
|
||||||
|
_cache_control = String(cache_control);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
PsychicStaticFileHandler& PsychicStaticFileHandler::setLastModified(const char* last_modified){
|
||||||
|
_last_modified = String(last_modified);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
PsychicStaticFileHandler& PsychicStaticFileHandler::setLastModified(struct tm* last_modified){
|
||||||
|
char result[30];
|
||||||
|
strftime (result,30,"%a, %d %b %Y %H:%M:%S %Z", last_modified);
|
||||||
|
return setLastModified((const char *)result);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PsychicStaticFileHandler::canHandle(PsychicRequest *request)
|
||||||
|
{
|
||||||
|
if(request->method() != HTTP_GET || !request->uri().startsWith(_uri) )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (_getFile(request))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PsychicStaticFileHandler::_getFile(PsychicRequest *request)
|
||||||
|
{
|
||||||
|
// Remove the found uri
|
||||||
|
String path = request->uri().substring(_uri.length());
|
||||||
|
|
||||||
|
// We can skip the file check and look for default if request is to the root of a directory or that request path ends with '/'
|
||||||
|
bool canSkipFileCheck = (_isDir && path.length() == 0) || (path.length() && path[path.length()-1] == '/');
|
||||||
|
|
||||||
|
path = _path + path;
|
||||||
|
|
||||||
|
// Do we have a file or .gz file
|
||||||
|
if (!canSkipFileCheck && _fileExists(path))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// Can't handle if not default file
|
||||||
|
if (_default_file.length() == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Try to add default file, ensure there is a trailing '/' ot the path.
|
||||||
|
if (path.length() == 0 || path[path.length()-1] != '/')
|
||||||
|
path += "/";
|
||||||
|
path += _default_file;
|
||||||
|
|
||||||
|
return _fileExists(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define FILE_IS_REAL(f) (f == true && !f.isDirectory())
|
||||||
|
|
||||||
|
bool PsychicStaticFileHandler::_fileExists(const String& path)
|
||||||
|
{
|
||||||
|
bool fileFound = false;
|
||||||
|
bool gzipFound = false;
|
||||||
|
|
||||||
|
String gzip = path + ".gz";
|
||||||
|
|
||||||
|
if (_gzipFirst) {
|
||||||
|
_file = _fs.open(gzip, "r");
|
||||||
|
gzipFound = FILE_IS_REAL(_file);
|
||||||
|
if (!gzipFound){
|
||||||
|
_file = _fs.open(path, "r");
|
||||||
|
fileFound = FILE_IS_REAL(_file);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_file = _fs.open(path, "r");
|
||||||
|
fileFound = FILE_IS_REAL(_file);
|
||||||
|
if (!fileFound){
|
||||||
|
_file = _fs.open(gzip, "r");
|
||||||
|
gzipFound = FILE_IS_REAL(_file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool found = fileFound || gzipFound;
|
||||||
|
|
||||||
|
if (found)
|
||||||
|
{
|
||||||
|
_filename = path;
|
||||||
|
|
||||||
|
// Calculate gzip statistic
|
||||||
|
_gzipStats = (_gzipStats << 1) + (gzipFound ? 1 : 0);
|
||||||
|
if (_gzipStats == 0x00) _gzipFirst = false; // All files are not gzip
|
||||||
|
else if (_gzipStats == 0xFF) _gzipFirst = true; // All files are gzip
|
||||||
|
else _gzipFirst = _countBits(_gzipStats) > 4; // IF we have more gzip files - try gzip first
|
||||||
|
}
|
||||||
|
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t PsychicStaticFileHandler::_countBits(const uint8_t value) const
|
||||||
|
{
|
||||||
|
uint8_t w = value;
|
||||||
|
uint8_t n;
|
||||||
|
for (n=0; w!=0; n++) w&=w-1;
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t PsychicStaticFileHandler::handleRequest(PsychicRequest *request)
|
||||||
|
{
|
||||||
|
if (_file == true)
|
||||||
|
{
|
||||||
|
DUMP(_filename);
|
||||||
|
|
||||||
|
//is it not modified?
|
||||||
|
String etag = String(_file.size());
|
||||||
|
if (_last_modified.length() && _last_modified == request->header("If-Modified-Since"))
|
||||||
|
{
|
||||||
|
DUMP("Last Modified Hit");
|
||||||
|
TRACE();
|
||||||
|
_file.close();
|
||||||
|
request->reply(304); // Not modified
|
||||||
|
}
|
||||||
|
//does our Etag match?
|
||||||
|
else if (_cache_control.length() && request->hasHeader("If-None-Match") && request->header("If-None-Match").equals(etag))
|
||||||
|
{
|
||||||
|
DUMP("Etag Hit");
|
||||||
|
DUMP(etag);
|
||||||
|
DUMP(_cache_control);
|
||||||
|
|
||||||
|
_file.close();
|
||||||
|
|
||||||
|
PsychicResponse response(request);
|
||||||
|
response.addHeader("Cache-Control", _cache_control.c_str());
|
||||||
|
response.addHeader("ETag", etag.c_str());
|
||||||
|
response.setCode(304);
|
||||||
|
response.send();
|
||||||
|
}
|
||||||
|
//nope, send them the full file.
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DUMP("No cache hit");
|
||||||
|
DUMP(_last_modified);
|
||||||
|
DUMP(_cache_control);
|
||||||
|
|
||||||
|
PsychicFileResponse response(request, _fs, _filename);
|
||||||
|
|
||||||
|
if (_last_modified.length())
|
||||||
|
response.addHeader("Last-Modified", _last_modified.c_str());
|
||||||
|
if (_cache_control.length()) {
|
||||||
|
response.addHeader("Cache-Control", _cache_control.c_str());
|
||||||
|
response.addHeader("ETag", etag.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.send();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return request->reply(404);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
41
lib/PsychicHttp/src/PsychicStaticFileHandler.h
Normal file
41
lib/PsychicHttp/src/PsychicStaticFileHandler.h
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
#ifndef PsychicStaticFileHandler_h
|
||||||
|
#define PsychicStaticFileHandler_h
|
||||||
|
|
||||||
|
#include "PsychicCore.h"
|
||||||
|
#include "PsychicWebHandler.h"
|
||||||
|
#include "PsychicRequest.h"
|
||||||
|
#include "PsychicResponse.h"
|
||||||
|
#include "PsychicFileResponse.h"
|
||||||
|
|
||||||
|
class PsychicStaticFileHandler : public PsychicWebHandler {
|
||||||
|
using File = fs::File;
|
||||||
|
using FS = fs::FS;
|
||||||
|
private:
|
||||||
|
bool _getFile(PsychicRequest *request);
|
||||||
|
bool _fileExists(const String& path);
|
||||||
|
uint8_t _countBits(const uint8_t value) const;
|
||||||
|
protected:
|
||||||
|
FS _fs;
|
||||||
|
File _file;
|
||||||
|
String _filename;
|
||||||
|
String _uri;
|
||||||
|
String _path;
|
||||||
|
String _default_file;
|
||||||
|
String _cache_control;
|
||||||
|
String _last_modified;
|
||||||
|
bool _isDir;
|
||||||
|
bool _gzipFirst;
|
||||||
|
uint8_t _gzipStats;
|
||||||
|
public:
|
||||||
|
PsychicStaticFileHandler(const char* uri, FS& fs, const char* path, const char* cache_control);
|
||||||
|
bool canHandle(PsychicRequest *request) override;
|
||||||
|
esp_err_t handleRequest(PsychicRequest *request) override;
|
||||||
|
PsychicStaticFileHandler& setIsDir(bool isDir);
|
||||||
|
PsychicStaticFileHandler& setDefaultFile(const char* filename);
|
||||||
|
PsychicStaticFileHandler& setCacheControl(const char* cache_control);
|
||||||
|
PsychicStaticFileHandler& setLastModified(const char* last_modified);
|
||||||
|
PsychicStaticFileHandler& setLastModified(struct tm* last_modified);
|
||||||
|
//PsychicStaticFileHandler& setTemplateProcessor(AwsTemplateProcessor newCallback) {_callback = newCallback; return *this;}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* PsychicHttp_h */
|
||||||
89
lib/PsychicHttp/src/PsychicStreamResponse.cpp
Normal file
89
lib/PsychicHttp/src/PsychicStreamResponse.cpp
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
#include "PsychicStreamResponse.h"
|
||||||
|
#include "PsychicResponse.h"
|
||||||
|
#include "PsychicRequest.h"
|
||||||
|
|
||||||
|
PsychicStreamResponse::PsychicStreamResponse(PsychicRequest *request, const String& contentType)
|
||||||
|
: PsychicResponse(request), _buffer(NULL) {
|
||||||
|
|
||||||
|
setContentType(contentType.c_str());
|
||||||
|
addHeader("Content-Disposition", "inline");
|
||||||
|
}
|
||||||
|
|
||||||
|
PsychicStreamResponse::PsychicStreamResponse(PsychicRequest *request, const String& contentType, const String& name)
|
||||||
|
: PsychicResponse(request), _buffer(NULL) {
|
||||||
|
|
||||||
|
setContentType(contentType.c_str());
|
||||||
|
|
||||||
|
char buf[26+name.length()];
|
||||||
|
snprintf(buf, sizeof (buf), "attachment; filename=\"%s\"", name);
|
||||||
|
addHeader("Content-Disposition", buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
PsychicStreamResponse::~PsychicStreamResponse()
|
||||||
|
{
|
||||||
|
endSend();
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t PsychicStreamResponse::beginSend()
|
||||||
|
{
|
||||||
|
if(_buffer)
|
||||||
|
return ESP_OK;
|
||||||
|
|
||||||
|
//Buffer to hold ChunkPrinter and stream buffer. Using placement new will keep us at a single allocation.
|
||||||
|
_buffer = (uint8_t*)malloc(STREAM_CHUNK_SIZE + sizeof(ChunkPrinter));
|
||||||
|
|
||||||
|
if(!_buffer)
|
||||||
|
{
|
||||||
|
/* Respond with 500 Internal Server Error */
|
||||||
|
httpd_resp_send_err(_request->request(), HTTPD_500_INTERNAL_SERVER_ERROR, "Unable to allocate memory.");
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
_printer = new (_buffer) ChunkPrinter(this, _buffer + sizeof(ChunkPrinter), STREAM_CHUNK_SIZE);
|
||||||
|
|
||||||
|
sendHeaders();
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t PsychicStreamResponse::endSend()
|
||||||
|
{
|
||||||
|
esp_err_t err = ESP_OK;
|
||||||
|
|
||||||
|
if(!_buffer)
|
||||||
|
err = ESP_FAIL;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//flush & send remaining data.
|
||||||
|
_printer->flush();
|
||||||
|
err = finishChunking();
|
||||||
|
|
||||||
|
//Free memory
|
||||||
|
_printer->~ChunkPrinter();
|
||||||
|
free(_buffer);
|
||||||
|
_buffer = NULL;
|
||||||
|
}
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PsychicStreamResponse::flush()
|
||||||
|
{
|
||||||
|
if(_buffer)
|
||||||
|
_printer->flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t PsychicStreamResponse::write(uint8_t data)
|
||||||
|
{
|
||||||
|
return _buffer ? _printer->write(data) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t PsychicStreamResponse::copyFrom(Stream &stream)
|
||||||
|
{
|
||||||
|
size_t sentCount = 0;
|
||||||
|
|
||||||
|
if(_buffer)
|
||||||
|
{
|
||||||
|
while(stream.available())
|
||||||
|
sentCount += _printer->write(stream.read());
|
||||||
|
}
|
||||||
|
return sentCount;
|
||||||
|
}
|
||||||
33
lib/PsychicHttp/src/PsychicStreamResponse.h
Normal file
33
lib/PsychicHttp/src/PsychicStreamResponse.h
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
#ifndef PsychicStreamResponse_h
|
||||||
|
#define PsychicStreamResponse_h
|
||||||
|
|
||||||
|
#include "PsychicCore.h"
|
||||||
|
#include "PsychicResponse.h"
|
||||||
|
#include "ChunkPrinter.h"
|
||||||
|
|
||||||
|
class PsychicRequest;
|
||||||
|
|
||||||
|
class PsychicStreamResponse : public PsychicResponse, public Print
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
ChunkPrinter *_printer;
|
||||||
|
uint8_t *_buffer;
|
||||||
|
public:
|
||||||
|
|
||||||
|
PsychicStreamResponse(PsychicRequest *request, const String& contentType);
|
||||||
|
PsychicStreamResponse(PsychicRequest *request, const String& contentType, const String& name); //Download
|
||||||
|
|
||||||
|
~PsychicStreamResponse();
|
||||||
|
|
||||||
|
esp_err_t beginSend();
|
||||||
|
esp_err_t endSend();
|
||||||
|
|
||||||
|
virtual void flush() override;
|
||||||
|
|
||||||
|
size_t write(uint8_t data);
|
||||||
|
size_t copyFrom(Stream &stream);
|
||||||
|
|
||||||
|
using Print::write;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // PsychicStreamResponse_h
|
||||||
395
lib/PsychicHttp/src/PsychicUploadHandler.cpp
Normal file
395
lib/PsychicHttp/src/PsychicUploadHandler.cpp
Normal file
@@ -0,0 +1,395 @@
|
|||||||
|
#include "PsychicUploadHandler.h"
|
||||||
|
|
||||||
|
PsychicUploadHandler::PsychicUploadHandler() :
|
||||||
|
PsychicWebHandler()
|
||||||
|
, _temp()
|
||||||
|
, _parsedLength(0)
|
||||||
|
, _multiParseState(EXPECT_BOUNDARY)
|
||||||
|
, _boundaryPosition(0)
|
||||||
|
, _itemStartIndex(0)
|
||||||
|
, _itemSize(0)
|
||||||
|
, _itemName()
|
||||||
|
, _itemFilename()
|
||||||
|
, _itemType()
|
||||||
|
, _itemValue()
|
||||||
|
, _itemBuffer(0)
|
||||||
|
, _itemBufferIndex(0)
|
||||||
|
, _itemIsFile(false)
|
||||||
|
{}
|
||||||
|
PsychicUploadHandler::~PsychicUploadHandler() {}
|
||||||
|
|
||||||
|
bool PsychicUploadHandler::canHandle(PsychicRequest *request) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t PsychicUploadHandler::handleRequest(PsychicRequest *request)
|
||||||
|
{
|
||||||
|
esp_err_t err = ESP_OK;
|
||||||
|
|
||||||
|
//save it for later (multipart)
|
||||||
|
_request = request;
|
||||||
|
|
||||||
|
/* File cannot be larger than a limit */
|
||||||
|
if (request->contentLength() > request->server()->maxUploadSize)
|
||||||
|
{
|
||||||
|
ESP_LOGE(PH_TAG, "File too large : %d bytes", request->contentLength());
|
||||||
|
|
||||||
|
/* Respond with 400 Bad Request */
|
||||||
|
char error[50];
|
||||||
|
sprintf(error, "File size must be less than %u bytes!", request->server()->maxUploadSize);
|
||||||
|
httpd_resp_send_err(request->request(), HTTPD_400_BAD_REQUEST, error);
|
||||||
|
|
||||||
|
/* Return failure to close underlying connection else the incoming file content will keep the socket busy */
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
//we might want to access some of these params
|
||||||
|
request->loadParams();
|
||||||
|
|
||||||
|
//TODO: support for the 100 header. not sure if we can do it.
|
||||||
|
// if (request->header("Expect").equals("100-continue"))
|
||||||
|
// {
|
||||||
|
// char response[] = "100 Continue";
|
||||||
|
// httpd_socket_send(self->server, httpd_req_to_sockfd(req), response, strlen(response), 0);
|
||||||
|
// }
|
||||||
|
|
||||||
|
//2 types of upload requests
|
||||||
|
if (request->isMultipart())
|
||||||
|
err = _multipartUploadHandler(request);
|
||||||
|
else
|
||||||
|
err = _basicUploadHandler(request);
|
||||||
|
|
||||||
|
//we can also call onRequest for some final processing and response
|
||||||
|
if (err == ESP_OK)
|
||||||
|
{
|
||||||
|
if (_requestCallback != NULL)
|
||||||
|
err = _requestCallback(request);
|
||||||
|
else
|
||||||
|
err = request->reply("Upload Successful.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
request->reply(500, "text/html", "Error processing upload.");
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t PsychicUploadHandler::_basicUploadHandler(PsychicRequest *request)
|
||||||
|
{
|
||||||
|
esp_err_t err = ESP_OK;
|
||||||
|
|
||||||
|
String filename = request->getFilename();
|
||||||
|
|
||||||
|
/* Retrieve the pointer to scratch buffer for temporary storage */
|
||||||
|
char *buf = (char *)malloc(FILE_CHUNK_SIZE);
|
||||||
|
int received;
|
||||||
|
unsigned long index = 0;
|
||||||
|
|
||||||
|
/* Content length of the request gives the size of the file being uploaded */
|
||||||
|
int remaining = request->contentLength();
|
||||||
|
|
||||||
|
while (remaining > 0)
|
||||||
|
{
|
||||||
|
#ifdef ENABLE_ASYNC
|
||||||
|
httpd_sess_update_lru_counter(request->server()->server, request->client()->socket());
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ESP_LOGI(PH_TAG, "Remaining size : %d", remaining);
|
||||||
|
|
||||||
|
/* Receive the file part by part into a buffer */
|
||||||
|
if ((received = httpd_req_recv(request->request(), buf, min(remaining, FILE_CHUNK_SIZE))) <= 0)
|
||||||
|
{
|
||||||
|
/* Retry if timeout occurred */
|
||||||
|
if (received == HTTPD_SOCK_ERR_TIMEOUT)
|
||||||
|
continue;
|
||||||
|
//bail if we got an error
|
||||||
|
else if (received == HTTPD_SOCK_ERR_FAIL)
|
||||||
|
{
|
||||||
|
ESP_LOGE(PH_TAG, "Socket error");
|
||||||
|
err = ESP_FAIL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//call our upload callback here.
|
||||||
|
if (_uploadCallback != NULL)
|
||||||
|
{
|
||||||
|
err = _uploadCallback(request, filename, index, (uint8_t *)buf, received, (remaining - received == 0));
|
||||||
|
if (err != ESP_OK)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ESP_LOGE(PH_TAG, "No upload callback specified!");
|
||||||
|
err = ESP_FAIL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Keep track of remaining size of the file left to be uploaded */
|
||||||
|
remaining -= received;
|
||||||
|
index += received;
|
||||||
|
}
|
||||||
|
|
||||||
|
//dont forget to free our buffer
|
||||||
|
free(buf);
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t PsychicUploadHandler::_multipartUploadHandler(PsychicRequest *request)
|
||||||
|
{
|
||||||
|
esp_err_t err = ESP_OK;
|
||||||
|
|
||||||
|
String value = request->header("Content-Type");
|
||||||
|
if (value.startsWith("multipart/")){
|
||||||
|
_boundary = value.substring(value.indexOf('=')+1);
|
||||||
|
_boundary.replace("\"","");
|
||||||
|
} else {
|
||||||
|
ESP_LOGE(PH_TAG, "No multipart boundary found.");
|
||||||
|
return request->reply(400, "text/html", "No multipart boundary found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
char *buf = (char *)malloc(FILE_CHUNK_SIZE);
|
||||||
|
int received;
|
||||||
|
unsigned long index = 0;
|
||||||
|
|
||||||
|
/* Content length of the request gives the size of the file being uploaded */
|
||||||
|
int remaining = request->contentLength();
|
||||||
|
|
||||||
|
while (remaining > 0)
|
||||||
|
{
|
||||||
|
#ifdef ENABLE_ASYNC
|
||||||
|
httpd_sess_update_lru_counter(request->server()->server, request->client()->socket());
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ESP_LOGI(PH_TAG, "Remaining size : %d", remaining);
|
||||||
|
|
||||||
|
/* Receive the file part by part into a buffer */
|
||||||
|
if ((received = httpd_req_recv(request->request(), buf, min(remaining, FILE_CHUNK_SIZE))) <= 0)
|
||||||
|
{
|
||||||
|
/* Retry if timeout occurred */
|
||||||
|
if (received == HTTPD_SOCK_ERR_TIMEOUT)
|
||||||
|
continue;
|
||||||
|
//bail if we got an error
|
||||||
|
else if (received == HTTPD_SOCK_ERR_FAIL)
|
||||||
|
{
|
||||||
|
ESP_LOGE(PH_TAG, "Socket error");
|
||||||
|
err = ESP_FAIL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//parse it 1 byte at a time.
|
||||||
|
for (int i=0; i<received; i++)
|
||||||
|
{
|
||||||
|
/* Keep track of remaining size of the file left to be uploaded */
|
||||||
|
remaining--;
|
||||||
|
index++;
|
||||||
|
|
||||||
|
//send it to our parser
|
||||||
|
_parseMultipartPostByte(buf[i], !remaining);
|
||||||
|
_parsedLength++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//dont forget to free our buffer
|
||||||
|
free(buf);
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
PsychicUploadHandler * PsychicUploadHandler::onUpload(PsychicUploadCallback fn) {
|
||||||
|
_uploadCallback = fn;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PsychicUploadHandler::_handleUploadByte(uint8_t data, bool last)
|
||||||
|
{
|
||||||
|
_itemBuffer[_itemBufferIndex++] = data;
|
||||||
|
|
||||||
|
if(last || _itemBufferIndex == FILE_CHUNK_SIZE)
|
||||||
|
{
|
||||||
|
if(_uploadCallback)
|
||||||
|
_uploadCallback(_request, _itemFilename, _itemSize - _itemBufferIndex, _itemBuffer, _itemBufferIndex, last);
|
||||||
|
_itemBufferIndex = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#define itemWriteByte(b) do { _itemSize++; if(_itemIsFile) _handleUploadByte(b, last); else _itemValue+=(char)(b); } while(0)
|
||||||
|
|
||||||
|
void PsychicUploadHandler::_parseMultipartPostByte(uint8_t data, bool last)
|
||||||
|
{
|
||||||
|
if (_multiParseState == PARSE_ERROR)
|
||||||
|
{
|
||||||
|
// not sure we can end up with an error during buffer fill, but jsut to be safe
|
||||||
|
if (_itemBuffer != NULL)
|
||||||
|
{
|
||||||
|
free(_itemBuffer);
|
||||||
|
_itemBuffer = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!_parsedLength){
|
||||||
|
_multiParseState = EXPECT_BOUNDARY;
|
||||||
|
_temp = String();
|
||||||
|
_itemName = String();
|
||||||
|
_itemFilename = String();
|
||||||
|
_itemType = String();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(_multiParseState == WAIT_FOR_RETURN1){
|
||||||
|
if(data != '\r'){
|
||||||
|
itemWriteByte(data);
|
||||||
|
} else {
|
||||||
|
_multiParseState = EXPECT_FEED1;
|
||||||
|
}
|
||||||
|
} else if(_multiParseState == EXPECT_BOUNDARY){
|
||||||
|
if(_parsedLength < 2 && data != '-'){
|
||||||
|
ESP_LOGE(PH_TAG, "Multipart: No boundary");
|
||||||
|
_multiParseState = PARSE_ERROR;
|
||||||
|
return;
|
||||||
|
} else if(_parsedLength - 2 < _boundary.length() && _boundary.c_str()[_parsedLength - 2] != data){
|
||||||
|
ESP_LOGE(PH_TAG, "Multipart: Multipart malformed");
|
||||||
|
_multiParseState = PARSE_ERROR;
|
||||||
|
return;
|
||||||
|
} else if(_parsedLength - 2 == _boundary.length() && data != '\r'){
|
||||||
|
ESP_LOGE(PH_TAG, "Multipart: Multipart missing carriage return");
|
||||||
|
_multiParseState = PARSE_ERROR;
|
||||||
|
return;
|
||||||
|
} else if(_parsedLength - 3 == _boundary.length()){
|
||||||
|
if(data != '\n'){
|
||||||
|
ESP_LOGE(PH_TAG, "Multipart: Multipart missing newline");
|
||||||
|
_multiParseState = PARSE_ERROR;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_multiParseState = PARSE_HEADERS;
|
||||||
|
_itemIsFile = false;
|
||||||
|
}
|
||||||
|
} else if(_multiParseState == PARSE_HEADERS){
|
||||||
|
if((char)data != '\r' && (char)data != '\n')
|
||||||
|
_temp += (char)data;
|
||||||
|
if((char)data == '\n'){
|
||||||
|
if(_temp.length()){
|
||||||
|
if(_temp.length() > 12 && _temp.substring(0, 12).equalsIgnoreCase("Content-Type")){
|
||||||
|
_itemType = _temp.substring(14);
|
||||||
|
_itemIsFile = true;
|
||||||
|
} else if(_temp.length() > 19 && _temp.substring(0, 19).equalsIgnoreCase("Content-Disposition")){
|
||||||
|
_temp = _temp.substring(_temp.indexOf(';') + 2);
|
||||||
|
while(_temp.indexOf(';') > 0){
|
||||||
|
String name = _temp.substring(0, _temp.indexOf('='));
|
||||||
|
String nameVal = _temp.substring(_temp.indexOf('=') + 2, _temp.indexOf(';') - 1);
|
||||||
|
if(name == "name"){
|
||||||
|
_itemName = nameVal;
|
||||||
|
} else if(name == "filename"){
|
||||||
|
_itemFilename = nameVal;
|
||||||
|
_itemIsFile = true;
|
||||||
|
}
|
||||||
|
_temp = _temp.substring(_temp.indexOf(';') + 2);
|
||||||
|
}
|
||||||
|
String name = _temp.substring(0, _temp.indexOf('='));
|
||||||
|
String nameVal = _temp.substring(_temp.indexOf('=') + 2, _temp.length() - 1);
|
||||||
|
if(name == "name"){
|
||||||
|
_itemName = nameVal;
|
||||||
|
} else if(name == "filename"){
|
||||||
|
_itemFilename = nameVal;
|
||||||
|
_itemIsFile = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_temp = String();
|
||||||
|
} else {
|
||||||
|
_multiParseState = WAIT_FOR_RETURN1;
|
||||||
|
//value starts from here
|
||||||
|
_itemSize = 0;
|
||||||
|
_itemStartIndex = _parsedLength;
|
||||||
|
_itemValue = String();
|
||||||
|
if(_itemIsFile){
|
||||||
|
if(_itemBuffer)
|
||||||
|
free(_itemBuffer);
|
||||||
|
_itemBuffer = (uint8_t*)malloc(FILE_CHUNK_SIZE);
|
||||||
|
if(_itemBuffer == NULL){
|
||||||
|
ESP_LOGE(PH_TAG, "Multipart: Failed to allocate buffer");
|
||||||
|
_multiParseState = PARSE_ERROR;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_itemBufferIndex = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if(_multiParseState == EXPECT_FEED1){
|
||||||
|
if(data != '\n'){
|
||||||
|
_multiParseState = WAIT_FOR_RETURN1;
|
||||||
|
itemWriteByte('\r'); _parseMultipartPostByte(data, last);
|
||||||
|
} else {
|
||||||
|
_multiParseState = EXPECT_DASH1;
|
||||||
|
}
|
||||||
|
} else if(_multiParseState == EXPECT_DASH1){
|
||||||
|
if(data != '-'){
|
||||||
|
_multiParseState = WAIT_FOR_RETURN1;
|
||||||
|
itemWriteByte('\r'); itemWriteByte('\n'); _parseMultipartPostByte(data, last);
|
||||||
|
} else {
|
||||||
|
_multiParseState = EXPECT_DASH2;
|
||||||
|
}
|
||||||
|
} else if(_multiParseState == EXPECT_DASH2){
|
||||||
|
if(data != '-'){
|
||||||
|
_multiParseState = WAIT_FOR_RETURN1;
|
||||||
|
itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-'); _parseMultipartPostByte(data, last);
|
||||||
|
} else {
|
||||||
|
_multiParseState = BOUNDARY_OR_DATA;
|
||||||
|
_boundaryPosition = 0;
|
||||||
|
}
|
||||||
|
} else if(_multiParseState == BOUNDARY_OR_DATA){
|
||||||
|
if(_boundaryPosition < _boundary.length() && _boundary.c_str()[_boundaryPosition] != data){
|
||||||
|
_multiParseState = WAIT_FOR_RETURN1;
|
||||||
|
itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-'); itemWriteByte('-');
|
||||||
|
uint8_t i;
|
||||||
|
for(i=0; i<_boundaryPosition; i++)
|
||||||
|
itemWriteByte(_boundary.c_str()[i]);
|
||||||
|
_parseMultipartPostByte(data, last);
|
||||||
|
} else if(_boundaryPosition == _boundary.length() - 1){
|
||||||
|
_multiParseState = DASH3_OR_RETURN2;
|
||||||
|
if(!_itemIsFile){
|
||||||
|
_request->addParam(_itemName, _itemValue);
|
||||||
|
//_addParam(new AsyncWebParameter(_itemName, _itemValue, true));
|
||||||
|
} else {
|
||||||
|
if(_itemSize){
|
||||||
|
if(_uploadCallback)
|
||||||
|
_uploadCallback(_request, _itemFilename, _itemSize - _itemBufferIndex, _itemBuffer, _itemBufferIndex, true);
|
||||||
|
_itemBufferIndex = 0;
|
||||||
|
_request->addParam(new PsychicWebParameter(_itemName, _itemFilename, true, true, _itemSize));
|
||||||
|
}
|
||||||
|
free(_itemBuffer);
|
||||||
|
_itemBuffer = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
_boundaryPosition++;
|
||||||
|
}
|
||||||
|
} else if(_multiParseState == DASH3_OR_RETURN2){
|
||||||
|
if(data == '-' && (_request->contentLength() - _parsedLength - 4) != 0){
|
||||||
|
ESP_LOGE(PH_TAG, "ERROR: The parser got to the end of the POST but is expecting more bytes!");
|
||||||
|
_multiParseState = PARSE_ERROR;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(data == '\r'){
|
||||||
|
_multiParseState = EXPECT_FEED2;
|
||||||
|
} else if(data == '-' && _request->contentLength() == (_parsedLength + 4)){
|
||||||
|
_multiParseState = PARSING_FINISHED;
|
||||||
|
} else {
|
||||||
|
_multiParseState = WAIT_FOR_RETURN1;
|
||||||
|
itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-'); itemWriteByte('-');
|
||||||
|
uint8_t i; for(i=0; i<_boundary.length(); i++) itemWriteByte(_boundary.c_str()[i]);
|
||||||
|
_parseMultipartPostByte(data, last);
|
||||||
|
}
|
||||||
|
} else if(_multiParseState == EXPECT_FEED2){
|
||||||
|
if(data == '\n'){
|
||||||
|
_multiParseState = PARSE_HEADERS;
|
||||||
|
_itemIsFile = false;
|
||||||
|
} else {
|
||||||
|
_multiParseState = WAIT_FOR_RETURN1;
|
||||||
|
itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-'); itemWriteByte('-');
|
||||||
|
uint8_t i; for(i=0; i<_boundary.length(); i++) itemWriteByte(_boundary.c_str()[i]);
|
||||||
|
itemWriteByte('\r'); _parseMultipartPostByte(data, last);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
68
lib/PsychicHttp/src/PsychicUploadHandler.h
Normal file
68
lib/PsychicHttp/src/PsychicUploadHandler.h
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
#ifndef PsychicUploadHandler_h
|
||||||
|
#define PsychicUploadHandler_h
|
||||||
|
|
||||||
|
#include "PsychicCore.h"
|
||||||
|
#include "PsychicHttpServer.h"
|
||||||
|
#include "PsychicRequest.h"
|
||||||
|
#include "PsychicWebHandler.h"
|
||||||
|
#include "PsychicWebParameter.h"
|
||||||
|
|
||||||
|
//callback definitions
|
||||||
|
typedef std::function<esp_err_t(PsychicRequest *request, const String& filename, uint64_t index, uint8_t *data, size_t len, bool final)> PsychicUploadCallback;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* HANDLER :: Can be attached to any endpoint or as a generic request handler.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class PsychicUploadHandler : public PsychicWebHandler {
|
||||||
|
protected:
|
||||||
|
PsychicUploadCallback _uploadCallback;
|
||||||
|
|
||||||
|
PsychicRequest *_request;
|
||||||
|
|
||||||
|
String _temp;
|
||||||
|
size_t _parsedLength;
|
||||||
|
uint8_t _multiParseState;
|
||||||
|
String _boundary;
|
||||||
|
uint8_t _boundaryPosition;
|
||||||
|
size_t _itemStartIndex;
|
||||||
|
size_t _itemSize;
|
||||||
|
String _itemName;
|
||||||
|
String _itemFilename;
|
||||||
|
String _itemType;
|
||||||
|
String _itemValue;
|
||||||
|
uint8_t *_itemBuffer;
|
||||||
|
size_t _itemBufferIndex;
|
||||||
|
bool _itemIsFile;
|
||||||
|
|
||||||
|
esp_err_t _basicUploadHandler(PsychicRequest *request);
|
||||||
|
esp_err_t _multipartUploadHandler(PsychicRequest *request);
|
||||||
|
|
||||||
|
void _handleUploadByte(uint8_t data, bool last);
|
||||||
|
void _parseMultipartPostByte(uint8_t data, bool last);
|
||||||
|
|
||||||
|
public:
|
||||||
|
PsychicUploadHandler();
|
||||||
|
~PsychicUploadHandler();
|
||||||
|
|
||||||
|
bool canHandle(PsychicRequest *request) override;
|
||||||
|
esp_err_t handleRequest(PsychicRequest *request) override;
|
||||||
|
|
||||||
|
PsychicUploadHandler * onUpload(PsychicUploadCallback fn);
|
||||||
|
};
|
||||||
|
|
||||||
|
enum {
|
||||||
|
EXPECT_BOUNDARY,
|
||||||
|
PARSE_HEADERS,
|
||||||
|
WAIT_FOR_RETURN1,
|
||||||
|
EXPECT_FEED1,
|
||||||
|
EXPECT_DASH1,
|
||||||
|
EXPECT_DASH2,
|
||||||
|
BOUNDARY_OR_DATA,
|
||||||
|
DASH3_OR_RETURN2,
|
||||||
|
EXPECT_FEED2,
|
||||||
|
PARSING_FINISHED,
|
||||||
|
PARSE_ERROR
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // PsychicUploadHandler_h
|
||||||
74
lib/PsychicHttp/src/PsychicWebHandler.cpp
Normal file
74
lib/PsychicHttp/src/PsychicWebHandler.cpp
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
#include "PsychicWebHandler.h"
|
||||||
|
|
||||||
|
PsychicWebHandler::PsychicWebHandler() :
|
||||||
|
PsychicHandler(),
|
||||||
|
_requestCallback(NULL),
|
||||||
|
_onOpen(NULL),
|
||||||
|
_onClose(NULL)
|
||||||
|
{}
|
||||||
|
PsychicWebHandler::~PsychicWebHandler() {}
|
||||||
|
|
||||||
|
bool PsychicWebHandler::canHandle(PsychicRequest *request) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t PsychicWebHandler::handleRequest(PsychicRequest *request)
|
||||||
|
{
|
||||||
|
//lookup our client
|
||||||
|
PsychicClient *client = checkForNewClient(request->client());
|
||||||
|
if (client->isNew)
|
||||||
|
openCallback(client);
|
||||||
|
|
||||||
|
/* Request body cannot be larger than a limit */
|
||||||
|
if (request->contentLength() > request->server()->maxRequestBodySize)
|
||||||
|
{
|
||||||
|
ESP_LOGE(PH_TAG, "Request body too large : %d bytes", request->contentLength());
|
||||||
|
|
||||||
|
/* Respond with 400 Bad Request */
|
||||||
|
char error[60];
|
||||||
|
sprintf(error, "Request body must be less than %u bytes!", request->server()->maxRequestBodySize);
|
||||||
|
httpd_resp_send_err(request->request(), HTTPD_400_BAD_REQUEST, error);
|
||||||
|
|
||||||
|
/* Return failure to close underlying connection else the incoming file content will keep the socket busy */
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
//get our body loaded up.
|
||||||
|
esp_err_t err = request->loadBody();
|
||||||
|
if (err != ESP_OK)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
//load our params in.
|
||||||
|
request->loadParams();
|
||||||
|
|
||||||
|
//okay, pass on to our callback.
|
||||||
|
if (this->_requestCallback != NULL)
|
||||||
|
err = this->_requestCallback(request);
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
PsychicWebHandler * PsychicWebHandler::onRequest(PsychicHttpRequestCallback fn) {
|
||||||
|
_requestCallback = fn;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PsychicWebHandler::openCallback(PsychicClient *client) {
|
||||||
|
if (_onOpen != NULL)
|
||||||
|
_onOpen(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PsychicWebHandler::closeCallback(PsychicClient *client) {
|
||||||
|
if (_onClose != NULL)
|
||||||
|
_onClose(getClient(client));
|
||||||
|
}
|
||||||
|
|
||||||
|
PsychicWebHandler * PsychicWebHandler::onOpen(PsychicClientCallback fn) {
|
||||||
|
_onOpen = fn;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
PsychicWebHandler * PsychicWebHandler::onClose(PsychicClientCallback fn) {
|
||||||
|
_onClose = fn;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
34
lib/PsychicHttp/src/PsychicWebHandler.h
Normal file
34
lib/PsychicHttp/src/PsychicWebHandler.h
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
#ifndef PsychicWebHandler_h
|
||||||
|
#define PsychicWebHandler_h
|
||||||
|
|
||||||
|
// #include "PsychicCore.h"
|
||||||
|
// #include "PsychicHttpServer.h"
|
||||||
|
// #include "PsychicRequest.h"
|
||||||
|
#include "PsychicHandler.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* HANDLER :: Can be attached to any endpoint or as a generic request handler.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class PsychicWebHandler : public PsychicHandler {
|
||||||
|
protected:
|
||||||
|
PsychicHttpRequestCallback _requestCallback;
|
||||||
|
PsychicClientCallback _onOpen;
|
||||||
|
PsychicClientCallback _onClose;
|
||||||
|
|
||||||
|
public:
|
||||||
|
PsychicWebHandler();
|
||||||
|
~PsychicWebHandler();
|
||||||
|
|
||||||
|
virtual bool canHandle(PsychicRequest *request) override;
|
||||||
|
virtual esp_err_t handleRequest(PsychicRequest *request) override;
|
||||||
|
PsychicWebHandler * onRequest(PsychicHttpRequestCallback fn);
|
||||||
|
|
||||||
|
virtual void openCallback(PsychicClient *client);
|
||||||
|
virtual void closeCallback(PsychicClient *client);
|
||||||
|
|
||||||
|
PsychicWebHandler *onOpen(PsychicClientCallback fn);
|
||||||
|
PsychicWebHandler *onClose(PsychicClientCallback fn);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
25
lib/PsychicHttp/src/PsychicWebParameter.h
Normal file
25
lib/PsychicHttp/src/PsychicWebParameter.h
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
#ifndef PsychicWebParameter_h
|
||||||
|
#define PsychicWebParameter_h
|
||||||
|
|
||||||
|
/*
|
||||||
|
* PARAMETER :: Chainable object to hold GET/POST and FILE parameters
|
||||||
|
* */
|
||||||
|
|
||||||
|
class PsychicWebParameter {
|
||||||
|
private:
|
||||||
|
String _name;
|
||||||
|
String _value;
|
||||||
|
size_t _size;
|
||||||
|
bool _isForm;
|
||||||
|
bool _isFile;
|
||||||
|
|
||||||
|
public:
|
||||||
|
PsychicWebParameter(const String& name, const String& value, bool form=false, bool file=false, size_t size=0): _name(name), _value(value), _size(size), _isForm(form), _isFile(file){}
|
||||||
|
const String& name() const { return _name; }
|
||||||
|
const String& value() const { return _value; }
|
||||||
|
size_t size() const { return _size; }
|
||||||
|
bool isPost() const { return _isForm; }
|
||||||
|
bool isFile() const { return _isFile; }
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //PsychicWebParameter_h
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user