Content-type: text/html Downes.ca ~ Stephen's Web ~ Remote Comments

Stephen Downes

Knowledge, Learning, Community

Half an Hour, Jan 30, 2021

I've had an idea in the back of my mind for a long time now, and as I get closer to a real-world gRSShopper other people can actually use I began to ask around is anyone had seen an implementation. In a word, the response was "no". So I decided to create an implementation of it to see whether people can show me why it's such a bad idea. Or - maybe - take it as something useful.

Here's the background:

I want people to comment on my web posts. But I don't want to host those comments. That's a common sentiment, and has led to the creation of things like Disqus and Hypothes.is and the like. But these are all centralized third-party services that exist to monetize our comments to each other and do things like insert tracking cookies onto website. After all, they have to pay for those centralized servers somehow! No thanks.

Now there is a decentralized approach to commenting called WebMention. It's a W3C specification and pretty simple to implement. If you write a post, you can put a 'webmention end point' in your headers. It looks like this:

view raw gistfile1.txt hosted with ❤ by GitHub

If I want to comment on your post, I go back to my own blog and write a blog post. Then I send a request to your webmention end point specifying a source - ie., my comment - and a target - ie., your post. My request might look like this:

GET http://www.mooc.ca/cgi-bin/api.cgi?source=downes.ca/post/1234&target=mooc.ca/post/4556

What do I do when I receive this WebMention request? Whatever I want. I could put a link to your comment under my post. I could check and make sure the comment comes from a friend of mine, or a subscriber, or whatever, and then post the link. I could fetch the entire comment and post it as a comment under my post. Or I could just ignore it. It's totally up to me.

But all that's a lot of work. I have something much more elegant in mind. Here's the idea:

I have a webpost and I want people to comment on my web post. So I give them a comment form. It looks just like every comment form you've ever used. You can type your comment into the form and click 'Submit'.

There's just one difference. I don't want your comment to be posted on my web server. I want it to be posted on your web server. It's just like the WebMention I described above, except you're creating your comment in a form I've provided right on my page, instead of forcing you to go back to your website to write your comment.

That's what I asked about.

 

You can read the thread yourself, but the gist was that it would be pretty difficult and/or clumsy and/or insecure. But I know something like this can be done, because there are so many other ways we do it for other things.

 

Anyhow, that's what I've implemented.

Here's how it works:

I put a comment form on my blog post, just as I described above. It might look like this:

 

You can view the actual working comment form on my website here: https://mooc.ca/rmail.html

In order to comment, you need to enter two things: first, the comment itself, in the big comment field (I could probably have made that smaller, but I like long comments; none of this microcontent stuff for me).

