Join Evan Johnson as he speaks with security professionals about recent security news!
All right, welcome to Hacker Time. We're live and this is the number one security show on any television, anywhere, but definitely Cloudflare TV.
Thanks for joining me.
Today we are going to finish what we started. We've got a URL shortener we've been working on and we've made a lot of progress, but the last 10% of a project is usually the last 50% of a project and it's important to finish.
So that's what we're doing today.
So where we were when we left off is we have this little server on ejcx.net and it does some URL shortening.
And let's just get that set up real quick.
Let's do an example URL shortener. It's actually, if you recall, an elongator.
It's more like a URL alias. And if we have time, we can add a feature to make it an actual shortener.
But to use it, you send a POST request to ejcx .net with your URL and a JSON.
And you get back a shortened URL here.
And when you go to this one, this should forward me to my personal website.
And it does. So it's working pretty basic. But the last thing we need is an actual UI, somewhere you can go and put your URL in a little text box and get back a response.
We're going to cut corners, we're going to ship this thing. And it's going to be gross, but it's going to work and it's going to work well.
So first things first, we started kind of making the UI.
If you recall, we didn't get the content type right here.
And this form, this whole idea of the form won't work since we're submitting over ejcx in a JSON with JSON.
So instead, I think what we want to do is first make this actual render as HTML.
And then we want to create a little form just like this.
So let's get started. The first thing we need to do is a header to make sure that this is a response with the content type HTML, text HTML.
So I believe we have a header somewhere, an example of us using a header.
Let's pull up the docs. Poplar workers, headers, content type. Someone will have this.
Here we go. So in the response, we have to set the content type. Here we go.
So just a little object with headers, content type, text, text HTML. So that's an easy one.
Let's get that working right now. So let's go here. We're at the end of our thing here.
And let's just all inline this. Doesn't need to be pretty. Content type, text, HTML.
We don't actually need this comma. And it should be HTML now.
So that's easy. Let's give it a go. Let's go to my Cloudflare TV directory.
And now this should render.
We should see just a little input field. Yep. There we go.
All right. We got this working. Next up, I want to make this pretty. So I have a thing with websites.
I really like the simple look of my own website. And it looks good because, one, there's not a lot of content.
You can make not much content look good.
But it also looks good because I really just like the CSS I included in basically all my websites.
So I am going to just copy this over, copy my own CSS over and include it here.
We're going to turn off auto indent when we paste.
And it should look better.
We should immediately notice a difference when we publish this. Here we go.
Did it work? Okay.
The style is there. Did I do it wrong? I think I did it wrong. Yeah. There's a CSS parsing issue.
I have inside my CSS a doc type HTML. This makes no sense.
Let me just fix that. I guess technically it's a semantic error.
And I think it worked. I think it's looking better.
If we zoom out, you'll notice the margins are moved in a little bit. The page width is a lot thinner.
And when you don't have much content, it tends to look good.
I'm not a designer. And if anybody from our design team is watching, feel free to give me some pointers.
But this is about all I've learned about design. Step one, don't have much content.
Step two, move the margins in and pick a good font. So, okay.
So, we've got, oh, boy.
So, we've got nice style.
Now we need the form. We need the actual URL, the place where we're putting in our URL.
And since we don't have jQuery or anything, and I don't really want to include jQuery, that is a valid strategy.
So, let's just get started and we'll pick whichever we'll run into a problem before we make a decision.
So, what do I need to do?
I used to do it as part of my full-time job. And I really just haven't done it enough.
So, it's not really something that is fresh in my mind.
Okay. This looks good. Handle submit. It prevents the default. That looks right.
It looks like it's going to submit it as a form the whole post data string that I'm sure you've seen before.
This looks a little better.
Let's try this. Okay.
I think if we just copy and paste this. Copy pasta. And we'll throw it in a script tag.
Just the most minimal amount. Okay.
So, we need to get rid of this plus sign. That's not actually a thing. And okay.
So, what we're doing here, let's walk through it. Let's walk through the code we just copied and pasted.
So, we're finding the form. And we're adding an event listener to the form with this function handle submit.
Okay. So, this when we click the submit button, when we click this thing, it's going to run this code.
And first thing it's going to do is it's going to prevent the form submission from doing its normal thing, which is submitting the form.
It's going to say don't do that.
We'll take care of it. Second, it's going to take the form data, put it in an object, and then print it out.
So, this won't send it to the server. But I think it's a good step for us to test.
See if it's doing what we want. Let's try it.
Let's see what we get. I don't think it's work exactly, but I think it'll be close.
Let's give it a go. Okay. ASDF. And it prints value URL ASDF. Okay. This is pretty much what we want.
I don't think we want these curly braces around value. I think we just want plain value.
And it'll be exactly what we want. Let's try it.
That looks right to me. Okay. We have a blob with URL and a URL. And so, I think all we want to do is submit that up to the server and then render it.
Okay. So, in order to do that, we can use is fetch a thing in browsers?
It is. Okay. So, we can use fetch.
So, it's new and it's slick. I really like it. It works really well.
So, it's not that new.
Okay. This should just print out. We want to do HTTPS EJCX.NET.
This will submit a GET request. So, we need to submit a POST request with our value here.
This might be a little hard to see. Let me enlarge just a bit.
Just a lot. This might be a little better.
Especially since it's all red. Okay. So, this will submit a GET request, parse the JSON, and then console log hit.
But it's not going to be a JSON response.
Because this is a GET request, it'll be HTML. So, we need to make this a POST, and then we'll get back JSON.
So, how do we do that? URL and then method POST.
We want some of this junk. Here we go. Okay. So, Fetch has two arguments.
There are some differences. Like you're not going to have all of the browser APIs that exist in Cloudflare Workers.
But a lot of stuff like Fetch is identical.
So, or pretty much identical. So, we want to do method POST.
So, this is running on within Cloudflare.
So, this should actually work. I think this will just console log our shortened URL.
So, let's give this a go. That was a little easier than I thought.
And then we have to make our decision about how do we render the URL.
So, let's type in HTTPS, pjj.io.
We got a 400.
Let's check what happened, though. Bad request.
POST request. With our URL properly there as we expect. Let me make this enlarged.
I don't care about the timing.
Can't really get rid of it. It was a 400.
And invalid request. Send JSON containing URL. So, something is not quite right.
I'm going to use this handy copy as curl feature.
And actually look at the request that we just submitted.
So, this is a direct copy of what was submitted. And it looks pretty good to me.
So, you'll see data binary here is EJJ is what I'd expect. It's URL with my valid URL here.
That assumes it's a POST request. Since there's no other HTTP verb there.
Goes to ejcx.net.
And we hit this error message. Invalid request. Send JSON containing URL.
And in our code, that's actually an error message we have in our code.
It doesn't have URL capital.
I wonder if this is a case sensitivity issue. Let's try it. Oh, I'm really bad at these.
So, this is a hack.
This is just a straight up hack. So, if we're right, if this is a case sensitivity issue, what's going to happen is where it's B.capital URL, we're checking to see if the URL was submitted.
We're going to also say we're only going to error out if we can't find a capital and we can't find a lowercase URL.
And then here, when we're storing the URL that was submitted to us, we are going to check to see if capital URL exists.
If it does, we have a little ternary if statement here.
If it does, we're going to use a capital URL. Otherwise, we'll use the lowercase URL since we can assume that one of them is set.
Okay. So, it's a hack. This ain't pretty.
Look away if you're squeamish because this is not the best code that anyone's ever written.
But I believe it should work. We don't have to reload the page or anything.
We can just run it again and it worked. Okay. We got back a shortened URL and it printed out shortened URL.
Look at that. Okay. The last thing we have to do is we have to render the shortened URL on the screen.
I think I'm going to make the executive decision here to render it in like a text field or something.
Perhaps a paragraph tag. I think that would work well. So, we've got 11 minutes.
We're actually great on time here. So, we can figure this out. We can hack this together in 11 minutes.
What we need to do is when we receive this response, instead of printing it out, we need to put it in a paragraph tag.
So, first thing, we need a paragraph tag. I think it's paragraph. Is that what P stands for in HTML?
That would make sense to me. So, we're going to give it an ID called shortened URL.
I think that's it.
I think that's all we have to do. And then when we parse this data, we can...
What is the syntax here for a function, inline function in a promise? Will this work?
I really have no idea what I'm doing here.
I think this is wrong.
It's pretty close.
Pretty close. I think I got to get rid of... this.
Maybe just this will work. Sorry, moving things around, folks.
Reset the page.
This one might work. Aha!
Okay. So, this works. And we now have a function. We can write multiple lines of code in.
So, we can do document .getElementById and shortened URL. Let ID...
let a short URL. And then we can do s.url. I believe we can use the inner text function.
s.url equals data.shortenedURL. And this will work with no error checking or anything.
This is just... it works. This is the happy path that we're working through.
And I think it's going to work.
I think it's going to work on the very first try here.
And that would be slick. I'll give it one more refresh. Since I said it was going to work on the first try, got to make sure it does.
No, inner text is not a real thing.
I can. InnerHTML, if for... so, there's some security content to show.
It's really important to recognize that innerHTML is sometimes a risky function to use.
You have to understand what you're rendering in that.
In this case, it would be safe to use, so I'm going to use it. But if you're...
if you use innerHTML and have the user-supplied data that you're putting into innerHTML, it leads to cross-site scripting quite easily.
I believe that's what happens with dangerously set innerHTML.
And React has this function called dangerously set innerHTML.
And under the hood, it's just using regular old innerHTML.
But they put this dangerously set in front of the... in front of the function call to make sure that people who use it are aware that it's dangerous.
In this case, I know what I'm doing. This is a toy example. And if the server responded with something malicious, could lead...
or any user-supplied data, like a username or something, it would...
it would be dangerous. But I don't think it is.
innerHTML is not a real function. Okay. So let's dev in the browser here.
document.getElementById .serl. Let's see what we... what code we need to have.
Oh. Just... that didn't work. Oh.
Duh. I think this is a... no. ShortenedURL. Spelled correctly. I spelled it incorrectly here.
ShortenedURL. serl .innerText. Okay.
That should work. innerText should work. So why wouldn't it? Let's do innerText here.
And then... Let's just try this one more time.
And then try to debug. We can use the browser debugger in a moment.
That'll be... that'll be nice. innerText is not a function.
Are you sure? Okay. I set a debug breakpoint. Let's see what happens.
serl is set. shortenedURL is our string.
It's just has... having a problem with innerText. I -N-N-E-R-T-E-X-T.
But when we did it up here... Are we calling it like a function? Yes.
We're calling it like a function instead of setting it like an attribute. I'm sure all you at home were waiting for me to figure it out.
Good one. That was a fun one.
Okay. Let's unset this breakpoint. And now, end-to-end, this thing should be working right on schedule.
There we go.
Nice. And then the very last thing I'll do is give this a little bit of pizazz.
We will add a nice, nice kind of pastel green, maybe. I like that.
Okay. Background-color, and then border-radius.
Everything looks better with a five-point border-radius.
And I think that should be the finished product here.
All right. So, we are going to elongate this URL.
Let us do that.
https.Cloudflare.com. Oh, the border-color didn't work. For the background-color...
Oh, I have this dot there. Why did I put that there? All right.
One more try. Okay.
There we go. Resubmitted our worker, and this one is going to work. URL is Cloudflare.com, and we have this awesome, awesome green shortened URL, and it just works.
End to end. Voila. We have built a real product. We shipped it in a total of an hour and a half by Googling, by cutting and pasting, by copy-pasting other people's code, by hacking around in the browser in a really gross way.
We got this thing working, and it works well.
Could add some more error checking, but overall, this works really well.
I'm going to submit this to GitHub and open-source it and make sure you all can see it, but I really appreciate you joining me for this episode.
We'll have to start a new project next week, and thanks for watching Hacker Time.
I'll see you then. Adios.