Day 71: Reverse Engineering Success AGAIN

I finished reading through the code for the http-errors package. I now understand how it works under the hood and it feels great. Not so much because it was confusing but because this is my second reverse engineering endeavor and it went way faster and I was a lot more confident with my approach. I can now take my reverse engineering hat off and get back to finishing the final project in the web security section of The Odin Project’s Node.js course.

I have outlined the package below with some more details than my original notes. There were some things that the documentation was not the most clear on. Reading through the code and then reading the documentation helped to make sense of a few things.

The biggest thing that I am now becoming more conscious of is backwards compatibility. I am a JavaScript developer that prefers not using ; semicolons at the end of my statements let alone using var. In these older packages there is so much consideration for backwards compatibility. I wonder if I would need to use older syntax if I were to write a package today or if I would just need to maintain functionality from today moving forward. It is stressful to think about maintenance concerns and versioning issues. I have had this problem working with some Microsoft tools like dotnet. It is not fun mapping what works with which version.

TLDR;

Okay, so here are the highlights of what I did:

  • Backend -> Paused working on the Members Only Project from the web security section. Finished reading through the code base for the http-errors package and updating my notes on the package. I now have a stronger understanding of what the package does and what I can do it. For my current project, the package is not that helpful but I can see it helping with bigger apps.

Notes on http-errors Package

What is the http-errors package?

The http-errors package is a convenience library. It contains tools that make it easier to construct errors in popular Node.js frameworks like ExpressKoa, and Connect(I wonder if there are other frameworks it can be used with or if it can be used as a stand-alone package as well.)

What does the http-errors package do?

The package provides us with a function that returns an error object. These returned error objects are customized by the package to correspond with HTTP Response errors (400 – 599 HTTP status codes) when called with the appropriate arguments.

How Do You Use The http-errors package?

After importing the package. Call the function with an HTTP status code or status name that corresponds with a valid HTTP error status. The function will then return the corresponding error object that can then be passed to our application’s error handler.

We can also add an optional message to replace the default message on the returned Error object by passing in the message as a second parameter. There are multiple ways to call the function that effect the returned Error object:

  • createError([status], [message], [properties])
  • createError([status], [error], [properties])

Lastly, we can construct our HTTP Error object by directly calling one of the corresponding Constructors. There are roughly 80 that exist on the imported function object as added methods.

  • new createError([msg]))

Create a new error object with the given message msg. The error object inherits from createError.HttpError.

var err = new createError.NotFound()

// OR

var err = new createError.[404]()

  • code - the status code as a number
  • name - the name of the error as a "bumpy case", i.e. NotFound or InternalServerError.

Install

This is a Node.js module available through the npm registry. Installation is done using the npm install command:

$ npm install http-errors

Example

var createError = require("http-errors");
var express = require("express");
var app = express();

app.use(function (req, res, next) {
  if (!req.user)
    return next(createError(401, "Please login to view this page."));
  next();
});


API

This is the current API, currently extracted from Koa and subject to change.

There are multiple ways to call the function that effect the returned Error object:

  • createError([status], [message], [properties])
  • createError([status], [error], [properties])

Returned Error Object Properties

  • expose - can be used to signal if message should be sent to the client, defaulting to false when status >= 500
  • headers - can be an object of header names to values to be sent to the client, defaulting to undefined. When defined, the key names should all be lower-cased
  • message - the traditional error message, which should be kept short and all single line
  • status - the status code of the error, mirroring statusCode for general compatibility
  • statusCode - the status code of the error, defaulting to 500

createError([status], [message], [properties])

Create a new error object with the given message msg. The error object inherits from createError.HttpError.

var err = createError(404, "This video does not exist!");

  • status: 500 - the status code as a number
  • message - the message of the error, defaulting to node's text for that status code.
  • properties - custom properties to attach to the object

createError([status], [error], [properties])

Extend the given error object with createError.HttpError properties. This will not alter the inheritance of the given error object, and the modified error object is the return value.

fs.readFile("foo.txt", function (err, buf) {
  if (err) {
    if (err.code === "ENOENT") {
      var httpError = createError(404, err, { expose: false });
    } else {
      var httpError = createError(500, err);
    }
  }
});

  • status - the status code as a number
  • error - the error object to extend
  • properties - custom properties to attach to the object

createError.isHttpError(val)

Determine if the provided val is an HttpError. This will return true if the error inherits from the HttpError constructor of this module or matches the "duck type" for an error this module creates. All outputs from the createError factory will return true for this function, including if an non-HttpError was passed into the factory.

new createError([msg]))

Create a new error object with the given message msg. The error object inherits from createError.HttpError.

var err = new createError.NotFound();

  • code - the status code as a number
  • name - the name of the error as a "bumpy case", i.e. NotFound or InternalServerError.