And second, you need to enter the URL for comment submission on your own website. This is where your comment will actually be posted. This needs to be the exact URL (you can't redirect it with a .htaccess, so I learned) and it needs to be able to accept a POST request.

(Back in the day I created something called mIDm which did something very similar, but instead of forcing people to type their URL every time I put it in the web browser headers. It was a brilliant solution to a vexing problem, but OpenID came out as a concept five days later, and then sites like Google and Facebook and Twitter took over decentralized logins and essentially killed OpenID for widespread use, so... anyhow, you have to type it into the form. At least for now.)

What this form does is take the URL you've typed into the form and makes it the 'action' part of the form. In other words, instead of submitting this form to my website, it's going to submit the form to the URL you just typed in (I found out how to do this here; it uses jQuery but obviously I could also write it in plain Javascript ).

Now you might be thinking, "Wait a minute!!! Isn't this really insecure?"

Well, there's nothing in this HTML form that anybody anywhere couldn't create. So if this is insecure, my website was already insecure. Because anybody could have been creating forms like this (but actually disguising them or hiding them in something else) all along.

I have forms on my website, as do many other people. I protect my website by requiring that the user be logged in to my website before making a comment. I have exactly one user account on my website: me. (I could have more if I wanted, but I'm keeping this really simple for now). To log in, I have to enter a userid and password. What's nice is that I can stay logged in as long as I want (or as long as the website allows me).

So here's the URL I would put into the form if I were making the comment:

http://www.downes.ca/cgi-bin/admin.cgi

This is the link to my own gRSShopper system running on my website. That's what I used to implement this system. I know, it's old-style CGI, but but it could be implemented however you want. The details aren't that important, except for the security thing.

If I put this URL into a form and submit a comment, the website will handle my request because I'm already logged in. But if you put this URL into the form and try to submit a comment, it will reject you, because you're not logged in (at least: it should reject you, otherwise I have a serious problem). If you want to post your own comment, you will need your own version of remote commenting.

(And I have you covered here - if you go to my repository on GitHub you'll find instructions for creating your own container running gRSShopper, and them you'll be able to put your own URL into the form - something like

https://env-5384513.ca.reclaim.cloud/cgi-bin/api.cgi

, say - and successfully enter the comment. Then the post will display on your web page, on the website you are hosting in your container. If you have any difficulties making that work, send me an email, though it should mostly be working now).

I also do a bunch of other stuff to stay secure. For example, gRSShopper will only accept an action of 'rcomment' from a remote site; try any other action and you'll get an error. It limits the input to just the content in the form. It also sanitizes the content coming in. So even if a website somehow tricks me into clicking on such a form (which it has always been able to do, remember) I have protected myself against such spoofing. Is it enough? It's hard to say - but I'm not risking anything more than my own website; it's not like it's a large centralized website with thousands of userid codes and passwords.

That's the gist of it. Here are some more details:

The form actually provides a number of hidden fields. Here's what it the code looks like:

Remote Comment Example

Blog Post

Here is the text of my blog post. Don't you juyst want to comment?

Comment:

(URL for content submission on my own website)
(Auto-populated with the title of the post I'm commenting on)
Hidden fields: 'author' and 'feed', auto-populated with the information about the post I'm cpommenting on (optional, but better)

view raw gistfile1.txt hosted with ❤ by GitHub

 

You can see the WebMention URL in the head, an the Javascript that takes the URL you submit and converts it into the form action. There's also a hidden field called 'action' which has the value 'rcomment'. For gRSShopper, this is mandatory. In my world, I also provide some information about the post I'm commenting on: the author of the post, the post title, and the feed or website that hosts the post. These are optional, but I like to be able to add these to my own comment. There's also the textarea for the post description (which could be a wysiwyg text editor, if you wanted to be fancy). 

This post is submitted to admin.cgi. You can view the full source of admin.cgi on GitHub, but let me highlight what's happening.

 

# ------- Remote Comment ----------------------------------------------------
 
sub rcomment {
 
&record_sanitize_input($vars);
while (my ($vkey,$vval) = each %$vars) {
$vars->{$vkey} =~ s/\0/,/g; # Replace 'multi' delimiter with comma
}
 
die "Not allowwed to comment" unless (&is_allowed("create",$table));
# Do some stuff
my $refer = $ENV{HTTP_REFERER};
my $table="post";
my $post = {
post_type => 'link',
post_link => $refer,
post_description => $vars->{description},
post_title => $vars->{title},
post_author => $vars->{author},
post_feed => $vars->{feed},
post_id => "new",
post_pub_date => &cal_date(time),
post_crdate => time,
post_creator => $Person->{person_id},
};
 
# Uniqueness Constraints
my $l;
if (($l = &db_locate($dbh,"post",{post_link => $post->{post_link}})) ||
($l = &db_locate($dbh,"post",{post_title => $post->{post_title}})) ) {
print "Content-type: text/html\n\n";
my $url = $Site->{st_url} . "post/" . $l;
printf(qq|

Duplicate Entry: Post %s

|,$url,$l);
exit;
}
 
# Clean up
post->{post_description} =~ s/href=('|'|')(.*?)"/href="$2"/ig; #'
 
# Submit and print record
my $id_number = &db_insert($dbh,$query,$table,$post) || die "Couldn't inset post";
&rcomment_keylist_update($id_number,"author",$post->{post_author}) || die "Couldn't associate author";
&rcomment_keylist_update($id_number,"feed",$post->{post_feed}) || die "Couldn't associate author";
print_record($dbh,$query,"post",$id_number,"html",$Site->{context});
 
# Send WebMention
 
my $content = get($post->{post_link});
die "Source page is unreachable" unless ($content);
my $endpoint = &find_webmention_endpoint($content);
if ($endpoint) {
&send_webmention($endpoint,$post->{post_link},$Site->{st_url}."post/".$id_number);
}
 
print "Content-type: text/html\n";
print "Location: ".$refer."#$id_number!\n\n";
exit;
 
}

view raw gistfile1.txt hosted with ❤ by GitHub

This is the script in admin.cgi that handles 'rcomment' requests. Yes, it's written in Perl, and so may appear archaic to most people, but the same functionality could be implemented in any language you want. Here's what it does:

Sanitize Input

It sends all of the input (inside the variable $vars) to an input sanitization function designed to prevent things like SQL injection, secret programs, embedded scripts, and other sorts of nastiness. It will also prevent the comment from being created unless the user (ie., me) is allowed to comment (I can turn that off any time in gRSShopper's permissions screen).

Create the Post Object

I create my comment as a 'post' object on my website. I get the URL of the original blog post from the HTTP referrer value (yes, the referrer can be spoofed, but I don't think there's any way to leverage that) and define that as $post->{post_link}. The rest of the 'post' object is then created (as $post).

I don't like to have more than one post for each external resource I comment on, so I enforce a uniqueness constraint (for both the URL and the title). Obviously you might not want to have this for yourself.

Submit and Print Record

First of all, I store the post object in the database (my function, &db_insert(), used Perl's DBI and does more error checking and SQL injection preventing). I then create separate records for each of the author and the feed (there can be more than one, separated by a semi-colon) and associate them in the graph with my comment. I then print the record onto my website.

Here's what my comment looks like:

 

The title comes from the original title of the post I'm commenting on, though I can change that. The text 'This is my great comment' is the actual text I typed into the comment field. The author and feed ('Stephen Downes' and 'mooc.ca' respectively) were found to already exist on my system, so this new comment was associated with these existing objects. Printing the post produced the header and the footer. And if you look at the source of the post, you'll see:

view raw gistfile1.txt hosted with ❤ by GitHub

 

which means, yes, you could comment on my post the same way (if I added an rcomment form, which I'll do once I think the system all works; WebMention works now and would leave a link to your comment at the bottom of my post).

Send WebMention

After the post is printed, my website sends the WebMention back to the original website host. It's a simple GET request executed by the script. 

Return to the original post

This is the piece de resistance. Everything described above was done silently by my website. It's done, and there were no errors, so it sends you right back to the blog post you commented upon to continue your day. You can tell you were successful because it's added a #postid to the end of the URL.

What will you experience when you return? It depends on the website. The comment will probably have disappeared from the form. Maybe the link to your comment will have appeared on the wwebsite - it depends on how fast it processes the WebMention, and whether it posts them at all. 

Usually, what the remote website will do, if it supports WebMention, is it will automatically check your comment page. It's looking for some hidden fields on the page telling who authored the comment, when it was posted, even a small image icon, etc. These will appear alongside the link to your comment. 

But the main thing is, you've entered your comment, it exists on your site, and you haven't had to do anything more complicated than to submit that URL into the form.

---

So that's it. Remote Comments. I'd love to hear your thoughts. You'll have to drop them into the form here and wait for them to be moderated, though. Blogger doesn't support rcomments.

Yet.

 

Update, February 1, 2021

Some experimentation has convinced me of the wisdom of adding a confirm comment page into the process, just so the commenter is aware of what is being commented. This is similar to the 'confirm tweet' page posted from services that offer to add a tweet to twitter for you.

 

While I'm writing, I also should note that on my website I enabled Cross-Origin Resource Sharing (CORS) while setting up my API services, and this may be playing a role here in making Remote Comments possible. This is what I did on Apache. Here's some more information.

 

 

Mentions

- Disqus – The #1 way to build your audience, Feb 01, 2021
, - Home : Hypothesis, Jan 07, 2022
, - Webmention, Jun 13, 2022
, - , Feb 01, 2021
, - WebMention Header · GitHub, Feb 01, 2021
, - GitHub: Where the world builds software · GitHub, Feb 01, 2021
, - Downes 🍁: "Is there a way for me to provide a comment form o…" - Mastodon, Feb 01, 2021
, - , Feb 01, 2021
, - , Feb 01, 2021
, - Remote Comment Example, Feb 01, 2021
, - Stephen's Web ~ by Stephen Downes ~ mIDm, Feb 01, 2021
, - OpenID - Wikipedia, Feb 01, 2021
, - Jquery / Javascript change form action based on input field - Stack Overflow, Feb 01, 2021
, - GitHub - Downes/gRSShopper: gRSShopper is a tool that aggregates, organizes and distributes resources to support online learning, Feb 01, 2021
, - Remote Comment Example, Feb 01, 2021
, - Remote Comment Form · GitHub, Feb 01, 2021
, - gRSShopper/admin.cgi at master · Downes/gRSShopper · GitHub, Feb 01, 2021
, - , Feb 01, 2021
, - Remote Comment Server Side · GitHub, Feb 01, 2021
, - How to spoof http referer - Stack Overflow, Feb 01, 2021
, - Stephen's Web ~ The Title of the post again ~ Stephen Downes, Feb 01, 2021
, - , Feb 01, 2021
, - , Feb 01, 2021
, - gist:e26a580b4dbdacb2eaa45459638f03ae · GitHub, Feb 01, 2021
, - , Feb 01, 2021
, - , Feb 01, 2021
, - , Feb 01, 2021
, - Remote Comments, Feb 01, 2021



Stephen Downes Stephen Downes, Casselman, Canada
stephen@downes.ca

Copyright 2024
Last Updated: Dec 23, 2024 02:52 a.m.

Canadian Flag Creative Commons License.

Force:yes