Issues (46)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

code/EmailDownloadPage.php (4 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * Page for Email Download
4
 * Easiest changed by extending this page
5
 * and using the hide_ancestor static to hide this page itself.
6
 */
7
8
class EmailDownloadPage extends Page
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
9
{
10
11
    /**
12
     * standard SS Variable
13
     */
14
    private static $description = "Allow the user to download a file through their e-mail.";
15
16
    /**
17
     * standard SS Variable
18
     */
19
    private static $icon = "downloadtoemail/images/treeicons/EmailDownloadPage";
20
21
    /**
22
     * standard SS Variable
23
     */
24
    private static $db = array(
25
        "LinkToThirdPartyDownload" => "Varchar(255)",
26
        "TitleOfFile" => "Varchar(50)",
27
        "EmailSubject" => "Varchar(200)",
28
        "NoAccessContent" => "Varchar(255)",
29
        "ValidityInDays" => "Float",
30
        "AllowReRequest" => "Boolean",
31
        "AllowReRequestLabel" => "Varchar(255)",
32
        "DeclineReRequestLabel" => "Varchar(255)",
33
        "ThankYouForRequesting" => "Varchar(255)",
34
        "ThankYouLink" => "Varchar(255)",
35
        "CopyOfAllEmailsToAdmin" => "Boolean"
36
    );
37
38
    /**
39
     * standard SS Variable
40
     */
41
    private static $has_one = array(
42
        "DownloadFile" => "File"
43
    );
44
45
    /**
46
     * standard SS Variable
47
     */
48
    private static $has_many = array(
49
        "EmailsSent" => "EmailDownloadPage_Registration"
50
    );
51
52
    /**
53
     * standard SS Variable
54
     */
55
    private static $defaults = array(
56
        "NoAccessContent" => "Sorry, you do not have access to this file right now.  Please request access again.",
57
        "ThankYouForRequesting" => "Thank you for requesting this download, please check your e-mail for more information ...",
58
        "AllowReRequest" => true,
59
        "AllowReRequestLabel" => "Request another copy.",
60
        "DeclineReRequestLabel" => "You have already requested this file and you can not request it again."
61
    );
62
63
    /**
64
     *
65
     * @param boolean $includerelations a boolean value to indicate if the labels returned include relation fields
66
     *
67
     */
68
    public function fieldLabels($includerelations = true)
69
    {
70
        $labels = parent::fieldLabels($includerelations);
71
        $labels["TitleOfFile"] = _t("EmailDownloadPage.TITLEOFFILE", "Title of file");
72
        $labels["LinkToThirdPartyDownload"] = _t("EmailDownloadPage.LINKTOTHIRDPARTYDOWNLOAD", "Link to third-party download file / page");
73
        $labels["ValidityInDays"] = _t("EmailDownloadPage.VALIDITYINDAYS", "Validity in days (you can use 0.5 for 12 hours, etc...)");
74
        $labels["DownloadFile"] = $labels["DownloadFileID"] = _t("EmailDownloadPage.DOWNLOADFILE", "Select file to download");
75
        $labels["ThankYouForRequesting"] = _t("EmailDownloadPage.THANKYOUFORREQUESTING", "Thank you for requesting message");
76
        $labels["ThankYouLink"] = _t("EmailDownloadPage.THANKYOULINK", "Thank you link");
77
        $labels["EmailSubject"] = _t("EmailDownloadPage.EMAILSUBJECT", "E-mail Subject");
78
        $labels["AllowReRequest"] = _t("EmailDownloadPage.ALLOWREREQUEST", "Allow the user to make more than one request for the file (not strictly enforced) - change and reload to see more options");
79
        $labels["AllowReRequestLabel"] = _t("EmailDownloadPage.ALLOWREREQUESTLABEL", "Label for requesting another copy");
80
        $labels["DeclineReRequestLabel"] = _t("EmailDownloadPage.DECLINEREREQUESTLABEL", "Explanation of why the user can not request another copy");
81
        $labels["NoAccessContent"] = _t("EmailDownloadPage.NOACCESSCONTENT", "Content shown when the user does not have access");
82
        $labels["EmailsSent"] = _t("EmailDownloadPage.EMAILSSENT", "Downloads requested");
83
        $labels["CopyOfAllEmailsToAdmin"] = _t("EmailDownloadPage.COPYOFALLEMAILSTOADMIN", "Send a copy of all e-mails to the website administrator ");
84
        return $labels;
85
    }
86
87
    /**
88
     * standard SS Method
89
     */
90
    public function getCMSFields()
91
    {
92
        $fields = parent::getCMSFields();
93
        $labels = $this->fieldLabels(true);
94
        $fieldsToAdd = array(
95
            new TextField("TitleOfFile", $labels["TitleOfFile"]),
96
            $uploadField = new UploadField("DownloadFile", $labels["DownloadFile"])
97
        );
98
99
100
        if ($this->DownloadFileID) {
101
            $fieldsToAdd = array_merge($fieldsToAdd, array(
102
                new NumericField("ValidityInDays", $label["ValidityInDays"])
103
            ));
104
        } else {
105
            $fieldsToAdd = array_merge($fieldsToAdd, array(
106
                $linkToThirdPartyDownloadField = new TextField("LinkToThirdPartyDownload", $labels["LinkToThirdPartyDownload"])
107
            ));
108
            $linkToThirdPartyDownloadField->setRightTitle(_t("EmailDownloadPage.LINKTOTHIRDPARTYDOWNLOAD_RIGHT_TITLE", "Set this to a third-party website link (e.g. dropbox) - e.g. http://www.mycooldownloadpage.com/mydownloadpage/"));
109
        }
110
        $fieldsToAdd = array_merge($fieldsToAdd, array(
111
            new CheckboxField("AllowReRequest", $labels["AllowReRequest"]),
112
            new TextField("EmailSubject", $labels["EmailSubject"]),
113
            new CheckboxField("CopyOfAllEmailsToAdmin", $labels["CopyOfAllEmailsToAdmin"]." (".Email::getAdminEmail().")"),
114
            new TextField("ThankYouForRequesting", $labels["ThankYouForRequesting"]),
115
            new TextField("ThankYouLink", $labels["ThankYouLink"])
116
        ));
117
        if ($this->AllowReRequest) {
118
            $fieldsToAdd = array_merge($fieldsToAdd, array(
119
                new TextField("AllowReRequestLabel", $labels["AllowReRequestLabel"]),
120
            ));
121
        } else {
122
            $fieldsToAdd = array_merge($fieldsToAdd, array(
123
                new TextField("DeclineReRequestLabel", $labels["DeclineReRequestLabel"]),
124
            ));
125
        }
126
        $fieldsToAdd = array_merge($fieldsToAdd, array(
127
            new TextField("NoAccessContent", $labels["NoAccessContent"]),
128
            $gridField = new GridField("EmailsSent", $labels["EmailsSent"], $this->EmailsSent(), GridFieldConfig_RelationEditor::create())
129
        ));
130
        $gridField->getConfig()->addComponent(new GridFieldExportButton());
131
        $fields->addFieldsToTab(
132
            "Root.DownloadToEmail",
133
            $fieldsToAdd
134
        );
135
        return $fields;
136
    }
137
}
138
139
class EmailDownloadPage_Controller extends Page_Controller
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
140
{
141
142
    /**
143
     * standard SS Variable
144
     */
145
    private static $allowed_actions = array(
146
        "DownloadForm",
147
        "dodownload",
148
        "thankyou",
149
        "requestrerequest",
150
        "noaccess"
151
    );
152
153
    /**
154
     * Template to be used for sending e-mail.
155
     * @var String
156
     */
157
    private static $email_template = "DownloadToEmailEmail";
158
159
    /**
160
     * Show the download form?
161
     * @var Boolean
162
     */
163
    protected $showDownloadForm = true;
164
165
    /**
166
     * Message to user (e.g. you do not have access to this file)
167
     * @var String
168
     */
169
    protected $feedbackMessage = "";
170
171
    /**
172
     * Type of feedback (Good | Bad | Warning)
173
     * @var String
174
     */
175
    protected $feedbackMessageStyle = "";
176
177
178
    /**
179
     * Standard SS method
180
     */
181
    public function init()
182
    {
183
        parent::init();
184
        $this->showDownloadForm = $this->AlreadyRequestedSuccessfully() ? false : true;
185
    }
186
187
188
    /**
189
     *
190
     * @return Boolean
191
     */
192
    public function AlreadyRequestedSuccessfully()
193
    {
194
        return Session::get($this->sessionVarNameForSending()) ? true : false;
195
    }
196
197
    public function ReRequestLink()
198
    {
199
        return $this->Link("requestrerequest");
200
    }
201
202
    /**
203
     * feedback message for user
204
     * @return Varchar
205
     */
206
    public function ShowDownloadForm()
207
    {
208
        return $this->showDownloadForm;
209
    }
210
211
    /**
212
     * feedback message for user
213
     * @return Varchar
214
     */
215
    public function FeedbackMessage()
216
    {
217
        return DBField::create_field('Varchar', $this->feedbackMessage);
218
    }
219
220
    /**
221
     * feedback message for user
222
     * @return Varchar
223
     */
224
    public function FeedbackMessageStyle()
225
    {
226
        return DBField::create_field('Varchar', $this->feedbackMessageStyle);
227
    }
228
229
    /**
230
     * show the download form.
231
     *
232
     * @return Form
233
     */
234
    public function DownloadForm()
235
    {
236
        Requirements::javascript(THIRDPARTY_DIR . '/jquery/jquery.js');
237
        Requirements::javascript('downloadtoemail/javascript/DownloadToEmail.js');
238
        $form = new Form(
239
            $this,
240
            'DownloadForm',
241
            new FieldList($emailField = new EmailField('EmailDownloadPageEmail', _t("EmailDownloadPage.EMAIL", "enter your e-mail address"))),
242
            new FieldList(new FormAction('sendmail', _t("EmailDownloadPage.REQUEST_ACCESS", "request access"))),
243
            RequiredFields::create(array("EmailDownloadPageEmail"))
244
        );
245
        return $form;
246
    }
247
248
    /**
249
     * Sent the e-mail.
250
     *
251
     * @param Array $data
252
     * @param Form $form
253
     */
254
    public function sendmail($data, $form)
255
    {
256
        $email = Convert::raw2sql($data["EmailDownloadPageEmail"]);
257
        $obj = EmailDownloadPage_Registration::get()
258
            ->filter(array("Email" => $email, "DownloadFileID" => $this->DownloadFileID))
259
            ->first();
260
        if (!$obj) {
261
            $obj = new EmailDownloadPage_Registration();
262
            $obj->Email = $email;
263
            $obj->DownloadFileID = $this->DownloadFileID;
264
        } else {
265
            $obj->Used = false;
266
        }
267
        $obj->EmailDownloadPageID = $this->ID;
268
        $obj->write();
269
        $adminEmail = Email::getAdminEmail();
270
        if (!$adminEmail) {
271
            user_error("You need to set an admin email in order to use this page", E_USER_NOTICE);
272
        }
273
        $email = new Email($adminEmail, $data["EmailDownloadPageEmail"], $this->EmailSubject);
274
        if ($this->CopyOfAllEmailsToAdmin) {
275
            $email->setBcc($adminEmail);
276
        }
277
        $email->setTemplate($this->config()->get("email_template"));
278
        // You can call this multiple times or bundle everything into an array, including DataSetObjects
279
        $email->populateTemplate(
280
            new ArrayData(
281
                array(
282
                    "EmailSubject" => DBField::create_field('Varchar', $this->EmailSubject),
283
                    "TitleOfFile" => DBField::create_field('Varchar', $this->TitleOfFile),
284
                    "ValidUntil" => date('Y-M-d', strtotime("+".($this->ValidityInDays * 86400)." seconds")),
285
                    "HasLink" => $this->LinkToThirdPartyDownload ? true : false,
286
                    "HasFile" => $this->DownloadFileID ? true : false,
287
                    "LinkToThirdPartyDownload" => $this->LinkToThirdPartyDownload,
288
                    "File" => $this->DownloadFile(),
289
                    "DownloadLink" => Director::absoluteURL($this->Link("dodownload/".$obj->ID."/".$obj->Code.'/')),
290
                    "FileLocation" => Director::absoluteURL($this->DownloadFile()->Link())
291
                )
292
            )
293
        );
294
        $outcome = $email->send();
295
        Session::set($this->sessionVarNameForSending(), $outcome);
296
        $this->redirect($this->Link("thankyou/".($outcome ? "success" : "fail")."/"));
297
        return array();
298
    }
299
300
    /**
301
     * Do the download itself.
302
     * URL should be formatted as
303
     * /thankyou/outcome/
304
     *
305
     * @param HTTPRequest
306
     */
307
    public function thankyou($request)
308
    {
309
        $outcome = $request->param("ID");
310
        if ($outcome == "success") {
311
            $this->feedbackMessage = $this->ThankYouForRequesting;
312
            $this->feedbackMessageStyle = "good";
313
            $this->showDownloadForm = false;
314
            $this->DeclineReRequestLabel = "";
315
        } else {
316
            $this->feedbackMessage = "E-mail could not be sent.";
317
            $this->feedbackMessageStyle = "bad";
318
            $this->DeclineReRequestLabel = "";
319
        }
320
        return array();
321
    }
322
323
    /**
324
     * Do the download itself.
325
     * URL should be formatted as
326
     * /dodownload/$ID/$CodeForObject/
327
     *
328
     * @param HTTPRequest
329
     */
330
    public function dodownload($request)
331
    {
332
        Session::set($this->sessionVarNameForSending(), true);
333
        $id = intval($request->param("ID"));
334
        $code = Convert::raw2sql($request->param("OtherID"));
335
        if ($id && $code) {
336
            $obj = EmailDownloadPage_Registration::get()->filter(
337
                array(
338
                    "ID" => $id,
339
                    "Code" => $code,
340
                    "Used" => 0
341
                )
342
            )->First();
343
            if ($obj) {
344
                if ($this->ValidityInDays) {
345
                    $tsNow = strtotime("NOW");
346
                    $validUntilTs = strtotime($obj->Created." +".(86400 * $this->ValidityInDays)." seconds");
347
                    if ($tsNow > $validUntilTs) {
348
                        return $this->redirect($this->Link("noaccess"));
349
                    }
350
                }
351
                $obj->DownloadTimes++;
352
                $obj->Used = true;
353
                $obj->write();
354
                return $this->sendFile($obj->DownloadFile());
355
            }
356
        }
357
        $this->redirect($this->Link("noaccess"));
358
    }
359
360
    /**
361
     *
362
     * What happens when the person does not have access.
363
     */
364
    public function noaccess()
365
    {
366
        $this->feedbackMessage = $this->NoAccessContent;
367
        $this->feedbackMessageStyle = "warning";
368
        return array();
369
    }
370
371
    /**
372
     *
373
     * What happens when the person does not have access.
374
     */
375
    public function requestrerequest()
376
    {
377
        if ($this->AllowReRequest) {
378
            Session::set($this->sessionVarNameForSending(), false);
379
            Session::clear($this->sessionVarNameForSending());
380
            $this->redirect($this->Link());
381
        } else {
382
            $this->redirect($this->Link("noaccess"));
383
        }
384
        return array();
385
    }
386
387
    // We calculate the timelimit based on the filesize. Set to 0 to give unlimited timelimit.
388
    // The calculation is: give enough time for the user with x kB/s connection to donwload the entire file.
389
    // E.g. The default 50kB/s equates to 348 minutes per 1GB file.
390
    private static $min_download_bandwidth = 50; // [in kilobytes per second]
391
392
    /**
393
     *
394
     * COPIED CODE!!!!!
395
     *
396
     * This is copied from here:
397
     * https://github.com/silverstripe-labs/silverstripe-secureassets/blob/master/code/SecureFileController.php
398
     *
399
     * @param File $file
400
     */
401
    protected function sendFile($file)
402
    {
403
        $path = $file->getFullPath();
404
        if (SapphireTest::is_running_test()) {
405
            return file_get_contents($path);
406
        }
407
        header('Content-Description: File Transfer');
408
        // Quotes needed to retain spaces (http://kb.mozillazine.org/Filenames_with_spaces_are_truncated_upon_download)
409
        header('Content-Disposition: inline; filename="' . basename($path) . '"');
410
        header('Content-Length: ' . $file->getAbsoluteSize());
411
        header('Content-Type: ' . HTTP::get_mime_type($file->getRelativePath()));
412
        header('Content-Transfer-Encoding: binary');
413
        // Fixes IE6,7,8 file downloads over HTTPS bug (http://support.microsoft.com/kb/812935)
414
        header('Pragma: ');
415
        if ($this->config()->min_download_bandwidth) {
416
            // Allow the download to last long enough to allow full download with min_download_bandwidth connection.
417
            increase_time_limit_to((int)(filesize($path)/($this->config()->min_download_bandwidth*1024)));
418
        } else {
419
            // Remove the timelimit.
420
            increase_time_limit_to(0);
421
        }
422
        // Clear PHP buffer, otherwise the script will try to allocate memory for entire file.
423
        while (ob_get_level() > 0) {
424
            ob_end_flush();
425
        }
426
        // Prevent blocking of the session file by PHP. Without this the user can't visit another page of the same
427
        // website during download (see http://konrness.com/php5/how-to-prevent-blocking-php-requests/)
428
        session_write_close();
429
        readfile($path);
430
        die();
0 ignored issues
show
Coding Style Compatibility introduced by
The method sendFile() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
431
    }
432
433
    /**
434
     *
435
     * @return String
436
     */
437
    protected function sessionVarNameForSending()
438
    {
439
        return "EmailDownloadPage_Controller_".$this->ID."_Sent";
440
    }
441
}
442