List of all constructors

Below is a list of Status Code's and Constructor Name's:

  • 400 = BadRequest
  • 401 = Unauthorized
  • 402 = PaymentRequired
  • 403 = Forbidden
  • 404 = NotFound
  • 405 = MethodNotAllowed
  • 406 = NotAcceptable
  • 407 = ProxyAuthenticationRequired
  • 408 = RequestTimeout
  • 409 = Conflict
  • 410 = Gone
  • 411 = LengthRequired
  • 412 = PreconditionFailed
  • 413 = PayloadTooLarge
  • 414 = URITooLong
  • 415 = UnsupportedMediaType
  • 416 = RangeNotSatisfiable
  • 417 = ExpectationFailed
  • 418 = ImATeapot
  • 421 = MisdirectedRequest
  • 422 = UnprocessableEntity
  • 423 = Locked
  • 424 = FailedDependency
  • 425 = TooEarly
  • 426 = UpgradeRequired
  • 428 = PreconditionRequired
  • 429 = TooManyRequests
  • 431 = RequestHeaderFieldsTooLarge
  • 451 = UnavailableForLegalReasons
  • 500 = InternalServerError
  • 501 = NotImplemented
  • 502 = BadGateway
  • 503 = ServiceUnavailable
  • 504 = GatewayTimeout
  • 505 = HTTPVersionNotSupported
  • 506 = VariantAlsoNegotiates
  • 507 = InsufficientStorage
  • 508 = LoopDetected
  • 509 = BandwidthLimitExceeded
  • 510 = NotExtended
  • 511 = NetworkAuthenticationRequired

Reverse Engineering The http-errors Package

The package only has one file index.js. It has a few dependencies and quite a few devDependencies.

Code Design

The code is designed with the main code execution written at the top and all the other functions are written at the bottom. This design style utilizes JavaScript's hoisting functionality which allows for the variable and function declaration to be compiled prior to executing the code. If this functionality did not exist or was not utilized the main code execution could have been written before the function and variable declarations in the file.

What Happens on Import

When we import the package:

  1. All the package's dependencies are imported for use in the only module (index.js).
  • depd package
  • setprototypeof package
  • statuses package
  • statuses package
  • inherits package
  • toidentifier package
  1. The module.exports property is assigned the value of the createError binding. createError is the binding for a function which is declared later in the file but is hoisted for use earlier in the file.
  2. Custom properties are then added to the createError function prior to being exported.
  • HttpError
  • isHttpError
  1. The populateConstructorExports(exports, codes, HttpError) function is called. This function manually adds constructed HTTP Error object Constructor functions as methods to the exports argument. The methods correspond with the HTTP status codes and names provided. In our case, we are using the HTTP response error status codes provided by the statuses package. The function adds one method matching the status code and one method matching the name of the status code. Each receives the same Error object Constructor as it's value. As a result there are more than 80 methods added to the exports object (i.e. the createError function) once the populateConstructorExports function has completed executing. These are the values being passed in when the function is called:
  • exports = module.exports = createError
  • codes = statuses.codes
  • HttpError = module.exports.HttpError = createHttpErrorConstructor()
  1. Finally when we call the function we will get an customized HTTP Error (if we pass in a valid status code / name) or a generic Error object returned. This can then be passed on to our error handler in our application.
populateConstructorExports(module.exports, statuses.codes, module.exports.HttpError)

Exports from Package

The package exports a function (createError) but it also seems to add properties to the Function object. Here is the organization of what I have first understood to be function with added properties that we get when we import the package.

