forked from electro/esp-irblaster
				
			
							parent
							
								
									3a9ba9940e
								
							
						
					
					
						commit
						c83042658e
					
				@ -1,20 +0,0 @@ | 
				
			||||
hum1   10000000 01111111 00010000 11101111 1 | 
				
			||||
hum2   10000000 01111111 01010000 10101111 1 | 
				
			||||
hum3   10000000 01111111 10010000 01101111 1 | 
				
			||||
mode1  10000000 01111111 00011000 11100111 1 | 
				
			||||
mode2  10000000 01111111 10011000 01100111 1 | 
				
			||||
mode3  10000000 01111111 00001000 11110111 1 | 
				
			||||
mode4  10000000 01111111 10001000 01110111 1 | 
				
			||||
night  10000000 01111111 10000000 01111111 1 | 
				
			||||
power  10000000 01111111 00000000 11111111 1 | 
				
			||||
speed1 10000000 01111111 10101000 01010111 1 | 
				
			||||
speed2 10000000 01111111 01101000 10010111 1 | 
				
			||||
speed3 10000000 01111111 00101000 11010111 1 | 
				
			||||
 | 
				
			||||
Format: | 
				
			||||
 | 
				
			||||
38.5kHz | 
				
			||||
 | 
				
			||||
Preamble: 9ms transmit, 4.5ms gap | 
				
			||||
Zero: 600us transmit, 600us gap. | 
				
			||||
One: 600us transmit, 1.7ms gap | 
				
			||||
@ -1,16 +0,0 @@ | 
				
			||||
set(COMPONENT_ADD_INCLUDEDIRS | 
				
			||||
        "include" "files") | 
				
			||||
 | 
				
			||||
set(COMPONENT_SRCDIRS | 
				
			||||
        "src" "files") | 
				
			||||
 | 
				
			||||
set(COMPONENT_REQUIRES tcpip_adapter esp_http_server httpd_utils common_utils) | 
				
			||||
 | 
				
			||||
#begin staticfiles | 
				
			||||
# generated by rebuild_file_tables | 
				
			||||
set(COMPONENT_EMBED_FILES | 
				
			||||
        "files/embed/favicon.ico" | 
				
			||||
        "files/embed/index.html") | 
				
			||||
#end staticfiles | 
				
			||||
 | 
				
			||||
register_component() | 
				
			||||
@ -1,2 +0,0 @@ | 
				
			||||
File and template serving support for the http_server bundled with ESP-IDF. | 
				
			||||
 | 
				
			||||
@ -1,3 +0,0 @@ | 
				
			||||
 | 
				
			||||
COMPONENT_SRCDIRS := src
 | 
				
			||||
COMPONENT_ADD_INCLUDEDIRS := include
 | 
				
			||||
| 
		 Before Width: | Height: | Size: 4.2 KiB  | 
@ -1,108 +0,0 @@ | 
				
			||||
<!DOCTYPE html> | 
				
			||||
<html> | 
				
			||||
<head> | 
				
			||||
<meta charset="utf-8"> | 
				
			||||
<title>ESP node</title> | 
				
			||||
<style> | 
				
			||||
html { font-family:sans-serif } | 
				
			||||
table { border-collapse:collapse; } | 
				
			||||
td,th { padding: 4px 10px; min-width: 65px; } | 
				
			||||
td { min-width: 70px; } | 
				
			||||
 | 
				
			||||
th { text-align:left; } | 
				
			||||
tbody th { text-align:center; } | 
				
			||||
tbody th {border-right: 1px solid black;} | 
				
			||||
thead th { text-align:center;padding-top:0; } | 
				
			||||
td { text-align:right; } | 
				
			||||
td[c],[c] td { text-align:center; } | 
				
			||||
td[l],[l] td { text-align:left; } | 
				
			||||
td[r],[r] td { text-align:right; } | 
				
			||||
td[ed] {cursor:pointer;} | 
				
			||||
td[ed]:hover {text-decoration:underline;} | 
				
			||||
table[thl] tbody th {text-align:left;} | 
				
			||||
figure { | 
				
			||||
	border-top:3px solid black; | 
				
			||||
	border-bottom:3px solid black; | 
				
			||||
	display: inline-block; | 
				
			||||
	padding: 5px 0; | 
				
			||||
	margin: 0 0 15px 15px; | 
				
			||||
} | 
				
			||||
thead tr { border-bottom: 2px solid black; } | 
				
			||||
</style> | 
				
			||||
</head> | 
				
			||||
<body> | 
				
			||||
<h1>ESP node {version}</h1> | 
				
			||||
 | 
				
			||||
IR blaster | 
				
			||||
 | 
				
			||||
<a href="/reboot">Restart node</a> | 
				
			||||
 | 
				
			||||
<script> | 
				
			||||
var kv_re = /^(-?[0-9.-]+)\s+(.+)$/; | 
				
			||||
var Qi = function (x) { return document.getElementById(x) }; | 
				
			||||
var data = {}; | 
				
			||||
function update(data) { | 
				
			||||
	if (data) { | 
				
			||||
		var rows = data.split('\x1e'); | 
				
			||||
		rows.forEach(function (v) { | 
				
			||||
			var kv = v.split('\x1f'); | 
				
			||||
			var el = Qi(kv[0]); | 
				
			||||
			if (!el) return; | 
				
			||||
			var suf = ''; | 
				
			||||
			var res = kv_re.exec(el.textContent); | 
				
			||||
			if (res) suf = ' ' + res[2]; | 
				
			||||
			el.textContent = kv[1] + suf; | 
				
			||||
			data[kv[0]] = kv[1]; | 
				
			||||
		}); | 
				
			||||
	} else { | 
				
			||||
		var xhr=new XMLHttpRequest(); | 
				
			||||
		xhr.onreadystatechange = function () { | 
				
			||||
			if (xhr.readyState===4){ | 
				
			||||
				if (xhr.status===200) { | 
				
			||||
					update(xhr.responseText); | 
				
			||||
				} | 
				
			||||
				setTimeout(update, 1000); | 
				
			||||
			} | 
				
			||||
		}; | 
				
			||||
		xhr.onerror = function () { | 
				
			||||
			setTimeout(update, 1000); | 
				
			||||
		}; | 
				
			||||
		xhr.open('GET', '/data'); | 
				
			||||
		xhr.send(); | 
				
			||||
	} | 
				
			||||
} | 
				
			||||
 | 
				
			||||
function mkXhr() { | 
				
			||||
	var xhr=new XMLHttpRequest(); | 
				
			||||
	xhr.onreadystatechange = function () { | 
				
			||||
		if (xhr.readyState===4 && xhr.status!==200) { | 
				
			||||
			alert(xhr.responseText || xhr.statusText || 'Request failed'); | 
				
			||||
		} | 
				
			||||
	}; | 
				
			||||
	return xhr; | 
				
			||||
} | 
				
			||||
 | 
				
			||||
function editOpt() { | 
				
			||||
	var k = this.id; | 
				
			||||
	var v = prompt(k+' =', data[k]||''); | 
				
			||||
	if (v !== null) { | 
				
			||||
		var xhr = mkXhr(); | 
				
			||||
		xhr.open('POST', '/set'); | 
				
			||||
		xhr.send("key="+k+'&value='+encodeURIComponent(v)); | 
				
			||||
	} | 
				
			||||
} | 
				
			||||
 | 
				
			||||
function toggleOpt() { | 
				
			||||
	var xhr = mkXhr(); | 
				
			||||
	xhr.open('POST', '/toggle'); | 
				
			||||
	xhr.send("x="+this.id); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
/* | 
				
			||||
setTimeout(update, 500); | 
				
			||||
for(var i=1;i<8;i++) { | 
				
			||||
	if(i<7) Qi('i'+i+'_max').addEventListener('click', editOpt); | 
				
			||||
	Qi('ch'+i+'_state').addEventListener('click', toggleOpt); | 
				
			||||
} | 
				
			||||
*/ | 
				
			||||
</script> | 
				
			||||
@ -1,163 +0,0 @@ | 
				
			||||
#!/usr/bin/env php | 
				
			||||
<?php | 
				
			||||
// This script rebuilds the static files enum, extern symbols pointing to the embedded byte buffers, | 
				
			||||
// and the look-up structs table. To add more files, simply add them in the 'files' directory. | 
				
			||||
 | 
				
			||||
// Note that all files will be accessible by the webserver, unless you filter them in embedded_files.c. | 
				
			||||
 | 
				
			||||
 | 
				
			||||
// List all files | 
				
			||||
$files = scandir(__DIR__.'/embed'); | 
				
			||||
 | 
				
			||||
$files = array_filter(array_map(function ($f) { | 
				
			||||
  if (!is_file(__DIR__.'/embed/'.$f)) return null; | 
				
			||||
  if (preg_match('/^\.|\.kate-swp|\.bak$|~$|\.sh$/', $f)) return null; | 
				
			||||
 | 
				
			||||
  echo "Found: $f\n"; | 
				
			||||
  return $f; | 
				
			||||
}, $files)); | 
				
			||||
 | 
				
			||||
sort($files); | 
				
			||||
 | 
				
			||||
$formatted = array_filter(array_map(function ($f) { | 
				
			||||
  return "\"files/embed/$f\""; | 
				
			||||
}, $files)); | 
				
			||||
 | 
				
			||||
$cmake = file_get_contents(__DIR__.'/../CMakeLists.txt'); | 
				
			||||
 | 
				
			||||
$cmake = preg_replace('/#begin staticfiles\n.*#end staticfiles/s',  | 
				
			||||
  "#begin staticfiles\n". | 
				
			||||
  "# generated by rebuild_file_tables\n". | 
				
			||||
  "set(COMPONENT_EMBED_FILES\n        ". | 
				
			||||
  implode("\n        ", $formatted) . ")\n". | 
				
			||||
  "#end staticfiles",  | 
				
			||||
  $cmake); | 
				
			||||
 | 
				
			||||
file_put_contents(__DIR__.'/../CMakeLists.txt', $cmake); | 
				
			||||
 | 
				
			||||
 | 
				
			||||
// Generate a list of files | 
				
			||||
 | 
				
			||||
$num = 0; | 
				
			||||
$enum_keys = array_map(function ($f) use(&$num) { | 
				
			||||
  $a = preg_replace("/[^A-Z0-9_]+/", "_", strtoupper($f)); | 
				
			||||
  return 'FILE_'. $a.' = '.($num++); | 
				
			||||
}, $files); | 
				
			||||
 | 
				
			||||
$keylist = implode(",\n    ", $enum_keys); | 
				
			||||
 | 
				
			||||
$struct_array = []; | 
				
			||||
 | 
				
			||||
$externs = array_map(function ($f) use (&$struct_array) { | 
				
			||||
  $a = preg_replace("/[^A-Z0-9_]+/", "_", strtoupper($f)); | 
				
			||||
   | 
				
			||||
  $start = '_binary_'. strtolower($a).'_start'; | 
				
			||||
  $end = '_binary_'. strtolower($a).'_end'; | 
				
			||||
   | 
				
			||||
  static $mimes = array( | 
				
			||||
    'txt' => 'text/plain', | 
				
			||||
    'htm' => 'text/html', | 
				
			||||
    'html' => 'text/html', | 
				
			||||
    'php' => 'text/html', | 
				
			||||
    'css' => 'text/css', | 
				
			||||
    'js' => 'application/javascript', | 
				
			||||
    'json' => 'application/json', | 
				
			||||
    'xml' => 'application/xml', | 
				
			||||
    'swf' => 'application/x-shockwave-flash', | 
				
			||||
    'flv' => 'video/x-flv', | 
				
			||||
     | 
				
			||||
    'pem' => 'application/x-pem-file', | 
				
			||||
 | 
				
			||||
    // images | 
				
			||||
    'png' => 'image/png', | 
				
			||||
    'jpe' => 'image/jpeg', | 
				
			||||
    'jpeg' => 'image/jpeg', | 
				
			||||
    'jpg' => 'image/jpeg', | 
				
			||||
    'gif' => 'image/gif', | 
				
			||||
    'bmp' => 'image/bmp', | 
				
			||||
    'ico' => 'image/vnd.microsoft.icon', | 
				
			||||
    'tiff' => 'image/tiff', | 
				
			||||
    'tif' => 'image/tiff', | 
				
			||||
    'svg' => 'image/svg+xml', | 
				
			||||
    'svgz' => 'image/svg+xml', | 
				
			||||
 | 
				
			||||
    // archives | 
				
			||||
    'zip' => 'application/zip', | 
				
			||||
    'rar' => 'application/x-rar-compressed', | 
				
			||||
    'exe' => 'application/x-msdownload', | 
				
			||||
    'msi' => 'application/x-msdownload', | 
				
			||||
    'cab' => 'application/vnd.ms-cab-compressed', | 
				
			||||
 | 
				
			||||
    // audio/video | 
				
			||||
    'mp3' => 'audio/mpeg', | 
				
			||||
    'qt' => 'video/quicktime', | 
				
			||||
    'mov' => 'video/quicktime', | 
				
			||||
 | 
				
			||||
    // adobe | 
				
			||||
    'pdf' => 'application/pdf', | 
				
			||||
    'psd' => 'image/vnd.adobe.photoshop', | 
				
			||||
    'ai' => 'application/postscript', | 
				
			||||
    'eps' => 'application/postscript', | 
				
			||||
    'ps' => 'application/postscript', | 
				
			||||
 | 
				
			||||
    // ms office | 
				
			||||
    'doc' => 'application/msword', | 
				
			||||
    'rtf' => 'application/rtf', | 
				
			||||
    'xls' => 'application/vnd.ms-excel', | 
				
			||||
    'ppt' => 'application/vnd.ms-powerpoint', | 
				
			||||
 | 
				
			||||
    // open office | 
				
			||||
    'odt' => 'application/vnd.oasis.opendocument.text', | 
				
			||||
    'ods' => 'application/vnd.oasis.opendocument.spreadsheet', | 
				
			||||
  ); | 
				
			||||
   | 
				
			||||
  $parts = explode('.', $f); | 
				
			||||
  $suffix = end($parts); | 
				
			||||
  $mime = $mimes[$suffix] ?? 'application/octet-stream'; | 
				
			||||
   | 
				
			||||
  $len = filesize('embed/'.$f); | 
				
			||||
   | 
				
			||||
  $struct_array[] = "[FILE_$a] = {{$start}, {$end}, \"{$f}\", \"{$mime}\"},"; | 
				
			||||
   | 
				
			||||
  return  | 
				
			||||
    'extern const uint8_t '.$start.'[];'."\n". | 
				
			||||
    'extern const uint8_t '.$end.'[];'; | 
				
			||||
}, $files); | 
				
			||||
 | 
				
			||||
$externlist = implode("\n", $externs); | 
				
			||||
$structlist = implode("\n    ", $struct_array); | 
				
			||||
 | 
				
			||||
 | 
				
			||||
file_put_contents('www_files_enum.h', <<<FILE | 
				
			||||
// Generated by 'rebuild_file_tables' | 
				
			||||
 | 
				
			||||
#ifndef _EMBEDDED_FILES_ENUM_H | 
				
			||||
#define _EMBEDDED_FILES_ENUM_H | 
				
			||||
 | 
				
			||||
#include "fileserver/embedded_files.h" | 
				
			||||
 | 
				
			||||
enum embedded_file_id { | 
				
			||||
    $keylist | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
#endif // _EMBEDDED_FILES_ENUM_H | 
				
			||||
 | 
				
			||||
FILE | 
				
			||||
); | 
				
			||||
 | 
				
			||||
$files_count = count($struct_array); | 
				
			||||
file_put_contents("www_files_enum.c", <<<FILE | 
				
			||||
// Generated by 'rebuild_file_tables' | 
				
			||||
#include <stdint.h> | 
				
			||||
#include "www_files_enum.h" | 
				
			||||
 | 
				
			||||
$externlist | 
				
			||||
 | 
				
			||||
const struct embedded_file_info EMBEDDED_FILE_LOOKUP[] = { | 
				
			||||
    $structlist | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
const size_t EMBEDDED_FILE_LOOKUP_LEN = $files_count; | 
				
			||||
 | 
				
			||||
FILE | 
				
			||||
); | 
				
			||||
@ -1,15 +0,0 @@ | 
				
			||||
// Generated by 'rebuild_file_tables'
 | 
				
			||||
#include <stdint.h> | 
				
			||||
#include "www_files_enum.h" | 
				
			||||
 | 
				
			||||
extern const uint8_t _binary_favicon_ico_start[]; | 
				
			||||
extern const uint8_t _binary_favicon_ico_end[]; | 
				
			||||
extern const uint8_t _binary_index_html_start[]; | 
				
			||||
extern const uint8_t _binary_index_html_end[]; | 
				
			||||
 | 
				
			||||
const struct embedded_file_info EMBEDDED_FILE_LOOKUP[] = { | 
				
			||||
    [FILE_FAVICON_ICO] = {_binary_favicon_ico_start, _binary_favicon_ico_end, "favicon.ico", "image/vnd.microsoft.icon"}, | 
				
			||||
    [FILE_INDEX_HTML] = {_binary_index_html_start, _binary_index_html_end, "index.html", "text/html"}, | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
const size_t EMBEDDED_FILE_LOOKUP_LEN = 2; | 
				
			||||
@ -1,13 +0,0 @@ | 
				
			||||
// Generated by 'rebuild_file_tables'
 | 
				
			||||
 | 
				
			||||
#ifndef _EMBEDDED_FILES_ENUM_H | 
				
			||||
#define _EMBEDDED_FILES_ENUM_H | 
				
			||||
 | 
				
			||||
#include "fileserver/embedded_files.h" | 
				
			||||
 | 
				
			||||
enum embedded_file_id { | 
				
			||||
    FILE_FAVICON_ICO = 0, | 
				
			||||
    FILE_INDEX_HTML = 1 | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
#endif // _EMBEDDED_FILES_ENUM_H
 | 
				
			||||
@ -1,51 +0,0 @@ | 
				
			||||
//
 | 
				
			||||
// Created on 2018/10/17 by Ondrej Hruska <ondra@ondrovo.com>
 | 
				
			||||
//
 | 
				
			||||
 | 
				
			||||
#ifndef FBNODE_EMBEDDED_FILES_H | 
				
			||||
#define FBNODE_EMBEDDED_FILES_H | 
				
			||||
 | 
				
			||||
#include <stdint.h> | 
				
			||||
#include <esp_err.h> | 
				
			||||
#include <stdbool.h> | 
				
			||||
 | 
				
			||||
struct embedded_file_info { | 
				
			||||
    const uint8_t * start; | 
				
			||||
    const uint8_t * end; | 
				
			||||
    const char * name; | 
				
			||||
    const char * mime; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
enum file_access_level { | 
				
			||||
    /** Public = file accessed by a wildcard route */ | 
				
			||||
    FILE_ACCESS_PUBLIC = 0, | 
				
			||||
    /** Protected = file included in a template or explicitly specified in a route */ | 
				
			||||
    FILE_ACCESS_PROTECTED = 1, | 
				
			||||
    /** Files protected against read-out */ | 
				
			||||
    FILE_ACCESS_PRIVATE = 2, | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
extern const struct embedded_file_info EMBEDDED_FILE_LOOKUP[]; | 
				
			||||
extern const size_t EMBEDDED_FILE_LOOKUP_LEN; | 
				
			||||
 | 
				
			||||
/**
 | 
				
			||||
 * Find an embedded file by its name. | 
				
			||||
 * 
 | 
				
			||||
 * This function is weak. It crawls the EMBEDDED_FILE_LOOKUP table and checks for exact match, also | 
				
			||||
 * testing with www_get_static_file_access_check if the access is allowed. | 
				
			||||
 * | 
				
			||||
 * @param name - file name | 
				
			||||
 * @param access - access level (public - wildcard fallthrough, protected - files for the server, loaded explicitly) | 
				
			||||
 * @param[out] file - the file struct is stored here if found, unchanged if not found. | 
				
			||||
 * @return status code | 
				
			||||
 */ | 
				
			||||
esp_err_t www_get_static_file(const char *name, enum file_access_level access, const struct embedded_file_info **file); | 
				
			||||
 | 
				
			||||
/**
 | 
				
			||||
 * Check file access permission (if using the default www_get_static_file implementation). | 
				
			||||
 * 
 | 
				
			||||
 * This function is weak. The default implementation returns always true. | 
				
			||||
 */ | 
				
			||||
bool www_get_static_file_access_check(const struct embedded_file_info *file, enum file_access_level access); | 
				
			||||
 | 
				
			||||
#endif //FBNODE_EMBEDDED_FILES_H
 | 
				
			||||
@ -1,216 +0,0 @@ | 
				
			||||
//
 | 
				
			||||
// This module implements token substitution in files served by the server.
 | 
				
			||||
//
 | 
				
			||||
// Tokens are in the form {token}, or {escape:token}, where escape can be:
 | 
				
			||||
// - h ... html escape (plain text in a html file, attribute value)
 | 
				
			||||
// - j ... js escape (for use in JS strings)
 | 
				
			||||
//
 | 
				
			||||
// When no escape is specified, the token substitution is written verbatim into the response.
 | 
				
			||||
//
 | 
				
			||||
//   var foo = "{j:foo}";
 | 
				
			||||
//   <input value="{h:old_value}">
 | 
				
			||||
//   {generated-html-goes-here}
 | 
				
			||||
//
 | 
				
			||||
// Token can be made optional by adding '?' at the end (this can't be used for includes).
 | 
				
			||||
// Such token then simply becomes empty string when not substituted, as opposed to being included in the page verbatim.
 | 
				
			||||
//
 | 
				
			||||
//   <input value="{h:old_value?}">
 | 
				
			||||
//
 | 
				
			||||
// token names can contain alnum, dash, period and underscore, and are case sensitive.
 | 
				
			||||
//
 | 
				
			||||
//
 | 
				
			||||
// It is further possible to include a static file with optional key-value replacements. These serve as defaults.
 | 
				
			||||
//
 | 
				
			||||
//   {@_subfile.html}
 | 
				
			||||
//   {@_subfile.html|key=value lalala}
 | 
				
			||||
//   {@_subfile.html|key=value lalala|other=value}
 | 
				
			||||
//
 | 
				
			||||
// File inclusion can be nested, and the files can use replacement tokens as specified by the include statement
 | 
				
			||||
//
 | 
				
			||||
// Created on 2019/01/24 by Ondrej Hruska <ondra@ondrovo.com>
 | 
				
			||||
//
 | 
				
			||||
 | 
				
			||||
#ifndef FBNODE_TOKEN_SUBS_H | 
				
			||||
#define FBNODE_TOKEN_SUBS_H | 
				
			||||
 | 
				
			||||
#include "embedded_files.h" | 
				
			||||
#include <sys/queue.h> | 
				
			||||
#include <esp_err.h> | 
				
			||||
#include <esp_http_server.h> | 
				
			||||
 | 
				
			||||
/** Max length of a token buffer (must suffice for all included filenames) */ | 
				
			||||
#define MAX_TOKEN_LEN 32 | 
				
			||||
 | 
				
			||||
/** Max length of a key-value substitution when using tpl_kv_replacer;
 | 
				
			||||
 * This is also used internally for in-line replacements in file imports. */ | 
				
			||||
#define TPL_KV_KEY_LEN 24 | 
				
			||||
/** Max length of a substituion in tpl_kv_replacer */ | 
				
			||||
#define TPL_KV_SUBST_LEN 64 | 
				
			||||
 | 
				
			||||
/**
 | 
				
			||||
 * Escape type - argument for httpd_resp_send_chunk_escaped() | 
				
			||||
 */ | 
				
			||||
typedef enum { | 
				
			||||
    TPL_ESCAPE_NONE = 0, | 
				
			||||
    TPL_ESCAPE_HTML, | 
				
			||||
    TPL_ESCAPE_JS, | 
				
			||||
} tpl_escape_t; | 
				
			||||
 | 
				
			||||
enum { | 
				
			||||
    HTOPT_NONE = 0, | 
				
			||||
    HTOPT_NO_HEADERS = 1 << 0, | 
				
			||||
    HTOPT_NO_CLOSE = 1 << 1, | 
				
			||||
    HTOPT_INCLUDE = HTOPT_NO_HEADERS|HTOPT_NO_CLOSE, | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
/**
 | 
				
			||||
 * Send string using a given escaping scheme | 
				
			||||
 * | 
				
			||||
 * @param r | 
				
			||||
 * @param buf - buf to send | 
				
			||||
 * @param len - buf len, or HTTPD_RESP_USE_STRLEN | 
				
			||||
 * @param escape - escaping type | 
				
			||||
 * @return success | 
				
			||||
 */ | 
				
			||||
esp_err_t httpd_resp_send_chunk_escaped(httpd_req_t *r, const char *buf, ssize_t len, tpl_escape_t escape); | 
				
			||||
 | 
				
			||||
/**
 | 
				
			||||
 * Template substitution callback. Data shall be sent using httpd_resp_send_chunk_escaped(). | 
				
			||||
 * | 
				
			||||
 * @param[in,out] context - user-defined page state data | 
				
			||||
 * @param[in] token - replacement token | 
				
			||||
 * @return ESP_OK if the token was substituted, ESP_ERR_NOT_FOUND if it is unknown, other errors on e.g. send failure | 
				
			||||
 */ | 
				
			||||
typedef esp_err_t (*template_subst_t)(httpd_req_t *r, void *context, const char *token, tpl_escape_t escape); | 
				
			||||
 | 
				
			||||
/**
 | 
				
			||||
 * Send a template file as a response. The content type from the file struct will be used. | 
				
			||||
 * | 
				
			||||
 * Use HTOPT_INCLUDE when used to embed a file inside a template. | 
				
			||||
 * | 
				
			||||
 * @param r - request | 
				
			||||
 * @param file_index - file index in EMBEDDED_FILE_LOOKUP | 
				
			||||
 * @param replacer - substitution callback, can be NULL if only includes are to be processed | 
				
			||||
 * @param context - arbitrary context, will be passed to the replacer function; can be NULL | 
				
			||||
 * @param opts - flag options (HTOPT_*) | 
				
			||||
 */ | 
				
			||||
esp_err_t httpd_send_template_file(httpd_req_t *r, int file_index, template_subst_t replacer, void *context, uint32_t opts); | 
				
			||||
 | 
				
			||||
/**
 | 
				
			||||
 * Same as httpd_send_template_file, but using an `embedded_file_info` struct. | 
				
			||||
 */ | 
				
			||||
esp_err_t httpd_send_template_file_struct(httpd_req_t *r, const struct embedded_file_info *file, template_subst_t replacer, void *context, uint32_t opts); | 
				
			||||
 | 
				
			||||
/**
 | 
				
			||||
 * Process and send a string template. | 
				
			||||
 * The content-type header should be set beforehand, if different from the default (text/html). | 
				
			||||
 * | 
				
			||||
 * Use HTOPT_INCLUDE when used to embed a file inside a template. | 
				
			||||
 * | 
				
			||||
 * @param r - request | 
				
			||||
 * @param template - template string (does not have to be terminated by a null byte) | 
				
			||||
 * @param template_len - length of the template string; -1 to use strlen() | 
				
			||||
 * @param replacer - substitution callback, can be NULL if only includes are to be processed | 
				
			||||
 * @param context - arbitrary context, will be passed to the replacer function; can be NULL | 
				
			||||
 * @param opts - flag options (HTOPT_*) | 
				
			||||
 */ | 
				
			||||
esp_err_t httpd_send_template(httpd_req_t *r, const char *template, ssize_t template_len, template_subst_t replacer, void *context, uint32_t opts); | 
				
			||||
 | 
				
			||||
/**
 | 
				
			||||
 * Send a static file. This can be used to just send a file, or to embed a static template as a token substitution. | 
				
			||||
 * | 
				
			||||
 * Use HTOPT_INCLUDE when used to embed a file inside a template. | 
				
			||||
 * | 
				
			||||
 * Note: use httpd_resp_send_chunk_escaped() or httpd_resp_send_chunk() to send a plain string. | 
				
			||||
 * | 
				
			||||
 * @param r - request | 
				
			||||
 * @param file_index - file index in EMBEDDED_FILE_LOOKUP | 
				
			||||
 * @param escape - escape option | 
				
			||||
 * @param opts - flag options (HTOPT_*) | 
				
			||||
 * @return | 
				
			||||
 */ | 
				
			||||
esp_err_t httpd_send_static_file(httpd_req_t *r, int file_index, tpl_escape_t escape, uint32_t opts); | 
				
			||||
 | 
				
			||||
/**
 | 
				
			||||
 * Same as httpd_send_template_file, but using an `embedded_file_info` struct. | 
				
			||||
 */ | 
				
			||||
esp_err_t httpd_send_static_file_struct(httpd_req_t *r, const struct embedded_file_info *file, tpl_escape_t escape, uint32_t opts); | 
				
			||||
 | 
				
			||||
struct tpl_kv_entry { | 
				
			||||
    char key[TPL_KV_KEY_LEN]; // copied here
 | 
				
			||||
    char subst[TPL_KV_SUBST_LEN]; // copied here
 | 
				
			||||
    SLIST_ENTRY(tpl_kv_entry) link; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
SLIST_HEAD(tpl_kv_list, tpl_kv_entry); | 
				
			||||
 | 
				
			||||
/**
 | 
				
			||||
 * key-value replacer that works with a dynamically allocated SLIST. | 
				
			||||
 * | 
				
			||||
 * @param r - request | 
				
			||||
 * @param context - context - must be a pointer to `struct tpl_kv_list` | 
				
			||||
 * @param token - token to replace | 
				
			||||
 * @param escape - escape option | 
				
			||||
 * @return OK/not found/other | 
				
			||||
 */ | 
				
			||||
esp_err_t tpl_kv_replacer(httpd_req_t *r, void *context, const char *token, tpl_escape_t escape); | 
				
			||||
 | 
				
			||||
/**
 | 
				
			||||
 * Add a pair into the substitutions list | 
				
			||||
 * | 
				
			||||
 * @param head - list head | 
				
			||||
 * @param key - key, copied | 
				
			||||
 * @param subst - value, copied | 
				
			||||
 * @return success (fails if malloc failed) | 
				
			||||
 */ | 
				
			||||
esp_err_t tpl_kv_add(struct tpl_kv_list *head, const char *key, const char *subst); | 
				
			||||
 | 
				
			||||
/**
 | 
				
			||||
 * Convenience function that converts an IP address to string and adds it as a substitution | 
				
			||||
 * | 
				
			||||
 * @param head - list head | 
				
			||||
 * @param key - key, copied | 
				
			||||
 * @param ip4h - host order ipv4 address | 
				
			||||
 * @return success | 
				
			||||
 */ | 
				
			||||
esp_err_t tpl_kv_add_ipv4str(struct tpl_kv_list *head, const char *key, uint32_t ip4h); | 
				
			||||
 | 
				
			||||
/** add int as a substitution; key is copied */ | 
				
			||||
esp_err_t tpl_kv_add_int(struct tpl_kv_list *head, const char *key, int32_t num); | 
				
			||||
 | 
				
			||||
/** add long as a substitution; key is copied */ | 
				
			||||
esp_err_t tpl_kv_add_long(struct tpl_kv_list *head, const char *key, int64_t num); | 
				
			||||
 | 
				
			||||
/** add printf-formatted value; key is copied */ | 
				
			||||
esp_err_t tpl_kv_sprintf(struct tpl_kv_list *head, const char *key, const char *format, ...) | 
				
			||||
    __attribute__((format(printf,3,4))); | 
				
			||||
 | 
				
			||||
/**
 | 
				
			||||
 * Init a substitutions list (on the stack) | 
				
			||||
 * | 
				
			||||
 * @return the list | 
				
			||||
 */ | 
				
			||||
static inline struct tpl_kv_list tpl_kv_init(void) | 
				
			||||
{ | 
				
			||||
    return (struct tpl_kv_list) {.slh_first = NULL}; | 
				
			||||
} | 
				
			||||
 | 
				
			||||
/**
 | 
				
			||||
 * Free the list (head is left alone because it was allocated on the stack) | 
				
			||||
 * @param head | 
				
			||||
 */ | 
				
			||||
void tpl_kv_free(struct tpl_kv_list *head); | 
				
			||||
 | 
				
			||||
/**
 | 
				
			||||
 * Send the map as an ASCII table separated by Record Separator (30) and Unit Separator (31). | 
				
			||||
 * Content type is set to application/octet-stream. | 
				
			||||
 * | 
				
			||||
 * key 31 value 30 | 
				
			||||
 * key 31 value 30 | 
				
			||||
 * key 31 value | 
				
			||||
 * | 
				
			||||
 * @param req | 
				
			||||
 */ | 
				
			||||
esp_err_t tpl_kv_send_as_ascii_map(httpd_req_t *req, struct tpl_kv_list *head); | 
				
			||||
 | 
				
			||||
#endif //FBNODE_TOKEN_SUBS_H
 | 
				
			||||
@ -1,29 +0,0 @@ | 
				
			||||
Place the `rebuild_file_tables.php` script in a `files` subfolder of the main component. | 
				
			||||
It requires PHP 7 to run.  | 
				
			||||
 | 
				
			||||
This is what the setup should look like | 
				
			||||
 | 
				
			||||
``` | 
				
			||||
main/files/embed/index.html | 
				
			||||
main/files/rebuild_file_tables.php | 
				
			||||
main/CMakeLists.txt | 
				
			||||
main/main.c | 
				
			||||
``` | 
				
			||||
 | 
				
			||||
Add this to your CMakeLists.txt before `register_component`: | 
				
			||||
 | 
				
			||||
``` | 
				
			||||
#begin staticfiles | 
				
			||||
#end staticfiles | 
				
			||||
``` | 
				
			||||
 | 
				
			||||
The script will update CMakeLists.txt and generate `files_enum.c` and `files_enum.h` when run. | 
				
			||||
 | 
				
			||||
``` | 
				
			||||
main/files/files_enum.h | 
				
			||||
main/files/files_enum.c | 
				
			||||
``` | 
				
			||||
 | 
				
			||||
Ensure `files/files_enum.c` is included in the build. | 
				
			||||
 | 
				
			||||
`www_get_static_file()` is implemented as weak to let you provide custom access authentication logic. | 
				
			||||
@ -1,170 +0,0 @@ | 
				
			||||
#!/usr/bin/env php | 
				
			||||
<?php | 
				
			||||
// This script rebuilds the static files enum, extern symbols pointing to the embedded byte buffers, | 
				
			||||
// and the look-up structs table. To add more files, simply add them in the 'files' directory. | 
				
			||||
 | 
				
			||||
// Note that all files will be accessible by the webserver, unless you filter them in embedded_files.c. | 
				
			||||
 | 
				
			||||
 | 
				
			||||
// List all files | 
				
			||||
$files = scandir(__DIR__.'/embed'); | 
				
			||||
 | 
				
			||||
$files = array_filter(array_map(function ($f) { | 
				
			||||
  if (!is_file(__DIR__.'/embed/'.$f)) return null; | 
				
			||||
  if (preg_match('/^\.|\.kate-swp|\.bak$|~$|\.sh$/', $f)) return null; | 
				
			||||
 | 
				
			||||
  echo "Found: $f\n"; | 
				
			||||
  return $f; | 
				
			||||
}, $files)); | 
				
			||||
 | 
				
			||||
sort($files); | 
				
			||||
 | 
				
			||||
$formatted = array_filter(array_map(function ($f) { | 
				
			||||
  return "\"files/embed/$f\""; | 
				
			||||
}, $files)); | 
				
			||||
 | 
				
			||||
$cmake = file_get_contents(__DIR__.'/../CMakeLists.txt'); | 
				
			||||
 | 
				
			||||
$cmake = preg_replace('/#begin staticfiles\n.*#end staticfiles/s',  | 
				
			||||
  "#begin staticfiles\n". | 
				
			||||
  "set(COMPONENT_EMBED_FILES\n        ". | 
				
			||||
  implode("\n        ", $formatted) . ")\n". | 
				
			||||
  "#end staticfiles",  | 
				
			||||
  $cmake); | 
				
			||||
 | 
				
			||||
file_put_contents(__DIR__.'/../CMakeLists.txt', $cmake); | 
				
			||||
 | 
				
			||||
 | 
				
			||||
// Generate a list of files | 
				
			||||
 | 
				
			||||
$num = 0; | 
				
			||||
$enum_keys = array_map(function ($f) use(&$num) { | 
				
			||||
  $a = preg_replace("/[^A-Z0-9_]+/", "_", strtoupper($f)); | 
				
			||||
  return 'FILE_'. $a.' = '.($num++); | 
				
			||||
}, $files); | 
				
			||||
 | 
				
			||||
$keylist = implode(",\n    ", $enum_keys); | 
				
			||||
 | 
				
			||||
$struct_array = []; | 
				
			||||
 | 
				
			||||
$externs = array_map(function ($f) use (&$struct_array) { | 
				
			||||
  $a = preg_replace("/[^A-Z0-9_]+/", "_", strtoupper($f)); | 
				
			||||
   | 
				
			||||
  $start = '_binary_'. strtolower($a).'_start'; | 
				
			||||
  $end = '_binary_'. strtolower($a).'_end'; | 
				
			||||
   | 
				
			||||
  static $mimes = array( | 
				
			||||
    'txt' => 'text/plain', | 
				
			||||
    'htm' => 'text/html', | 
				
			||||
    'html' => 'text/html', | 
				
			||||
    'php' => 'text/html', | 
				
			||||
    'css' => 'text/css', | 
				
			||||
    'js' => 'application/javascript', | 
				
			||||
    'json' => 'application/json', | 
				
			||||
    'xml' => 'application/xml', | 
				
			||||
    'swf' => 'application/x-shockwave-flash', | 
				
			||||
    'flv' => 'video/x-flv', | 
				
			||||
     | 
				
			||||
    'pem' => 'application/x-pem-file', | 
				
			||||
 | 
				
			||||
    // images | 
				
			||||
    'png' => 'image/png', | 
				
			||||
    'jpe' => 'image/jpeg', | 
				
			||||
    'jpeg' => 'image/jpeg', | 
				
			||||
    'jpg' => 'image/jpeg', | 
				
			||||
    'gif' => 'image/gif', | 
				
			||||
    'bmp' => 'image/bmp', | 
				
			||||
    'ico' => 'image/vnd.microsoft.icon', | 
				
			||||
    'tiff' => 'image/tiff', | 
				
			||||
    'tif' => 'image/tiff', | 
				
			||||
    'svg' => 'image/svg+xml', | 
				
			||||
    'svgz' => 'image/svg+xml', | 
				
			||||
 | 
				
			||||
    // archives | 
				
			||||
    'zip' => 'application/zip', | 
				
			||||
    'rar' => 'application/x-rar-compressed', | 
				
			||||
    'exe' => 'application/x-msdownload', | 
				
			||||
    'msi' => 'application/x-msdownload', | 
				
			||||
    'cab' => 'application/vnd.ms-cab-compressed', | 
				
			||||
 | 
				
			||||
    // audio/video | 
				
			||||
    'mp3' => 'audio/mpeg', | 
				
			||||
    'qt' => 'video/quicktime', | 
				
			||||
    'mov' => 'video/quicktime', | 
				
			||||
 | 
				
			||||
    // adobe | 
				
			||||
    'pdf' => 'application/pdf', | 
				
			||||
    'psd' => 'image/vnd.adobe.photoshop', | 
				
			||||
    'ai' => 'application/postscript', | 
				
			||||
    'eps' => 'application/postscript', | 
				
			||||
    'ps' => 'application/postscript', | 
				
			||||
 | 
				
			||||
    // ms office | 
				
			||||
    'doc' => 'application/msword', | 
				
			||||
    'rtf' => 'application/rtf', | 
				
			||||
    'xls' => 'application/vnd.ms-excel', | 
				
			||||
    'ppt' => 'application/vnd.ms-powerpoint', | 
				
			||||
 | 
				
			||||
    // open office | 
				
			||||
    'odt' => 'application/vnd.oasis.opendocument.text', | 
				
			||||
    'ods' => 'application/vnd.oasis.opendocument.spreadsheet', | 
				
			||||
  ); | 
				
			||||
   | 
				
			||||
  $parts = explode('.', $f); | 
				
			||||
  $suffix = end($parts); | 
				
			||||
  $mime = $mimes[$suffix] ?? 'application/octet-stream'; | 
				
			||||
   | 
				
			||||
  $len = filesize('embed/'.$f); | 
				
			||||
   | 
				
			||||
  $struct_array[] = "[FILE_$a] = {{$start}, {$end}, \"{$f}\", \"{$mime}\"},"; | 
				
			||||
   | 
				
			||||
  return  | 
				
			||||
    'extern const uint8_t '.$start.'[];'."\n". | 
				
			||||
    'extern const uint8_t '.$end.'[];'; | 
				
			||||
}, $files); | 
				
			||||
 | 
				
			||||
$externlist = implode("\n", $externs); | 
				
			||||
$structlist = implode("\n    ", $struct_array); | 
				
			||||
 | 
				
			||||
 | 
				
			||||
file_put_contents('files_enum.h', <<<FILE | 
				
			||||
// Do not change, auto-generated by gen_staticfiles.php | 
				
			||||
 | 
				
			||||
#ifndef _EMBEDDED_FILES_ENUM_H | 
				
			||||
#define _EMBEDDED_FILES_ENUM_H | 
				
			||||
 | 
				
			||||
#include <stddef.h> | 
				
			||||
#include <stdint.h> | 
				
			||||
 | 
				
			||||
enum embedded_file_id { | 
				
			||||
    $keylist, | 
				
			||||
    FILE_MAX | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
struct embedded_file_info { | 
				
			||||
  const uint8_t * start; | 
				
			||||
  const uint8_t * end; | 
				
			||||
  const char * name; | 
				
			||||
  const char * mime; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
$externlist | 
				
			||||
 | 
				
			||||
extern const struct embedded_file_info EMBEDDED_FILE_LOOKUP[]; | 
				
			||||
 | 
				
			||||
#endif // _EMBEDDED_FILES_ENUM_H | 
				
			||||
 | 
				
			||||
FILE | 
				
			||||
); | 
				
			||||
 | 
				
			||||
file_put_contents("files_enum.c", <<<FILE | 
				
			||||
// Do not change, auto-generated by gen_staticfiles.php | 
				
			||||
#include <stdint.h> | 
				
			||||
#include "files_enum.h" | 
				
			||||
 | 
				
			||||
const struct embedded_file_info EMBEDDED_FILE_LOOKUP[] = { | 
				
			||||
    $structlist | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
FILE | 
				
			||||
); | 
				
			||||
@ -1,22 +0,0 @@ | 
				
			||||
#include <esp_err.h> | 
				
			||||
#include "fileserver/embedded_files.h" | 
				
			||||
#include "string.h" | 
				
			||||
 | 
				
			||||
esp_err_t __attribute__((weak)) 
 | 
				
			||||
www_get_static_file(const char *name, enum file_access_level access, const struct embedded_file_info **file) | 
				
			||||
{ | 
				
			||||
    // simple search by name
 | 
				
			||||
    for(int i = 0; i < EMBEDDED_FILE_LOOKUP_LEN; i++) { | 
				
			||||
        if (0 == strcmp(EMBEDDED_FILE_LOOKUP[i].name, name)) { | 
				
			||||
            *file = &EMBEDDED_FILE_LOOKUP[i]; | 
				
			||||
            return ESP_OK; | 
				
			||||
        } | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    return ESP_ERR_NOT_FOUND; | 
				
			||||
} | 
				
			||||
 | 
				
			||||
bool __attribute__((weak)) 
 | 
				
			||||
www_get_static_file_access_check(const struct embedded_file_info *file, enum file_access_level access) { | 
				
			||||
  return true; | 
				
			||||
} | 
				
			||||
@ -1,580 +0,0 @@ | 
				
			||||
//#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
 | 
				
			||||
 | 
				
			||||
#include <esp_log.h> | 
				
			||||
#include <esp_http_server.h> | 
				
			||||
#include <sys/queue.h> | 
				
			||||
#include <lwip/ip4_addr.h> | 
				
			||||
#include <sys/param.h> | 
				
			||||
#include <common_utils/utils.h> | 
				
			||||
#include <fileserver/token_subs.h> | 
				
			||||
 | 
				
			||||
#include "fileserver/embedded_files.h" | 
				
			||||
#include "fileserver/token_subs.h" | 
				
			||||
 | 
				
			||||
#define ESP_TRY(x) \ | 
				
			||||
    do {  \
 | 
				
			||||
        esp_err_t try_er = (x);  \
 | 
				
			||||
        if (try_er != ESP_OK) return try_er;  \
 | 
				
			||||
    } while(0) | 
				
			||||
 | 
				
			||||
static const char* TAG = "token_subs"; | 
				
			||||
 | 
				
			||||
// TODO implement buffering to avoid sending many tiny chunks when escaping
 | 
				
			||||
 | 
				
			||||
/* encode for HTML. returns 0 or 1 - 1 = success */ | 
				
			||||
static esp_err_t send_html_chunk(httpd_req_t *r, const char *data, ssize_t len) | 
				
			||||
{ | 
				
			||||
    assert(r); | 
				
			||||
    assert(data); | 
				
			||||
 | 
				
			||||
    int start = 0, end = 0; | 
				
			||||
    char c; | 
				
			||||
    if (len < 0) len = (int) strlen(data); | 
				
			||||
    if (len==0) return ESP_OK; | 
				
			||||
 | 
				
			||||
    for (end = 0; end < len; end++) { | 
				
			||||
        c = data[end]; | 
				
			||||
        if (c == 0) { | 
				
			||||
            // we found EOS
 | 
				
			||||
            break; // not return - the last chunk is printed after the loop
 | 
				
			||||
        } | 
				
			||||
 | 
				
			||||
        if (c == '"' || c == '\'' || c == '<' || c == '>') { | 
				
			||||
            if (start < end) ESP_TRY(httpd_resp_send_chunk(r, data + start, end - start)); | 
				
			||||
            start = end + 1; | 
				
			||||
        } | 
				
			||||
 | 
				
			||||
        if (c == '"') ESP_TRY(httpd_resp_send_chunk(r, """, 5)); | 
				
			||||
        else if (c == '\'') ESP_TRY(httpd_resp_send_chunk(r, "'", 5)); | 
				
			||||
        else if (c == '<') ESP_TRY(httpd_resp_send_chunk(r, "<", 4)); | 
				
			||||
        else if (c == '>') ESP_TRY(httpd_resp_send_chunk(r, ">", 4)); | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    if (start < end) ESP_TRY(httpd_resp_send_chunk(r, data + start, end - start)); | 
				
			||||
    return ESP_OK; | 
				
			||||
} | 
				
			||||
 | 
				
			||||
/* encode for JS. returns 0 or 1 - 1 = success */ | 
				
			||||
static esp_err_t send_js_chunk(httpd_req_t *r, const char *data, ssize_t len) | 
				
			||||
{ | 
				
			||||
    assert(r); | 
				
			||||
    assert(data); | 
				
			||||
 | 
				
			||||
    int start = 0, end = 0; | 
				
			||||
    char c; | 
				
			||||
    if (len < 0) len = (int) strlen(data); | 
				
			||||
    if (len==0) return ESP_OK; | 
				
			||||
 | 
				
			||||
    for (end = 0; end < len; end++) { | 
				
			||||
        c = data[end]; | 
				
			||||
        if (c == 0) { | 
				
			||||
            // we found EOS
 | 
				
			||||
            break; // not return - the last chunk is printed after the loop
 | 
				
			||||
        } | 
				
			||||
 | 
				
			||||
        if (c == '"' || c == '\\' || c == '/' || c == '\'' || c == '<' || c == '>' || c == '\n' || c == '\r') { | 
				
			||||
            if (start < end) ESP_TRY(httpd_resp_send_chunk(r, data + start, end - start)); | 
				
			||||
            start = end + 1; | 
				
			||||
        } | 
				
			||||
 | 
				
			||||
        if (c == '"') ESP_TRY(httpd_resp_send_chunk(r, "\\\"", 2)); | 
				
			||||
        else if (c == '\'') ESP_TRY(httpd_resp_send_chunk(r, "\\'", 2)); | 
				
			||||
        else if (c == '\\') ESP_TRY(httpd_resp_send_chunk(r, "\\\\", 2)); | 
				
			||||
        else if (c == '/') ESP_TRY(httpd_resp_send_chunk(r, "\\/", 2)); | 
				
			||||
        else if (c == '<') ESP_TRY(httpd_resp_send_chunk(r, "\\u003C", 6)); | 
				
			||||
        else if (c == '>') ESP_TRY(httpd_resp_send_chunk(r, "\\u003E", 6)); | 
				
			||||
        else if (c == '\n') ESP_TRY(httpd_resp_send_chunk(r, "\\n", 2)); | 
				
			||||
        else if (c == '\r') ESP_TRY(httpd_resp_send_chunk(r, "\\r", 2)); | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    if (start < end) ESP_TRY(httpd_resp_send_chunk(r, data + start, end - start)); | 
				
			||||
    return ESP_OK; | 
				
			||||
} | 
				
			||||
 | 
				
			||||
 | 
				
			||||
esp_err_t httpd_resp_send_chunk_escaped(httpd_req_t *r, const char *buf, ssize_t len, tpl_escape_t escape) | 
				
			||||
{ | 
				
			||||
    switch (escape) { | 
				
			||||
        default: // this enum should be exhaustive, but in case something went wrong, just print it verbatim
 | 
				
			||||
 | 
				
			||||
        case TPL_ESCAPE_NONE: | 
				
			||||
            return httpd_resp_send_chunk(r, buf, len); | 
				
			||||
 | 
				
			||||
        case TPL_ESCAPE_HTML: | 
				
			||||
            return send_html_chunk(r, buf, len); | 
				
			||||
 | 
				
			||||
        case TPL_ESCAPE_JS: | 
				
			||||
            return send_js_chunk(r, buf, len); | 
				
			||||
    } | 
				
			||||
} | 
				
			||||
 | 
				
			||||
esp_err_t httpd_send_static_file(httpd_req_t *r, int file_index, tpl_escape_t escape, uint32_t opts) | 
				
			||||
{ | 
				
			||||
    assert(file_index < EMBEDDED_FILE_LOOKUP_LEN); | 
				
			||||
    const struct embedded_file_info *file = &EMBEDDED_FILE_LOOKUP[file_index]; | 
				
			||||
 | 
				
			||||
    return httpd_send_static_file_struct(r, file, escape, opts); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
esp_err_t httpd_send_static_file_struct(httpd_req_t *r, const struct embedded_file_info *file, tpl_escape_t escape, uint32_t opts) | 
				
			||||
{ | 
				
			||||
    if (0 == (opts & HTOPT_NO_HEADERS)) { | 
				
			||||
        ESP_TRY(httpd_resp_set_type(r, file->mime)); | 
				
			||||
        ESP_TRY(httpd_resp_set_hdr(r, "Cache-Control", "max-age=86400, public, must-revalidate")); | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    ESP_TRY(httpd_resp_send_chunk_escaped(r, (const char *) file->start, (size_t)(file->end - file->start), escape)); | 
				
			||||
 | 
				
			||||
    if (0 == (opts & HTOPT_NO_CLOSE)) { | 
				
			||||
        ESP_TRY(httpd_resp_send_chunk(r, NULL, 0)); | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    return ESP_OK; | 
				
			||||
} | 
				
			||||
 | 
				
			||||
esp_err_t httpd_send_template_file(httpd_req_t *r, | 
				
			||||
                                   int file_index, | 
				
			||||
                                   template_subst_t replacer, | 
				
			||||
                                   void *context, | 
				
			||||
                                   uint32_t opts) | 
				
			||||
{ | 
				
			||||
    assert(file_index < EMBEDDED_FILE_LOOKUP_LEN); | 
				
			||||
    const struct embedded_file_info *file = &EMBEDDED_FILE_LOOKUP[file_index]; | 
				
			||||
    return httpd_send_template_file_struct(r,file,replacer,context,opts); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
esp_err_t httpd_send_template_file_struct(httpd_req_t *r, | 
				
			||||
                                   const struct embedded_file_info *file, | 
				
			||||
                                   template_subst_t replacer, | 
				
			||||
                                   void *context, | 
				
			||||
                                   uint32_t opts) | 
				
			||||
{ | 
				
			||||
    if (0 == (opts & HTOPT_NO_HEADERS)) { | 
				
			||||
        ESP_TRY(httpd_resp_set_type(r, file->mime)); | 
				
			||||
        ESP_TRY(httpd_resp_set_hdr(r, "Cache-Control", "no-cache, no-store, must-revalidate")); | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    return httpd_send_template(r, (const char *) file->start, (size_t)(file->end - file->start), replacer, context, opts); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
esp_err_t tpl_kv_replacer(httpd_req_t *r, void *context, const char *token, tpl_escape_t escape) | 
				
			||||
{ | 
				
			||||
    assert(context); | 
				
			||||
    assert(token); | 
				
			||||
 | 
				
			||||
    struct tpl_kv_entry *entry; | 
				
			||||
    struct tpl_kv_list *head = context; | 
				
			||||
    SLIST_FOREACH(entry, head, link) { | 
				
			||||
        if (0==strcmp(entry->key, token)) { | 
				
			||||
            return httpd_resp_send_chunk_escaped(r, entry->subst, -1, escape); | 
				
			||||
        } | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    return ESP_ERR_NOT_FOUND; | 
				
			||||
} | 
				
			||||
 | 
				
			||||
struct stacked_replacer_context { | 
				
			||||
    template_subst_t replacer0; | 
				
			||||
    void *context0; | 
				
			||||
    template_subst_t replacer1; | 
				
			||||
    void *context1; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
esp_err_t stacked_replacer(httpd_req_t *r, void *context, const char *token, tpl_escape_t escape) | 
				
			||||
{ | 
				
			||||
    assert(context); | 
				
			||||
    assert(token); | 
				
			||||
 | 
				
			||||
    struct stacked_replacer_context *combo = context; | 
				
			||||
 | 
				
			||||
    if (ESP_OK == combo->replacer0(r, combo->context0, token, escape)) { | 
				
			||||
        return ESP_OK; | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    if (ESP_OK == combo->replacer1(r, combo->context1, token, escape)) { | 
				
			||||
        return ESP_OK; | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    return ESP_ERR_NOT_FOUND; | 
				
			||||
} | 
				
			||||
 | 
				
			||||
esp_err_t httpd_send_template(httpd_req_t *r, | 
				
			||||
                              const char *template, ssize_t template_len, | 
				
			||||
                              template_subst_t replacer, | 
				
			||||
                              void *context, | 
				
			||||
                              uint32_t opts) | 
				
			||||
{ | 
				
			||||
    if (template_len < 0) template_len = strlen(template); | 
				
			||||
 | 
				
			||||
    // replacer and context may be NULL
 | 
				
			||||
    assert(template); | 
				
			||||
    assert(r); | 
				
			||||
 | 
				
			||||
    // data end
 | 
				
			||||
    const char * const end = template + template_len; | 
				
			||||
 | 
				
			||||
    // start of to-be-processed data
 | 
				
			||||
    const char * pos = template; | 
				
			||||
 | 
				
			||||
    // start position for finding opening braces, updated after a failed match to avoid infinite loop on the same bad token
 | 
				
			||||
    const char * searchpos = pos; | 
				
			||||
 | 
				
			||||
    // tokens must be copied to a buffer to allow adding the terminating null byte
 | 
				
			||||
    char token_buf[MAX_TOKEN_LEN]; | 
				
			||||
 | 
				
			||||
    while (pos < end) { | 
				
			||||
        const char * openbr = strchr(searchpos, '{'); | 
				
			||||
        if (openbr == NULL) { | 
				
			||||
            // no more templates
 | 
				
			||||
            ESP_TRY(httpd_resp_send_chunk(r, pos, (size_t) (end - pos))); | 
				
			||||
            break; | 
				
			||||
        } | 
				
			||||
 | 
				
			||||
        // this brace could start a valid template. check if it seems valid...
 | 
				
			||||
        const char * closebr = strchr(openbr, '}'); | 
				
			||||
        if (closebr == NULL) { | 
				
			||||
            // there are no further closing braces, so it can't be a template
 | 
				
			||||
 | 
				
			||||
            // we also know there can't be any more substitutions, because they would lack a closing } too
 | 
				
			||||
            ESP_TRY(httpd_resp_send_chunk(r, pos, (size_t) (end - pos))); | 
				
			||||
            break; | 
				
			||||
        } | 
				
			||||
 | 
				
			||||
        // see if the braces content looks like a token
 | 
				
			||||
        const char *t = openbr + 1; | 
				
			||||
        bool token_valid = true; | 
				
			||||
        struct tpl_kv_list substitutions_head = tpl_kv_init(); | 
				
			||||
        struct tpl_kv_entry *new_subst_pair = NULL; | 
				
			||||
 | 
				
			||||
        // a token can be either a name for replacement by the replacer func, or an include with static kv replacements
 | 
				
			||||
        bool is_include = false; | 
				
			||||
        bool token_is_optional = false; | 
				
			||||
        const char *token_end = NULL; // points one char after the end of the token
 | 
				
			||||
 | 
				
			||||
        // parsing the token
 | 
				
			||||
        { | 
				
			||||
            if (*t == '@') { | 
				
			||||
                ESP_LOGD(TAG, "Parsing an Include token"); | 
				
			||||
                is_include = true; | 
				
			||||
                t++; | 
				
			||||
            } | 
				
			||||
 | 
				
			||||
            enum { | 
				
			||||
                P_NAME, P_KEY, P_VALUE | 
				
			||||
            } state = P_NAME; | 
				
			||||
 | 
				
			||||
            const char *kv_start = NULL; | 
				
			||||
            while (t != closebr || state == P_VALUE) { | 
				
			||||
                char c = *t; | 
				
			||||
 | 
				
			||||
                if (state == P_NAME) { | 
				
			||||
                    if (!((c >= 'a' && c <= 'z') || | 
				
			||||
                          (c >= 'A' && c <= 'Z') || | 
				
			||||
                          (c >= '0' && c <= '9') || | 
				
			||||
                          c == '.' || c == '_' || c == '-' || c == ':')) { | 
				
			||||
 | 
				
			||||
                        if (!is_include && c == '?') { | 
				
			||||
                            token_end = t; | 
				
			||||
                            token_is_optional = true; | 
				
			||||
                        } else { | 
				
			||||
                            if (is_include && c == '|') { | 
				
			||||
                                token_end = t; | 
				
			||||
                                state = P_KEY; | 
				
			||||
                                kv_start = t + 1; | 
				
			||||
                                // pipe separates the include's filename and literal substitutions
 | 
				
			||||
                                // we know there is a closing } somewhere, and {@....| doesn't occur normally, so let's assume it's correct
 | 
				
			||||
                            } | 
				
			||||
                            else { | 
				
			||||
                                token_valid = false; | 
				
			||||
                                break; | 
				
			||||
                            } | 
				
			||||
                        } | 
				
			||||
                    } | 
				
			||||
                } | 
				
			||||
                else if (state == P_KEY) { | 
				
			||||
                    if (!((c >= 'a' && c <= 'z') || | 
				
			||||
                          (c >= 'A' && c <= 'Z') || | 
				
			||||
                          (c >= '0' && c <= '9') || | 
				
			||||
                          c == '.' || c == '_' || c == '-')) { | 
				
			||||
                        if (c == '=') { | 
				
			||||
                            new_subst_pair = calloc(sizeof(struct tpl_kv_entry), 1); | 
				
			||||
                            const size_t klen = MIN(TPL_KV_KEY_LEN, t - kv_start); | 
				
			||||
                            strncpy(new_subst_pair->key, kv_start, klen); | 
				
			||||
                            new_subst_pair->key[klen] = 0; | 
				
			||||
 | 
				
			||||
                            kv_start = t + 1; | 
				
			||||
 | 
				
			||||
                            state = P_VALUE; | 
				
			||||
                            // pipe separates the include's filename and literal substitutions
 | 
				
			||||
                            // we know there is a closing } somewhere, and {@....| doesn't occur normally, so let's assume it's correct
 | 
				
			||||
                        } | 
				
			||||
                    } | 
				
			||||
                } | 
				
			||||
                else if (state == P_VALUE) { | 
				
			||||
                    if (c == '|' || c == '}') { | 
				
			||||
                        const size_t vlen = MIN(TPL_KV_SUBST_LEN, t - kv_start); | 
				
			||||
                        strncpy(new_subst_pair->subst, kv_start, vlen); | 
				
			||||
                        new_subst_pair->subst[vlen] = 0; | 
				
			||||
 | 
				
			||||
                        // attach the kv pair to the list
 | 
				
			||||
                        SLIST_INSERT_HEAD(&substitutions_head, new_subst_pair, link); | 
				
			||||
                        ESP_LOGD(TAG, "Adding subs kv %s -> %s", new_subst_pair->key, new_subst_pair->subst); | 
				
			||||
                        new_subst_pair = NULL; | 
				
			||||
 | 
				
			||||
                        kv_start = t + 1; // go past the pipe
 | 
				
			||||
                        state = P_KEY; | 
				
			||||
 | 
				
			||||
                        if (t == closebr) { | 
				
			||||
                            break; // found the ending brace, so let's quit the kv parse loop
 | 
				
			||||
                        } | 
				
			||||
                    } | 
				
			||||
                } | 
				
			||||
 | 
				
			||||
                t++; | 
				
			||||
            } | 
				
			||||
            // clean up after a messed up subs kv pairs syntax
 | 
				
			||||
            if (new_subst_pair != NULL) { | 
				
			||||
                free(new_subst_pair); | 
				
			||||
            } | 
				
			||||
        } | 
				
			||||
 | 
				
			||||
        if (!token_valid) { | 
				
			||||
            // false match, include it in the block to send before the next token
 | 
				
			||||
            searchpos = openbr + 1; | 
				
			||||
            ESP_LOGD(TAG, "Skip invalid token near %10s", openbr); | 
				
			||||
            continue; | 
				
			||||
        } | 
				
			||||
 | 
				
			||||
        // now we know it looks like a substitution token
 | 
				
			||||
 | 
				
			||||
        // flush data before the token
 | 
				
			||||
        if (pos != openbr) ESP_TRY(httpd_resp_send_chunk(r, pos, (size_t) (openbr - pos))); | 
				
			||||
 | 
				
			||||
        const char *token_start = openbr; | 
				
			||||
 | 
				
			||||
        tpl_escape_t escape = TPL_ESCAPE_NONE; | 
				
			||||
 | 
				
			||||
        // extract and terminate the token
 | 
				
			||||
        size_t token_len = MIN(MAX_TOKEN_LEN-1, closebr - openbr - 1); | 
				
			||||
        if (token_end) { | 
				
			||||
            token_len = MIN(token_len, token_end - openbr - 1); | 
				
			||||
        } | 
				
			||||
 | 
				
			||||
        if (is_include) { | 
				
			||||
            token_start += 1; // skip the @
 | 
				
			||||
            token_len -= 1; | 
				
			||||
        } else { | 
				
			||||
            if (0 == strncmp("h:", openbr + 1, 2)) { | 
				
			||||
                escape = TPL_ESCAPE_HTML; | 
				
			||||
                token_start += 2; | 
				
			||||
                token_len -= 2; | 
				
			||||
            } | 
				
			||||
            else if (0 == strncmp("j:", openbr + 1, 2)) { | 
				
			||||
                escape = TPL_ESCAPE_JS; | 
				
			||||
                token_start += 2; | 
				
			||||
                token_len -= 2; | 
				
			||||
            } | 
				
			||||
        } | 
				
			||||
 | 
				
			||||
        strncpy(token_buf, token_start+1, token_len); | 
				
			||||
        token_buf[token_len] = 0; | 
				
			||||
 | 
				
			||||
        ESP_LOGD(TAG, "Token: %s", token_buf); | 
				
			||||
 | 
				
			||||
        esp_err_t rv; | 
				
			||||
 | 
				
			||||
        if (is_include) { | 
				
			||||
            ESP_LOGD(TAG, "Trying to include a sub-file"); | 
				
			||||
 | 
				
			||||
            const struct embedded_file_info *file = NULL; | 
				
			||||
            rv = www_get_static_file(token_buf, FILE_ACCESS_PROTECTED, &file); | 
				
			||||
            if (rv != ESP_OK) { | 
				
			||||
                ESP_LOGE(TAG, "Failed to statically include \"%s\" in a template - %s", token_buf, esp_err_to_name(rv)); | 
				
			||||
                // this will cause the token to be emitted verbatim
 | 
				
			||||
            } else { | 
				
			||||
                ESP_LOGD(TAG, "Descending..."); | 
				
			||||
 | 
				
			||||
                // combine the two replacers
 | 
				
			||||
                struct stacked_replacer_context combo = { | 
				
			||||
                    .replacer0 = replacer, | 
				
			||||
                    .context0 = context, | 
				
			||||
                    .replacer1 = tpl_kv_replacer, | 
				
			||||
                    .context1 = &substitutions_head | 
				
			||||
                }; | 
				
			||||
 | 
				
			||||
                rv = httpd_send_template_file_struct(r, file, stacked_replacer, &combo, HTOPT_INCLUDE); | 
				
			||||
                ESP_LOGD(TAG, "...back in parent"); | 
				
			||||
            } | 
				
			||||
 | 
				
			||||
            // tear down the list
 | 
				
			||||
            tpl_kv_free(&substitutions_head); | 
				
			||||
 | 
				
			||||
            if (rv != ESP_OK) { | 
				
			||||
                // just send it verbatim...
 | 
				
			||||
                ESP_TRY(httpd_resp_send_chunk(r, openbr, (size_t) (closebr - openbr + 1))); | 
				
			||||
            } | 
				
			||||
        } else { | 
				
			||||
            if (replacer) { | 
				
			||||
                ESP_LOGD(TAG, "Running replacer for \"%s\" with escape %d", token_buf, escape); | 
				
			||||
                rv = replacer(r, context, token_buf, escape); | 
				
			||||
 | 
				
			||||
                if (rv != ESP_OK) { | 
				
			||||
                    if (rv == ESP_ERR_NOT_FOUND) { | 
				
			||||
                        ESP_LOGD(TAG, "Token rejected"); | 
				
			||||
                        // optional token becomes empty string if not replaced
 | 
				
			||||
                        if (!token_is_optional) { | 
				
			||||
                            ESP_LOGD(TAG, "Not optional, keeping verbatim"); | 
				
			||||
                            // replacer rejected the token, keep it verbatim
 | 
				
			||||
                            ESP_TRY(httpd_resp_send_chunk(r, openbr, (size_t) (closebr - openbr + 1))); | 
				
			||||
                        } | 
				
			||||
                    } | 
				
			||||
                    else { | 
				
			||||
                        ESP_LOGE(TAG, "Unexpected error from replacer func: 0x%02x - %s", rv, esp_err_to_name(rv)); | 
				
			||||
                        return rv; | 
				
			||||
                    } | 
				
			||||
                } | 
				
			||||
            } else { | 
				
			||||
                ESP_LOGD(TAG, "Not replacer"); | 
				
			||||
                // no replacer, only includes - used for 'static' files
 | 
				
			||||
                if (!token_is_optional) { | 
				
			||||
                    ESP_LOGD(TAG, "Token not optional, keeping verbatim"); | 
				
			||||
                    ESP_TRY(httpd_resp_send_chunk(r, openbr, (size_t) (closebr - openbr + 1))); | 
				
			||||
                } | 
				
			||||
            } | 
				
			||||
        } | 
				
			||||
 | 
				
			||||
        searchpos = pos = closebr + 1; | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    if (0 == (opts & HTOPT_NO_CLOSE)) { | 
				
			||||
        ESP_TRY(httpd_resp_send_chunk(r, NULL, 0)); | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    return ESP_OK; | 
				
			||||
} | 
				
			||||
 | 
				
			||||
 | 
				
			||||
esp_err_t tpl_kv_add_int(struct tpl_kv_list *head, const char *key, int32_t num) | 
				
			||||
{ | 
				
			||||
    char buf[12]; | 
				
			||||
    itoa(num, buf, 10); | 
				
			||||
    return tpl_kv_add(head, key, buf); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
esp_err_t tpl_kv_add_long(struct tpl_kv_list *head, const char *key, int64_t num) | 
				
			||||
{ | 
				
			||||
    char buf[21]; | 
				
			||||
    sprintf(buf, "%"PRIi64, num); | 
				
			||||
    return tpl_kv_add(head, key, buf); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
esp_err_t tpl_kv_add_ipv4str(struct tpl_kv_list *head, const char *key, uint32_t ip4h) | 
				
			||||
{ | 
				
			||||
    char buf[IP4ADDR_STRLEN_MAX]; | 
				
			||||
    ip4_addr_t addr; | 
				
			||||
    addr.addr = lwip_htonl(ip4h); | 
				
			||||
    ip4addr_ntoa_r(&addr, buf, IP4ADDR_STRLEN_MAX); | 
				
			||||
 | 
				
			||||
    return tpl_kv_add(head, key, buf); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
esp_err_t tpl_kv_add(struct tpl_kv_list *head, const char *key, const char *subst) | 
				
			||||
{ | 
				
			||||
    ESP_LOGD(TAG, "kv add subs %s := %s", key, subst); | 
				
			||||
    struct tpl_kv_entry *entry = calloc(sizeof(struct tpl_kv_entry), 1); | 
				
			||||
    if (entry == NULL) return ESP_ERR_NO_MEM; | 
				
			||||
 | 
				
			||||
    assert(strlen(key) < TPL_KV_KEY_LEN); | 
				
			||||
    assert(strlen(subst) < TPL_KV_SUBST_LEN); | 
				
			||||
 | 
				
			||||
    strncpy(entry->key, key, TPL_KV_KEY_LEN); | 
				
			||||
    entry->key[TPL_KV_KEY_LEN - 1] = 0; | 
				
			||||
 | 
				
			||||
    strncpy(entry->subst, subst, TPL_KV_SUBST_LEN - 1); | 
				
			||||
    entry->subst[TPL_KV_KEY_LEN - 1] = 0; | 
				
			||||
 | 
				
			||||
    SLIST_INSERT_HEAD(head, entry, link); | 
				
			||||
    return ESP_OK; | 
				
			||||
} | 
				
			||||
 | 
				
			||||
esp_err_t tpl_kv_sprintf(struct tpl_kv_list *head, const char *key, const char *format, ...) | 
				
			||||
{ | 
				
			||||
    ESP_LOGD(TAG, "kv printf %s := %s", key, format); | 
				
			||||
    struct tpl_kv_entry *entry = calloc(sizeof(struct tpl_kv_entry), 1); | 
				
			||||
    if (entry == NULL) return ESP_ERR_NO_MEM; | 
				
			||||
 | 
				
			||||
    assert(strlen(key) < TPL_KV_KEY_LEN); | 
				
			||||
 | 
				
			||||
    strncpy(entry->key, key, TPL_KV_KEY_LEN); | 
				
			||||
    entry->key[TPL_KV_KEY_LEN - 1] = 0; | 
				
			||||
 | 
				
			||||
    va_list list; | 
				
			||||
    va_start(list, format); | 
				
			||||
    vsnprintf(entry->subst, TPL_KV_SUBST_LEN, format, list); | 
				
			||||
    va_end(list); | 
				
			||||
    entry->subst[TPL_KV_KEY_LEN - 1] = 0; | 
				
			||||
 | 
				
			||||
    SLIST_INSERT_HEAD(head, entry, link); | 
				
			||||
    return ESP_OK; | 
				
			||||
} | 
				
			||||
 | 
				
			||||
void tpl_kv_free(struct tpl_kv_list *head) | 
				
			||||
{ | 
				
			||||
    struct tpl_kv_entry *item, *next; | 
				
			||||
    SLIST_FOREACH_SAFE(item, head, link, next) { | 
				
			||||
        free(item); | 
				
			||||
    } | 
				
			||||
} | 
				
			||||
 | 
				
			||||
esp_err_t tpl_kv_send_as_ascii_map(httpd_req_t *req, struct tpl_kv_list *head) | 
				
			||||
{ | 
				
			||||
    httpd_resp_set_type(req, "text/plain; charset=utf-8"); | 
				
			||||
 | 
				
			||||
#define BUF_CAP 512 | 
				
			||||
    char *buf_head = calloc(BUF_CAP, 1); | 
				
			||||
    if (!buf_head) { | 
				
			||||
        ESP_LOGE(TAG, "Malloc err"); | 
				
			||||
        return ESP_FAIL; | 
				
			||||
    } | 
				
			||||
    char *buf = buf_head; | 
				
			||||
    size_t cap = BUF_CAP; | 
				
			||||
    struct tpl_kv_entry *entry; | 
				
			||||
 | 
				
			||||
    // GCC nested function
 | 
				
			||||
    esp_err_t send_part() { | 
				
			||||
        esp_err_t suc = httpd_resp_send_chunk(req, buf_head, BUF_CAP-cap); | 
				
			||||
        buf = buf_head; | 
				
			||||
        cap = BUF_CAP; | 
				
			||||
        if (suc != ESP_OK) { | 
				
			||||
            ESP_LOGE(TAG, "Error sending buffer"); | 
				
			||||
            free(buf_head); | 
				
			||||
            httpd_resp_send_chunk(req, NULL, 0); | 
				
			||||
        } | 
				
			||||
        return suc; | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    SLIST_FOREACH(entry, head, link) { | 
				
			||||
        buf = append(buf, entry->key, &cap); | 
				
			||||
        if (!buf) ESP_TRY(send_part()); | 
				
			||||
        buf = append(buf, "\x1f", &cap); | 
				
			||||
        if (!buf) ESP_TRY(send_part()); | 
				
			||||
        buf = append(buf, entry->subst, &cap); | 
				
			||||
        if (!buf) ESP_TRY(send_part()); | 
				
			||||
        if (entry->link.sle_next) { | 
				
			||||
            buf = append(buf, "\x1e", &cap); | 
				
			||||
            if (!buf) ESP_TRY(send_part()); | 
				
			||||
        } | 
				
			||||
    } | 
				
			||||
    // send leftovers
 | 
				
			||||
    if (buf != buf_head) { | 
				
			||||
        esp_err_t suc = httpd_resp_send_chunk(req, buf_head, BUF_CAP-cap); | 
				
			||||
        if (suc != ESP_OK) { | 
				
			||||
            ESP_LOGE(TAG, "Error sending buffer"); | 
				
			||||
        } | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    // Commit
 | 
				
			||||
    httpd_resp_send_chunk(req, NULL, 0); | 
				
			||||
    free(buf_head); | 
				
			||||
    return ESP_OK; | 
				
			||||
} | 
				
			||||
@ -1,9 +0,0 @@ | 
				
			||||
set(COMPONENT_ADD_INCLUDEDIRS | 
				
			||||
        "include") | 
				
			||||
 | 
				
			||||
set(COMPONENT_SRCDIRS | 
				
			||||
        "src") | 
				
			||||
 | 
				
			||||
set(COMPONENT_REQUIRES tcpip_adapter esp_http_server common_utils) | 
				
			||||
 | 
				
			||||
register_component() | 
				
			||||
@ -1,4 +0,0 @@ | 
				
			||||
Functions enriching the HTTP server bundled with ESP-IDF. | 
				
			||||
This package includes HTTP-related utilities, captive | 
				
			||||
portal implementation, and a cookie-based session system with a | 
				
			||||
key-value store and expirations. | 
				
			||||
@ -1,3 +0,0 @@ | 
				
			||||
 | 
				
			||||
COMPONENT_SRCDIRS := src
 | 
				
			||||
COMPONENT_ADD_INCLUDEDIRS := include
 | 
				
			||||
@ -1,32 +0,0 @@ | 
				
			||||
#ifndef HTTPD_UTILS_CAPTIVE_H | 
				
			||||
#define HTTPD_UTILS_CAPTIVE_H | 
				
			||||
 | 
				
			||||
#include <esp_http_server.h> | 
				
			||||
#include <esp_err.h> | 
				
			||||
 | 
				
			||||
/**
 | 
				
			||||
 * Redirect if needed when a captive portal capture is detected | 
				
			||||
 * | 
				
			||||
 * @param r | 
				
			||||
 * @return ESP_OK on redirect, ESP_ERR_NOT_FOUND if not needed, other error on failure | 
				
			||||
 */ | 
				
			||||
esp_err_t httpd_captive_redirect(httpd_req_t *r); | 
				
			||||
 | 
				
			||||
/**
 | 
				
			||||
 * Get URL to redirect to. WEAK. | 
				
			||||
 * | 
				
			||||
 * @param r | 
				
			||||
 * @param buf | 
				
			||||
 * @param maxlen | 
				
			||||
 * @return http(s)://foo.bar/
 | 
				
			||||
 */ | 
				
			||||
esp_err_t httpd_captive_redirect_get_url(httpd_req_t *r, char *buf, size_t maxlen); | 
				
			||||
 | 
				
			||||
/**
 | 
				
			||||
 * Get captive portal domain. WEAK. | 
				
			||||
 * | 
				
			||||
 * @return foo.bar | 
				
			||||
 */ | 
				
			||||
const char * httpd_captive_redirect_get_domain(); | 
				
			||||
 | 
				
			||||
#endif //HTTPD_UTILS_CAPTIVE_H
 | 
				
			||||
@ -1,13 +0,0 @@ | 
				
			||||
#ifndef HTTPD_FDIPV4_H | 
				
			||||
#define HTTPD_FDIPV4_H | 
				
			||||
 | 
				
			||||
/**
 | 
				
			||||
 * Get IP address for a FD | 
				
			||||
 * | 
				
			||||
 * @param fd | 
				
			||||
 * @param[out] ipv4 | 
				
			||||
 * @return success | 
				
			||||
 */ | 
				
			||||
esp_err_t fd_to_ipv4(int fd, in_addr_t *ipv4); | 
				
			||||
 | 
				
			||||
#endif //HTTPD_FDIPV4_H
 | 
				
			||||
@ -1,16 +0,0 @@ | 
				
			||||
#ifndef HTTPD_UTILS_REDIRECT_H | 
				
			||||
#define HTTPD_UTILS_REDIRECT_H | 
				
			||||
 | 
				
			||||
#include <esp_http_server.h> | 
				
			||||
#include <esp_err.h> | 
				
			||||
 | 
				
			||||
/**
 | 
				
			||||
 * Redirect to other URI - sends a HTTP response from the http server | 
				
			||||
 * | 
				
			||||
 * @param r - request | 
				
			||||
 * @param uri - target uri | 
				
			||||
 * @return success | 
				
			||||
 */ | 
				
			||||
esp_err_t httpd_redirect_to(httpd_req_t *r, const char *uri); | 
				
			||||
 | 
				
			||||
#endif // HTTPD_UTILS_REDIRECT_H
 | 
				
			||||
@ -1,55 +0,0 @@ | 
				
			||||
/**
 | 
				
			||||
 * Session store system main include file | 
				
			||||
 * | 
				
			||||
 * Created on 2019/07/13. | 
				
			||||
 */ | 
				
			||||
 | 
				
			||||
#ifndef HTTPD_UTILS_SESSION_H | 
				
			||||
#define HTTPD_UTILS_SESSION_H | 
				
			||||
 | 
				
			||||
#include "session_kvmap.h" | 
				
			||||
#include "session_store.h" | 
				
			||||
 | 
				
			||||
// Customary keys
 | 
				
			||||
 | 
				
			||||
/** Session key for OK flash message */ | 
				
			||||
#define SESS_FLASH_OK "flash_ok" | 
				
			||||
/** Session key for error flash message */ | 
				
			||||
#define SESS_FLASH_ERR "flash_err" | 
				
			||||
/** Session key for a "logged in" flag. Value is 1 if logged in. */ | 
				
			||||
#define SESS_AUTHED "authed" | 
				
			||||
 | 
				
			||||
// ..
 | 
				
			||||
 | 
				
			||||
/**
 | 
				
			||||
 * Redirect to /login form if unauthed, | 
				
			||||
 * but also retrieve the session key-value store for further use | 
				
			||||
 */ | 
				
			||||
#define HTTP_GET_AUTHED_SESSION(kvstore, r) do {                            \ | 
				
			||||
    kvstore = httpd_req_find_session_and((r), SESS_GET_DATA);               \
 | 
				
			||||
    if (NULL == kvstore || NULL == sess_kv_map_get(kvstore, SESS_AUTHED)) { \
 | 
				
			||||
        return httpd_redirect_to((r), "/login");                            \
 | 
				
			||||
    }                                                                       \
 | 
				
			||||
} while(0) | 
				
			||||
 | 
				
			||||
/**
 | 
				
			||||
 * Start or resume a session without checking for authed status. | 
				
			||||
 * When started, the session cookie is added to the response immediately. | 
				
			||||
 * | 
				
			||||
 * @param[out] kvstore - a place to store the allocated or retrieved session kvmap | 
				
			||||
 * @param[in] r - request | 
				
			||||
 * @return success | 
				
			||||
 */ | 
				
			||||
esp_err_t HTTP_GET_SESSION(sess_kv_map_t **kvstore, httpd_req_t *r); | 
				
			||||
 | 
				
			||||
/**
 | 
				
			||||
 * Redirect to the login form if unauthed. | 
				
			||||
 * This is the same as `HTTP_GET_AUTHED_SESSION`, except the kvstore variable is | 
				
			||||
 * not needed in the uri handler calling this, so it is declared internally. | 
				
			||||
 */ | 
				
			||||
#define HTTP_REDIRECT_IF_UNAUTHED(r) do {                             \ | 
				
			||||
    sess_kv_map_t *_kvstore;                                          \
 | 
				
			||||
    HTTP_GET_AUTHED_SESSION(_kvstore, r);                             \
 | 
				
			||||
} while(0) | 
				
			||||
 | 
				
			||||
#endif // HTTPD_UTILS_SESSION_H
 | 
				
			||||
@ -1,77 +0,0 @@ | 
				
			||||
/**
 | 
				
			||||
 * Simple key-value map for session data storage. | 
				
			||||
 * Takes care of dynamic allocation and cleanup. | 
				
			||||
 * 
 | 
				
			||||
 * Created on 2019/01/28. | 
				
			||||
 */ | 
				
			||||
 | 
				
			||||
#ifndef SESSION_KVMAP_H | 
				
			||||
#define SESSION_KVMAP_H | 
				
			||||
 | 
				
			||||
/**
 | 
				
			||||
 * Prototype for a free() func to clean up session-held objects | 
				
			||||
 */ | 
				
			||||
typedef void (*sess_kv_free_func_t)(void *obj); | 
				
			||||
 | 
				
			||||
typedef struct sess_kv_map sess_kv_map_t; | 
				
			||||
 | 
				
			||||
#define SESS_KVMAP_KEY_LEN 16 | 
				
			||||
 | 
				
			||||
/**
 | 
				
			||||
 * Allocate a new session key-value store | 
				
			||||
 * | 
				
			||||
 * @return the store, NULL on error | 
				
			||||
 */ | 
				
			||||
sess_kv_map_t *sess_kv_map_alloc(void); | 
				
			||||
 | 
				
			||||
/**
 | 
				
			||||
 * Free the session kv store. | 
				
			||||
 * | 
				
			||||
 * @param head - store head | 
				
			||||
 */ | 
				
			||||
void sess_kv_map_free(void *head); | 
				
			||||
 | 
				
			||||
/**
 | 
				
			||||
 * Get a value from the session kv store. | 
				
			||||
 * | 
				
			||||
 * @param head - store head | 
				
			||||
 * @param key - key to get a value for | 
				
			||||
 * @return the value, or NULL if not found | 
				
			||||
 */ | 
				
			||||
void *sess_kv_map_get(sess_kv_map_t *head, const char *key); | 
				
			||||
 | 
				
			||||
/**
 | 
				
			||||
 * Get and remove a value from the session store. | 
				
			||||
 * | 
				
			||||
 * The free function is not called in this case and the recipient is | 
				
			||||
 * responsible for cleaning it up correctly. | 
				
			||||
 * | 
				
			||||
 * @param head - store head | 
				
			||||
 * @param key - key to get a value for | 
				
			||||
 * @return the value, or NULL if not found | 
				
			||||
 */ | 
				
			||||
void * sess_kv_map_take(sess_kv_map_t *head, const char *key); | 
				
			||||
 | 
				
			||||
/**
 | 
				
			||||
 * Remove an entry from the session by its key name. | 
				
			||||
 * The slot is not free'd yet, but is made available for reuse. | 
				
			||||
 * | 
				
			||||
 * @param head - store head | 
				
			||||
 * @param key - key to remove | 
				
			||||
 * @return success | 
				
			||||
 */ | 
				
			||||
esp_err_t sess_kv_map_remove(sess_kv_map_t *head, const char *key); | 
				
			||||
 | 
				
			||||
/**
 | 
				
			||||
 * Set a key value. If there is an old value stored, it will be freed by its free function and replaced by the new one. | 
				
			||||
 * Otherwise a new slot is allocated for it, or a previously released one is reused. | 
				
			||||
 * | 
				
			||||
 * @param head - store head | 
				
			||||
 * @param key - key to assign to | 
				
			||||
 * @param value - new value | 
				
			||||
 * @param free_fn - value free func | 
				
			||||
 * @return success | 
				
			||||
 */ | 
				
			||||
esp_err_t sess_kv_map_set(sess_kv_map_t *head, const char *key, void *value, sess_kv_free_func_t free_fn); | 
				
			||||
 | 
				
			||||
#endif //SESSION_KVMAP_H
 | 
				
			||||
@ -1,100 +0,0 @@ | 
				
			||||
/**
 | 
				
			||||
 * Cookie-based session store | 
				
			||||
 */ | 
				
			||||
 | 
				
			||||
#ifndef SESSION_STORE_H | 
				
			||||
#define SESSION_STORE_H | 
				
			||||
 | 
				
			||||
#include "esp_http_server.h" | 
				
			||||
 | 
				
			||||
#define SESSION_EXPIRY_TIME_S 60*30 | 
				
			||||
#define SESSION_COOKIE_NAME "SESSID" | 
				
			||||
 | 
				
			||||
/** function that frees a session data object */ | 
				
			||||
typedef void (*sess_data_free_fn_t)(void *); | 
				
			||||
 | 
				
			||||
enum session_find_action { | 
				
			||||
    SESS_DROP, SESS_GET_DATA | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
/**
 | 
				
			||||
 * Find session and either get data, or drop it. | 
				
			||||
 * | 
				
			||||
 * @param cookie | 
				
			||||
 * @param action | 
				
			||||
 * @return | 
				
			||||
 */ | 
				
			||||
void *session_find_and(const char *cookie, enum session_find_action action); | 
				
			||||
 | 
				
			||||
/**
 | 
				
			||||
 * Initialize the session store. | 
				
			||||
 * Safely empty it if initialized | 
				
			||||
 */ | 
				
			||||
void session_store_init(void); | 
				
			||||
 | 
				
			||||
// placeholder for when no data is stored
 | 
				
			||||
#define SESSION_DUMMY ((void *) 1) | 
				
			||||
 | 
				
			||||
/**
 | 
				
			||||
 * Create a new session. Data must not be NULL, because it wouldn't be possible | 
				
			||||
 * to distinguish between NULL value and session not found in return values. | 
				
			||||
 * It can be e.g. 1 if no data storage is needed. | 
				
			||||
 * | 
				
			||||
 * @param data - data object to attach to the session | 
				
			||||
 * @param free_fn - function that disposes of the data when the session expires | 
				
			||||
 * @return NULL on error, or the new session ID. This is a live pointer into the session structure, | 
				
			||||
 *         must be copied if stored, as it can become invalid at any time | 
				
			||||
 */ | 
				
			||||
const char *session_new(void *data, sess_data_free_fn_t free_fn); | 
				
			||||
 | 
				
			||||
/**
 | 
				
			||||
 * Find a session by it's ID (from a cookie) | 
				
			||||
 * | 
				
			||||
 * @param cookie - session ID string | 
				
			||||
 * @return session data (void*), or NULL | 
				
			||||
 */ | 
				
			||||
void *session_find(const char *cookie); | 
				
			||||
 | 
				
			||||
/**
 | 
				
			||||
 * Loop through all sessions and drop these that expired. | 
				
			||||
 */ | 
				
			||||
void session_drop_expired(void); | 
				
			||||
 | 
				
			||||
/**
 | 
				
			||||
 * Drop a session by its ID. Does nothing if not found. | 
				
			||||
 * | 
				
			||||
 * @param cookie - session ID string | 
				
			||||
 */ | 
				
			||||
void session_drop(const char *cookie); | 
				
			||||
 | 
				
			||||
/**
 | 
				
			||||
 * Parse the Cookie header from a request, and do something with the corresponding session. | 
				
			||||
 * | 
				
			||||
 * To also delete the cookie, use req_delete_session_cookie(r) | 
				
			||||
 * | 
				
			||||
 * @param r - request | 
				
			||||
 * @param action - what to do with the session | 
				
			||||
 * @return session data, NULL if removed or not found | 
				
			||||
 */ | 
				
			||||
void *httpd_req_find_session_and(httpd_req_t *r, enum session_find_action action); | 
				
			||||
 | 
				
			||||
/**
 | 
				
			||||
 * Add a header that deletes the session cookie | 
				
			||||
 * | 
				
			||||
 * @param r - request | 
				
			||||
 */ | 
				
			||||
void httpd_resp_delete_session_cookie(httpd_req_t *r); | 
				
			||||
 | 
				
			||||
/**
 | 
				
			||||
 * Add a header that sets the session cookie. | 
				
			||||
 * | 
				
			||||
 * This must be called after creating a session (e.g. user logged in) to make it persistent. | 
				
			||||
 * | 
				
			||||
 * @attention NOT RE-ENTRANT, CAN'T BE USED AGAIN UNTIL THE REQUEST IT WAS CALLED FOR IS DISPATCHED. | 
				
			||||
 * | 
				
			||||
 * @param r - request | 
				
			||||
 * @param cookie - cookie ID | 
				
			||||
 */ | 
				
			||||
void httpd_resp_set_session_cookie(httpd_req_t *r, const char *cookie); | 
				
			||||
 | 
				
			||||
#endif //SESSION_STORE_H
 | 
				
			||||
@ -1,106 +0,0 @@ | 
				
			||||
#include <esp_wifi_types.h> | 
				
			||||
#include <esp_wifi.h> | 
				
			||||
#include <esp_log.h> | 
				
			||||
#include <fcntl.h> | 
				
			||||
#include <sys/socket.h> | 
				
			||||
#include <sys/param.h> | 
				
			||||
 | 
				
			||||
#include "httpd_utils/captive.h" | 
				
			||||
#include "httpd_utils/fd_to_ipv4.h" | 
				
			||||
#include "httpd_utils/redirect.h" | 
				
			||||
#include <common_utils/utils.h> | 
				
			||||
 | 
				
			||||
static const char *TAG="captive"; | 
				
			||||
 | 
				
			||||
const char * __attribute__((weak)) | 
				
			||||
httpd_captive_redirect_get_domain(void) | 
				
			||||
{ | 
				
			||||
    return "fb_node.captive"; | 
				
			||||
} | 
				
			||||
 | 
				
			||||
esp_err_t __attribute__((weak)) | 
				
			||||
httpd_captive_redirect_get_url(httpd_req_t *r, char *buf, size_t maxlen) | 
				
			||||
{ | 
				
			||||
    buf = append(buf, "http://", &maxlen); | 
				
			||||
    buf = append(buf, httpd_captive_redirect_get_domain(), &maxlen); | 
				
			||||
    append(buf, "/", &maxlen); | 
				
			||||
 | 
				
			||||
    return ESP_OK; | 
				
			||||
} | 
				
			||||
 | 
				
			||||
esp_err_t httpd_captive_redirect(httpd_req_t *r) | 
				
			||||
{ | 
				
			||||
    // must be static to survive being used in the redirect header
 | 
				
			||||
    static char s_buf[64]; | 
				
			||||
 | 
				
			||||
    wifi_mode_t mode = 0; | 
				
			||||
    esp_wifi_get_mode(&mode); | 
				
			||||
 | 
				
			||||
    // Check if we have an softap interface. No point checking IPs and hostnames if the client can't be on AP.
 | 
				
			||||
    if (mode == WIFI_MODE_STA || mode == WIFI_MODE_NULL) { | 
				
			||||
        goto no_redirect; | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    int fd = httpd_req_to_sockfd(r); | 
				
			||||
 | 
				
			||||
    tcpip_adapter_ip_info_t apip; | 
				
			||||
    tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_AP, &apip); | 
				
			||||
 | 
				
			||||
    u32_t client_addr; | 
				
			||||
    if(ESP_OK != fd_to_ipv4(fd, &client_addr)) { | 
				
			||||
        return ESP_FAIL; | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    ESP_LOGD(TAG, "[captive] Client addr = 0x%08x, ap addr 0x%08x, ap nmask 0x%08x", | 
				
			||||
             client_addr, | 
				
			||||
             apip.ip.addr, | 
				
			||||
             apip.netmask.addr | 
				
			||||
    ); | 
				
			||||
 | 
				
			||||
    // Check if client IP looks like from our AP dhcps
 | 
				
			||||
    if ((client_addr & apip.netmask.addr) != (apip.ip.addr & apip.netmask.addr)) { | 
				
			||||
        ESP_LOGD(TAG, "[captive] Client not in AP IP range"); | 
				
			||||
        goto no_redirect; | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    // Get requested hostname from the header
 | 
				
			||||
    esp_err_t rv = httpd_req_get_hdr_value_str(r, "Host", s_buf, 64); | 
				
			||||
    if (rv != ESP_OK) { | 
				
			||||
        ESP_LOGW(TAG, "[captive] No host in request?"); | 
				
			||||
        goto no_redirect; | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    ESP_LOGD(TAG, "[captive] Candidate for redirect: %s%s", s_buf, r->uri); | 
				
			||||
 | 
				
			||||
    // Never redirect if host is an IP
 | 
				
			||||
    if (strlen(s_buf)>7) { | 
				
			||||
        bool isIP = 1; | 
				
			||||
        for (int x = 0; x < strlen(s_buf); x++) { | 
				
			||||
            if (s_buf[x] != '.' && (s_buf[x] < '0' || s_buf[x] > '9')) { | 
				
			||||
                isIP = 0; | 
				
			||||
                break; | 
				
			||||
            } | 
				
			||||
        } | 
				
			||||
 | 
				
			||||
        if (isIP) { | 
				
			||||
            ESP_LOGD(TAG, "[captive] Access via IP, no redirect needed"); | 
				
			||||
            goto no_redirect; | 
				
			||||
        } | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    // Redirect if host differs
 | 
				
			||||
    // - this can be e.g. connectivitycheck.gstatic.com or the equivalent for ios
 | 
				
			||||
 | 
				
			||||
    if (0 != strcmp(s_buf, httpd_captive_redirect_get_domain())) { | 
				
			||||
        ESP_LOGD(TAG, "[captive] Host differs, redirecting..."); | 
				
			||||
 | 
				
			||||
        httpd_captive_redirect_get_url(r, s_buf, 64); | 
				
			||||
        return httpd_redirect_to(r, s_buf); | 
				
			||||
    } else { | 
				
			||||
        ESP_LOGD(TAG, "[captive] Host is OK"); | 
				
			||||
        goto no_redirect; | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    no_redirect: | 
				
			||||
    return ESP_ERR_NOT_FOUND; | 
				
			||||
} | 
				
			||||
@ -1,42 +0,0 @@ | 
				
			||||
#include <esp_wifi_types.h> | 
				
			||||
#include <esp_wifi.h> | 
				
			||||
#include <esp_log.h> | 
				
			||||
#include <fcntl.h> | 
				
			||||
#include <sys/socket.h> | 
				
			||||
 | 
				
			||||
#include "httpd_utils/fd_to_ipv4.h" | 
				
			||||
 | 
				
			||||
static const char *TAG = "fd2ipv4"; | 
				
			||||
 | 
				
			||||
/**
 | 
				
			||||
 * Get IP address for a FD | 
				
			||||
 * | 
				
			||||
 * @param fd | 
				
			||||
 * @param[out] ipv4 | 
				
			||||
 * @return success | 
				
			||||
 */ | 
				
			||||
esp_err_t fd_to_ipv4(int fd, in_addr_t *ipv4) | 
				
			||||
{ | 
				
			||||
    struct sockaddr_in6 addr; | 
				
			||||
    size_t len = sizeof(addr); | 
				
			||||
    int rv = getpeername(fd, (struct sockaddr *) &addr, &len); | 
				
			||||
    if (rv != 0) { | 
				
			||||
        ESP_LOGE(TAG, "Failed to get IP addr for fd %d", fd); | 
				
			||||
        return ESP_FAIL; | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    uint32_t client_addr = 0; | 
				
			||||
    if (addr.sin6_family == AF_INET6) { | 
				
			||||
        // this would fail in a real ipv6 network
 | 
				
			||||
        // with ipv4 the addr is simply in the last ipv6 byte
 | 
				
			||||
        struct sockaddr_in6 *s = &addr; | 
				
			||||
        client_addr = s->sin6_addr.un.u32_addr[3]; | 
				
			||||
    } | 
				
			||||
    else { | 
				
			||||
        struct sockaddr_in *s = (struct sockaddr_in *) &addr; | 
				
			||||
        client_addr = s->sin_addr.s_addr; | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    *ipv4 = client_addr; | 
				
			||||
    return ESP_OK; | 
				
			||||
} | 
				
			||||
@ -1,20 +0,0 @@ | 
				
			||||
#include <esp_wifi_types.h> | 
				
			||||
#include <esp_wifi.h> | 
				
			||||
#include <esp_log.h> | 
				
			||||
#include <fcntl.h> | 
				
			||||
#include <sys/socket.h> | 
				
			||||
 | 
				
			||||
#include "httpd_utils/redirect.h" | 
				
			||||
 | 
				
			||||
static const char *TAG="redirect"; | 
				
			||||
 | 
				
			||||
esp_err_t httpd_redirect_to(httpd_req_t *r, const char *uri) | 
				
			||||
{ | 
				
			||||
    ESP_LOGD(TAG, "Redirect to %s", uri); | 
				
			||||
 | 
				
			||||
    httpd_resp_set_hdr(r, "Location", uri); | 
				
			||||
    httpd_resp_set_status(r, "303 See Other"); | 
				
			||||
    httpd_resp_set_type(r, HTTPD_TYPE_TEXT); | 
				
			||||
    const char *msg = "Redirect"; | 
				
			||||
    return httpd_resp_send(r, msg, -1); | 
				
			||||
} | 
				
			||||
@ -1,181 +0,0 @@ | 
				
			||||
//#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
 | 
				
			||||
 | 
				
			||||
#include <sys/queue.h> | 
				
			||||
#include <malloc.h> | 
				
			||||
#include <assert.h> | 
				
			||||
#include <esp_log.h> | 
				
			||||
#include <esp_err.h> | 
				
			||||
#include <string.h> | 
				
			||||
#include "httpd_utils/session_kvmap.h" | 
				
			||||
 | 
				
			||||
static const char *TAG = "sess_kvmap"; | 
				
			||||
 | 
				
			||||
// this struct is opaque, a stub like this is sufficient for the head pointer.
 | 
				
			||||
struct sess_kv_entry; | 
				
			||||
 | 
				
			||||
/** Session head structure, dynamically allocated */ | 
				
			||||
SLIST_HEAD(sess_kv_map, sess_kv_entry); | 
				
			||||
 | 
				
			||||
struct sess_kv_entry { | 
				
			||||
    SLIST_ENTRY(sess_kv_entry) link; | 
				
			||||
    char key[SESS_KVMAP_KEY_LEN]; | 
				
			||||
    void *value; | 
				
			||||
    sess_kv_free_func_t free_fn; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
struct sess_kv_map *sess_kv_map_alloc(void) | 
				
			||||
{ | 
				
			||||
    ESP_LOGD(TAG, "kv store alloc"); | 
				
			||||
    struct sess_kv_map *map = calloc(sizeof(struct sess_kv_map), 1); | 
				
			||||
    assert(map); | 
				
			||||
    SLIST_INIT(map); | 
				
			||||
    return map; | 
				
			||||
} | 
				
			||||
 | 
				
			||||
void sess_kv_map_free(void *head_v) | 
				
			||||
{ | 
				
			||||
    struct sess_kv_map* head = head_v; | 
				
			||||
 | 
				
			||||
    ESP_LOGD(TAG, "kv store free"); | 
				
			||||
    assert(head); | 
				
			||||
    struct sess_kv_entry *item, *tmp; | 
				
			||||
    SLIST_FOREACH_SAFE(item, head, link, tmp) { | 
				
			||||
        if (item->free_fn) { | 
				
			||||
            item->free_fn(item->value); | 
				
			||||
            free(item); | 
				
			||||
        } | 
				
			||||
    } | 
				
			||||
    free(head); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
 | 
				
			||||
void * sess_kv_map_get(struct sess_kv_map *head, const char *key) | 
				
			||||
{ | 
				
			||||
    assert(head); | 
				
			||||
    assert(key); | 
				
			||||
    ESP_LOGD(TAG, "kv store get %s", key); | 
				
			||||
 | 
				
			||||
    struct sess_kv_entry *item; | 
				
			||||
    SLIST_FOREACH(item, head, link) { | 
				
			||||
        if (0==strcmp(item->key, key)) { | 
				
			||||
            ESP_LOGD(TAG, "got ok"); | 
				
			||||
            return item->value; | 
				
			||||
        } | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    ESP_LOGD(TAG, "not found in store"); | 
				
			||||
    return NULL; | 
				
			||||
} | 
				
			||||
 | 
				
			||||
void * sess_kv_map_take(struct sess_kv_map *head, const char *key) | 
				
			||||
{ | 
				
			||||
    assert(head); | 
				
			||||
    assert(key); | 
				
			||||
    ESP_LOGD(TAG, "kv store take %s", key); | 
				
			||||
 | 
				
			||||
    struct sess_kv_entry *item; | 
				
			||||
    SLIST_FOREACH(item, head, link) { | 
				
			||||
        if (0==strcmp(item->key, key)) { | 
				
			||||
            item->key[0] = 0; | 
				
			||||
            item->free_fn = NULL; | 
				
			||||
            ESP_LOGD(TAG, "taken ok"); | 
				
			||||
            return item->value; | 
				
			||||
        } | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    ESP_LOGD(TAG, "not found in store"); | 
				
			||||
    return NULL; | 
				
			||||
} | 
				
			||||
 | 
				
			||||
esp_err_t sess_kv_map_remove(struct sess_kv_map *head, const char *key) | 
				
			||||
{ | 
				
			||||
    assert(head); | 
				
			||||
    assert(key); | 
				
			||||
    ESP_LOGD(TAG, "kv store remove %s", key); | 
				
			||||
 | 
				
			||||
    struct sess_kv_entry *item; | 
				
			||||
    SLIST_FOREACH(item, head, link) { | 
				
			||||
        if (0==strcmp(item->key, key)) { | 
				
			||||
            if (item->free_fn) { | 
				
			||||
                item->free_fn(item->value); | 
				
			||||
            } | 
				
			||||
            item->key[0] = 0; | 
				
			||||
            item->value = NULL; | 
				
			||||
            item->free_fn = NULL; | 
				
			||||
            return ESP_OK; | 
				
			||||
        } | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    ESP_LOGD(TAG, "couldn't remove, not found: %s", key); | 
				
			||||
    return ESP_ERR_NOT_FOUND; | 
				
			||||
} | 
				
			||||
 | 
				
			||||
 | 
				
			||||
esp_err_t sess_kv_map_set(struct sess_kv_map *head, const char *key, void *value, sess_kv_free_func_t free_fn) | 
				
			||||
{ | 
				
			||||
    assert(head); | 
				
			||||
    assert(key); | 
				
			||||
    ESP_LOGD(TAG, "kv set value for key %s", key); | 
				
			||||
 | 
				
			||||
    size_t key_len = strlen(key); | 
				
			||||
    if (key_len > SESS_KVMAP_KEY_LEN-1) { | 
				
			||||
        ESP_LOGE(TAG, "Key too long: %s", key); | 
				
			||||
        // discard illegal key
 | 
				
			||||
        return ESP_FAIL; | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    if (key_len == 0) { | 
				
			||||
        ESP_LOGE(TAG, "Key too short: \"%s\"", key); | 
				
			||||
        // discard illegal key
 | 
				
			||||
        return ESP_FAIL; | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    struct sess_kv_entry *item = NULL; | 
				
			||||
    struct sess_kv_entry *empty_item = NULL; // found item with no content
 | 
				
			||||
    SLIST_FOREACH(item, head, link) { | 
				
			||||
        ESP_LOGD(TAG, "test item with key %s, ptr %p > %p", item->key, item, item->link.sle_next); | 
				
			||||
        if (0 == item->key[0]) { | 
				
			||||
            ESP_LOGD(TAG, "found an empty slot"); | 
				
			||||
            empty_item = item; | 
				
			||||
        } | 
				
			||||
        else if (0==strcmp(item->key, key)) { | 
				
			||||
            ESP_LOGD(TAG, "old value replaced"); | 
				
			||||
            if (item->free_fn) { | 
				
			||||
                item->free_fn(item->value); | 
				
			||||
            } | 
				
			||||
            item->value = value; | 
				
			||||
            item->free_fn = free_fn; | 
				
			||||
            return ESP_OK; | 
				
			||||
        } else { | 
				
			||||
            ESP_LOGD(TAG, "skip this one"); | 
				
			||||
        } | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    struct sess_kv_entry *new_item = NULL; | 
				
			||||
 | 
				
			||||
    // insert new or reuse an empty item
 | 
				
			||||
    if (empty_item) { | 
				
			||||
        new_item = empty_item; | 
				
			||||
        ESP_LOGD(TAG, "empty item reused (%p)", new_item); | 
				
			||||
    } else { | 
				
			||||
        ESP_LOGD(TAG, "alloc new item"); | 
				
			||||
        // key not found, add a new entry.
 | 
				
			||||
        new_item = calloc(sizeof(struct sess_kv_entry), 1); | 
				
			||||
        if (!new_item) { | 
				
			||||
            ESP_LOGE(TAG, "New entry alloc failed"); | 
				
			||||
            return ESP_ERR_NO_MEM; | 
				
			||||
        } | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    strcpy(new_item->key, key); | 
				
			||||
    new_item->free_fn = free_fn; | 
				
			||||
    new_item->value = value; | 
				
			||||
 | 
				
			||||
    if (!empty_item) { | 
				
			||||
        ESP_LOGD(TAG, "insert new item into list"); | 
				
			||||
        // this item was malloc'd
 | 
				
			||||
        SLIST_INSERT_HEAD(head, new_item, link); | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    return ESP_OK; | 
				
			||||
} | 
				
			||||
@ -1,220 +0,0 @@ | 
				
			||||
//#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
 | 
				
			||||
 | 
				
			||||
#include <malloc.h> | 
				
			||||
#include <esp_log.h> | 
				
			||||
#include <freertos/FreeRTOS.h> | 
				
			||||
#include <freertos/semphr.h> | 
				
			||||
#include <esp_http_server.h> | 
				
			||||
#include <sys/queue.h> | 
				
			||||
 | 
				
			||||
#include "httpd_utils/session_store.h" | 
				
			||||
 | 
				
			||||
// TODO add a limit on simultaneously open sessions (can cause memory exhaustion DoS)
 | 
				
			||||
 | 
				
			||||
#define COOKIE_LEN 32 | 
				
			||||
static const char *TAG = "session"; | 
				
			||||
 | 
				
			||||
struct session { | 
				
			||||
    char cookie[COOKIE_LEN + 1]; | 
				
			||||
    void *data; | 
				
			||||
    TickType_t last_activity_time; | 
				
			||||
    LIST_ENTRY(session) link; | 
				
			||||
    sess_data_free_fn_t free_fn; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
static LIST_HEAD(sessions_, session) s_store; | 
				
			||||
 | 
				
			||||
static SemaphoreHandle_t sess_store_lock = NULL; | 
				
			||||
static bool sess_store_inited = false; | 
				
			||||
 | 
				
			||||
 | 
				
			||||
void session_store_init(void) | 
				
			||||
{ | 
				
			||||
    if (sess_store_inited) { | 
				
			||||
        xSemaphoreTake(sess_store_lock, portMAX_DELAY); | 
				
			||||
        { | 
				
			||||
            struct session *it, *tit; | 
				
			||||
            LIST_FOREACH_SAFE(it, &s_store, link, tit) { | 
				
			||||
                ESP_LOGW(TAG, "Session cookie expired: \"%s\"", it->cookie); | 
				
			||||
                if (it->free_fn) it->free_fn(it->data); | 
				
			||||
                // no relink, we dont care if the list breaks after this - we're removing all of it
 | 
				
			||||
                free(it); | 
				
			||||
            } | 
				
			||||
        } | 
				
			||||
        LIST_INIT(&s_store); | 
				
			||||
        xSemaphoreGive(sess_store_lock); | 
				
			||||
    } else { | 
				
			||||
        LIST_INIT(&s_store); | 
				
			||||
        sess_store_lock = xSemaphoreCreateMutex(); | 
				
			||||
        sess_store_inited = true; | 
				
			||||
    } | 
				
			||||
} | 
				
			||||
 | 
				
			||||
/**
 | 
				
			||||
 * Fill buffer with base64 symbols. Does not add a trailing null byte | 
				
			||||
 * | 
				
			||||
 * @param buf | 
				
			||||
 * @param len | 
				
			||||
 */ | 
				
			||||
static void esp_fill_random_alnum(char *buf, size_t len) | 
				
			||||
{ | 
				
			||||
#define alphabet_len 64 | 
				
			||||
    static const char alphabet[alphabet_len] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/"; | 
				
			||||
 | 
				
			||||
    unsigned int seed = xTaskGetTickCount(); | 
				
			||||
 | 
				
			||||
    assert(buf != NULL); | 
				
			||||
    for (int i = 0; i < len; i++) { | 
				
			||||
        int index = rand_r(&seed) % alphabet_len; | 
				
			||||
        *buf++ = (uint8_t) alphabet[index]; | 
				
			||||
    } | 
				
			||||
} | 
				
			||||
 | 
				
			||||
const char *session_new(void *data, sess_data_free_fn_t free_fn) | 
				
			||||
{ | 
				
			||||
    assert(data != NULL); | 
				
			||||
 | 
				
			||||
    struct session *item = calloc(sizeof(struct session), 1); | 
				
			||||
    if (item == NULL) return NULL; | 
				
			||||
 | 
				
			||||
    item->data = data; | 
				
			||||
    item->free_fn = free_fn; | 
				
			||||
    esp_fill_random_alnum(item->cookie, COOKIE_LEN); | 
				
			||||
    item->cookie[COOKIE_LEN] = 0; // add the terminator
 | 
				
			||||
 | 
				
			||||
    xSemaphoreTake(sess_store_lock, portMAX_DELAY); | 
				
			||||
    { | 
				
			||||
        item->last_activity_time = xTaskGetTickCount(); | 
				
			||||
 | 
				
			||||
        LIST_INSERT_HEAD(&s_store, item, link); | 
				
			||||
    } | 
				
			||||
    xSemaphoreGive(sess_store_lock); | 
				
			||||
 | 
				
			||||
    ESP_LOGD(TAG, "New HTTP session: %s", item->cookie); | 
				
			||||
 | 
				
			||||
    return item->cookie; | 
				
			||||
} | 
				
			||||
 | 
				
			||||
void *session_find_and(const char *cookie, enum session_find_action action) | 
				
			||||
{ | 
				
			||||
    // no point in searching if the length is wrong
 | 
				
			||||
    if (strlen(cookie) != COOKIE_LEN) { | 
				
			||||
        ESP_LOGW(TAG, "Wrong session cookie length: \"%s\"", cookie); | 
				
			||||
        return NULL; | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    struct session *it = NULL; | 
				
			||||
 | 
				
			||||
    bool found = false; | 
				
			||||
    xSemaphoreTake(sess_store_lock, portMAX_DELAY); | 
				
			||||
    { | 
				
			||||
        LIST_FOREACH(it, &s_store, link) { | 
				
			||||
            if (0==strcmp(it->cookie, cookie)) { | 
				
			||||
                ESP_LOGD(TAG, "Session cookie matched: \"%s\"", cookie); | 
				
			||||
 | 
				
			||||
                it->last_activity_time = xTaskGetTickCount(); | 
				
			||||
                found = true; | 
				
			||||
                break; | 
				
			||||
            } | 
				
			||||
        } | 
				
			||||
        if (found && action == SESS_DROP) { | 
				
			||||
            if (it->free_fn) it->free_fn(it->data); | 
				
			||||
            LIST_REMOVE(it, link); | 
				
			||||
            free(it); | 
				
			||||
            ESP_LOGD(TAG, "Dropped session: \"%s\"", cookie); | 
				
			||||
        } | 
				
			||||
    } | 
				
			||||
    xSemaphoreGive(sess_store_lock); | 
				
			||||
    if (found) { | 
				
			||||
        if (action == SESS_DROP) { | 
				
			||||
            // it was dropped inside the guarded block
 | 
				
			||||
            // the return value is not used with DROP
 | 
				
			||||
            return NULL; | 
				
			||||
        } | 
				
			||||
        else if(action == SESS_GET_DATA) { | 
				
			||||
            return it->data; | 
				
			||||
        } | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    ESP_LOGW(TAG, "Session cookie not found: \"%s\"", cookie); | 
				
			||||
    return NULL; | 
				
			||||
} | 
				
			||||
 | 
				
			||||
void *session_find(const char *cookie) | 
				
			||||
{ | 
				
			||||
    return session_find_and(cookie, SESS_GET_DATA); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
void session_drop(const char *cookie) | 
				
			||||
{ | 
				
			||||
    session_find_and(cookie, SESS_DROP); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
void session_drop_expired(void) | 
				
			||||
{ | 
				
			||||
    struct session *it; | 
				
			||||
    struct session *tit; | 
				
			||||
 | 
				
			||||
    xSemaphoreTake(sess_store_lock, portMAX_DELAY); | 
				
			||||
    { | 
				
			||||
        TickType_t now = xTaskGetTickCount(); | 
				
			||||
 | 
				
			||||
        LIST_FOREACH_SAFE(it, &s_store, link, tit) { | 
				
			||||
            TickType_t elapsed = now - it->last_activity_time; | 
				
			||||
            if (elapsed > pdMS_TO_TICKS(SESSION_EXPIRY_TIME_S*1000)) { | 
				
			||||
                ESP_LOGD(TAG, "Session cookie expired: \"%s\"", it->cookie); | 
				
			||||
                if (it->free_fn) it->free_fn(it->data); | 
				
			||||
                LIST_REMOVE(it, link); | 
				
			||||
                free(it); | 
				
			||||
            } | 
				
			||||
        } | 
				
			||||
    } | 
				
			||||
    xSemaphoreGive(sess_store_lock); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
 | 
				
			||||
void *httpd_req_find_session_and(httpd_req_t *r, enum session_find_action action) | 
				
			||||
{ | 
				
			||||
    // this could be called periodically, but it's sufficient to run it at each request
 | 
				
			||||
    // it won't slow anything down unless there are hundreds of sessions
 | 
				
			||||
    session_drop_expired(); | 
				
			||||
 | 
				
			||||
    static char buf[256]; | 
				
			||||
    esp_err_t rv = httpd_req_get_hdr_value_str(r, "Cookie", buf, 256); | 
				
			||||
    if (rv == ESP_OK || rv == ESP_ERR_HTTPD_RESULT_TRUNC) { | 
				
			||||
        ESP_LOGD(TAG, "Cookie header: %s", buf); | 
				
			||||
 | 
				
			||||
        // probably OK, see if we have a cookie
 | 
				
			||||
        char *start = strstr(buf, SESSION_COOKIE_NAME"="); | 
				
			||||
        if (start != 0) { | 
				
			||||
            start += strlen(SESSION_COOKIE_NAME"="); | 
				
			||||
            char *end = strchr(start, ';'); | 
				
			||||
            if (end != NULL) *end = 0; | 
				
			||||
 | 
				
			||||
            ESP_LOGD(TAG, "Cookie is: %s", start); | 
				
			||||
            return session_find_and(start, action); | 
				
			||||
        } | 
				
			||||
    } else { | 
				
			||||
        ESP_LOGD(TAG, "No cookie."); | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    return NULL; | 
				
			||||
} | 
				
			||||
 | 
				
			||||
void httpd_resp_delete_session_cookie(httpd_req_t *r) | 
				
			||||
{ | 
				
			||||
    httpd_resp_set_hdr(r, "Set-Cookie", SESSION_COOKIE_NAME"="); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
 | 
				
			||||
// Static because the value is passed and stored by reference, so it wouldn't live long enough if it was on stack,
 | 
				
			||||
// and there also isn't any hook for freeing it if we used malloc(). This is an SDK bug.
 | 
				
			||||
static char cookie_hdr_buf[COOKIE_LEN + 10]; | 
				
			||||
 | 
				
			||||
// !!! this must not be called concurrently from a different thread.
 | 
				
			||||
// That is no problem so long as the server stays single-threaded
 | 
				
			||||
void httpd_resp_set_session_cookie(httpd_req_t *r, const char *cookie) | 
				
			||||
{ | 
				
			||||
    snprintf(cookie_hdr_buf, COOKIE_LEN + 10, "SESSID=%s", cookie); | 
				
			||||
    httpd_resp_set_hdr(r, "Set-Cookie", cookie_hdr_buf); | 
				
			||||
} | 
				
			||||
@ -1,41 +0,0 @@ | 
				
			||||
/**
 | 
				
			||||
 * TODO file description | 
				
			||||
 * 
 | 
				
			||||
 * Created on 2019/07/13. | 
				
			||||
 */ | 
				
			||||
 | 
				
			||||
#ifndef SESSION_UTILS_C_H | 
				
			||||
#define SESSION_UTILS_C_H | 
				
			||||
 | 
				
			||||
#include <esp_err.h> | 
				
			||||
#include <esp_http_server.h> | 
				
			||||
#include "httpd_utils/session_kvmap.h" | 
				
			||||
#include "httpd_utils/session_store.h" | 
				
			||||
 | 
				
			||||
/**
 | 
				
			||||
 * Start or resume a session. | 
				
			||||
 */ | 
				
			||||
esp_err_t HTTP_GET_SESSION(sess_kv_map_t **ppkvstore, httpd_req_t *r) | 
				
			||||
{ | 
				
			||||
    sess_kv_map_t *kvstore; | 
				
			||||
    kvstore = httpd_req_find_session_and((r), SESS_GET_DATA); | 
				
			||||
    if (NULL == kvstore) { | 
				
			||||
        kvstore = sess_kv_map_alloc(); | 
				
			||||
        if (!kvstore) return ESP_ERR_NO_MEM; | 
				
			||||
 | 
				
			||||
        const char *cookie = session_new(kvstore, sess_kv_map_free); | 
				
			||||
        if (cookie == NULL) { | 
				
			||||
            // session alloc failed
 | 
				
			||||
            sess_kv_map_free(kvstore); | 
				
			||||
            *ppkvstore = NULL; | 
				
			||||
            return ESP_ERR_NO_MEM; | 
				
			||||
        } | 
				
			||||
        httpd_resp_set_session_cookie(r, cookie); | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    *ppkvstore = kvstore; | 
				
			||||
    return ESP_OK; | 
				
			||||
} | 
				
			||||
 | 
				
			||||
 | 
				
			||||
#endif //SESSION_UTILS_C_H
 | 
				
			||||
@ -1,217 +0,0 @@ | 
				
			||||
#include <esp_log.h> | 
				
			||||
#include <esp_err.h> | 
				
			||||
 | 
				
			||||
#include <fileserver/token_subs.h> | 
				
			||||
#include <application.h> | 
				
			||||
#include <httpd_utils/captive.h> | 
				
			||||
 | 
				
			||||
#include "websrv.h" | 
				
			||||
#include "esp_http_server.h" | 
				
			||||
#include "utils.h" | 
				
			||||
#include "irblast.h" | 
				
			||||
 | 
				
			||||
#include "www_files_enum.h" | 
				
			||||
 | 
				
			||||
static const char *TAG = "websrv"; | 
				
			||||
 | 
				
			||||
static httpd_handle_t s_hServer = NULL; | 
				
			||||
 | 
				
			||||
// Embedded files (must also be listed in CMakeLists.txt as COMPONENT_EMBED_TXTFILES)
 | 
				
			||||
efile(index_file, "index_html"); | 
				
			||||
 | 
				
			||||
static struct tpl_kv_list build_index_replacements_kv(void) { | 
				
			||||
    //char name[TPL_KV_KEY_LEN];
 | 
				
			||||
    struct tpl_kv_list kv = tpl_kv_init(); | 
				
			||||
    tpl_kv_add(&kv, "version", APP_VERSION); | 
				
			||||
    return kv; | 
				
			||||
} | 
				
			||||
 | 
				
			||||
/* Main page */ | 
				
			||||
static esp_err_t handler_index(httpd_req_t *req) { | 
				
			||||
    struct tpl_kv_list kv = build_index_replacements_kv(); | 
				
			||||
 | 
				
			||||
    esp_err_t suc = httpd_send_template_file(req, FILE_INDEX_HTML, tpl_kv_replacer, &kv, 0); | 
				
			||||
    tpl_kv_free(&kv); | 
				
			||||
    return suc; | 
				
			||||
} | 
				
			||||
 | 
				
			||||
/* Update XHR for new index page data */ | 
				
			||||
static esp_err_t handler_update(httpd_req_t *req) { | 
				
			||||
    struct tpl_kv_list kv = build_index_replacements_kv(); | 
				
			||||
 | 
				
			||||
    esp_err_t suc = tpl_kv_send_as_ascii_map(req, &kv); | 
				
			||||
    tpl_kv_free(&kv); | 
				
			||||
    return suc; | 
				
			||||
} | 
				
			||||
 | 
				
			||||
/* Set a param */ | 
				
			||||
static esp_err_t handler_set(httpd_req_t *req) { | 
				
			||||
    char buf[64]; | 
				
			||||
    int n = httpd_req_recv(req, buf, 63); | 
				
			||||
    if (n < 0) { | 
				
			||||
        ESP_LOGW(TAG, "rx er"); | 
				
			||||
        goto err; | 
				
			||||
    } | 
				
			||||
    buf[n] = 0; | 
				
			||||
 | 
				
			||||
    char keybuf[20]; | 
				
			||||
    char valbuf[20]; | 
				
			||||
    if (ESP_OK != httpd_query_key_value(buf, "key", keybuf, 20)) goto err; | 
				
			||||
    if (ESP_OK != httpd_query_key_value(buf, "value", valbuf, 20)) goto err; | 
				
			||||
 | 
				
			||||
    // TODO
 | 
				
			||||
    ESP_LOGW(TAG, "bad key %s", keybuf); | 
				
			||||
    goto err; | 
				
			||||
 | 
				
			||||
    return httpd_resp_send(req, NULL, 0); | 
				
			||||
 | 
				
			||||
    err: | 
				
			||||
    return httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, NULL); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
/* Request emulator reboot */ | 
				
			||||
static esp_err_t handler_reboot(httpd_req_t *req) { | 
				
			||||
    httpd_resp_send(req, "<!DOCTYPE html><html><head>" | 
				
			||||
                         "<meta http-equiv=\"refresh\" content=\"10; url=/\">" | 
				
			||||
                         "</head>" | 
				
			||||
                         "<body>" | 
				
			||||
                         "Reboot requested. Reloading in 10 seconds.<br>" | 
				
			||||
                         "<a href=\"/\">Try now.</a>", -1); | 
				
			||||
 | 
				
			||||
    ESP_LOGI(TAG, "Restarting ESP..."); | 
				
			||||
    esp_restart(); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
/* Request emulator reboot */ | 
				
			||||
static esp_err_t handler_irblast(httpd_req_t *req) { | 
				
			||||
    char buf[64]; | 
				
			||||
    esp_err_t err = httpd_req_get_url_query_str(req, buf, 63); | 
				
			||||
    if (err != ESP_OK) { | 
				
			||||
        ESP_LOGW(TAG, "param er: %s", esp_err_to_name(err)); | 
				
			||||
        goto err; | 
				
			||||
    } | 
				
			||||
    ESP_LOGI(TAG, "querystring: %s", buf); | 
				
			||||
 | 
				
			||||
    char valbuf[20]; | 
				
			||||
    if (ESP_OK != httpd_query_key_value(buf, "do", valbuf, 20)) { | 
				
			||||
        ESP_LOGW(TAG, "fail to get \"do\" param"); | 
				
			||||
        goto err; | 
				
			||||
    } | 
				
			||||
    valbuf[19] = 0; | 
				
			||||
 | 
				
			||||
    ESP_LOGI(TAG, "IR cmd to send: %s", valbuf); | 
				
			||||
 | 
				
			||||
    const char *cmdnames[] = { | 
				
			||||
            [IRBLAST_ONOFF] = "onoff", | 
				
			||||
            [IRBLAST_DAYNIGHT] = "daynight", | 
				
			||||
            [IRBLAST_SPEED1] = "speed1", | 
				
			||||
            [IRBLAST_SPEED2] = "speed2", | 
				
			||||
            [IRBLAST_SPEED3] = "speed3", | 
				
			||||
            [IRBLAST_MODE1] = "mode1", | 
				
			||||
            [IRBLAST_MODE2] = "mode2", | 
				
			||||
            [IRBLAST_MODE3] = "mode3", | 
				
			||||
            [IRBLAST_MODE4] = "mode4", | 
				
			||||
            [IRBLAST_HUM1] = "hum1", | 
				
			||||
            [IRBLAST_HUM2] = "hum2", | 
				
			||||
            [IRBLAST_HUM3] = "hum3", | 
				
			||||
            NULL | 
				
			||||
    }; | 
				
			||||
 | 
				
			||||
    for (int i = 0; cmdnames[i]; i++) { | 
				
			||||
        if (strncasecmp(cmdnames[i], valbuf, 20) == 0) { | 
				
			||||
            httpd_resp_send(req, "{\"success\":true}", -1); | 
				
			||||
            irblast_send((enum irblast_cmd) i); | 
				
			||||
            return ESP_OK; | 
				
			||||
        } | 
				
			||||
    } | 
				
			||||
    err: | 
				
			||||
    return httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, NULL); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
/* An HTTP GET handler */ | 
				
			||||
static esp_err_t handler_staticfiles(httpd_req_t *r) { | 
				
			||||
    const struct embedded_file_info *file; | 
				
			||||
    const char *fname = r->user_ctx; | 
				
			||||
    enum file_access_level access = FILE_ACCESS_PROTECTED; | 
				
			||||
 | 
				
			||||
    // wildcard files must be public
 | 
				
			||||
    if (fname == NULL) { | 
				
			||||
        fname = r->uri + 1; // URI always starts with slash, but we dont want a slash in the file name
 | 
				
			||||
        access = FILE_ACCESS_PUBLIC; | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
#if USE_CAPTIVE_PORTAL | 
				
			||||
    // First check if this is a phone taken here by the captive portal
 | 
				
			||||
    esp_err_t rv = httpd_captive_redirect(r); | 
				
			||||
    if (rv != ESP_ERR_NOT_FOUND) return rv; | 
				
			||||
#endif | 
				
			||||
 | 
				
			||||
    if (ESP_OK != www_get_static_file(fname, access, &file)) { | 
				
			||||
        ESP_LOGW(TAG, "File not found: %s", fname); | 
				
			||||
        return httpd_resp_send_404(r); | 
				
			||||
    } else { | 
				
			||||
        if (streq(file->mime, "text/html")) { | 
				
			||||
            // using the template func to allow includes
 | 
				
			||||
            return httpd_send_template_file_struct(r, file, /*replacer*/NULL, /*ctx*/NULL, /*opts*/0); | 
				
			||||
        } else { | 
				
			||||
            return httpd_send_static_file_struct(r, file, TPL_ESCAPE_NONE, 0); | 
				
			||||
        } | 
				
			||||
    } | 
				
			||||
} | 
				
			||||
 | 
				
			||||
static const httpd_uri_t routes[] = { | 
				
			||||
        { | 
				
			||||
                .uri = "/", | 
				
			||||
                .method = HTTP_GET, | 
				
			||||
                .handler = handler_index, | 
				
			||||
        }, | 
				
			||||
        { | 
				
			||||
                .uri = "/data", | 
				
			||||
                .method = HTTP_GET, | 
				
			||||
                .handler = handler_update, | 
				
			||||
        }, | 
				
			||||
        { | 
				
			||||
                .uri = "/set", | 
				
			||||
                .method = HTTP_POST, | 
				
			||||
                .handler = handler_set, | 
				
			||||
        }, | 
				
			||||
        { | 
				
			||||
                .uri = "/reboot", | 
				
			||||
                .method = HTTP_GET, | 
				
			||||
                .handler = handler_reboot, | 
				
			||||
        }, | 
				
			||||
        { | 
				
			||||
                .uri = "/irblast", | 
				
			||||
                .method = HTTP_GET, | 
				
			||||
                .handler = handler_irblast, | 
				
			||||
        }, | 
				
			||||
        { | 
				
			||||
                .uri = "*", // any file except protected (e.g. not HTML, PEM etc)
 | 
				
			||||
                .method = HTTP_GET, | 
				
			||||
                .handler = handler_staticfiles, | 
				
			||||
        }, | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
esp_err_t websrv_init(void) { | 
				
			||||
    httpd_config_t config = HTTPD_DEFAULT_CONFIG(); | 
				
			||||
    config.max_open_sockets = 3; | 
				
			||||
 | 
				
			||||
    config.uri_match_fn = httpd_uri_match_wildcard; | 
				
			||||
    config.lru_purge_enable = true; | 
				
			||||
 | 
				
			||||
    ESP_LOGI(TAG, "Starting HTTP server on port: '%d'", config.server_port); | 
				
			||||
 | 
				
			||||
    esp_err_t suc = httpd_start(&s_hServer, &config); | 
				
			||||
    if (suc == ESP_OK) { | 
				
			||||
        ESP_LOGI(TAG, "HTTP server started"); | 
				
			||||
 | 
				
			||||
        for (int i = 0; i < sizeof(routes) / sizeof(httpd_uri_t); i++) { | 
				
			||||
            httpd_register_uri_handler(s_hServer, &routes[i]); | 
				
			||||
        } | 
				
			||||
 | 
				
			||||
        return ESP_OK; | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    ESP_LOGE(TAG, "Error starting server!"); | 
				
			||||
    return suc; | 
				
			||||
} | 
				
			||||
@ -1,14 +0,0 @@ | 
				
			||||
/**
 | 
				
			||||
 * Integrated webserver | 
				
			||||
 * 
 | 
				
			||||
 * Created on 2019/07/13. | 
				
			||||
 */ | 
				
			||||
 | 
				
			||||
#ifndef CSPEMU_WEBSRV_H | 
				
			||||
#define CSPEMU_WEBSRV_H | 
				
			||||
 | 
				
			||||
#include <esp_err.h> | 
				
			||||
 | 
				
			||||
esp_err_t websrv_init(void); | 
				
			||||
 | 
				
			||||
#endif //CSPEMU_WEBSRV_H
 | 
				
			||||
					Loading…
					
					
				
		Reference in new issue