Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
72b8a07
[WIP] Make fileuploader handle creating embeddable html iframes
ItsOnlyBinary Nov 27, 2020
83ac2e4
Use shardedfilestore for image-proxy
ItsOnlyBinary Dec 8, 2020
1eb851c
Fixes and cleanup
ItsOnlyBinary Dec 10, 2020
9dded0a
Rename embed.go && fixes
ItsOnlyBinary Dec 11, 2020
69dd56e
Make fallback embed more generalised && bundle html templates in the …
ItsOnlyBinary Dec 14, 2020
42ca466
Fix template scrollbars
ItsOnlyBinary Dec 15, 2020
396d0bb
Rename template to webpreview.html
ItsOnlyBinary Dec 16, 2020
7521407
Add controls for removing deleted uploads from database
ItsOnlyBinary Dec 16, 2020
c7e8b3a
Allow templates to contain backticks
ItsOnlyBinary Dec 17, 2020
98b9056
include error message when url not supported
ItsOnlyBinary Dec 21, 2020
9bd5f38
Delete completed upload instead of move if the file already exists in…
ItsOnlyBinary Dec 18, 2020
c4b7c14
Switch around registering handlers, as tusd adds middlewares to the m…
ItsOnlyBinary Dec 22, 2020
517cd32
Fix merge issue
ItsOnlyBinary Dec 22, 2020
ed5fad9
Correctly encode url in fallback embed && better error handling
ItsOnlyBinary Jan 4, 2021
8a1aff6
Better web-preview startup, allow local oEmbed providers json
ItsOnlyBinary Jan 4, 2021
3010bad
Create webpreview plugin components
ItsOnlyBinary Jan 7, 2021
2889c4f
centre error when pinned
ItsOnlyBinary Jan 8, 2021
d9a1195
remove debug code
ItsOnlyBinary Jan 8, 2021
aa004c5
Rename PreviewProvider to UrlEmbed
ItsOnlyBinary Jan 8, 2021
869045a
Switch to using go1.16 embed feature for web-preview template
ItsOnlyBinary Feb 17, 2021
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
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ server and then offer them as file downloads with a unique URL. The option to de
after a set time period discourages users from using the server as a permanent file store.

