1<?php
2
3// Load dependencies
4require_once __DIR__ . '/../vendor/autoload.php';
5
6// Check arguments
7if (empty($argv[1]))
8{
9    exit(_('Configured hostname required as argument!') . PHP_EOL);
10}
11
12// Check cert exists
13if (!file_exists(__DIR__ . '/../host/' . $argv[1] . '/cert.pem'))
14{
15    exit(
16        sprintf(
17            _('Certificate for host "%s" not found!') . PHP_EOL,
18            $argv[1]
19        )
20    );
21}
22
23// Check key exists
24if (!file_exists(__DIR__ . '/../host/' . $argv[1] . '/key.rsa'))
25{
26    exit(
27        sprintf(
28            _('Key for host "%s" not found!') . PHP_EOL,
29            $argv[1]
30        )
31    );
32}
33
34// Check host configured
35if (!file_exists(__DIR__ . '/../host/' . $argv[1] . '/config.json'))
36{
37    exit(
38        sprintf(
39            _('Host "%s" not configured!') . PHP_EOL,
40            $argv[1]
41        )
42    );
43}
44
45// Check data directory exist
46if (!is_dir(__DIR__ . '/../host/' . $argv[1] . '/data'))
47{
48    exit(
49        sprintf(
50            _('Data directory "%s" not found!') . PHP_EOL,
51            $argv[1]
52        )
53    );
54}
55
56// Init config
57$config = json_decode(
58    file_get_contents(
59        __DIR__ . '/../host/' . $argv[1] . '/config.json'
60    )
61);
62
63// Init memory
64$memory = new \Yggverse\Cache\Memory(
65    $config->memcached->server->host,
66    $config->memcached->server->port,
67    $config->memcached->server->host.
68    $config->memcached->server->port,
69    $config->memcached->server->timeout
70);
71
72$memory->flush();
73
74// Init filesystem
75$filesystem = new \Yggverse\Gemini\Dokuwiki\Filesystem(
76    sprintf(
77        __DIR__ . '/../host/' . $argv[1] . '/data'
78    )
79);
80
81// Init reader
82$reader = new \Yggverse\Gemini\Dokuwiki\Reader();
83
84// Init helper
85$helper = new \Yggverse\Gemini\Dokuwiki\Helper(
86    $filesystem,
87    $reader
88);
89
90// Init search server
91$manticore = new \Manticoresearch\Client(
92    [
93        'host' => $config->manticore->server->host,
94        'port' => $config->manticore->server->port,
95    ]
96);
97
98// Init search index
99$index = $manticore->index(
100    $config->manticore->index->document->name
101);
102
103$index->drop(true);
104$index->create(
105    [
106        'uri' =>
107        [
108            'type' => 'text'
109        ],
110        'name' =>
111        [
112            'type' => 'text'
113        ],
114        'data' =>
115        [
116            'type' => 'text'
117        ]
118    ],
119    (array) $config->manticore->index->document->settings
120);
121
122foreach ($filesystem->getList() as $path)
123{
124    if (!str_ends_with($path, $config->manticore->index->extension))
125    {
126        continue;
127    }
128
129    if ($uri = $filesystem->getPageUriByPath($path))
130    {
131        if ($data = $filesystem->getDataByPath($path))
132        {
133            $gemini = $reader->toGemini(
134                $data
135            );
136
137            $index->addDocument(
138                [
139                    'uri'  => $uri,
140                    'name' => (string) $reader->getH1(
141                        $gemini
142                    ),
143                    'data' => (string) $reader->toGemini(
144                        $gemini
145                    )
146                ],
147                crc32(
148                    $uri
149                )
150            );
151        }
152    }
153}
154
155// Init server
156$server = new \Yggverse\TitanII\Server();
157
158$server->setCert(
159    __DIR__ . '/../host/' . $argv[1] . '/cert.pem'
160);
161
162$server->setKey(
163    __DIR__ . '/../host/' . $argv[1] . '/key.rsa'
164);
165
166$server->setHandler(
167    function (\Yggverse\TitanII\Request $request): \Yggverse\TitanII\Response
168    {
169        global $config;
170        global $memory;
171        global $index;
172        global $filesystem;
173        global $reader;
174        global $helper;
175
176        $response = new \Yggverse\TitanII\Response();
177
178        $response->setCode(
179            $config->gemini->response->default->code
180        );
181
182        $response->setMeta(
183            $config->gemini->response->default->meta
184        );
185
186        // Route begin
187        switch ($request->getPath())
188        {
189            // Static routes
190            case null:
191            case false:
192            case '':
193
194                $response->setCode(
195                    30
196                );
197
198                $response->setMeta(
199                    sprintf(
200                        'gemini://%s%s/',
201                        $config->gemini->server->host,
202                        $config->gemini->server->port == 1965 ? null : ':' . $config->gemini->server->port
203                    )
204                );
205
206                return $response;
207
208            break;
209
210            case '/search':
211
212                // Search query request
213                if (empty($request->getQuery()))
214                {
215                    $response->setMeta(
216                        'text/plain'
217                    );
218
219                    $response->setCode(
220                        10
221                    );
222
223                    return $response;
224                }
225
226                // Prepare query
227                $query = trim(
228                    urldecode(
229                        $request->getQuery()
230                    )
231                );
232
233                // Do search
234                $results = $index->search(
235                    @\Manticoresearch\Utils::escape(
236                        $query
237                    )
238                )->get();
239
240                // Init page content
241                $lines = [
242                    PHP_EOL
243                ];
244
245                // Append page title
246                $lines[] = sprintf(
247                    '# %s - %s',
248                    $config->string->search,
249                    $query
250                );
251
252                // Append search results
253                if ($total = $results->getTotal())
254                {
255                    $lines[] = sprintf(
256                        '%s: %d',
257                        $config->string->found,
258                        $total
259                    );
260
261                    $lines[] = sprintf(
262                        '## %s',
263                        $config->string->results
264                    );
265
266                    foreach($results as $result)
267                    {
268                        $lines[] = sprintf(
269                            '=> /%s %s',
270                            $result->get('uri'),
271                            $result->get('name')
272                        );
273                    }
274                }
275
276                // Nothing found
277                else
278                {
279                    $lines[] = $config->string->nothing;
280                }
281
282                // Append actions
283                $lines[] = sprintf(
284                    '## %s',
285                    $config->string->actions
286                );
287
288                // Append search link
289                $lines[] = sprintf(
290                    '=> /search %s',
291                    $config->string->search
292                );
293
294                // Append homepage link
295                $lines[] = sprintf(
296                    '=> / %s',
297                    $config->string->main
298                );
299
300                // Append source link
301                $lines[] = sprintf(
302                    '=> %s %s',
303                    $config->dokuwiki->url->source,
304                    $config->string->source
305                );
306
307                // Append about info
308                $lines[] = $config->string->about;
309
310                // Append aliases
311                if ($config->dokuwiki->url->alias)
312                {
313                    $lines[] = sprintf(
314                        '## %s',
315                        $config->string->alias
316                    );
317
318                    foreach ($config->dokuwiki->url->alias as $base => $name)
319                    {
320                        $lines[] = sprintf(
321                            '=> %s %s',
322                            $base,
323                            $name
324                        );
325                    }
326                }
327
328                // Build content
329                $response->setContent(
330                    implode(
331                        PHP_EOL,
332                        $lines
333                    )
334                );
335
336                return $response;
337
338            break;
339
340            default:
341
342                // Parse request
343                preg_match('/^\/([^\/]*)$/', $request->getPath(), $matches);
344
345                $uri = isset($matches[1]) ? $matches[1] : '';
346
347                // File request, get page content
348                if ($path = $filesystem->getPagePathByUri($uri))
349                {
350                    // Check for cached results
351                    if ($content = $memory->get($path))
352                    {
353                        $response->setContent(
354                            $content
355                        );
356
357                        return $response;
358                    }
359
360                    // Define base URL
361                    $reader->setMacros(
362                        '~URL:base~',
363                        sprintf(
364                            'gemini://%s%s/',
365                            $request->getHost(),
366                            $config->gemini->server->port == 1965 ? null : ':' . $config->gemini->server->port
367                        )
368                    );
369
370                    // Define index menu
371                    $menu = [];
372
373                    // Append index sections
374                    if ($sections = $helper->getChildrenSectionLinksByUri($uri))
375                    {
376                        // Append header
377                        $menu[] = sprintf(
378                            '### %s',
379                            $config->string->sections
380                        );
381
382                        // Append sections
383                        foreach ($sections as $section)
384                        {
385                            $menu[] = $section;
386                        }
387                    }
388
389                    // Get children pages
390                    if ($pages = $helper->getChildrenPageLinksByUri($uri))
391                    {
392                        // Append header
393                        $menu[] = sprintf(
394                            '### %s',
395                            $config->string->pages
396                        );
397
398                        // Append pages
399                        foreach ($pages as $page)
400                        {
401                            $menu[] = $page;
402                        }
403                    }
404
405                    // Set macros value
406                    if ($menu)
407                    {
408                        $reader->setRule(
409                            '/\{\{indexmenu>:([^\}]+)\}\}/i',
410                            implode(
411                                PHP_EOL,
412                                $menu
413                            )
414                        );
415                    }
416
417                    // Convert
418                    $gemini = $reader->toGemini(
419                        $filesystem->getDataByPath(
420                            $path
421                        )
422                    );
423
424                    $lines = [
425                        $gemini
426                    ];
427
428                    // Get page links
429                    if ($links = $reader->getLinks($gemini))
430                    {
431                        $lines[] = sprintf(
432                            '## %s',
433                            $config->string->links
434                        );
435
436                        foreach ($links as $link)
437                        {
438                            $lines[] = sprintf(
439                                '=> %s',
440                                $link
441                            );
442                        }
443                    }
444
445                    // Append actions header
446                    $lines[] = sprintf(
447                        '## %s',
448                        $config->string->actions
449                    );
450
451                    // Append search link
452                    $lines[] = sprintf(
453                        '=> /search %s',
454                        $config->string->search
455                    );
456
457                    // Append homepage link
458                    $lines[] = sprintf(
459                        '=> / %s',
460                        $config->string->main
461                    );
462
463                    // Append source link
464                    $lines[] = sprintf(
465                        '=> %s/%s %s',
466                        $config->dokuwiki->url->source,
467                        $uri,
468                        $config->string->source
469                    );
470
471                    // Append about info
472                    $lines[] = $config->string->about;
473
474                    // Append aliases
475                    if ($config->dokuwiki->url->alias)
476                    {
477                        $lines[] = sprintf(
478                            '## %s',
479                            $config->string->alias
480                        );
481
482                        foreach ($config->dokuwiki->url->alias as $base => $name)
483                        {
484                            $lines[] = sprintf(
485                                '=> %s/%s %s',
486                                $base,
487                                $uri,
488                                $name
489                            );
490                        }
491                    }
492
493                    // Merge lines
494                    $content = implode(
495                        PHP_EOL,
496                        $lines
497                    );
498
499                    // Cache results
500                    $memory->set(
501                        $path,
502                        $content
503                    );
504
505                    // Response
506                    $response->setContent(
507                        $content
508                    );
509
510                    return $response;
511                }
512
513                // File not found, request directory for minimal navigation
514                else if ($path = $filesystem->getDirectoryPathByUri($uri))
515                {
516                    // Check for cached results
517                    if ($content = $memory->get($path))
518                    {
519                        $response->setContent(
520                            $content
521                        );
522
523                        return $response;
524                    }
525
526                    // Init home page content
527                    $lines = [
528                        PHP_EOL
529                    ];
530
531                    // Build header
532                    $h1 = [];
533
534                    $segments = [];
535
536                    foreach ((array) explode(':', $uri) as $segment)
537                    {
538                        $segments[] = $segment;
539
540                        // Find section index if exists
541                        if ($file = $filesystem->getPagePathByUri(implode(':', $segments) . ':' . $segment))
542                        {
543                            $h1[] = $reader->getH1(
544                                $reader->toGemini(
545                                    $filesystem->getDataByPath(
546                                        $file
547                                    )
548                                )
549                            );
550                        }
551
552                        // Find section page if exists
553                        else if ($file = $filesystem->getPagePathByUri(implode(':', $segments)))
554                        {
555                            $h1[] = $reader->getH1(
556                                $reader->toGemini(
557                                    $filesystem->getDataByPath(
558                                        $file
559                                    )
560                                )
561                            );
562                        }
563
564                        // Reset title of undefined segment
565                        else
566                        {
567                            $h1[] = null;
568                        }
569                    }
570
571                    // Append header
572                    $lines[] = sprintf(
573                        '# %s',
574                        empty($h1[0]) ? $config->string->welcome : implode(' ', $h1)
575                    );
576
577                    // Get children sections
578                    if ($sections = $helper->getChildrenSectionLinksByUri($uri))
579                    {
580                        // Append header
581                        $lines[] = sprintf(
582                            '## %s',
583                            $config->string->sections
584                        );
585
586                        // Append sections
587                        foreach ($sections as $section)
588                        {
589                            $lines[] = $section;
590                        }
591                    }
592
593                    // Get children pages
594                    if ($pages = $helper->getChildrenPageLinksByUri($uri))
595                    {
596                        // Append header
597                        $lines[] = sprintf(
598                            '## %s',
599                            $config->string->pages
600                        );
601
602                        // Append pages
603                        foreach ($pages as $page)
604                        {
605                            $lines[] = $page;
606                        }
607                    }
608
609                    // Append about info
610                    $lines[] = sprintf(
611                        '## %s',
612                        $config->string->actions
613                    );
614
615                    // Append search link
616                    $lines[] = sprintf(
617                        '=> /search %s',
618                        $config->string->search
619                    );
620
621                    // Append source link
622                    $lines[] = sprintf(
623                        '=> %s %s',
624                        $config->dokuwiki->url->source,
625                        $config->string->source
626                    );
627
628                    // Append about info
629                    $lines[] = $config->string->about;
630
631                    // Append aliases
632                    if ($config->dokuwiki->url->alias)
633                    {
634                        $lines[] = sprintf(
635                            '## %s',
636                            $config->string->alias
637                        );
638
639                        foreach ($config->dokuwiki->url->alias as $base => $name)
640                        {
641                            $lines[] = sprintf(
642                                '=> %s/%s %s',
643                                $base,
644                                $uri,
645                                $name
646                            );
647                        }
648                    }
649
650                    // Merge lines
651                    $content = implode(
652                        PHP_EOL,
653                        $lines
654                    );
655
656                    // Cache results
657                    $memory->set(
658                        $path,
659                        $content
660                    );
661
662                    // Response
663                    $response->setContent(
664                        $content
665                    );
666
667                    return $response;
668                }
669
670                // Media request
671                else if ($path = $filesystem->getMediaPathByUri($uri))
672                {
673                    if ($mime = $filesystem->getMimeByPath($path))
674                    {
675                        if ($data = $filesystem->getDataByPath($path))
676                        {
677                            // Set MIME
678                            $response->setMeta(
679                                $mime
680                            );
681
682                            // Append data
683                            $response->setContent(
684                                $data
685                            );
686
687                            // Response
688                            return $response;
689                        }
690                    }
691                }
692        }
693
694        // Not found
695        $response->setCode(
696            51
697        );
698
699        $response->setMeta(
700            $config->string->nothing
701        );
702
703        return $response;
704    }
705);
706
707// Start server
708echo sprintf(
709    _('Server "%s" started on %s:%d') . PHP_EOL,
710    $argv[1],
711    $config->gemini->server->host,
712    $config->gemini->server->port
713);
714
715$server->start(
716  $config->gemini->server->host,
717  $config->gemini->server->port
718);