Compare commits

..

14 Commits

Author SHA1 Message Date
KodeStar
c9ea2cdeb3 Merge pull request #1496 from KodeStar/bugfix/update_proxy_and_items_with_no_password
Some checks failed
Mark stale issues and pull requests / stale (push) Has been cancelled
Update items with no password
2025-09-10 16:15:23 +01:00
Chris Hunt
517f51ba90 Update items with no password 2025-09-10 16:14:14 +01:00
KodeStar
825f67a4a4 Merge pull request #1480 from Nyuwb/patch-1
feat(icon-upload): proxy management
2025-09-10 15:15:48 +01:00
KodeStar
05a552ffcf Merge pull request #1475 from micvog/fix-german-translation
Fixed multiple typos (German translation)
2025-09-10 15:14:55 +01:00
Adam
ad4584e548 Merge pull request #1488 from linuxserver/inherit-security-md
Some checks failed
Mark stale issues and pull requests / stale (push) Has been cancelled
2025-08-23 15:22:58 +01:00
Adam
7d93099f2c Delete SECURITY.md
Inherit LSIO standard security.md from https://github.com/linuxserver/.github/blob/main/SECURITY.md
2025-08-23 15:20:18 +01:00
KodeStar
31ca05f74f Merge pull request #1483 from KodeStar/2.x
Some checks failed
Mark stale issues and pull requests / stale (push) Has been cancelled
Fix for some enhanced apps not working
2025-08-02 17:50:17 +01:00
Chris Hunt
cd95fc3b92 Update search test 2025-08-02 17:43:49 +01:00
Chris Hunt
63e777b338 Redirect to search provider without error fixes #1482 2025-08-02 17:40:37 +01:00
Chris Hunt
fd926e983d Fix for some enhanced apps not working 2025-08-02 17:17:40 +01:00
Fabien Ehrlich
dce37c1412 feat(icon-upload): proxy management 2025-07-31 16:54:44 +02:00
KodeStar
6b9f61b0e6 Merge pull request #1477 from KodeStar/2.x
Escape search queries and add setting value on edit
2025-07-24 19:06:50 +01:00
Chris Hunt
d1a96dd752 Escape search queries and add setting value on edit 2025-07-24 19:05:16 +01:00
micvog
31db31d0f7 Fixed multiple typos (German translation) 2025-07-21 17:37:39 +02:00
10 changed files with 86 additions and 79 deletions

View File

@@ -1,14 +0,0 @@
# Security Policy
## Supported Versions
| Version | Supported |
| ------- | ------------------ |
| 2.3.x | :white_check_mark: |
| < 2.3 | :x: |
## Reporting a Vulnerability
You can report any vulnerabilities on our discord server by DM-ing a team member, or asking a team member to DM you.
https://discord.com/invite/YWrKVTn

View File

@@ -267,9 +267,16 @@ class ItemController extends Controller
],
];
// Proxy management
$httpsProxy = getenv('HTTPS_PROXY');
$httpsProxyLower = getenv('https_proxy');
if ($httpsProxy !== false || $httpsProxyLower !== false) {
$options['proxy']['http'] = $httpsProxy ?: $httpsProxyLower;
}
$file = $request->input('icon');
$path_parts = pathinfo($file);
if (!isset($path_parts['extension'])) {
if (!array_key_exists('extension', $path_parts)) {
throw ValidationException::withMessages(['file' => 'Icon URL must have a valid file extension.']);
}
$extension = $path_parts['extension'];
@@ -312,7 +319,11 @@ class ItemController extends Controller
$storedConfigObject = json_decode($storedItem->getAttribute('description'));
$configObject = json_decode($config);
$configObject->password = $storedConfigObject->password;
if ($storedConfigObject && property_exists($storedConfigObject, 'password')) {
$configObject->password = $storedConfigObject->password;
} else {
$configObject->password = null;
}
$config = json_encode($configObject);
}
@@ -429,20 +440,31 @@ 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(!isset($app->config)) { // class based config
$output['custom'] = className($appdetails->name) . '.config';
$output['appvalue'] = $item->description;
// }
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;
}
}
$output['colour'] = ($app->tile_background == 'light') ? '#fafbfc' : '#161b1f';
@@ -450,14 +472,12 @@ 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,10 +18,8 @@ class SearchController extends Controller
$requestprovider = $request->input('provider');
$query = $request->input('q');
// Validate the presence and non-emptiness of the query parameter
if (!$query || trim($query) === '') {
abort(400, 'Missing or empty query parameter');
}
// Sanitize the query to prevent XSS
$query = htmlspecialchars($query, ENT_QUOTES, 'UTF-8');
$provider = Search::providerDetails($requestprovider);
@@ -29,6 +27,11 @@ 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') {
@@ -36,5 +39,6 @@ class SearchController extends Controller
return $class->getResults($query, $provider);
}
abort(404, 'Provider type not supported');}
abort(404, 'Provider type not supported');
}
}