#### Dependencies
* golang (https://golang.org/dl/ - for building as standalone)
* yarn (https://yarnpkg.com/ - for the kiwiirc plugin UI)

#### Downloading the file upload server's source code
Expand All @@ -25,6 +26,7 @@ $ go run .
```

#### Building the server for production

```console
$ go build
```
Expand Down Expand Up @@ -75,7 +77,7 @@ Add the plugin javascript file to your kiwiirc `config.json` and configure the s
If you're running the fileuploader server as a webircgateway plugin, use the webircgateway hostname, e.g.

```json
"server": "https://ws.irc.example.com/files",
"server": "https://ws.irc.example.com/files",
```

## Database configuration
Expand Down
46 changes: 45 additions & 1 deletion expirer/expirer.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,19 @@ type Expirer struct {
store *shardedfilestore.ShardedFileStore
maxAge time.Duration
identifiedMaxAge time.Duration
deletedMaxAge time.Duration
jwtSecretsByIssuer map[string]string
quitChan chan struct{} // closes when ticker has been stopped
log *zerolog.Logger
}

func New(store *shardedfilestore.ShardedFileStore, maxAge, identifiedMaxAge, checkInterval time.Duration, jwtSecretsByIssuer map[string]string, log *zerolog.Logger) *Expirer {
func New(store *shardedfilestore.ShardedFileStore, maxAge, identifiedMaxAge, deletedMaxAge, checkInterval time.Duration, jwtSecretsByIssuer map[string]string, log *zerolog.Logger) *Expirer {
expirer := &Expirer{
ticker: time.NewTicker(checkInterval),
store: store,
maxAge: maxAge,
identifiedMaxAge: identifiedMaxAge,
deletedMaxAge: deletedMaxAge,
jwtSecretsByIssuer: jwtSecretsByIssuer,
quitChan: make(chan struct{}),
log: log,
Expand Down Expand Up @@ -81,6 +83,13 @@ func (expirer *Expirer) gc(t time.Time) {
Str("id", id).
Msg("Terminated upload id")
}

err = expirer.deleteExpired()
if err != nil {
expirer.log.Error().
Err(err).
Msg("Failed to purge deleted uploads from database")
}
}

func (expirer *Expirer) getExpired() (expiredIds []string, err error) {
Expand Down Expand Up @@ -115,3 +124,38 @@ func (expirer *Expirer) getExpired() (expiredIds []string, err error) {

return
}

func (expirer *Expirer) deleteExpired() (err error) {
switch expirer.store.DBConn.DBConfig.DriverName {
case "sqlite3":
_, err = expirer.store.DBConn.DB.Exec(`
DELETE FROM uploads
WHERE
CAST(strftime('%s', 'now') AS INTEGER) -- current time
>=
created_at + (CASE WHEN jwt_account IS NULL THEN $1 ELSE $2 END) + $3 -- expiration time
AND deleted == 1
`,
expirer.maxAge.Seconds(),
expirer.identifiedMaxAge.Seconds(),
expirer.deletedMaxAge.Seconds(),
)
case "mysql":
_, err = expirer.store.DBConn.DB.Exec(`
DELETE FROM uploads
WHERE
UNIX_TIMESTAMP() -- current time
>=
created_at + (CASE WHEN jwt_account IS NULL THEN ? ELSE ? END) + ? -- expiration time
AND deleted == 1
`,
expirer.maxAge.Seconds(),
expirer.identifiedMaxAge.Seconds(),
expirer.deletedMaxAge.Seconds(),
)
default:
panic("Unhandled database driver")
}

return
}
135 changes: 135 additions & 0 deletions fallback-embed/fallback-embed-provider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package fallbackembed

import (
"encoding/json"
"errors"
"io"
"io/ioutil"
"net/http"
"net/url"
"regexp"
"strconv"
"strings"
"time"
)

// FallbackEmbed represents this package
type FallbackEmbed struct {
data *Data
httpClient *http.Client
targetKey string
providerURL string
}

// Data represents the data for FallbackEmbed providers
type Data []struct {
Name string `json:"name"`
Patterns []Regex `json:"patterns"`
}

// New returns a FallbackEmbed object
func New(providerURL, targetKey string) *FallbackEmbed {
obj := &FallbackEmbed{
httpClient: &http.Client{
Timeout: time.Second * 30,
},
providerURL: providerURL,
targetKey: targetKey,
}

return obj
}

// ParseProviders parses the raw json obtained from noembed.com
func (f *FallbackEmbed) ParseProviders(buf io.Reader) error {
data, err := ioutil.ReadAll(buf)
if err != nil {
return err
}

var providerData Data
err = json.Unmarshal(data, &providerData)
if err != nil {
return err
}

f.data = &providerData
return nil
}

// Get returns html string
func (f *FallbackEmbed) Get(wantedURL string, width int, height int) (html string, err error) {
if !f.ValidURL(wantedURL) {
return
}

// Do replacements
reqURL := strings.Replace(f.providerURL, "{url}", url.QueryEscape(wantedURL), 1)
reqURL = strings.Replace(reqURL, "{width}", strconv.Itoa(width), 1)
reqURL = strings.Replace(reqURL, "{height}", strconv.Itoa(height), 1)

var httpResp *http.Response
httpResp, err = f.httpClient.Get(reqURL)
if err != nil {
return
}
defer httpResp.Body.Close()

var body []byte
body, err = ioutil.ReadAll(httpResp.Body)
if err != nil {
return
}

// Try to parse json response
resp := make(map[string]interface{})
err = json.Unmarshal(body, &resp)
if err != nil {
return
}

// Check targetKey exists
if jsonVal, ok := resp[f.targetKey]; ok {
// Check targetVal is string
if htmlString, ok := jsonVal.(string); ok {
html = htmlString
return
}
}

// Check for error key in json response
if jsonVal, ok := resp["error"]; ok {
// Check error is string
if errorString, ok := jsonVal.(string); ok {
err = errors.New(errorString)
return
}
}

err = errors.New(`Fallback embed provider did not include a JSON property of "` + f.targetKey + `"`)
return
}

// ValidURL is used to test if a url is supported by noembed
func (f *FallbackEmbed) ValidURL(url string) bool {
for _, entry := range *f.data {
for _, pattern := range entry.Patterns {
if pattern.Regexp.MatchString(url) {
return true
}
}
}
return false
}

// Regex Unmarshaler
type Regex struct {
regexp.Regexp
}

// UnmarshalText used to unmarshal regexp's from text
func (r *Regex) UnmarshalText(text []byte) error {
reg, err := regexp.Compile(string(text))
r.Regexp = *reg
return err
}
146 changes: 146 additions & 0 deletions fileuploader-kiwiirc-plugin/src/components/WebPreview.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
<template>
<div
v-if="error"
class="kiwi-webpreview-error"
>{{ error }}</div>
<iframe
v-else
ref="previewFrame"
:sandbox="iframeSandboxOptions"
frameborder="0"
width="100%"
class="kiwi-webpreview-frame"
/>
</template>

<script>
'kiwi public';

export default {
props: ['url', 'showPin', 'iframeSandboxOptions'],
data() {
return {
error: '',
eventListener: null,
debouncedUpdateEmbed: null,
};
},
computed: {
settings() {
return this.$state.setting('fileuploader.webpreview');
},
},
watch: {
url() {
this.updateEmbed();
},
},
mounted() {
this.updateEmbed();
},
methods: {
updateEmbed() {
const iframe = this.$refs.previewFrame;
if (!iframe) {
// No iframe to work with so nothing to update
return;
}

let newUrl = this.settings.url
.replace('{url}', encodeURIComponent(this.url))
.replace('{center}', !this.showPin)
.replace('{width}', this.settings.maxWidth || 1000)
.replace('{height}', this.settings.maxHeight || 400);

// Set the iframe url
iframe.src = newUrl;

this.clearTimeout();

this.iframeTimeout = this.setTimeout(() => {
this.$emit('setHeight', 'auto');
this.$emit('setMaxHeight', '40%');
this.error = this.$t('preview_failed');
}, 2000);

// Add message event listener if it does not exist
this.maybeAddOrRemoveEventListener(true);
},
messageEventHandler(event) {
const iframe = this.$refs.previewFrame;
if (!iframe || event.source !== iframe.contentWindow) {
// The message event did not come from our iframe ignore it
return;
}

const data = event.data;
if (data.error) {
// Error message indicates the url cannot be embedded
this.error = (data.error === 'not_supported') ?
this.$t('preview_not_supported') :
data.error;

// stop hiding the media viewer to show the error
this.$emit('setHeight', 'auto');
this.$emit('setMaxHeight', '40%');
} else if (data.dimensions) {
// Dimensions message contains updated dimensions for the iframe content

const height = this.showPin ?
Math.min(data.dimensions.height, this.settings.maxHeight || 400)
: data.dimensions.height;
iframe.height = height + 'px';

// Now we have dimensions stop hiding the media viewer
// This is to stop the message list jumping when opened with invalid url
this.$emit('setHeight', 'auto');
this.$emit('setMaxHeight', '40%');
}

if (data.error || data.dimensions) {
this.clearTimeout();
}
},
maybeAddOrRemoveEventListener(add) {
if (add && !this.eventListener) {
this.eventListener = this.listen(window, 'message', this.messageEventHandler);
} else if (!add && this.eventListener) {
// Remove event listener
this.eventListener();
this.eventListener = null;
}
},
clearTimeout() {
if (!this.iframeTimeout) {
return;
}
clearTimeout(this.iframeTimeout);
this.iframeTimeout = 0;
}
},
};

</script>

<style>

.kiwi-webpreview-error {
display: inline-block;
padding: 10px 15px;
border-radius: 10px;
border: 1px solid var(--brand-midtone);
}

.kiwi-main-mediaviewer .kiwi-webpreview-error {
margin: 10px 0;
position: relative;
left: 50%;
transform: translate(-50%, 0);
}

.kiwi-main-mediaviewer .kiwi-webpreview-frame {
display: block;
max-height: initial;
}

</style>
Loading