Hello, dear friend, you can consult us at any time if you have any questions, add WeChat: daixieit

Homework 3: Image Uploads and Web Sockets

At this point, you have built a dynamic web application where users can login and chat with each other using only a TCP socket, your understanding of web protocols, and your programming skills. This is a great accomplishment, but let's take this further and allow users to upload images and interact in real time.

Several changes have been made to the front end in the starter code repository. You should pull these changes, or manually integrate them into your front end if you've customized your app. These changes will be referenced throughout this assignment.

Repo: https://github.com/jessehartloff/WebAppProject

Learning Objective 1: Image Uploads

Allow authenticated users to upload an image as their profile picture. The uploaded image for a user will replace the eagle image on the page.

A third HTML form has been added to the front end below the registration and login forms. This form is designed to send an image to the path "/profile-pic" using the multipart/form-data encoding. Your task is:

1.  Add an endpoint to your server that listens for requests at the path "/profile-pic" 2.   Process the request based on whether or not the user is authenticated

a.   If the request is from an authenticated user (based on a valid auth token):

i.      Parse the body of the request and extract the bytes of the image

ii.      Save this image as a file on your server

iii.      Store the filename of this image in your database as part of this user's profile

b.   If the request is from an unauthenticated user, do not process the request and move on to the next step

3.   Respond with a 302 redirect to your home page "/"

a.   If the user was authenticated, their profile picture should now be displayed on the home page

When a user makes a request for your homepage, use HTML templating to send a page containing their profile image where the eagle originally appeared. If the user is not logged in, or if they have not yet uploaded a profile picture, you should display a default image (or no image) in this place on the page.

When an image is uploaded, your server will save the image as a file. It is recommended that you devise a naming convention for the image files instead of using the names submitted by your users. Naming the images "image1.jpg", "image2.jpg", "image3.jpg", etc is fine. Alternatively, since there is at most 1 image per user, you can name them using the username for that image.

It is ok if your site only handles .jpg images and assumes that every file upload is a .jpg file.


