Compare commits

..

8 Commits

Author SHA1 Message Date
KodeStar
1ccc0da2a7 Merge pull request #1476 from KodeStar/2.x
Load in configs values if class has been lost
2025-07-22 15:59:00 +01:00
KodeStar
e42f78bd49 Merge pull request #1473 from KodeStar/2.x
Add misisng input type in the form builder
2025-07-21 10:12:27 +01:00
KodeStar
57e0a335b7 Merge pull request #1471 from KodeStar/2.x
Add cache table
2025-07-17 14:46:16 +01:00
KodeStar
abe08a7b04 Merge pull request #1470 from KodeStar/2.x
Fix importing apps and logging in
2025-07-17 14:23:09 +01:00
KodeStar
4fa41d8114 Merge pull request #1469 from KodeStar/2.x
Add tests and fix user edit form
2025-07-15 17:10:49 +01:00
KodeStar
53fd6242db Merge pull request #1467 from KodeStar/2.x
Fixes to reduce the SSRF attack vector plus display image names
2025-07-14 11:15:13 +01:00
KodeStar
0388ee939a Merge pull request #1466 from KodeStar/2.x
Fix uploads and displaying of malicious SVG files
2025-07-13 17:02:27 +01:00
KodeStar
abbc78ee8d Merge pull request #1464 from KodeStar/2.x
Update framework to laravel 11 and fix images with no extension causing error 500
2025-07-11 16:36:40 +01:00
8 changed files with 60 additions and 75 deletions

View File

@@ -267,10 +267,9 @@ class ItemController extends Controller
],
];
$file = $request->input('icon');
$path_parts = pathinfo($file);
if (!array_key_exists('extension', $path_parts)) {
if (!isset($path_parts['extension'])) {
throw ValidationException::withMessages(['file' => 'Icon URL must have a valid file extension.']);
}
$extension = $path_parts['extension'];
@@ -313,11 +312,7 @@ class ItemController extends Controller
$storedConfigObject = json_decode($storedItem->getAttribute('description'));
$configObject = json_decode($config);
if ($storedConfigObject && property_exists($storedConfigObject, 'password')) {
$configObject->password = $storedConfigObject->password;
} else {
$configObject->password = null;
}
$configObject->password = $storedConfigObject->password;
$config = json_encode($configObject);
}
@@ -434,31 +429,20 @@ class ItemController extends Controller
return null;
}
$output['config'] = null;
$output['custom'] = null;
$app = Application::single($appid);
if (!$app) {
return response()->json(['error' => 'Application not found.'], 404);
}
$output = (array)$app;
$appdetails = Application::getApp($appid);
if (!$appdetails) {
return response()->json(['error' => 'Application details not found.'], 404);
}
if ((bool)$app->enhanced === true) {
$item = $itemId ? Item::find($itemId) : Item::where('appid', $appid)->first();
if ($item) {
$output['custom'] = className($appdetails->name) . '.config';
$output['appvalue'] = $item->description;
} else {
// Ensure the app is installed if not found
$output['custom'] = className($appdetails->name) . '.config';
$output['appvalue'] = null;
}
// if(!isset($app->config)) { // class based config
$output['custom'] = className($appdetails->name) . '.config';
$output['appvalue'] = $item->description;
// }
}
$output['colour'] = ($app->tile_background == 'light') ? '#fafbfc' : '#161b1f';
@@ -466,12 +450,14 @@ class ItemController extends Controller
if (strpos($app->icon, '://') !== false) {
$output['iconview'] = $app->icon;
} elseif (strpos($app->icon, 'icons/') !== false) {
// Private apps have the icon locally
$output['iconview'] = URL::to('/') . '/storage/' . $app->icon;
$output['icon'] = str_replace('icons/', '', $output['icon']);
} else {
$output['iconview'] = config('app.appsource') . 'icons/' . $app->icon;
}
return json_encode($output);
}

View File

@@ -18,8 +18,10 @@ class SearchController extends Controller
$requestprovider = $request->input('provider');
$query = $request->input('q');
// Sanitize the query to prevent XSS
$query = htmlspecialchars($query, ENT_QUOTES, 'UTF-8');
// Validate the presence and non-emptiness of the query parameter
if (!$query || trim($query) === '') {
abort(400, 'Missing or empty query parameter');
}
$provider = Search::providerDetails($requestprovider);
@@ -27,11 +29,6 @@ class SearchController extends Controller
abort(404, 'Invalid provider');
}
// If the query is empty, redirect to the provider's base URL
if (!$query || trim($query) === '') {
return redirect($provider->url);
}
if ($provider->type == 'standard') {
return redirect($provider->url.'?'.$provider->query.'='.urlencode($query));
} elseif ($provider->type == 'external') {
@@ -39,6 +36,5 @@ class SearchController extends Controller
return $class->getResults($query, $provider);
}
abort(404, 'Provider type not supported');
}
abort(404, 'Provider type not supported');}
}

View File

