Objectives
-
Become familiar with HTTP.
-
Apply familiar techniques in unfamiliar contexts.
-
Transition from C to web programming.
Academic Honesty
This course’s philosophy on academic honesty is best stated as "be reasonable." The course recognizes that interactions with classmates and others can facilitate mastery of the course’s material. However, there remains a line between enlisting the help of another and submitting the work of another. This policy characterizes both sides of that line.
The essence of all work that you submit to this course must be your own. Collaboration on problem sets is not permitted except to the extent that you may ask classmates and others for help so long as that help does not reduce to another doing your work for you. Generally speaking, when asking for help, you may show your code to others, but you may not view theirs, so long as you and they respect this policy’s other constraints. Collaboration on quizzes is not permitted at all. Collaboration on the course’s final project is permitted to the extent prescribed by its specification.
Below are rules of thumb that (inexhaustively) characterize acts that the course considers reasonable and not reasonable. If in doubt as to whether some act is reasonable, do not commit it until you solicit and receive approval in writing from the course’s heads. Acts considered not reasonable by the course are handled harshly. If the course refers some matter for disciplinary action and the outcome is punitive, the course reserves the right to impose local sanctions on top of that outcome that may include an unsatisfactory or failing grade for work submitted or for the course itself.
If you commit some act that is not reasonable but bring it to the attention of the course’s heads within 72 hours, the course may impose local sanctions that may include an unsatisfactory or failing grade for work submitted, but the course will not refer the matter for further disciplinary action except in cases of repeated acts.
Reasonable
-
Communicating with classmates about problem sets' problems in English (or some other spoken language).
-
Discussing the course’s material with others in order to understand it better.
-
Helping a classmate identify a bug in his or her code at office hours, elsewhere, or even online, as by viewing, compiling, or running his or her code, even on your own computer.
-
Incorporating snippets of code that you find online or elsewhere into your own code, provided that those snippets are not themselves solutions to assigned problems and that you cite the snippets' origins.
-
Reviewing past semesters' quizzes and solutions thereto.
-
Sending or showing code that you’ve written to someone, possibly a classmate, so that he or she might help you identify and fix a bug.
-
Sharing snippets of your own code online so that others might help you identify and fix a bug.
-
Turning to the web or elsewhere for instruction beyond the course’s own, for references, and for solutions to technical difficulties, but not for outright solutions to problem set’s problems or your own final project.
-
Whiteboarding solutions to problem sets with others using diagrams or pseudocode but not actual code.
-
Working with (and even paying) a tutor to help you with the course, provided the tutor does not do your work for you.
Not Reasonable
-
Accessing a solution to some problem prior to (re-)submitting your own.
-
Asking a classmate to see his or her solution to a problem set’s problem before (re-)submitting your own.
-
Decompiling, deobfuscating, or disassembling the staff’s solutions to problem sets.
-
Failing to cite (as with comments) the origins of code or techniques that you discover outside of the course’s own lessons and integrate into your own work, even while respecting this policy’s other constraints.
-
Giving or showing to a classmate a solution to a problem set’s problem when it is he or she, and not you, who is struggling to solve it.
-
Looking at another individual’s work during a quiz.
-
Paying or offering to pay an individual for work that you may submit as (part of) your own.
-
Providing or making available solutions to problem sets to individuals who might take this course in the future.
-
Searching for, soliciting, or viewing a quiz’s questions or answers prior to taking the quiz.
-
Searching for or soliciting outright solutions to problem sets online or elsewhere.
-
Splitting a problem set’s workload with another individual and combining your work.
-
Submitting (after possibly modifying) the work of another individual beyond allowed snippets.
-
Submitting the same or similar work to this course that you have submitted or will submit to another.
-
Submitting work to this course that you intend to use outside of the course (e.g., for a job) without prior approval from the course’s heads.
-
Using resources during a quiz beyond those explicitly allowed in the quiz’s instructions.
-
Viewing another’s solution to a problem set’s problem and basing your own solution on it.
Getting Ready
First, join David (hey, that’s me) for a tour of HTTP, the "protocol" via which web browsers and web servers communicate.
Next, consider reviewing some of these examples from Week 7, via which we introduced HTML, the language in which web pages are written.
And also some of these examples, via which we introduced CSS, the language with which web pages can be stylized.
Next, consider reviewing some of these examples, via which we introduced HTML forms, which we used to submit GET queries to Google.
For another perspective altogether, join Daven for a tour of HTML too. Don’t miss the bloopers at the end!
Finally, join Joseph (and Rob) for a closer look at CSS.
Getting Started
Log into CS50 IDE and, in a terminal window, execute
update50
to ensure that your workspace is up-to-date!
Like Problem Set 5, this problem set comes with some distribution code that you’ll need to download before getting started. Go ahead and execute
cd ~/workspace
in order to navigate to your ~/workspace
directory. Then execute
wget http://cdn.cs50.net/2015/fall/psets/6/pset6/pset6.zip
in order to download a ZIP (i.e., compressed version) of this problem set’s distro. If you then execute
ls
you should see that you now have a file called pset6.zip
in your ~/workspace
directory. Unzip it by executing the below.
unzip pset6.zip
If you again execute
ls
you should see that you now also have a pset6
directory. You’re now welcome to delete the ZIP file with the below.
rm -f pset6.zip
Now dive into that pset6
directory by executing the below.
cd pset6
Now execute
tree
(which is a hierarchical, recursive variant of ls
), and you should see that the directory contains the below.
.
├── Makefile
├── public
│ ├── cat.html
│ ├── cat.jpg
│ ├── favicon.ico
│ ├── hello.html
│ ├── hello.php
│ └── test
│ └── index.html
└── server.c
Dang it, still C. But some other stuff too!
Go ahead and take a look at cat.html
. Pretty simple, right? Looks like it has an img
tag, the value of whose src
attribute is cat.jpg
.
Next, take a look at hello.html
. Notice how it has a form
that’s configured to submit via GET a text
field called name
to hello.php
. Make sense? If not, try taking another look at the walkthrough for search-0.html
from Week 7!
Now take a look at hello.php
. Notice how it’s mostly HTML but inside its body
is a bit of PHP code:
<?= htmlspecialchars($_GET["name"]) ?>
The <?=
notation just means "echo the following value here". htmlspecialchars
, meanwhile, is just an attrociously named function whose purpose in life is to ensure that special (even dangerous!) characters like <
are properly "escaped" as HTML "entities." See http://php.net/manual/en/function.htmlspecialchars.php for more details if curious. Anyhow, $_GET
is a "superglobal" variable inside of which are any HTTP parameters that were passed via GET to hello.php
. More specifically, it’s an "associative array" (i.e., hash table) with keys and values. Per that HTML form in hello.html
, one such key should be name
! But more on all that in a bit.
Now the fun part. Open up server.c
.
Yup. You guessed it. The challenge ahead is to implement your own web server that knows how to serve static content (i.e., files ending in .html
, .jpg
, et al.) and dynamic content (i.e., files ending in .php
).
Want to try out the staff’s solution before we dive into the distribution code? Execute the below to download the latest version of the staff’s solution, as the version in CS50 IDE by default is outdated. Note that the O
in -O
is a capitalized letter O
, not a zero.
sudo wget -O ~cs50/pset6/server http://cdn.cs50.net/2015/fall/psets/6/pset6/server
sudo chmod a+x ~cs50/pset6/server
Then execute the below to run the staff’s implementation of server
.
~cs50/pset6/server
You should see these instructions:
Usage: server [-p port] /path/to/root
Looks a bit complex, but that’s just a conventional way of saying:
-
This program’s name is
server
. -
To specify a (TCP) port number on which
server
should listen for HTTP requests, include-p
as a command-line argument, followed by (presumably) a number. The brackets imply that specifying a port is optional. (If you don’t specify, the program will default to port 8080, which is required by CS50 IDE.) -
The last command-line argument to
server
should be the path to your server’s "root" (the directory from which files will be served).
Let’s try it out. Execute the below from within your own ~/workspace/pset6
directory so that the staff’s solution uses your own copy of public
as its root.
~cs50/pset6/server public
You should see output like the below.
Using /home/ubuntu/workspace/pset6/public for server's root
Listening on port 8080
Toward the top-left corner of CS50 IDE, meanwhile, click CS50 IDE > Web Server, that should open your workspace’s "fully qualified domain name," an address of the form ide50-username.cs50.io
, where username
is your own username, in a new browser tab. You should see a "directory listing" (i.e., an unordered list) of everything that’s in public
, yes? And if you click cat.jpg, you should see a happy cat?? If not, do just reach out to classmates or staff for a hand!
Incidentally, even though server
is running on port 8080, CS50 IDE is "port-forwarding" port 80 (which, recall, is browsers' default) to 8080 for you. That’s why 8080 is not specified in the URL you just visited.
Anyhow, assuming you indeed saw a happy cat in that tab, you should also see
GET /cat.jpg HTTP/1.1
in your terminal window, which is the "request line" that your browser sent to the server (which is being outputted by server
via printf
for diagnostics' sake). Below that you should see all of the headers that your browser sent to server
followed by
HTTP/1.1 200 OK
which is the server’s response to the browser (which is also being outputted by server
via printf
for diagnostics' sake).
Next, just like I did in that short on HTTP, open up Chrome’s developer tools, per the instructions at https://developer.chrome.com/devtools. Then, once open, click the tools' Network tab, and then, while holding down Shift, reload the page.
Not only should you see Happy Cat again. You should also see the below in your terminal window.
GET /cat.jpg HTTP/1.1
HTTP/1.1 200 OK
You might also see the below.
GET /favicon.ico HTTP/1.1
HTTP/1.1 200 OK
What’s going on if so? Well, by convention, a lot of websites have in their root directory a favicon.ico
file, which is a tiny icon that’s meant to be displayed a browser’s address bar or tab. If you do see those lines in your terminal window, that just means Chrome is guessing that your server, too, might have favicon.ico
file, which it does!
Here’s a quick walkthrough if a demo might help.
Alright, now try visiting https://ide50-username.cs50.io/cat.html
. (Note the .html
instead of .jpg
this time.) You should see Happy Cat again, possibly with a bit of a margin around him (simply because of Chrome’s default CSS properties). If you look at the developer tools' Network tab (possibly after reloading, if they weren’t still open), you should see that Chrome first requested cat.html
followed by cat.jpg
, since the latter, recall, was specified as the value of that img
element’s src
attribute that we saw earlier in cat.html
. To confirm as much, take a look at the developer tools' Elements tab, wherein you’ll see a pretty-printed version of the HTML in cat.html
. You can even change it but only Chrome’s in-memory copy thereof. To change the actual file, you’d need to do so in the usual way within CS50 IDE. Incidentally, you might find it interesting to tinker with the developer tools' Styles tab, too. Even though this page doesn’t have any CSS of its own, you can see and change (temporarily) Chrome’s default CSS properties via that tab.
Okay, one last test. Try visiting https://ide50-username.cs50.io/hello.html
. Go ahead and input your name into the form and then submit it, as by clicking the button or hitting Enter. You should find yourself at a URL like https://ide50-username.cs50.io/hello.php?name=Alice
(albeit with your name, not Alice’s, unless your name is also Alice), where a personalized hello awaits! That’s what we mean by "dynamic" content. By submitting that form, you provided input (i.e., your name) to the server, which then generated output just for you. (That input was in the form of an "HTTP parameter" called name
, the value of which was your name.) Indeed, if you look at the page’s source code (as via the developer tools' Elements tab), you’ll see your name embedded within the HTML! By contrast, files like cat.jpg
and cat.html
(and even hello.html
) are "static" content, since they’re not dynamically generated.
Neat, eh?? Though odds are you’ll find it easier to test your own code via a command line than with a browser. So let’s show you one other technique.
Open up a second terminal window and position it alongside your first. In the first terminal window, execute
~cs50/pset6/server public
from within your own ~/workspace/pset6
directory, if the server isn’t already running. Then, in the second terminal window, execute the below. (Note the http://
this time instead of https://
.)
curl -i http://localhost:8080/
If you haven’t used curl
before, it’s a command-line program with which you can send HTTP requests (and more) to a server in order to see its responses. The -i
flag tells curl
to include responses' HTTP headers in the output. Odds are, whilst debugging your server, you’ll find it more convenient (and revealing!) to see all of that via curl
than by poking around Chrome’s developer tools.
Incidentally, take care not to request cat.jpg
(or any binary file) via curl
, else you’ll see quite a mess! (You’re about to try, aren’t you.)
Unfortunately, your own copy of server.c
isn’t quite so featureful as the staff’s solution… yet! Let’s dive into that distribution code. Let’s start with a high-level overview.
And now a lower-level tour through the code.
server.c
Open up server.c
, if not open already. Let’s take a tour.
-
Atop the file are a bunch of "feature test macro requirements" that allow us to use certain functions that are declared (conditionally) in the header files further below.
-
Defined next are a few constants that specify limits on HTTP requests sizes. We’ve (arbitrarily) based their values on defaults used by Apache, a popular web server. See http://httpd.apache.org/docs/2.2/mod/core.html if curious.
-
Defined next is
BYTES
, a constant the specifies how many bytes we’ll eventually be reading into buffers at a time. -
Next are a bunch of header files, followed by a definition of
BYTE
, which we’ve indeed defined as an 8-bitchar
, followed by a bunch of prototypes. -
Finally, just above
main
are a just a few global variables.
main
Let’s now walk through main
.
-
Atop
main
is an initialization of what appears to be a global variable callederrno
. In fact,errno
is defined inerrno.h
and is used by quite a few functions to indicate (via anint
), in cases of error, precisely which error has occurred. Seeman errno
for more details. -
Shortly thereafter is a call to
getopt
, which is a function declared inunistd.h
that makes it easier to parse command-line arguments. Seeman 3 getopt
if curious. Notice how we usegetopt
(and some Boolean expressions) to ensure thatserver
is used properly. -
Next notice the call to
start
(for which you may have noticed a prototype earlier). More on that later. -
Below that is a declaration of a
struct sigaction
via which we’ll listen forSIGINT
(i.e., control-c), callinghandler
(a function defined by us elsewhere inserver.c
) if heard. -
And then, after declaring some variables,
main
enters an infinitewhile
loop.-
Atop that loop, we first free any memory that might have been allocated by a previous iteration of the loop.
-
We then check whether we’ve been "signalled" via control-c to stop the server.
-
Thereafter, within an
if
statment, is a call toconnected
, which returnstrue
if a client (e.g., a browser or evencurl
) has connected to the server. -
After that is a call to
parse
, which parses a browser’s HTTP request, storing its "absolute path" and "query" inside of two arrays that are passed into it by reference. -
Next is a bunch of code that decodes that path (decoding any URL-encoded characters like
%20
) and "resolves" the path to a local path, figuring out exactly what file was requested on the server itself. -
Below that, we ascertain whether that path leads to a directory or to a file and handle the request accordingly, ultimately calling
list
,interpret
, ortransfer
.-
For directories (that don’t have an
index.php
orindex.html
file inside them), we calllist
in order to display the directory’s contents. -
For files ending in
.php
(whose "MIME type" istext/x-php
), we callinterpret
. -
For other (supported) files, we call
transfer
.
-
-
And that’s it for main
! Notice, though, that throughout main
are a few uses of continue
, the effect of which is to jump back to the start of that infinite loop. Just before continue
in some cases, too, is a call to error
(another function we wrote) with an HTTP status code. Together, those lines allow the server to handle and respond to errors just before returning its attention to new requests.
connected
Take a quick peek at connected
below main
. Don’t fret if unsure how this function works, but do try to infer from the man
pages for memset
and accept
!
error
Spend a bit more time looking through error
, which is that function via which we respond to browsers with errors (e.g., 404). This function’s a bit longer but perhaps has some more familiar constructs. Before forging ahead, be sure you’re reasonably comfortable with how this function works. (If curious, we’re using log10
simply to figure out how many digits, and thus char
s, code
is.)
freedir
This function exists simply to facilitate freeing memory that’s allocated by a function called scandir
that we call in list
.
handler
Thankfully, a short one! This function (called whenever a user hits control-c) essentially tells main
to call stop
by setting signaled
, a global variable, to true
.
htmlspecialchars
This function, named identically to that PHP function we saw earlier, escapes characters (e.g., <
as <
) that might otherwise "break" an HTML page. We call it from list
, lest some file or directory we’re listing have a "dangerous" character in its name.
indexes
Uh oh, forgot to implement this one. About that…
interpret
This function enables the server to interpret PHP files. It’s a bit cryptic at first glance, but in a nutshell, all we’re doing, upon receiving a request for, say, hello.php
, is executing a line like
QUERY_STRING="name=Alice" REDIRECT_STATUS=200 SCRIPT_FILENAME=/home/ubuntu/workspace/pset6/public/hello.php php-cgi
the effect of which is to pass the contents of hello.php
to PHP’s interpreter (i.e., php-cgi
), with any HTTP parameters supplied via an "environment variable" called QUERY_STRING
. Via load
(a function we wrote), we then read the interpreter’s output into memory (via load
). And then we respond to the browser with (dynamically generated) output like:
HTTP/1.1 200 OK
X-Powered-By: PHP/5.5.9-1ubuntu4.12
Content-type: text/html
<!DOCTYPE html>
<html>
<head>
<title>hello</title>
</head>
<body>
hello, Alice
</body>
</html>
Even though the PHP code in hello.php
is pretty-printed, it’s output isn’t quite as pretty. (Take a look at hello.php
. Can you deduce why?)
Odds are you’re unfamiliar with popen
. That function opens a "pipe" to a process (php-cgi
in our case), which provides us with a FILE
pointer via which we can read that process’s standard output (as though it were an actual file).
Notice how this function calls load
, though, in order to read the PHP interpreter’s output into memory.
list
Ah, here’s that function that generates a directory listing. Notice how much code it takes to generate HTML using C, thanks to requisite memory management. (No more, come PHP in Problem Set 7!)
load
Phew, a short one. Oh, wait.
lookup
Dang, another.
parse
Aaaaand, another. But the last of our TODOs!
reason
This function simply maps HTTP "status codes" (e.g., 200
) to "reason phrases" (e.g., OK
).
redirect
Ah, neat, this function redirects a client to another location (i.e., URL) by sending a status code of 301
plus a Location
header.
request
Ah, this one’s a biggie. But worth reading through. When the server receives a request from a client, the server doesn’t know in advance how many characters the request will comprise. And so this function iteratively reads bytes from the client, one buffer’s worth at a time, calling realloc
as needed to store the entire message (i.e., request).
Notice this function’s use of pointers, dynamic memory allocation, pointer arithmetic, and more. All somewhat familiar by now, but definitely a lot of it all in once place! Do try to understand each and every line, if only for the practice. Ultimately, it keeps reading bytes from the client until it encounters \r\n\r\n (aka CRLF CRLF), which, according to HTTP’s spec, marks the end of a request’s headers.
If curious, know that read
is quite like fread
except that it reads from a "file descriptor" (i.e., an int
) instead of from a FILE
pointer (i.e., FILE*
). See its man
page for more.
Phew.
respond
It’s this function that actually sends to a client an HTTP response, given a status code, heads, a body, and that body’s length. For instance, it’s this function that sends a response like the below.
HTTP/1.1 200 OK
X-Powered-By: PHP/5.5.9-1ubuntu4.12
Content-type: text/html
<!DOCTYPE html>
<html>
<head>
<title>hello</title>
</head>
<body>
hello, Alice
</body>
</html>
Know that dprintf
is quite like printf
(or, really, fprintf
) except that the former, like read
, writes to a "file descriptor" instead of to a FILE*
.
start
Here’s that function that started it all (pun intended). Don’t worry if (even with man
) you don’t understand all of its lines, particularly the networking code. But do keep in mind that start
is the function that configures the server to listen for connections on a particular TCP port!
stop
And stop
does the opposite, freeing all memory and ultimately compelling the server to exit, without even returning control to main
.
transfer
This function’s purpose in life is to transfer a file from the server to a client. Whereas interpret
handles dynamic content (generated by PHP scripts), transfer
handles static content (e.g., JPEGs). Notice how this function calls load
in order to read some file from disk.
urldecode
This function, also named after a PHP function, URL-decodes a string, converting special characters like %20
back to their original values.
What To Do
Alright, let’s tackle those TODO
s.
lookup
Complete the implementation of lookup
in such a way that it returns
-
text/css
for any file whosepath
ends in.css
(or any capitalization thereof), -
text/html
for any file whosepath
ends in.html
(or any capitalization thereof), -
image/gif
for any file whosepath
ends in.gif
(or any capitalization thereof), -
image/x-icon
for any file whosepath
ends in.ico
(or any capitalization thereof), -
image/jpeg
(notimage/jpg
) for any file whosepath
ends in.jpg
(or any capitalization thereof), -
text/javascript
for any file whosepath
ends in.js
(or any capitalization thereof), -
text/x-php
for any file whosepath
ends in.php
(or any capitalization thereof), or -
image/png
for any file whosepath
ends in.png
(or any capitalization thereof), or -
NULL
otherwise.
Odds are you’ll find functions like strcasecmp
, strcpy
, and/or strrchr
of help!
parse
Complete the implementation of parse
in such a way that the function parses (i.e., iterates over) line
, extracting its absolute-path and query and storing them at abs_path
and query
, respectively.
Here’s how.
abs_path
Per 3.1.1 of http://tools.ietf.org/html/rfc7230, a request-line
is defined as
method SP request-target SP HTTP-version CRLF
wherein SP
represents a single space (
) and CRLF
represents \r\n
. None of method
, request-target
, and HTTP-version
, meanwhile, may contain SP
.
Per 5.3 of the same RFC, request-target
, meanwhile, can take several forms, the only one of which your server needs to support is
absolute-path [ "?" query ]
whereby absolute-path
(which will not contain ?
) must start with /
and might optionally be followed by a ?
followed by a query
, which may not contain "
.
Ensure that request-line
(which is passed into parse
as line
) is consistent with these rules. If it is not, respond to the browser with 400 Bad Request and return false
.
Even if request-line
is consistent with these rules,
-
if
method
is notGET
, respond to the browser with 405 Method Not Allowed and returnfalse
; -
if
request-target
does not begin with/
, respond to the browser with 501 Not Implemented and returnfalse
; -
if
request-target
contains a"
, respond to the browser with 400 Bad Request and returnfalse
; -
if
HTTP-version
is notHTTP/1.1
, respond to the browser with 505 HTTP Version Not Supported and returnfalse
.
Odds are you’ll find functions like strchr
, strcpy
, strncmp
, strncpy
, and/or strstr
of help!
If all is well, store absolute-path
at the address in abs_path
(which was also passed into parse
as an argument). You may assume that the memory to which abs_path
points will be at least of length LimitRequestLine
+ 1.
If you’d like to test your code with check50
, you can execute the below.
check50 2015.fall.pset6.server1 server.c
query
Store at the address in query
the query
substring from request-target
. If that substring is absent (even if a ?
is present), then query
should be ""
, thereby consuming one byte, whereby query[0]
is '\0'
. You may assume that the memory to which query
points will be at least of length LimitRequestLine
+ 1.
For instance, if request-target
is /hello.php
or /hello.php?
, then query
should have a value of ""
. And if request-target
is /hello.php?q=Alice
, then query
should have a value of q=Alice
.
Odds are you’ll find functions like strchr
, strcpy
, strncpy
, and/or strstr
of help!
load
Complete the implementation of load
in such a way that the function:
-
reads all available bytes from
file
, -
stores those bytes contiguously in dynamically allocated memory on the heap,
-
stores the address of the first of those bytes in
*content
, and -
stores the number of bytes in
*length
.
Note that content
is a "pointer to a pointer" (i.e., BYTE**
), which means that you can effectively "return" a BYTE*
to whichever function calls load
by dereferencing content
and storing the address of a BYTE
at *content
. Meanwhile, length
is a pointer (i.e., size_t*
), which you can also dereference in order to "return" a size_t
to whichever function calls load
by dereferencing length
and storing a number at *length
.
indexes
Complete the implementation of indexes
in such a way that the function, given a /path/to/a/directory
, returns /path/to/a/directory/index.php
if index.php
actually exists therein, or /path/to/a/directory/index.html
if index.html
actually exists therein, or NULL
. In the first of those cases, this function should dynamically allocate memory on the heap for the returned string.
If you’d like to test your code further with check50
(once parse
, query
, load
, and indexes
are implemented), you can execute the below.
check50 2015.fall.pset6.server2 server.c
How to Submit
Step 1 of 2
-
When ready to submit, log into CS50 IDE.
-
Toward CS50 IDE’s top-left corner, within its "file browser" (not within a terminal window), control-click or right-click your
pset6
folder and then select Download. You should find that your browser has downloadedpset6.zip
. -
In a separate tab or window, log into CS50 Submit, logging in if prompted.
-
Click Submit toward the window’s top-left corner.
-
Under Problem Set 6 on the screen that appears, click Upload New Submission.
-
On the screen that appears, click Add files…. A window entitled Open Files should appear.
-
Navigate your way to
pset6.zip
. Odds are it’s in your Downloads folder or wherever your browser downloads files by default. Once you findpset6.zip
, click it once to select it, then click Open (or the like). -
Click Start upload to upload all of your files at once to CS50’s servers.
-
On the screen that appears, you should see a window with No File Selected. If you move your mouse toward the window’s lefthand side, you should see a list of the files you uploaded. Click each to confirm the contents of each. (No need to click any other buttons or icons.) If confident that you submitted the files you intended, consider your source code submitted! If you’d like to re-submit different (or modified) files, simply return to CS50 Submit and repeat these steps. You may re-submit as many times as you’d like; we’ll grade your most recent submission, so long as it’s before the deadline.
Step 2 of 2
Head to http://cs50.edx.org/2016/psets/6/ where a short form awaits. Once you have submitted that form (as well as your source code), you are done! If you end up resubmitting your files (per step 1 of 2), no need to resubmit the form.
This was Problem Set 6.