Your uploads must persist through a server restart. You should store your images in files (It's generally bad practice to store large files in a database), and store the filenames in your database. Since your images are stored in files, they will already persist through a restart.

Note:  You   may   need  to  set   up  your  buffer  to  complete  this  objective  depending  on  which browser/version  used  during  testing.  Some  browsers  (Chrome)  will  send  the  headers  of  an  HTTP request before sending the body so you will only read the headers the first time you read from the TCP socket. You need to read again to receive the bytes of the image. You can start by testing with very small images to limit the amount of buffering to at most 2 reads from the socket in this objective. Later in this assignment, you will expand this to arbitrarily large files. A very compressed image "elephant-small.jpg" was added to the repo and can be used for testing.

Security:  Don't  allow  the  user  to  request  arbitrary  files  on  your  server  machine.  Starting  with this objective, you will  be  hosting content at paths that cannot be hardcoded since you don’t know what images  will  be  uploaded  to  your  site.  Even  if  you  replace  the  file  names with  your  own  naming convention (eg. "image1.jpg" "image2.jpg") you still don't know how many images will be uploaded. This means that you must accept some variable from the user that you will use to read, and send, a file from your server. You must ensure that an attacker cannot use this variable to access files that you don’t want them to access. (In this course, it is sufficient to not allow any '/' characters in the file path. Eg. remove any "/" characters from the requested filename after extracting it from the path)

[Optional] Add the profile picture of the user who posted the message next to each message in the chat.

Testing Procedure

1.   Start your server using docker compose up

2.   Open a browser (Only use Firefox) and navigate to http://localhost:8080/

3.   Use the image upload form to upload an image that is smaller than 1kb (Without logging in)

a.  Verify that you are taken to the homepage through a redirect and that the default image is still displayed

4.   Register an account and login

5.   Use the image upload form to upload an image that is smaller than 1kb

a.  Verify that you are taken to the homepage through a redirect and that the uploaded image is displayed

6.   Open a second Firefox browser in incognito mode

7.   Navigate to http://localhost:8080/ in the second browser

a.  Verify that the default image appears

8.   Register a second account and login

a.  Verify that the default image still appears

9.   Upload a second image that is smaller than 1kb

a.  Verify that you are taken to the homepage through a redirect and that the uploaded image is displayed

10. Go back to the first browser and refresh the page

a.  Verify that first uploaded image still appears

11. Restart the server using docker compose restart

12. Refresh both browsers and verify that both images appear as expected for each user

13. Security: Verify that '/' characters are not allowed in the requested filename (Using Postman or curl), or look through the code to ensure that this attack is addressed

14. Security:  Look through the code to verify that prepared statements are being used to protect against SQL injection attacks [If SQL is being used]

Learning Objective 2: Live Chat With WebSockets

You can make the following simplifying assumptions when working with WebSockets in this assignment:

   The 3 reserved bits will always be 0

   You can ignore any frames with an opcode that is not bx0001, or bx1000, or bx0000

●   Additional WebSocket  headers are compatible with what we discussed in class (ie. You don’t have to check the Sec-WebSocket-Version header)

In this objective, you will modify the chat feature to use WebSockets instead of HTTP, AJAX, and polling. This will make the chat "live" in that each user will receive new messages immediately [minus network delays] instead of waiting for the next poll request.

You should use the updated front end for this objective and change the "ws" variable in functions.js to true. This will change the frontend to use WebSockets when sending and receiving chat messages. It will still use your "/chat-history" path to retrieve old messages when the page loads, but will receive new messages over a WebSocket connection.

WebSocket Handshake

Implement the handshake of the WebSocket protocol at the path "/websocket".

const socket = new WebSocket('ws://' + window.location.host + '/websocket');

This line, which is in the provided JavaScript, will make a GET request to the path "/websocket" and attempt to upgrade the TCP socket to a WebSocket connection.

During this connection process, you must authenticate the user based on their authentication token in their cookies. This is your only chance to authenticate the WebSocket connection so it must be done during this handshake. For the duration of the connection, you can assume any WS frame sent over the connection is authenticated as this user. If they cannot be authenticated, you should still proceed with the connection as a guest user.

You may use libraries to compute the SHA1 hash and Base64 encoding.

WebSocket Frames

The provided JavaScript and HTML will send WebSocket frames containing chat messages when a user submits text to the chat. The payload of each frame will be a JSON string in the format:

{

'messageType': 'chatMessage',

'message': message_submitted_by_user

}

Please note the messageType as your server will handle 4 different messageTypes by the end of this assignment. You should check the messageType and run different code for each different type.

For  this  objective,  you  will  parse  WebSocket  frames  that  are  received  from  any  open  WebSocket connection,  parse  the  bits  of  the  frame  to  read  the  payload,  then  send  a  WebSocket frame to all connected WebSocket clients, including the sender, containing the new message. The message sent by your server must be in the format:

{

'messageType': 'chatMessage',

'username': username_of_the_sender,

'message': html_escaped_message_submitted_by_user,

'id': id_of_the_message

}

For the username, use the username that you authenticated during the WS handshake. If they are not authenticated, set their username to "Guest".

When a frame with an opcode of bx1000 (disconnect)   is received, the connection should be severed and removed from any storage on your server. When new messages are received, any disconnected WebSocket  connections  should   not  be  sent  the  new  message  (ie.  Disconnects  must  be  handled gracefully).

Security: Don't forget to escape the HTML in your users' comments.

For this objective, it is ok if your server only handles frames with fewer than 126 bytes of payload.

Database: Use your database to store all of the chat history for your app. This will allow the chat history to persist after a server restart.

A GET request is sent to /chat-history when the page loads to request this content and render it to the page. Once you build the /chat-history endpoint properly, you will see the message history appear when the page loads.

Suggestion:  There  are  a  few  separate  steps  involved  in  this  objective.  It  will  help  your  debugging process if you implement and test this functionality one step at a time. For example, make sure you can read and parse frames properly before trying to send frames and try sending a frame only to the user sending a message before broadcasting the messages to all users.

Testing Procedure

1.   Start your server using docker compose up

2.   Open a browser and navigate to http://localhost:8080/

3.   Open the network tab of the browser console (refresh the page if necessary)

4.  Verify that there is a successful WebSocket connection with a response code of 101

5.   Open a second browser (Use Chrome and Firefox) and repeat steps 2-4 to verify that the server supports multiple simultaneous WebSocket connections

6.   Register accounts and login in both browsers

7.   Enter chat several messages with < 50 characters in each browser

a.  Verify that each  user can see both their own messages, and messages sent by other users in real time

b.  Verify that the correct usernames show up with each message

c.   Verify that the messages were sent using WebSockets by checking the messages tab of the 101 request

8.   Close one browser, then send a message with < 50 characters in the other

a.  Verify that the app is still functional and that no errors appeared in the docker compose output

9.   Restart the server using docker compose restart

10. Open a new browser tab incognito window and navigate to http://localhost:8080/ 11. Verify that all messages are visible on the page (chat-history)

12. Refresh the tabs/browsers and verify the chat history appears as expected

13. Send a message with < 50 characters in the new tab incognito window

a.  Verify that it appears in all tabs/browsers with a username of "Guest"

14. Send a message with < 50 characters from the first browser/tab

a.  Verify  that  it  appears  in  all  tabs/browsers  with  the  authenticated  username  (ie.  Auth tokens persist through a server restart)

15. Security: Verify that the submitted HTML displays as text and is not rendered as HTML

Learning Objective 3: Buffering

Add buffering to both image uploads and WebSocket frames to enable arbitrarily large images and chat messages.

Images: Add buffering to your HTTP requests. Read the content length of the request and buffer until you read the whole body. Your buffering should be able to handle arbitrarily large files. You must use proper buffering for this. Do not increase the TCP buffer size by passing a large int to the recv method.

WebSocket Frames: Add the following features to your WebSocket connections:

●    Handle messages of arbitrary size. This includes both messages where the 7-bit payload is set to 126 and 127. We will test with frames larger than 65536 bytes to ensure you handle all three cases. We will not test with payloads > 131000, as chrome sends messages over this size in multiple frames. For an optional challenge, you can handle this case by checking the FIN bit and combining multiple frames for a single message

○    Note: You will  need to buffer your WebSocket frames. You should read from the TCP connection once,  parse only the headers of the frame, parse the payload length, then check  if  you've   read  that   many  bytes  of  payload.  If  not,  you   must  buffer  before de-masking and reading the payload.

●   Your server must also handle multiple large messages sent back-to-back. If another frame is sent while you are buffering, your last read from the TCP socket may contain the beginning of the next frame. Ensure that you are properly parsing these frames even when one read from the socket contains parts of multiple frames

Testing Procedure

1.   Images:

a.   Follow the testing  procedures of Learning Objective 1 with .jpg images large enough to overflow your TCP buffer (In Chrome and Firefox)

i.      TA Note: Do not test with images containing the byte sequences "\r\n" or "\r\n\r\n" this semester (They should still handle these, but since this is an LO instead of an AO this semester it's not required)

b.   Check the submitted code to ensure a very large TCP buffer was not used 2.  WebSockets:

a.   Open at least 3 browser tabs and navigate to http://localhost:8080/ in each

b.   Enter chat  messages of varying size. Ensure that at least one message has a payload length >=126 but <=65536, at least one has payload length >65536 but <131000

c.   Verify  that each  user can see  both their own  messages and messages sent by other users in real time

d.  Verify that the messages have been sent/received using a WebSocket connection

e.   Modify the JavaScript on at least 1 tab to send messages 2 times (Your server should handle any number of times and you should test for that, but for grading we will only  send  2)  when  the  user  sends  a  message  (These  messages  will  be  sent back-to-back such that the second message may be partially read when reading the first message from the TCP socket)

f.    Send several messages from the modified tab and verify that all users see all the sent messages twice

g.   Security: Verify that the submitted HTML displays as text and is not rendered as HTML

Application Objective: Video Chat Over Web RTC

For this objective, make sure you have all the updated front end code including the webrtc.js file.

Add  features  to  your  server  so  it can function as a Web RTC signaling server for your  users. This signaling will occur over your WebSocket connection. There are three different types of messages used to establish Web RTC connections:

{'messageType': 'webRTC-offer', 'offer': offer}

{'messageType': 'webRTC-answer', 'answer': answer}

{'messageType': 'webRTC-candidate', 'candidate': candidate}

Where offer, answer, and candidate are all generated by the Web RTC code built into your browser. Your task  is  to  forward  these  messages  to  the  other  user  via  their  WebSocket  connection.  To  avoid overcomplicating this objective, you may assume that there are exactly 2 WebSocket connections when receiving  Web RTC  messages.  This  means  that  when  a  message  is  received  on  one  WebSocket connection, you will send the message to the only other WebSocket connection.

Remember,  your  server  is  merely  acting  as  a  means  for  the  2  clients  to  communicate  while  they establish a peer-to-peer connection. The content of the messages you handle do not need to be parsed or processed. Your task is to extract the payload of these WebSocket messages, verify that they are not chat  messages  (and  are  Web RTC  messages),  then  send  the   payload  to  the  other  WebSocket connection. The clients will do the rest through the front end.

Testing Procedure

1.   Start your server using docker compose up

2.   Open  exactly 2  browser tabs (The same browser will be used for both clients which is either

Chrome or Firefox) and navigate to http://localhost:8080/ on each

3.   In each tab:

a.  Allow the camera/mic to be accessed

b.   If the local video has not started on the page load, click the "Start My Video" button 4.   In one of the 2 tabs, click the "Start Video Chat" button

5.  Verify that 2 videos are show in both of the tabs

a.   Both  videos  will  be  showing  the  same  feed,  but  the  remote  video should  be slightly behind  the  local  video  which  is  evidence  that  there  is  a  video streaming  connection between the two tabs. You'll also hear some horrible sounding audio with feedback. Don't worry, that means it's working :)

6.   Shut  down the server by pressing ctrl+c in the terminal running docker compose (or stop the containers using Docker desktop

7.  Verify that the video streams are not interrupted. After the signaling server is used to establish the connection, this is a true peer-to-peer stream