[Function: createError] {
  '400': [Function: BadRequestError],
  '401': [Function: UnauthorizedError],
  '402': [Function: PaymentRequiredError],
  '403': [Function: ForbiddenError],
  '404': [Function: NotFoundError],
  '405': [Function: MethodNotAllowedError],
  '406': [Function: NotAcceptableError],
  '407': [Function: ProxyAuthenticationRequiredError],
  '408': [Function: RequestTimeoutError],
  '409': [Function: ConflictError],
  '410': [Function: GoneError],
  '411': [Function: LengthRequiredError],
  '412': [Function: PreconditionFailedError],
  '413': [Function: PayloadTooLargeError],
  '414': [Function: URITooLongError],
  '415': [Function: UnsupportedMediaTypeError],
  '416': [Function: RangeNotSatisfiableError],
  '417': [Function: ExpectationFailedError],
  '418': [Function: ImATeapotError],
  '421': [Function: MisdirectedRequestError],
  '422': [Function: UnprocessableEntityError],
  '423': [Function: LockedError],
  '424': [Function: FailedDependencyError],
  '425': [Function: TooEarlyError],
  '426': [Function: UpgradeRequiredError],
  '428': [Function: PreconditionRequiredError],
  '429': [Function: TooManyRequestsError],
  '431': [Function: RequestHeaderFieldsTooLargeError],
  '451': [Function: UnavailableForLegalReasonsError],
  '500': [Function: InternalServerError],
  '501': [Function: NotImplementedError],
  '502': [Function: BadGatewayError],
  '503': [Function: ServiceUnavailableError],
  '504': [Function: GatewayTimeoutError],
  '505': [Function: HTTPVersionNotSupportedError],
  '506': [Function: VariantAlsoNegotiatesError],
  '507': [Function: InsufficientStorageError],
  '508': [Function: LoopDetectedError],
  '509': [Function: BandwidthLimitExceededError],
  '510': [Function: NotExtendedError],
  '511': [Function: NetworkAuthenticationRequiredError],
  HttpError: [Function: HttpError],
  isHttpError: [Function: isHttpError],
  BadRequest: [Function: BadRequestError],
  Unauthorized: [Function: UnauthorizedError],
  PaymentRequired: [Function: PaymentRequiredError],
  Forbidden: [Function: ForbiddenError],
  NotFound: [Function: NotFoundError],
  MethodNotAllowed: [Function: MethodNotAllowedError],
  NotAcceptable: [Function: NotAcceptableError],
  ProxyAuthenticationRequired: [Function: ProxyAuthenticationRequiredError],
  RequestTimeout: [Function: RequestTimeoutError],
  Conflict: [Function: ConflictError],
  Gone: [Function: GoneError],
  LengthRequired: [Function: LengthRequiredError],
  PreconditionFailed: [Function: PreconditionFailedError],
  PayloadTooLarge: [Function: PayloadTooLargeError],
  URITooLong: [Function: URITooLongError],
  UnsupportedMediaType: [Function: UnsupportedMediaTypeError],
  RangeNotSatisfiable: [Function: RangeNotSatisfiableError],
  ExpectationFailed: [Function: ExpectationFailedError],
  ImATeapot: [Function: ImATeapotError],
  MisdirectedRequest: [Function: MisdirectedRequestError],
  UnprocessableEntity: [Function: UnprocessableEntityError],
  Locked: [Function: LockedError],
  FailedDependency: [Function: FailedDependencyError],
  TooEarly: [Function: TooEarlyError],
  UpgradeRequired: [Function: UpgradeRequiredError],
  PreconditionRequired: [Function: PreconditionRequiredError],
  TooManyRequests: [Function: TooManyRequestsError],
  RequestHeaderFieldsTooLarge: [Function: RequestHeaderFieldsTooLargeError],
  UnavailableForLegalReasons: [Function: UnavailableForLegalReasonsError],
  InternalServerError: [Function: InternalServerError],
  NotImplemented: [Function: NotImplementedError],
  BadGateway: [Function: BadGatewayError],
  ServiceUnavailable: [Function: ServiceUnavailableError],
  GatewayTimeout: [Function: GatewayTimeoutError],
  HTTPVersionNotSupported: [Function: HTTPVersionNotSupportedError],
  VariantAlsoNegotiates: [Function: VariantAlsoNegotiatesError],
  InsufficientStorage: [Function: InsufficientStorageError],
  LoopDetected: [Function: LoopDetectedError],
  BandwidthLimitExceeded: [Function: BandwidthLimitExceededError],
  NotExtended: [Function: NotExtendedError],
  NetworkAuthenticationRequired: [Function: NetworkAuthenticationRequiredError]
}

References:


Goal For Round 8 of the #100DaysofCode Challenge

This is my eighth round of the "#100daysofcode" challenge. I will be continuing my work from round five, six, and seven into round eight. I was working through the book "Cracking the Coding Interview" by Gayle Laakmann McDowell. My goal was to become more familiar with algorithms and data structures. This goal was derived from my goal to better understand operating systems and key programs that I use in the terminal regularly e.g. Git. This goal was in turn derived from my desire to better understand the fundamental tools used for coding outside of popular GUIs. This in turn was derived from my desire to be a better back-end developer.

I am currently putting a pause on the algorithm work to build some backend/full stack projects. I primarily want to improve my skills with the back-end from an implementation perspective. I have improved tremendously in terminal and CLI skills but I lost focus due to how abstract the algorithm concepts got. I wanted to work on things that were more tangible until I can get to a position where I could directly benefit from improving my algorithm skills and theoretical knowledge. So that's the focus right now. Build my backend skills and prove my full stack capabilities by building some dope projects.

Again, I still have no idea if my path is correct but I am walking down this road anyways. Worst case scenario I learn a whole bunch of stuff that will help me out on my own personal projects. Best case scenario I actually become one of those unicorn developers that go on to start a billion dollar company... You never know LOL.