Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ There are some FreshRSS extensions out there, developed by community members:

* [Youtube](xExtension-YouTube) shows YouTube videos inline in the feed


### By [@JadedBlueEyes](https://github.com/JadedBlueEyes), [Web](https://jade.ellis.link/)

* [ImageCamo](xExtension-ImageCamo) Proxy images using a camo proxy instance for secure image delivery without mixed content warnings.

### By [@oYoX](https://github.com/oyox), [Web](https://oyox.de/)

* [Keep Folder State](https://github.com/oyox/FreshRSS-extensions/tree/master/xExtension-KeepFolderState): Stores the state of the folders locally and expand them automatically if necessary.
Expand Down Expand Up @@ -166,6 +171,6 @@ There are some FreshRSS extensions out there, developed by community members:

* [FreshVibes](https://github.com/tryallthethings/freshvibes): A fully customizable iGoogle / Netvibes-like dashboard view

### By [@pe1uca](https://github.com/pe1uca)
### By [@pe1uca](https://github.com/pe1uca)

* [Rate limiter](https://github.com/pe1uca/xExtension-RateLimiter/): Prevents FreshRSS from making too many requests to the same site in a defined amount of time.
* [Rate limiter](https://github.com/pe1uca/xExtension-RateLimiter/): Prevents FreshRSS from making too many requests to the same site in a defined amount of time.
11 changes: 11 additions & 0 deletions extensions.json
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,17 @@
"method": "git",
"directory": "xExtension-ImageProxy"
},
{
"name": "Image Camo Proxy",
"author": "Jade Ellis",
"description": "Proxy images using a camo proxy instance for secure image delivery without mixed content warnings.",
"version": "1.0.0",
"entrypoint": "ImageCamo",
"type": "user",
"url": "https://github.com/FreshRSS/Extensions",
"method": "git",
"directory": "xExtension-ImageCamo"
},
{
"name": "Invidious Video Feed",
"author": "Korbak (forked from Kevin Papst)",
Expand Down
674 changes: 674 additions & 0 deletions xExtension-ImageCamo/LICENSE

Large diffs are not rendered by default.

44 changes: 44 additions & 0 deletions xExtension-ImageCamo/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Image Camo Proxy Extension for FreshRSS

This extension allows FreshRSS to proxy images through a Camo server (eg. [go-camo](https://github.com/cactus/go-camo)), providing secure image delivery without mixed content warnings on HTTPS sites and without running an open proxy.

## Features

- Proxy HTTP and/or HTTPS images through go-camo
- Support for both Base64 and Hex URL encoding (go-camo compatible)
- Configurable scheme handling for protocol-relative URLs
- Preserves original URLs in data attributes for FreshRSS compatibility
- Supports responsive images with srcset attributes

## Requirements

- A running camo server instance (eg. go-camo)
- HMAC key configured in camo server

## Configuration

1. **Camo Proxy URL**: The base URL of your camo server (e.g., `https://your-camo-instance.example.com`)
2. **HMAC Key**: The shared secret key used to sign URLs (must match your camo server configuration)
3. **URL Encoding**: Choose between Base64 (recommended, shorter URLs) or Hex encoding
4. **Proxy HTTP images**: Enable/disable proxying of HTTP images
5. **Proxy HTTPS images**: Enable/disable proxying of HTTPS images (usually disabled for performance)
6. **Proxy protocol-relative URLs**: How to handle URLs starting with `//`
7. **Include http*:// in URL**: Whether to include the protocol scheme in the proxied URL

## How it works

1. The extension intercepts image URLs in RSS feed content
2. For each image URL that matches the configured criteria:
- Generates an HMAC-SHA1 signature using the configured key
- Encodes both the signature and URL (Base64 or Hex)
- Constructs a go-camo compatible URL: `{camo-url}/{signature}/{encoded-url}`
3. The original URLs are preserved in data attributes

## Security Considerations

- Keep your HMAC key secret and secure
- Use a strong, random HMAC key

## Based on

This extension is based on the [xExtension-ImageProxy](https://github.com/FreshRSS/Extensions/tree/master/xExtension-ImageProxy) extension but specifically designed for go-camo compatibility.
89 changes: 89 additions & 0 deletions xExtension-ImageCamo/configure.phtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<?php
declare(strict_types=1);
/** @var ImageCamoExtension $this */
?>
<div class="gocamo-security-notice">
<span class="icon">⚠️</span>
<strong><?= _t('ext.imagecamo.security_notice_title') ?>:</strong>
<?= _t('ext.imagecamo.security_notice_text') ?>
</div>

<form action="<?= _url('extension', 'configure', 'e', urlencode($this->getName())) ?>" method="post">
<input type="hidden" name="_csrf" value="<?= FreshRSS_Auth::csrfToken() ?>" />

<div class="form-group">
<label class="group-name" for="camo_proxy_url"><?= _t('ext.imagecamo.proxy_url') ?></label>
<div class="group-controls">
<input type="url" name="camo_proxy_url" id="camo_proxy_url"
value="<?= htmlspecialchars(FreshRSS_Context::userConf()->attributeString('camo_proxy_url') ?? '', ENT_COMPAT, 'UTF-8') ?>"
placeholder="https://your-camo-instance.example.com" required>
<div class="gocamo-help"><?= _t('ext.imagecamo.proxy_url_help') ?></div>
</div>
</div>

<div class="form-group">
<label class="group-name" for="camo_hmac_key"><?= _t('ext.imagecamo.hmac_key') ?></label>
<div class="group-controls">
<input type="password" name="camo_hmac_key" id="camo_hmac_key"
value="<?= htmlspecialchars(FreshRSS_Context::userConf()->attributeString('camo_hmac_key') ?? '', ENT_COMPAT, 'UTF-8') ?>"
placeholder="Your HMAC key for the camo server" required>
<div class="gocamo-help"><?= _t('ext.imagecamo.hmac_key_help') ?></div>
</div>
</div>

<div class="form-group">
<label class="group-name" for="camo_encoding"><?= _t('ext.imagecamo.encoding') ?></label>
<div class="group-controls">
<select name="camo_encoding" id="camo_encoding">
<option value="base64" <?= (FreshRSS_Context::userConf()->attributeString('camo_encoding') === 'base64') ? 'selected' : '' ?>>Base64 (<?= _t('ext.imagecamo.encoding_base64_desc') ?>)</option>
<option value="hex" <?= (FreshRSS_Context::userConf()->attributeString('camo_encoding') === 'hex') ? 'selected' : '' ?>>Hex (<?= _t('ext.imagecamo.encoding_hex_desc') ?>)</option>
</select>
<div class="gocamo-help"><?= _t('ext.imagecamo.encoding_help') ?></div>
</div>
</div>

<div class="form-group">
<label class="group-name" for="camo_scheme_http"><?= _t('ext.imagecamo.scheme_http') ?></label>
<div class="group-controls">
<input type="checkbox" name="camo_scheme_http" id="camo_scheme_http" value="1"
<?= FreshRSS_Context::userConf()->attributeBool('camo_scheme_http') ? 'checked' : '' ?>>
</div>
</div>

<div class="form-group">
<label class="group-name" for="camo_scheme_https"><?= _t('ext.imagecamo.scheme_https'); ?></label>
<div class="group-controls">
<input type="checkbox" name="camo_scheme_https" id="camo_scheme_https" value="1"
<?= FreshRSS_Context::userConf()->attributeBool('camo_scheme_https') ? 'checked' : '' ?>>
</div>
</div>

<div class="form-group">
<label class="group-name" for="camo_scheme_default"><?= _t('ext.imagecamo.scheme_default'); ?></label>
<div class="group-controls">
<select name="camo_scheme_default" id="camo_scheme_default">
<option value="<?= htmlspecialchars(FreshRSS_Context::userConf()->attributeString('camo_scheme_default') ?? '', ENT_COMPAT, 'UTF-8') ?>" selected="selected"><?=
htmlspecialchars(FreshRSS_Context::userConf()->attributeString('camo_scheme_default') ?? '', ENT_COMPAT, 'UTF-8') ?></option>
<option value="-">-</option>
<option value="auto">auto</option>
<option value="http">http</option>
<option value="https">https</option>
</select>
</div>
</div>

<div class="form-group">
<label class="group-name" for="camo_scheme_include"><?= _t('ext.imagecamo.scheme_include'); ?></label>
<div class="group-controls">
<input type="checkbox" name="camo_scheme_include" id="camo_scheme_include" value="1"
<?= FreshRSS_Context::userConf()->attributeBool('camo_scheme_include') ? 'checked' : '' ?>>
</div>
</div>

<div class="form-group form-actions">
<div class="group-controls">
<button type="submit" class="btn btn-important"><?= _t('gen.action.submit') ?></button>
<button type="reset" class="btn"><?= _t('gen.action.cancel') ?></button>
</div>
</div>
</form>
Loading