There are really two purposes of the parse function. First, to return an abs_path and query. The second is to "Ensure that request-line ... is consistent with [the] rules." This code more or less assumes that request-line is in the proper format and doesn't contain the necessary code to "detect violations".
Method of 'GETabc' returns error code 405
This test violates the rules because the method (GET) must be followed by a space. A simple change to this line
if (strncmp(line, "GET", 3) != 0) could correct that.
request-target of abc/hello.php returns error code 501
This test violates the rules because request-target must begin with '/'. While this line
char* abpathstart = strchr(line, '/'); ensures that a '/' exists, and this
if (abpathholder != '/' || abpathholder == 93) ensures that abpathholder begins with '/', it doesn't detect the violation that request-target begins with '/'. (Side note: ascii 93 is a ']'. That looks like a typo)
Requesting cat.exe returns error code 501
Requesting non-existant file returns error code 404
These two failures generally indicated that abs_path is not properly null-terminated. And that is the case because of
abs_path = abpathholder;. Scope. Pointers. This changes what the local abs_path points to, not the contents of the memory it points to. When program control returns to main, abs_path still points to its original memory location, which has not been changed. You can verify this by including similar printf's in main (when it returns from parse). As suggested in the spec, you might use some copy function to populate abs_path. (And query too!). Whatever you decide, don't forget the terminating null-byte.
I would expect this code to also fail the two-spaces-before-HTTP test because this
char* lastspace = strrchr(line, ' '); will not detect the second-to-last space. I don't know what I'm missing.