• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

cypht-org / cypht / 30095700391

24 Jul 2026 01:08PM UTC coverage: 87.092% (-0.2%) from 87.271%
30095700391

push

travis-ci

web-flow
Merge pull request #2045 from IrAlfred/fix-proxy-login-session-origin-cookie-domain

fix(other): fix proxy-related login loop and cookie domain mismatch

21 of 37 new or added lines in 3 files covered. (56.76%)

5816 of 6678 relevant lines covered (87.09%)

11.66 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.55
/lib/module.php
1
<?php
2

3
/**
4
 * Module classes
5
 * @package framework
6
 * @subpackage module
7
 */
8

9
/**
10
 * Module data management. These functions provide an interface for modules (both handler and output)
11
 * to fetch data set by other modules and to return their own output. Handler modules must use these
12
 * methods to set a response, output modules must if the format is AJAX, otherwise they should return
13
 * an HTML5 string
14
 */
15
trait Hm_Module_Output {
16

17
    /* module output */
18
    protected $output = [];
19

20
    /* protected output keys */
21
    protected $protected = [];
22

23
    /* list of appendable keys */
24
    protected $appendable = [];
25

26
    /**
27
     * @param string $name name to check for
28
     * @param array $list array to look for name in
29
     * @param string $type
30
     * @param mixed $value value
31
     * @return bool
32
     */
33
    protected function check_overwrite($name, $list, $type, $value) {
110✔
34
        if (in_array($name, $list, true)) {
110✔
35
            Hm_Debug::add(sprintf('MODULES: Cannot overwrite %s %s with %s', $type, $name, print_r($value,true)));
2✔
36
            return false;
2✔
37
        }
38
        return true;
110✔
39
    }
40

41
    /**
42
     * Add a name value pair to the output array
43
     * @param string $name name of value to store
44
     * @param mixed $value value
45
     * @param bool $protected true disallows overwriting
46
     * @return bool true on success
47
     */
48
    public function out($name, $value, $protected = true) {
107✔
49
        if (!$this->check_overwrite($name, $this->protected, 'protected', $value)) {
107✔
50
            return false;
1✔
51
        }
52
        if (!$this->check_overwrite($name, $this->appendable, 'protected', $value)) {
107✔
53
            return false;
1✔
54
        }
55
        if ($protected) {
107✔
56
            $this->protected[] = $name;
62✔
57
        }
58
        $this->output[$name] = $value;
107✔
59
        return true;
107✔
60
    }
61

62
    /**
63
     * append a value to an array, create it if does not exist
64
     * @param string $name array name
65
     * @param string $value value to add
66
     * @return bool true on success
67
     */
68
    public function append($name, $value) {
5✔
69
        if (!$this->check_overwrite($name, $this->protected, 'protected', $value)) {
5✔
70
            return false;
1✔
71
        }
72
        if (array_key_exists($name, $this->output)) {
5✔
73
            if (is_array($this->output[$name])) {
1✔
74
                $this->output[$name][] = $value;
1✔
75
                return true;
1✔
76
            } else {
77
                Hm_Debug::add(sprintf('Tried to append %s to scaler %s', $value, $name));
1✔
78
                return false;
1✔
79
            }
80
        } else {
81
            $this->output[$name] = [$value];
5✔
82
            $this->appendable[] = $name;
5✔
83
            return true;
5✔
84
        }
85
    }
86

87
    /**
88
     * Sanitize input string
89
     * @param string $string text to sanitize
90
     * @param bool $special_only only use htmlspecialchars not htmlentities
91
     * @return string sanitized value
92
     */
93
    public function html_safe($string, $special_only = false) {
45✔
94
        if ($special_only) {
45✔
95
            return htmlspecialchars((string) $string, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
4✔
96
        }
97
        return htmlentities((string) $string, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
45✔
98
    }
99

100
    /**
101
     * Concatenate a value
102
     * @param string $name name to add to
103
     * @param string $value value to add
104
     * @return bool true on success
105
     */
106
    public function concat($name, $value) {
14✔
107
        if (array_key_exists($name, $this->output)) {
14✔
108
            if (is_string($this->output[$name])) {
1✔
109
                $this->output[$name] .= $value;
1✔
110
                return true;
1✔
111
            } else {
112
                Hm_Debug::add(sprintf('Could not append %s to %s', print_r($value,true), $name));
1✔
113
                return false;
1✔
114
            }
115
        } else {
116
            $this->output[$name] = $value;
14✔
117
            return true;
14✔
118
        }
119
    }
120

121
    /**
122
     * Return module output from process()
123
     * @return array
124
     */
125
    public function module_output() {
265✔
126
        return $this->output;
265✔
127
    }
128

129
    /**
130
     * Return protected output field list
131
     * @return array
132
     */
133
    public function output_protected() {
132✔
134
        return $this->protected;
132✔
135
    }
136

137
    /**
138
     * Fetch an output value
139
     * @param string $name key to fetch the value for
140
     * @param mixed $default default return value if not found
141
     * @param string $typed if a default value is given, typecast the result to it's type
142
     * @return mixed value if found or default
143
     */
144
    public function get($name, $default = NULL, $typed = true) {
166✔
145
        if (array_key_exists($name, $this->output)) {
166✔
146
            $val = $this->output[$name];
89✔
147
            if (!is_null($default) && $typed) {
89✔
148
                if (gettype($default) != gettype($val)) {
56✔
149
                    Hm_Debug::add(sprintf('TYPE CONVERSION: %s to %s for %s', gettype($val), gettype($default), $name), 'info');
4✔
150
                    settype($val, gettype($default));
4✔
151
                }
152
            }
153
            return $val;
89✔
154
        }
155
        return $default;
131✔
156
    }
157

158
    /**
159
     * Check for a key
160
     * @param string $name key name
161
     * @return bool true if found
162
     */
163
    public function exists($name) {
2✔
164
        return array_key_exists($name, $this->output);
2✔
165
    }
166

167
    /**
168
     * Check to see if a value matches a list
169
     * @param string $name name to check
170
     * @param array $values list to check against
171
     * @return bool true if found
172
     */
173
    public function in($name, $values) {
2✔
174
        if (array_key_exists($name, $this->output) && in_array($this->output[$name], $values, true)) {
2✔
175
            return true;
2✔
176
        }
177
        return false;
2✔
178
    }
179
}
180

181
/**
182
 * Methods used to validate handler module operations, like the HTTP request
183
 * type and target/origin values
184
 */
185
trait Hm_Handler_Validate {
186

187
    /**
188
     * Validate HTTP request type, only GET and POST are allowed
189
     * @param object $session
190
     * @param object $request
191
     * @return bool
192
     */
193
    public function validate_method($session, $request) {
3✔
194
        if (!empty($request->method) && is_string($request->method)) {
3✔
195
            if (!in_array(mb_strtolower($request->method), ['get', 'post'], true)) {
3✔
196
                if ($session->loaded) {
1✔
197
                    $session->destroy($request);
1✔
198
                    Hm_Debug::add(sprintf('LOGGED OUT: invalid method %s', $request->method));
1✔
199
                }
200
                return false;
1✔
201
            }
202
            return true;
3✔
203
        }
204
        // Handle the case where method is null or invalid
205
        if ($session->loaded) {
×
206
            $session->destroy($request);
×
207
            Hm_Debug::add('LOGGED OUT: missing or invalid request method', 'warning');
×
208
        }
209
        return false;
×
210
    }
211

212
    /**
213
     * Validate that the request has matching source and target origins
214
     * @return bool
215
     */
216
    public function validate_origin($session, $request, $config) {
3✔
217
        if (!$session->loaded) {
3✔
218
            return true;
1✔
219
        }
220
        list($source, $target) = $this->source_and_target($request, $config);
3✔
221
        if (!$this->validate_target($target, $source, $session, $request) ||
3✔
222
            !$this->validate_source($target, $source, $session, $request)) {
3✔
223
            return false;
3✔
224
        }
225
        return true;
1✔
226
    }
227

228
    /**
229
     * Find source and target values for validate_origin
230
     * @return string[]
231
     */
232
    private function source_and_target($request, $config) {
3✔
233
        $source = false;
3✔
234
        $target = $config->get('cookie_domain', false);
3✔
235
        if ($target == 'none') {
3✔
236
            $target = false;
1✔
237
        }
238
        if (!empty($request->server['HTTP_HOST'])) {
3✔
239
            $target = $request->server['HTTP_HOST'];
1✔
240
        }
241
        if (!empty($request->server['HTTP_X_FORWARDED_HOST'])) {
3✔
NEW
242
            $target = trim(explode(',', $request->server['HTTP_X_FORWARDED_HOST'])[0]);
×
243
        }
244

245
        $origin = !empty($request->server['HTTP_ORIGIN']) ? $request->server['HTTP_ORIGIN'] : false;
3✔
246
        $referer = !empty($request->server['HTTP_REFERER']) ? $request->server['HTTP_REFERER'] : false;
3✔
247
        $source = $origin ?: $referer;
3✔
248

249
        // Some proxies rewrite Origin to localhost while Referer still has the public host.
250
        if ($origin && $referer && $target) {
3✔
NEW
251
            $origin_parts = parse_url($origin);
×
NEW
252
            $referer_parts = parse_url($referer);
×
NEW
253
            if (is_array($origin_parts) && array_key_exists('host', $origin_parts) &&
×
NEW
254
                is_array($referer_parts) && array_key_exists('host', $referer_parts)) {
×
NEW
255
                $origin_host = $origin_parts['host'];
×
NEW
256
                if (array_key_exists('port', $origin_parts)) {
×
NEW
257
                    $origin_host .= ':'.$origin_parts['port'];
×
258
                }
NEW
259
                $referer_host = $referer_parts['host'];
×
NEW
260
                if (array_key_exists('port', $referer_parts)) {
×
NEW
261
                    $referer_host .= ':'.$referer_parts['port'];
×
262
                }
NEW
263
                if ($origin_host !== $target && $referer_host === $target) {
×
NEW
264
                    $source = $referer;
×
265
                }
266
            }
267
        }
268
        return [$source, $target];
3✔
269
    }
270

271
    /**
272
     * @param string $target
273
     * @param string $source
274
     * @return boolean
275
     */
276
    private function validate_target($target, $source, $session, $request) {
3✔
277
        if (!$target || !$source) {
3✔
278
            $session->destroy($request);
3✔
279
            Hm_Debug::add('LOGGED OUT: missing target origin', 'warning');
3✔
280
            return false;
3✔
281
        }
282
        return true;
1✔
283
    }
284

285
    /**
286
     * @param string $target
287
     * @param string $source
288
     * @return boolean
289
     */
290
    private function validate_source($target, $source, $session, $request) {
1✔
291
        $source = parse_url($source);
1✔
292
        if (!is_array($source) || !array_key_exists('host', $source)) {
1✔
293
            $session->destroy($request);
1✔
294
            Hm_Debug::add('LOGGED OUT: invalid source origin', 'warning');
1✔
295
            return false;
1✔
296
        }
297
        if (array_key_exists('port', $source)) {
1✔
298
            $source['host'] .= ':'.$source['port'];
1✔
299
        }
300
        if ($source['host'] !== $target) {
1✔
301
            $session->destroy($request);
1✔
302
            Hm_Debug::add('LOGGED OUT: invalid source origin', 'warning');
1✔
303
            return false;
1✔
304
        }
305
        return true;
1✔
306
    }
307
}
308

309
/**
310
 * Base class for data input processing modules, called "handler modules"
311
 *
312
 * All modules that deal with processing input data extend from this class.
313
 * It provides access to input and state through the following member variables:
314
 *
315
 * $session      The session interface object
316
 * $request      The HTTP request details object
317
 * $config       The site config object
318
 * $user_config  The user settings object for the current user
319
 *
320
 * Modules that extend this class need to override the process function
321
 * Modules can pass information to the output modules using the out() and append() methods,
322
 * and see data from other modules with the get() method
323
 * @abstract
324
 */
325
abstract class Hm_Handler_Module {
326

327
    use Hm_Module_Output;
328
    use Hm_Handler_Validate;
329

330
    /* session object */
331
    public $session;
332

333
    /* request object */
334
    public $request;
335

336
    /* site configuration object */
337
    public $config;
338

339
    /* current request id */
340
    protected $page = '';
341

342
    /* user settings */
343
    public $user_config;
344

345
    public $cache;
346

347
    /**
348
     * Assign input and state sources
349
     * @param object $parent instance of the Hm_Request_Handler class
350
     * @param string $page page id
351
     * @param array $output data from handler modules
352
     * @param array $protected list of protected output names
353
     */
354
    public function __construct($parent, $page, $output = [], $protected = []) {
139✔
355
        $this->session = $parent->session;
139✔
356
        $this->request = $parent->request;
139✔
357
        $this->cache = $parent->cache;
139✔
358
        $this->page = $page;
139✔
359
        $this->config = $parent->site_config;
139✔
360
        $this->user_config = $parent->user_config;
139✔
361
        $this->output = $output;
139✔
362
        $this->protected = $protected;
139✔
363
    }
364

365
    /**
366
     * @return string
367
     */
368
    private function invalid_ajax_key() {
2✔
369
        if (DEBUG_MODE) {
2✔
370
            Hm_Debug::add('REQUEST KEY check failed', 'warning');
1✔
371
            Hm_Debug::load_page_stats();
1✔
372
            Hm_Debug::show();
1✔
373
        }
374
        Hm_Functions::cease(json_encode(['status' => 'not callable']));
2✔
375
        return 'exit';
2✔
376
    }
377

378
    /**
379
     * @return string
380
     */
381
    private function invalid_http_key() {
3✔
382
        if ($this->session->loaded) {
3✔
383
            $this->session->destroy($this->request);
3✔
384
            Hm_Debug::add('LOGGED OUT: request key check failed', 'warning');
3✔
385
        }
386
        Hm_Dispatch::page_redirect($this->build_page_url('home'));
3✔
387
        return 'redirect';
3✔
388
    }
389

390
    /**
391
     * Validate a form key. If this is a non-empty POST form from an
392
     * HTTP request or AJAX update, it will take the user to the home
393
     * page if the page_key value is either not present or not valid
394
     * @return false|string
395
     */
396
    public function process_key() {
4✔
397
        if (empty($this->request->post)) {
4✔
398
            return false;
2✔
399
        }
400
        $key = array_key_exists('hm_page_key', $this->request->post) ? $this->request->post['hm_page_key'] : false;
4✔
401
        $valid = Hm_Request_Key::validate($key);
4✔
402
        if ($valid) {
4✔
403
            return false;
1✔
404
        }
405
        if ($this->request->type == 'AJAX') {
4✔
406
            return $this->invalid_ajax_key();
2✔
407
        } else {
408
            return $this->invalid_http_key();
3✔
409
        }
410
    }
411

412
    /**
413
     * Validate a value in a HTTP POST form
414
     * @param mixed $val
415
     * @return mixed
416
     */
417
    private function check_field($val) {
63✔
418
        switch (true) {
419
            case is_array($val):
63✔
420
            case is_string($val):
63✔
421
            case is_int($val):
47✔
422
            case is_float($val):
29✔
423
            case is_bool($val):
29✔
424
            case $val === '0':
1✔
425
            case $val === 0:
1✔
426
                return $val;
63✔
427
            default:
428
                return NULL;
1✔
429
        }
430
    }
431

432
    /**
433
     * Process an HTTP POST form
434
     * @param array $form list of required field names in the form
435
     * @return array tuple with a bool indicating success, and an array of valid form values
436
     */
437
    public function process_form($form) {
65✔
438
        $new_form = [];
65✔
439
        foreach($form as $name) {
65✔
440
            if (!array_key_exists($name, $this->request->post)) {
65✔
441
                continue;
13✔
442
            }
443
            $val = $this->check_field($this->request->post[$name]);
63✔
444
            if ($val !== NULL) {
63✔
445
                $new_form[$name] = $val;
63✔
446
            }
447
        }
448
        return [(count($form) === count($new_form)), $new_form];
65✔
449
    }
450

451
    /**
452
     * Determine if a module set is enabled
453
     * @param string $name the module set name to check for
454
     * @return bool
455
     */
456
    public function module_is_supported($name) {
19✔
457
        return in_array(mb_strtolower($name), $this->config->get_modules(true), true);
19✔
458
    }
459

460
   /**
461
     * Checks if a config setting is disabled and signals whether to skip further execution.
462
     *
463
     * @param string $setting_key  The configuration key to check.
464
     * @param mixed  $default      The default value to use if the key is not set.
465
     * @return bool  True if the feature is disabled and should be skipped.
466
     */
467
    public function should_skip_execution($setting_key, $default = false) {
22✔
468
        return !$this->user_config->get($setting_key, $default);
22✔
469
    }
470

471
    public function save_hm_msgs() {
2✔
472
        $msgs = Hm_Msgs::getRaw();
2✔
473
        if (!empty($msgs)) {
2✔
474
            Hm_Msgs::flush();
1✔
475
            $this->session->secure_cookie($this->request, 'hm_msgs', base64_encode(json_encode($msgs)));
1✔
476
        }
477
    }
478

479
    public function build_page_url($page, $params = []) {
3✔
480
        $url = '?'.$this->config->get('page_param_name').'='.$page;
3✔
481
        foreach ($params as $key => $value) {
3✔
482
            $url .= '&'.urlencode($key).'='.urlencode($value);
×
483
        }
484
        return $url;
3✔
485
    }
486

487
    /**
488
     * Handler modules need to override this method to do work
489
     */
490
    abstract public function process();
491
}
492

493
/**
494
 * Base class for output modules
495
 * All modules that output data to a request must extend this class and define
496
 * an output() method. It provides form validation, html sanitizing,
497
 * and string translation services to modules
498
 * @abstract
499
 */
500
abstract class Hm_Output_Module {
501

502
    use Hm_Module_Output;
503

504
    /* translated language strings */
505
    protected $lstr = [];
506

507
    /* langauge name */
508
    protected $lang = false;
509

510
    /* UI layout direction */
511
    protected $dir = 'ltr';
512

513
    /* Output format (AJAX or HTML5) */
514
    protected $format = '';
515

516
    /**
517
     * Constructor
518
     * @param array $input data from handler modules
519
     * @param array $protected list of protected keys
520
     */
521
    public function __construct($input, $protected) {
166✔
522
        $this->output = $input;
166✔
523
        $this->protected = $protected;
166✔
524
    }
525

526
    /**
527
     * Return a translated string if possible
528
     * @param string $string the string to be translated
529
     * @return string translated string
530
     */
531
    public function trans($string) {
107✔
532
        if (array_key_exists($string, $this->lstr)) {
107✔
533
            if ($this->lstr[$string] === false) {
91✔
534
                return strip_tags($string);
91✔
535
            } else {
536
                return strip_tags($this->lstr[$string]);
1✔
537
            }
538
        }
539
        else {
540
            Hm_Debug::add(sprintf('TRANSLATION NOT FOUND :%s:', $string), 'warning');
23✔
541
        }
542
        return str_replace('\n', '<br />', strip_tags($string));
23✔
543
    }
544

545
    /**
546
     * Return all translations for earch supported language
547
     * @return array translations
548
     */
549
    public function all_trans() {
1✔
550
        // Get all files in the language directory
551
        $language_files = glob(APP_PATH.'language/'. '*.php');
1✔
552
        $translations = [];
1✔
553

554
        foreach ($language_files as $file) {
1✔
555
            // Extract the language code from the file name
556
            $language_code = pathinfo($file, PATHINFO_FILENAME);
1✔
557

558
            // Read the content of the file
559
            $content = include $file;
1✔
560

561
            // Store the content in the translations array
562
            $translations[$language_code] = $content;
1✔
563
        }
564

565
        return $translations;
1✔
566
    }
567

568

569
    /**
570
     * Return a translated string of numbers if possible and if language is farsi
571
     * @param string $string the string to be translated which has to be numbers
572
     * @return string translated string
573
     */
574
    public function translate_number($number) {
×
575
        if (!is_numeric($number) || !in_array($this->lang, ['fa'])) {
×
576
            return $number;
×
577
        }
578
        $number_splitted = mb_str_split($number);
×
579
        $translated_number = "";
×
580
        foreach ($number_splitted as $number_splitted) {
×
581
            $translated_number .= $this->trans($number_splitted);
×
582
        }
583
        return $translated_number;
×
584
    }
585

586
    /**
587
     * Build output by calling module specific output functions
588
     * @param string $format output type, either HTML5 or AJAX
589
     * @param array $lang_str list of language translation strings
590
     * @return string
591
     */
592
    public function output_content($format, $lang_str) {
145✔
593
        $this->lstr = $lang_str;
145✔
594
        $this->format = str_replace('Hm_Format_', '', $format);
145✔
595
        if (array_key_exists('interface_lang', $lang_str)) {
145✔
596
            $this->lang = $lang_str['interface_lang'];
144✔
597
        }
598
        if (array_key_exists('interface_direction', $lang_str)) {
145✔
599
            $this->dir = $lang_str['interface_direction'];
144✔
600
        }
601
        return $this->output();
145✔
602
    }
603

604
    public function build_page_url($page, $params = [], $htm_context = false) {
19✔
605
        $url = '?'.$this->get('page_param_name', 'page').'='.$page;
19✔
606
        foreach ($params as $key => $value) {
19✔
607
            $url .= ($htm_context ? '&amp;': '&').$key.'='.$value;
5✔
608
        }
609
        return $url;
19✔
610
    }
611

612
    /**
613
     * Output modules need to override this method to add to a page or AJAX response
614
     * @return string
615
     */
616
    abstract protected function output();
617
}
618

619
/**
620
 * Placeholder classes for disabling a module in a set. These allow a module set
621
 * to replace another module set's assignments with "false" to disable them
622
 */
623
class Hm_Output_ extends Hm_Output_Module { protected function output() {} }
624
class Hm_Handler_ extends Hm_Handler_Module { public function process() {} }
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc