Sip.php 11.5 KB
Newer Older
Carsten  Rose's avatar
Carsten Rose committed
1
2
3
4
5
6
7
8
<?php
/**
 * Created by PhpStorm.
 * User: crose
 * Date: 1/9/16
 * Time: 10:26 PM
 */

9
namespace qfq;
Carsten  Rose's avatar
Carsten Rose committed
10

11
12
13
14
require_once(__DIR__ . '/../helper/OnArray.php');
require_once(__DIR__ . '/../Constants.php');
require_once(__DIR__ . '/../store/Config.php');
require_once(__DIR__ . '/../exceptions/CodeException.php');
15
16
require_once(__DIR__ . '/Session.php');

Carsten  Rose's avatar
Carsten Rose committed
17

Carsten  Rose's avatar
Carsten Rose committed
18
19
20
21
/**
 * Class Sip
 * @package qfq\store
 */
Carsten  Rose's avatar
Carsten Rose committed
22
23
class Sip {

24
    private $phpUnit = false;
Carsten  Rose's avatar
Carsten Rose committed
25
    private $staticUniqId = false;
26
27
    private $getParamL = '';
    private $getParamType = '';
Carsten  Rose's avatar
Carsten Rose committed
28

29
30
31
    /**
     * @param bool|false $phpUnit
     */
32
    public function __construct($phpUnit = false) {
Carsten  Rose's avatar
Carsten Rose committed
33
34
35
36

        $this->phpUnit = $phpUnit;
    }

37

Carsten  Rose's avatar
Carsten Rose committed
38
    /**
Carsten  Rose's avatar
Carsten Rose committed
39
     * @param string $queryString Possible variants:
40
41
42
43
     *                            * http://www.math.uzh.ch/index.php?a=1&s=4b3403665fea6&r=45&type=99&id=person#pill12
     *                            * index.php?a=1&s=4b3403665fea6&r=45&type=99&id=person#pill12
     *                            * ?a=1&s=4b3403665fea6&r=45&type=99&id=person#pill12
     *                            * a=1&s=4b3403665fea6&r=45&type=99&id=person#pill12
Carsten  Rose's avatar
Carsten Rose committed
44
     *
Carsten  Rose's avatar
Carsten Rose committed
45
     * @param string $mode Possible values: RETURN_URL|RETURN_SIP
46
     *
47
     * @param string $phpScriptName
48
     * @return string/array
49
     *  * mode=RETURN_URL: return complete URL
Carsten  Rose's avatar
Carsten Rose committed
50
     *  * mode=RETURN_SIP: returns only the sip
Carsten  Rose's avatar
Carsten Rose committed
51
     *  * mode=RETURN_ARRAY: returns array with url ('sipUrl') and all decoded and created parameters.
Carsten  Rose's avatar
Carsten Rose committed
52
     * @throws CodeException
53
     * @throws UserFormException
Carsten  Rose's avatar
Carsten Rose committed
54
     */
55
    public function queryStringToSip($queryString, $mode = RETURN_URL, $phpScriptName = INDEX_PHP) {
Carsten  Rose's avatar
Carsten Rose committed
56
57
58
59
60

        $clientArray = array();
        $sipArray = array();

        // Split URL parameter:
61
        $paramArray = KeyValueStringParser::parse($queryString, "=", "&");
Carsten  Rose's avatar
Carsten Rose committed
62

63
64
65
66
67
        // If no 'r' is specified: define r=0
        if (!isset($paramArray[SIP_RECORD_ID])) {
            $paramArray[SIP_RECORD_ID] = 0;
        }

Carsten  Rose's avatar
Carsten Rose committed
68
        // Split parameter between Script, Client and SIP
69
70
71
72
73
        $anchor = '';
        $script = $this->splitParamClientSip($paramArray, $clientArray, $sipArray, $anchor);
        if ($anchor != '') {
            $anchor = '#' . $anchor;
        }
Carsten  Rose's avatar
Carsten Rose committed
74
        // Generate keyname for $_SESSION[]
Carsten  Rose's avatar
#2067    
Carsten Rose committed
75
        $sipParamString = $this->buildParamStringFromArray($sipArray);
Carsten  Rose's avatar
Carsten Rose committed
76

77
        $sessionParamSip = Session::get($sipParamString);
78
        if ($sessionParamSip === false) {
Carsten  Rose's avatar
Carsten Rose committed
79
            // Not found: create new entry
80
            $s = $this->sipUniqId('badcaffee1234');
81
82
            Session::set($sipParamString, $s);
            Session::set($s, $sipParamString);
83
84
        } else {
            $s = $sessionParamSip;
Carsten  Rose's avatar
Carsten Rose committed
85
86
        }

87
88
89
        // Append SIP to final parameter
        $clientArray[CLIENT_SIP] = $s;

Carsten  Rose's avatar
Carsten Rose committed
90
        if ($script[0] === '?') {
91
            $script = $phpScriptName . $script;
Carsten  Rose's avatar
Carsten Rose committed
92
        }
93

Carsten  Rose's avatar
Carsten Rose committed
94
        $clientArray[SIP_SIP_URL] = $script . OnArray::toString($clientArray) . $anchor;
95
96
97

        switch ($mode) {
            case RETURN_URL:
Carsten  Rose's avatar
Carsten Rose committed
98
                $rc = $clientArray[SIP_SIP_URL];
99
100
101
102
103
104
105
106
107
                break;
            case RETURN_SIP:
                $rc = $s;
                break;
            case RETURN_ARRAY:
                $rc = array_merge($clientArray, $sipArray);
                break;
            default:
                throw new CodeException('Unknown Mode: "' . $mode . '"', ERROR_UNKNOWN_MODE);
Carsten  Rose's avatar
Carsten Rose committed
108
        }
109
110

        return $rc;
Carsten  Rose's avatar
Carsten Rose committed
111
112
113
    }

    /**
114
     * Splits the $paramArray in &$clientArray and &$sipArray. $sipArray contains all key/values pairs which do not
Carsten  Rose's avatar
Carsten Rose committed
115
     * belong to Typo3.
Carsten  Rose's avatar
Carsten Rose committed
116
     *
Carsten  Rose's avatar
Carsten Rose committed
117
118
119
     * @param array $paramArray
     * @param array $clientArray
     * @param array $sipArray
120
121
     *
     * @param string $anchor
Carsten  Rose's avatar
Carsten Rose committed
122
     *
Carsten  Rose's avatar
Carsten Rose committed
123
     * @return string
124
     * @throws CodeException
Carsten  Rose's avatar
Carsten Rose committed
125
     */
126
    private function splitParamClientSip(array $paramArray, array &$clientArray, array &$sipArray, &$anchor) {
Carsten  Rose's avatar
Carsten Rose committed
127
128

        $script = '';
129
        $anchor = '';
Carsten  Rose's avatar
Carsten Rose committed
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145

        // Possible variants:
        //   http://www.math.uzh.ch/index.php?a=1&s=4b3403665fea6&r=45&type=99&id=person
        //   index.php?a=1&s=4b3403665fea6&r=45&type=99&id=person
        //   ?a=1&s=4b3403665fea6&r=45&type=99&id=person
        //   a=1&s=4b3403665fea6&r=45&type=99&id=person
        $flagFirst = true;
        foreach ($paramArray AS $key => $value) {

            // first key/value pair: a) save potential existing url/script part, b) if no keyname is specified, call it 'id'.
            if ($flagFirst) {
                $flagFirst = false;

                $script = $this->splitAndFix($key, $value);
            }

146
147
148
149
150
151
152
            //Check for anker in Parameter
            $anchorArray = explode('#', $value, 2);
            if (!empty($anchorArray[1])) {
                $value = $anchorArray[0];
                $anchor = $anchorArray[1];
            }

Carsten  Rose's avatar
Carsten Rose committed
153
154
155
            // copy every parameter either to $clientArray or to $sipArray
            switch ($key) {
                //special T3 parameter.
156
157
158
159
                case SIP_EXCLUDE_L:
                case SIP_EXCLUDE_TYPE:
                case SIP_EXCLUDE_ID:
                case SIP_EXCLUDE_XDEBUG_SESSION_START:
Carsten  Rose's avatar
Carsten Rose committed
160
161
162
                    $clientArray[$key] = $value;
                    break;
                case CLIENT_SIP:
163
164
165
166
167
                    if ($this->getQueryStringFromSip($value) === false) {
                        throw new CodeException('SIP Parameter ist not allowed to be stored as a regular URL Parameter', ERROR_SIP_NOT_ALLOWED_AS_PARAM);
                    }
                    $clientArray[$key] = $value;
                    break;
Carsten  Rose's avatar
Carsten Rose committed
168
                default:
169
170
                    // Values in SIP should not urlencoded.
                    $sipArray[$key] = urldecode($value);
Carsten  Rose's avatar
Carsten Rose committed
171
172
173
174
                    break;
            }
        }

175
176
177
178
179
180
181
182
        if (empty($clientArray[SIP_EXCLUDE_L]) && isset($_GET[CLIENT_PAGE_LANGUAGE]) != '' && ctype_digit($_GET[CLIENT_PAGE_LANGUAGE])) {
            $clientArray[CLIENT_PAGE_LANGUAGE] = $_GET[CLIENT_PAGE_LANGUAGE];
        }

        if (empty($clientArray[SIP_EXCLUDE_TYPE]) && isset($_GET[CLIENT_PAGE_TYPE]) != '' && ctype_digit($_GET[CLIENT_PAGE_TYPE])) {
            $clientArray[SIP_EXCLUDE_TYPE] = $_GET[CLIENT_PAGE_TYPE];
        }

Carsten  Rose's avatar
Carsten Rose committed
183
184
185
186
        return $script;
    }

    /**
187
     * Fix first parameter mix of hostname / script / Get parameter and optional missing keyname
Carsten  Rose's avatar
Carsten Rose committed
188
189
190
     *
     * @param $key
     * @param $value
Carsten  Rose's avatar
Carsten Rose committed
191
     *
Carsten  Rose's avatar
Carsten Rose committed
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
     * @return string Part upto first '?',
     */
    private function splitAndFix(&$key, &$value) {

        $script = '';
        $tmpArray = explode('?', $key, 2);

        if (count($tmpArray) > 1) {
            $script = $tmpArray[0];
            $key = $tmpArray[1]; // new key, now without the hostname / scriptname.
        }
        $script .= '?';


        // empty value: keyname omitted - define keyname as 'id'
        if ($value === '') {
            $value = $key;
            $key = 'id';
        }

        return $script;
    }

Carsten  Rose's avatar
#2067    
Carsten Rose committed
215
    /**
Carsten  Rose's avatar
Carsten Rose committed
216
217
     * Takes the values form an array and creates a urlparamstring. Skip values which should not passed to the
     * urlparamstring.
218
     * - SIP_TARGET_URL is necessary for 'delete' links (via 'report') - may be unecessary in other situations.
Carsten  Rose's avatar
Carsten Rose committed
219
     *
Carsten  Rose's avatar
#2067    
Carsten Rose committed
220
     * @param array $sipArray
Carsten  Rose's avatar
Carsten Rose committed
221
     *
Carsten  Rose's avatar
#2067    
Carsten Rose committed
222
223
224
225
226
227
228
229
     * @return string
     */
    private function buildParamStringFromArray(array $sipArray) {
        $tmpArray = array();

        foreach ($sipArray as $key => $value) {
            switch ($key) {
                case SIP_SIP:
230
231
//                case SIP_MODE_ANSWER:
//                case SIP_TABLE:
Carsten  Rose's avatar
#2067    
Carsten Rose committed
232
233
                case SIP_URLPARAM:
                    break;
234
235

                case SIP_TARGET_URL:  // Do not skip this param. Necessary for delete links (via 'report') - specifies the target where to jump after delete,php has been called (plain HTML, not AJAX)
Carsten  Rose's avatar
#2067    
Carsten Rose committed
236
237
238
239
240
241
242
243
244
245
246
                default:
                    $tmpArray[$key] = $value;
                    break;
            }
        }

        OnArray::sortKey($tmpArray);

        return OnArray::toString($tmpArray);
    }

Carsten  Rose's avatar
Carsten Rose committed
247
    /**
248
     * Returns a new uniqid (unique per session), which will be used as a SIP identifier.
Carsten  Rose's avatar
Carsten Rose committed
249
     *
Carsten  Rose's avatar
Carsten Rose committed
250
     * @param bool|false $staticUniqId
Carsten  Rose's avatar
Carsten Rose committed
251
     *
Carsten  Rose's avatar
Carsten Rose committed
252
253
254
     * @return bool|string
     */
    public function sipUniqId($staticUniqId = false) {
255

Carsten  Rose's avatar
Carsten Rose committed
256
257
258
259
        if ($this->phpUnit) {
            if ($staticUniqId !== false) {
                $this->staticUniqId = $staticUniqId;
            }
Carsten  Rose's avatar
Carsten Rose committed
260

Carsten  Rose's avatar
Carsten Rose committed
261
262
263
            return $this->staticUniqId;
        }

264
265
266
267
        $sip = uniqid();

        // It seems there is a chance that uniqid() is not unique: http://php.net/manual/en/function.uniqid.php
        // Check if the newly created uniqid() is unique for the current user (sufficient).
Carsten  Rose's avatar
Carsten Rose committed
268
        while (Session::get($sip) !== false) {
269
270
271
272
            $sip = uniqid();
        }

        return $sip;
Carsten  Rose's avatar
Carsten Rose committed
273
274
    }

Carsten  Rose's avatar
#2067    
Carsten Rose committed
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
    /**
     * Update the SIP in the Session according $sipArray.
     *
     * @param array $sipArray
     */
    public function updateSipToSession(array $sipArray) {
        $sip = $sipArray[SIP_SIP];

        // Remove old entry, cause the the 'key' will change.
        $sipParamStringOld = Session::get($sipArray[SIP_SIP]);
        Session::unsetItem($sipParamStringOld);

        // Generate keyname for $_SESSION[]
        $sipParamStringNew = $this->buildParamStringFromArray($sipArray);

        Session::set($sip, $sipParamStringNew);
        Session::set($sipParamStringNew, $sip);
    }

Carsten  Rose's avatar
Carsten Rose committed
294
295
296
297
    /**
     * Retrieve Params stored in $_SESSION[$s]
     *
     * @param $s
Carsten  Rose's avatar
Carsten Rose committed
298
     *
Carsten  Rose's avatar
Carsten Rose committed
299
     * @return array Parameter Array
Carsten  Rose's avatar
Carsten Rose committed
300
     * @throws CodeException
301
     * @throws UserFormException
302
     * @throws UserReportException
Carsten  Rose's avatar
Carsten Rose committed
303
304
305
306
     */
    public function getVarsFromSip($s) {

        # Check if parameter is manipulated
307
        if (strlen($s) != SIP_TOKEN_LENGTH) {
308
            Config::attackDetectedExitNow(array(), 'Invalid SIP token length: ' . strlen($s) . " _GET['s']=" . htmlentities($s)  );
Carsten  Rose's avatar
Carsten Rose committed
309
310
        }

311
        // Validate: Check if still the same fe_user is logged in.
312
//        $this->checkFeUserUid();
Carsten  Rose's avatar
Carsten Rose committed
313
314

        # Check if index 's' exists.
315
316
317
        $sessionVar = Session::get($s);

        if ($sessionVar === false) {
318
            throw new UserFormException("SIP '$s' not registered - please reload the previous site and try again.", ERROR_SIP_NOT_FOUND);
Carsten  Rose's avatar
Carsten Rose committed
319
320
321
        }

        // Decode parameter
322
        return KeyValueStringParser::parse($sessionVar, "=", "&");
Carsten  Rose's avatar
Carsten Rose committed
323
324
    }

Carsten  Rose's avatar
Carsten Rose committed
325
326
327
328
329
330
    /**
     * Formats the content of sip. Per variable one line. Decode base64 encoded variables.
     * If $vars is an array, iterate over it.
     * If $vars is a string, than this is the SIP - retrieve parameter from SIP and process those.
     *
     * @param string|array $vars
Carsten  Rose's avatar
Carsten Rose committed
331
     *
Carsten  Rose's avatar
Carsten Rose committed
332
     * @return string
Carsten  Rose's avatar
Carsten Rose committed
333
     * @throws CodeException
334
335
     * @throws UserFormException
     * @throws UserReportException
Carsten  Rose's avatar
Carsten Rose committed
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
     */
    public function debugSip($vars) {

        if (!is_array($vars)) {
            $vars = $this->getVarsFromSip($vars);
        }

        // Detect and decode base64 content
        $len = strlen(SIP_PREFIX_BASE64);
        foreach ($vars as $key => $value) {
            if (substr($key, 0, $len) == SIP_PREFIX_BASE64) {
                $vars[$key] = base64_decode($value);
            }
        }

        return OnArray::toString($vars, ' = ', PHP_EOL, "'");
    }

Carsten  Rose's avatar
Carsten Rose committed
354
    /**
355
356
     * Returns the sip for the given querystring. The querystring has to be sorted.
     *
357
     * @param $queryString
Carsten  Rose's avatar
Carsten Rose committed
358
     *
359
360
361
     * @return mixed
     */
    public function getSipFromQueryString($queryString) {
362
        return Session::get($queryString);
363
364
365
    }

    /**
366
367
368
     * Returns the querystring for the given $sip
     *
     * @param $sip
Carsten  Rose's avatar
Carsten Rose committed
369
     *
370
     * @return bool
Carsten  Rose's avatar
Carsten Rose committed
371
     */
372
    public function getQueryStringFromSip($sip) {
373
        return Session::get($sip);
Carsten  Rose's avatar
Carsten Rose committed
374
375
376
    }

}