ini) $nb = 0; foreach($gsConfigArray as &$line) { if($line === "[[server]]") { $line = "[server_".$nb."]"; ++$nb; } // comment lines with ; if(substr($line,0,1)=='#') $line = "; ".$line; } //reassemble file and parse as array $gsConfig = implode("\n", $gsConfigArray); $ini = parse_ini_string($gsConfig, true); //create temp array to contain config and put general config values in it $configTemp = array(); if(array_key_exists('port', $ini)) { $configTemp["port"] = (int) $ini["port"]; } else { die('Unable to process '.CONFIG_PATH.': Missing value "port"'); } //process for each capsule, from server_0 to server_$nb-1, put it in an array $capsules = array(); for($i = 0; $i < $nb; ++$i) { $key = "server_".$i; //check that all required fields are present if(array_key_exists('dir', $ini[$key]) && array_key_exists('hostname', $ini[$key]) && array_key_exists('hostname', $ini[$key])) { //check absolute path if($ini[$key]["dir"][0] == "/") { $folder = $ini[$key]["dir"]; } else { $folder = "{here}/".$ini[$key]["dir"]; } $capsules[$ini[$key]["hostname"]] = (object) array( "folder" => $folder, "lang" => $ini[$key]["lang"], "lang_regex" => "/\.([a-z][a-z])\./", "auto_index_ext" => array(".gmi"), "redirect" => false ); } } //convert capsules array to stdObj -> as in json_decode, and put it in config $configTemp["capsules"] = (object) $capsules; //convert array to stdObj -> as in json_decode $conf = (object) $configTemp; } else { die("Unknown config type: ".CONFIG_TYPE."\n"); } foreach($conf->capsules as $hostname => $capsule) { if(empty($conf->capsules->$hostname->redirect)) { $conf->capsules->$hostname->folder = str_replace("{here}",dirname($conf_filename),$capsule->folder); } else { unset($conf->capsules->$hostname->folder); } } if(strpos($_SERVER['HTTP_HOST'],':')!==false) $capsule = strtolower(substr($_SERVER['HTTP_HOST'], 0, strpos($_SERVER['HTTP_HOST'],':'))); else $capsule = strtolower($_SERVER['HTTP_HOST']); $response = false; $response_code = 0; $body = false; if(isset($_GET['qx'])) { $response = "OK"; $body = "# You are following a Gemini link to another server You can't access all the Geminispace with this proxy. If you want to follow this link, you have to install ans use a Gemini client. You asked to follow : ```gemini-url ".urldecode($_GET['qx'])." ```"; $mime="text/html"; $body=gmi2html($capsule, $body, 'en', $_GET['qx'], ''); } if(isset($_GET['q'])) $q = $_GET['q']; else $q = $_SERVER['REQUEST_URI']; if($response === false && !isset($conf->capsules->$capsule)) { $response = "HTTP/1.1 400 BAD REQUEST"; $response_code = 0; } if($response === false && strpos(str_replace("\\",'/',rawurldecode($q)),'/..')!==false) { $response = "HTTP/1.1 400 BAD REQUEST"; $response_code = 0; } if(!empty($conf->capsules->$capsule->redirect)) { // redirect to another capsule $response = "Location: ".str_replace('gemini://','http://',$conf->capsules->$capsule->redirect.$q); $response_code = 302; } elseif($response === false) { // search requested file $filename = $conf->capsules->$capsule->folder.rawurldecode($q); $lang = $conf->capsules->$capsule->lang; if(!empty($conf->capsules->$capsule->lang_regex)) { // search lang code in requested path (ex: file.fr.gmi) preg_match($conf->capsules->$capsule->lang_regex, rawurldecode($q), $matches); if(isset($matches[1])) $lang = strtolower($matches[1]); } // search favicon $favicon = @file_get_contents($conf->capsules->$capsule->folder.'/favicon.txt'); $favicon = mb_substr(trim($favicon),0,1); } if($response === false && $q==='/favicon.ico' && !empty($favicon)) { // generate favicon $image = new Imagick(); $draw = new ImagickDraw(); $pixel = new ImagickPixel( 'white' ); $image->newImage(128, 128, $pixel); $draw->setFont('TwitterColorEmoji-SVGinOT.ttf'); $draw->setFontSize( 120 ); $draw->setFillColor('#999'); $image->annotateImage($draw, 3, 107, 0, $favicon); $image->annotateImage($draw, 4, 106, 0, $favicon); $image->annotateImage($draw, 5, 107, 0, $favicon); $image->annotateImage($draw, 2, 108, 0, $favicon); $draw->setFillColor('#666'); $image->annotateImage($draw, 6, 108, 0, $favicon); $image->annotateImage($draw, 3, 109, 0, $favicon); $image->annotateImage($draw, 4, 110, 0, $favicon); $image->annotateImage($draw, 5, 109, 0, $favicon); $draw->setFillColor('#333'); $image->annotateImage($draw, 4, 108, 0, $favicon); $image->setImageFormat('png'); header('Content-type: image/png'); echo $image; exit; } if($response === false && file_exists($filename)) { if($response === false && is_file($filename)) { $mime = mime_content_type($filename); if($mime == "text/plain") { if(substr($q,-4)=='.gmi') $mime = "text/gemini"; elseif(substr($q,-3)=='.md') $mime = "text/markdown"; elseif(substr($q,-4)=='.html') $mime = "text/html"; } $response = "OK"; $body = file_get_contents($filename); if($mime=="text/gemini") { $mime="text/html"; $body=gmi2html($capsule, $body, $lang, 'gemini://'.$capsule.($conf->port==1965?'':(':'.$conf->port)).$q, $favicon); } } if($response === false && is_dir($filename)) { // if path is a directory name redirect into it if(substr($filename,-1)!='/') { $response = "Location: ".$q."/"; $response_code = 302; } else { $mime = "text/html"; if(file_exists($filename.'/index.gmi')) { // open default file index.gmi $response = "OK"; $filename = $filename.'/index.gmi'; $body = file_get_contents($filename); $body = gmi2html($capsule, $body, $lang, 'gemini://'.$capsule.($conf->port==1965?'':(':'.$conf->port)).$q, $favicon); } elseif(is_array($conf->capsules->$capsule->auto_index_ext)) { // build auto index $response = "OK"; $body = "# ".$capsule." ".basename($filename)."\r\n"; $body .= "=> ../ [..]\r\n"; // three blocks $items_dir=array(); // sub directories $items_gmi=array(); // gmi file chronogically desc $items_oth=array(); // other files $d = dir($filename); while (false !== ($entry = $d->read())) { if(substr($entry,0,1)=='.') { // dir itself continue; } if(is_dir($filename.'/'.$entry) && !in_array('/', $conf->capsules->$capsule->auto_index_ext)) { // folder ext "/" not in auto_index conf continue; } if(is_file($filename.'/'.$entry) && !in_array(substr($entry,strrpos($entry,'.')), $conf->capsules->$capsule->auto_index_ext)) { // ext not in auto_index conf continue; } $link_name = $entry; if(substr($entry,-4)=='.gmi') { // build feed for subscriptions for .gmi files, // adding date YYYY-MM-DD in link if not file name // see specs gemini://gemini.circumlunar.space/docs/companion/subscription.gmi $entry_name = str_replace('_',' ',substr($entry,0,-4)); if(!preg_match("/^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])\s$/",substr($entry_name,0,11))) $link_name = date("Y-m-d", filemtime($filename.'/'.$entry))." ".$entry_name; else $link_name = " ".$entry_name; $items_gmi[$link_name." ".$entry] = "=> ".rawurlencode($entry)." ".$link_name; } elseif(is_dir($filename.'/'.$entry)) { // sub directory $link_name = "[".$entry."]"; $items_dir[$entry] = "=> ".rawurlencode($entry)."/ ".$link_name; } else { // other file ext $items_oth[$entry] = "=> ".rawurlencode($entry)." ".$link_name; } } $d->close(); ksort($items_dir); krsort($items_gmi); ksort($items_oth); if(count($items_dir)>0) $body .= implode("\r\n", $items_dir)."\r\n"; if(count($items_gmi)>0) $body .= implode("\r\n", $items_gmi)."\r\n"; if(count($items_oth)>0) $body .= implode("\r\n", $items_oth)."\r\n"; $body = gmi2html($capsule, $body, $lang, 'gemini://'.$capsule.($conf->port==1965?'':(':'.$conf->port)).$q, $favicon); } } } } if($response === false) { $response = "HTTP/1.1 404 NOT FOUND"; $response_code = 0; } if($response != "OK") { header($response, true, $response_code); exit; } header("Content-Type: ".$mime, true); header("Content-Length: ".strlen($body), true); echo $body; exit; function gmi2html($capsule, $body, $lang, $urlgem, $favicon) { if(isset($_SERVER['REQUEST_SCHEME'])) { $scheme = $_SERVER['REQUEST_SCHEME']; } else if(isset($_SERVER['HTTPS']) && !empty($_SERVER['HTTPS'])) { $scheme = 'https'; } else { $scheme = 'http'; } $title=''; $lines=array(); $tocs=array(); $lev1=0; $lev2=0; $lev3=0; $pre=false; $glines = explode("\n", $body); foreach($glines as $line) { if($pre && substr(trim($line, "\r\n"),0,3)!='```') { $lines[] = str_replace(array('&','<','>','"',"'"), array('&','<','>','"','''), $line); continue; } $line=trim($line, "\r\n"); $prefix = explode(' ',substr($line,0,3),2); $prefix=$prefix[0]; // if no space before titles if(substr($line,0,1)=='#') $prefix='#'; if(substr($line,0,2)=='##') $prefix='##'; if(substr($line,0,3)=='###') $prefix='###'; if($prefix=="```") { if($pre) $lines[]=''; else $lines[]='
';
			$pre=!$pre;
			continue;
		}
		if($prefix=="#" && empty($title))
			$title = trim(substr($line,2));
		switch($prefix)
		{
			case "#":
				$lev1++;
				$lev2=0;
				$lev3=0;
				$levid = $lev1;
				$lines[] = '

'.trim(htmlentities(substr($line,1))).'

'; $tocs[] = '
  • '.trim(htmlentities(substr($line,1))).'
  • '; break; case "##": $lev2++; $lev3=0; $levid = $lev1.'-'.$lev2; $lines[] = '

    '.trim(htmlentities(substr($line,2))).'

    '; $tocs[] = '
  • '.trim(htmlentities(substr($line,2))).'
  • '; break; case "###": $lev3++; $levid = $lev1.'-'.$lev2.'-'.$lev3; $lines[] = '

    '.trim(htmlentities(substr($line,3))).'

    '; $tocs[] = '
  • '.trim(htmlentities(substr($line,3))).'
  • '; break; case ">": $lines[] = "
    ".htmlentities(substr($line,2))."
    "; break; case "*": $lines[] = "
  • ".htmlentities(substr($line,2))."
  • "; break; case "=>": $lines[]='

    '; $link = explode(' ', substr($line,3), 2); if(str_starts_with($link[0], 'gemini://'.$capsule)) { $lines[] = ''.htmlentities(empty($link[1])?rawurldecode($link[0]):$link[1]).""; } else if(str_starts_with($link[0], 'gemini://')) { $lines[] = ''.htmlentities(empty($link[1])?rawurldecode($link[0]):$link[1]).""; } else { $lines[] = ''.htmlentities(empty($link[1])?rawurldecode($link[0]):$link[1]).""; } if(strpos($link[0], '://')===false && // relative image in_array(strtolower(substr($link[0],-4)),array('.jpg','.png','.gif','jpeg','webp')) ) $lines[] = ' 🖼️

    '.htmlentities(empty($link[1])?rawurldecode($link[0]):$link[1]).'
    '; $lines[]='

    '; break; default: $lines[] = "

    ".htmlentities($line)."

    "; break; } } $style = file_get_contents(__DIR__.'/style.css'); ob_start(); include "template.php"; $html = ob_get_contents(); ob_end_clean(); return $html; }