View File

@@ -45,6 +45,7 @@ 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="'.(Input::get('q') ?? '').'" class="homesearch" autofocus placeholder="'.__('app.settings.search').'..." />';
$output .= '<input type="text" name="q" value="'.e(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,6 +21,20 @@ 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"></textarea>';
$value = '<textarea name="value" class="form-control" cols="44" rows="15">' . htmlspecialchars($this->value, ENT_QUOTES, 'UTF-8') . '</textarea>';
break;
default:
$value = '<input type="text" name="value" class="form-control" />';
$value = '<input type="text" name="value" class="form-control" value="' . htmlspecialchars($this->value, ENT_QUOTES, 'UTF-8') . '" />';
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.2',
'version' => '2.7.4',
'appsource' => env('APP_SOURCE', 'https://appslist.heimdall.site/'),

View File

@@ -20,7 +20,7 @@ return array (
'settings.language' => 'Sprache',
'settings.reset' => 'Zurücksetzen auf Standard',
'settings.remove' => 'Entfernen',
'settings.search' => 'suche',
'settings.search' => 'Suche',
'settings.no_items' => 'Keine Elemente gefunden',
'settings.label' => 'Bezeichnung',
'settings.value' => 'Wert',
@@ -33,7 +33,7 @@ return array (
'options.ddg' => 'DuckDuckGo',
'options.bing' => 'Bing',
'options.qwant' => 'Qwant',
'options.startpage' => 'StartSeite',
'options.startpage' => 'Startseite',
'options.yes' => 'Ja',
'options.no' => 'Nein',
'options.nzbhydra' => 'NZBHydra',
@@ -46,7 +46,7 @@ return array (
'dash.pin_item' => 'Element auf dem Dashboard anheften',
'dash.no_apps' => 'Derzeit gibt es keine angeheftete Anwendungen. :link1 oder :link2',
'dash.link1' => 'Anwendung neu hinzufügen',
'dash.link2' => 'anheften',
'dash.link2' => 'Anheften',
'dash.pinned_items' => 'Angeheftete Elemente',
'apps.app_list' => 'Anwendungsliste',
'apps.view_trash' => 'Ansicht Papierkorb',
@@ -66,7 +66,7 @@ return array (
'apps.add_tag' => 'Tag hinzufügen',
'apps.tag_name' => 'Tag Name',
'apps.tags' => 'Tags',
'apps.override' => 'Fals anders zur Haupt-URL',
'apps.override' => 'Falls anders zur Haupt-URL',
'apps.preview' => 'Vorschau',
'apps.apptype' => 'Anwendungstyp',
'apps.website' => 'Webseite',
@@ -81,7 +81,7 @@ return array (
'user.avatar' => 'Avatar',
'user.email' => 'Email',
'user.password_confirm' => 'Passwort bestätigen',
'user.secure_front' => 'Öffentlichen Zugang erlauben - Tritt nur bei gesetztem Passwort in kraft.',
'user.secure_front' => 'Öffentlichen Zugang erlauben - Tritt nur bei gesetztem Passwort in Kraft.',
'user.autologin' => 'Anmelden von spezieller URL erlauben. Jeder mit diesem Link kann sich anmelden.',
'url' => 'URL',
'title' => 'Titel',

View File

@@ -32,22 +32,4 @@ 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)
}
}