@@ -45,7 +45,6 @@ class SettingsController extends Controller
if (! is_null($setting)) {
return view('settings.edit')->with([
'setting' => $setting,
'value' => $setting->value,
]);
} else {
$route = route('settings.list', []);

View File

@@ -121,7 +121,7 @@ abstract class Search
$output .= '<option value="'.$key.'"'.$selected.'>'.$searchprovider['name'].'</option>';
}
$output .= '</select>';
$output .= '<input type="text" name="q" value="'.e(Input::get('q') ?? '').'" class="homesearch" autofocus placeholder="'.__('app.settings.search').'..." />';
$output .= '<input type="text" name="q" value="'.(Input::get('q') ?? '').'" class="homesearch" autofocus placeholder="'.__('app.settings.search').'..." />';
$output .= '<button type="submit">'.ucwords(__('app.settings.search')).'</button>';
$output .= '</div>';
$output .= '</form>';

View File

@@ -21,20 +21,6 @@ class CustomFormBuilder
);
}
public function hidden($name, $value = null, $options = [])
{
return new HtmlString(
$this->html->input('hidden', $name, $value)->attributes($options)
);
}
public function checkbox($name, $value = null, $checked = false, $options = [])
{
return new HtmlString(
$this->html->checkbox($name, $value, $checked)->attributes($options)
);
}
public function select($name, $list = [], $selected = null, $options = [])
{
return new HtmlString(

View File

@@ -150,41 +150,41 @@ class Setting extends Model
switch ($this->type) {
case 'image':
$value = '';
if (isset($this->value) && !empty($this->value)) {
$value .= '<a class="setting-view-image" href="' .
asset('storage/' . $this->value) .
'" title="' .
__('app.settings.view') .
'" target="_blank"><img src="' .
asset('storage/' .
$this->value) .
if (isset($this->value) && ! empty($this->value)) {
$value .= '<a class="setting-view-image" href="'.
asset('storage/'.$this->value).
'" title="'.
__('app.settings.view').
'" target="_blank"><img src="'.
asset('storage/'.
$this->value).
'" /></a>';
}
$value .= '<input type="file" name="value" class="form-control" />';
if (isset($this->value) && !empty($this->value)) {
$value .= '<a class="settinglink" href="' .
route('settings.clear', $this->id) .
'" title="' .
__('app.settings.remove') .
'">' .
__('app.settings.reset') .
if (isset($this->value) && ! empty($this->value)) {
$value .= '<a class="settinglink" href="'.
route('settings.clear', $this->id).
'" title="'.
__('app.settings.remove').
'">'.
__('app.settings.reset').
'</a>';
}
break;
case 'boolean':
$checked = false;
if (isset($this->value) && (bool)$this->value === true) {
if (isset($this->value) && (bool) $this->value === true) {
$checked = true;
}
$set_checked = ($checked) ? ' checked="checked"' : '';
$value = '
<input type="hidden" name="value" value="0" />
<label class="switch">
<input type="checkbox" name="value" value="1"' . $set_checked . ' />
<input type="checkbox" name="value" value="1"'.$set_checked.' />
<span class="slider round"></span>
</label>';
break;
case 'select':
$options = json_decode($this->options);
@@ -193,21 +193,21 @@ class Setting extends Model
}
$value = '<select name="value" class="form-control">';
foreach ($options as $key => $opt) {
$value .= '<option value="' . $key . '" ' . (($this->value == $key) ? 'selected' : '') . '>' . __($opt) . '</option>';
$value .= '<option value="'.$key.'" '.(($this->value == $key) ? 'selected' : '').'>'.__($opt).'</option>';
}
$value .= '</select>';
break;
case 'textarea':
$value = '<textarea name="value" class="form-control" cols="44" rows="15">' . htmlspecialchars($this->value, ENT_QUOTES, 'UTF-8') . '</textarea>';
$value = '<textarea name="value" class="form-control" cols="44" rows="15"></textarea>';
break;
default:
$value = '<input type="text" name="value" class="form-control" value="' . htmlspecialchars($this->value, ENT_QUOTES, 'UTF-8') . '" />';
$value = '<input type="text" name="value" class="form-control" />';
break;
}
return $value;
}
public function group(): BelongsTo
{
return $this->belongsTo(\App\SettingGroup::class, 'group_id');

View File

@@ -5,7 +5,7 @@ use Illuminate\Support\Facades\Facade;
return [
'version' => '2.7.4',
'version' => '2.7.2',
'appsource' => env('APP_SOURCE', 'https://appslist.heimdall.site/'),

View File

@@ -32,4 +32,22 @@ class SearchTest extends TestCase
$response->assertStatus(404); // Assert that the response status is 404
}
public function test_search_page_without_query_parameter(): void
{
$provider = 'google'; // Example provider
$response = $this->get(route('search', ['provider' => $provider]));
$response->assertStatus(400); // Assert that the response status is 400 (Bad Request)
}
public function test_search_page_with_empty_query(): void
{
$provider = 'google'; // Example provider
$query = ''; // Empty search term
$response = $this->get(route('search', ['provider' => $provider, 'q' => $query]));
$response->assertStatus(400); // Assert that the response status is 400 (Bad Request)
}
}