Dyphal is a web photo album designed to allow photographers to show off their work without needing to turn to commercial providers that inject advertising and may do ethically dubious things with the photos and information that they host.
An album is displayed as a list of photos; each photo can be opened to show a larger view with metadata. The photo’s size scales to best fit the browser window. An album can be navigated by mouse, touch, or keyboard.
An album can be hosted on any web document server; no server-side scripts or databases are required. All necessary files are generated from original photographs and their metadata up front and stored on the server. The album and its metadata files are designed to require minimal storage space and minimal network traffic.
There is a demo album at https://ciphertext.info/software/dyphal/#/demo.
Dyphal was designed to satisfy the following goals:
Dyphal albums are created using DyphalGenerator:
If you generated the album “Vacation” in a directory served as http://example.com/photos and installed the web template to the same directory, you can view the album at http://example.com/photos/#/Vacation.
To edit an album in DyphalGenerator, open the “.dyphal” file for the album.
DyphalGenerator does not currently have a metadata editor. Instead, it pulls metadata fields from embedded photo tags. The following tags are recognized as “properties” (using exiftool’s naming scheme):
Only three fields are recognized as “captions”: Description, Location, and Date. They are synthesized from various tags that are commonly used to store such information (again, using exiftool’s naming scheme):
Recent versions of gThumb use these fields for metadata; there are probably other photo managers that do as well. Older versions of gThumb stored metadata only in external XML files; the tool “gthumb-comment-update” can be used to import metadata from the XML files into embedded tags.
Nothing special is required to serve Dyphal albums, just a basic web document server such as Apache or lighttpd. For improved security, server operators are encouraged to serve Dyphal albums over HTTPS with the following headers:
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self'; connect-src 'self';
X-Frame-Options: DENY
Strict-Transport-Security: max-age=31536000
Since Dyphal uses asynchronous queries, it won’t work from file:// URIs.
The “.dyphal” files created by DyphalGeneraor should not be served with the web page. These files are intended to allow DyphalGenerator to re-open and edit albums.
An album is represented by a single web page, index.html. JavaScript running within that page determines what to display based on hash parameters. The first parameter, which must always be present, identifies the album. This value is the name of the JSON file that describes the album with the “.json” suffix omitted. If your directory structure looks like this:
album.css
common.css
dyphal.js
index.html
photo.css
vacation.json
photos/photo1.jpeg
metadata/photo1.jpeg.json
...
then the path to load the “vacation” album is
/#/vacation
This will load album view, which shows the title and description of the album as a whole alongside thumbnails for every photo in the album. Clicking on any one of the thumbnails will open photo view for that photo, containing a larger view of the selected photo and its metadata, along with controls to move to the previous or next photos in the album, or to return to album view. The photo will re-scale to the largest size that fits in your browser window without hiding the header, footer, or metadata or going beyond its physical dimensions. If the photo is not shown at its full size, clicking on it will open overlay view to show the photo at the largest size that can fit in the window, potentially hiding the header, footer, and metadata. There is also a help view that can be opened from either album or photo view using the question mark icon.
When loaded in a window smaller than 750 CSS-pixels wide or tall, the album will use compact layout that omits the metadata and footer from photo view in order to show the photo at the largest size possible. In compact layout, overlay view shows the metadata and footer rather than another view of the photo. The album is also displayed full-screen in browsers that support this feature if the display is less than 750 CSS-pixels wide or tall (though, for technical reasons, the user needs to interact with the page before it can enter full-screen mode).
Each photo view has its own URI, consisting of the URI to the album followed by the index number of the photo (indices start from 1). For example, the path for the third photo in the “Vacation” album might be
/#/vacation/3
The photo and help overlays do not have independent URIs.
Since only one HTML document is used for all views, its differences in appearance are implemented by switching styles and, to a lesser extent, by adding and removing document elements. Photos, thumbnails, and photo metadata are not stored in the HTML; they are loaded from JSON files and their contents are inserted or removed from the document when appropriate. The album JSON file (vacation.json
in the above example) contains a title, footer, and description of the album, plus a list of the photos in the album. Each photo has its own JSON file (the photo’s name from the album JSON with “.json
” appended), containing the name of the photo file, its dimensions, and its metadata. JSON files, thumbnails, and photos are loaded on demand; the JSON and photo for the next photo in the album are pre-cached to reduce load time.
Appending “/debug
” to any album or photo URI will load it in debug mode, which suppresses some of the styling, outlines various document elements in various colours, and logs some information to the browser console.
Everything is implemented in as standards-conformant a manner as possible.
Browser-specific logic is implemented using feature detection rather than browser detection where possible; browser detection is only used to work around the buggy touch tracking in Android browsers and the broken scrolling in full-screen Internet Explorer because no ways to fix these issues using feature detection could be found.
If my JavaScript has idiosyncrasies, it’s probably because I’m a C++ programmer who learned JavaScript by trial and error.
The original version of Dyphal was the BestFit template for gThumb, which I wrote in 2005 and became part of gThumb starting with version 2.9.1 in 2006. That template was designed to take scale the photo to fit in the browser window and show metadata around its edges; superficially, it looked a lot like this album. However, it was limited by the hacks required to support Internet Explorer 6 and albums built from that template consisted of a large number of repetitive generated HTML files.
At some point after 2008, gThumb removed the BestFit template without telling its author (i.e., me) why. Since working with the gThumb maintainer in the past had been a pain, I opted to not try to get it reinstated. Instead, I dropped support for IE6 (simplifying the code considerably) and started work on a version where photos and metadata would be inserted into a single page at run-time using JavaScript. I eventually merged the album and photo pages, accidentally re-invented JSON before learning how to use JSON properly, and ended up with this.
The main disadvantage of the new album over the old gThumb-generated one is that I can no longer rely on gThumb to select the photos to include, create down-sized versions of them, and extract their metadata. Instead, I wrote my own tool, DyphalGenerator. gThumb, despite a couple annoying mis-features, still has the best support for photo metadata of any Linux photo manager that I could find, so DyphalGenerator pulls metadata from the places where gThumb stores it and pulls lists of photos from gThumb collections.
DyphalGenerator, which generates the server-side data, fills the role of a server component. It is trusted, as are the shell commands that it invokes. The image files loaded by DyphalGenerator are not trusted and are assumed to contain arbitrary malicious content. The JSON files emitted by DyphalGenerator are partially trusted. The following fields are safe to insert into URIs with no additional encoding:
metadataDir
thumbnail
path
name
photo
The following fields are restricted to hard-coded values:
orientation
albumVersion
captionFields
propertyFields
photoResolution
properties
tupleAll other values should be assumed to be attacker-controlled.
To be secure, DyphalGenerator must never emit photo URIs outside of the album directory and albums must never execute any scripts or interpret any markup found in properties, captions, or other fields. Additionally, albums must never load JSON files or images from outside of the album directory.
See javascript.html for information about Dyphal’s use of JavaScript.
The “.dyphal” files created by DyphalGenerator contain local paths and should not be served with the web page.
Why am I so concerned about TOCTOU in DyphalGenerator when there’s no privilege separation to be attacked? Practice.
Dyphal is copyright (c) Rennie deGraaf, 2005-2023. It is distributed under the terms of the GNU General Public Licence, either version 2 or (at your option) version 3. See LICENCE or http://www.gnu.org/licenses/ for details.
Rennie deGraaf (rennie-dot-degraaf-at-gmail-dot-com)
The latest version of Dyphal, including full source code, can be obtained at https://ciphertext.info/software/dyphal/.
Version 3.0-beta2-22-g3c52816-dirty, 2023-04-16