fluffy – Rev 6

Subversion Repositories:
Rev:
#!/usr/bin/env node

/*************************************************************************/
/*    Copyright (C) 2017 Wizardry and Steamworks - License: GNU GPLv3    */
/*************************************************************************/

const url = require('url');
const path = require('path');
const fs = require('fs');
const mime = require('mime');
const auth = require("http-auth");
const stream = require('stream');
const util = require('util');
var formidable = require('formidable');
const EventEmitter = require('events').EventEmitter;

// Local imports.
const Cache = require(
    path
    .resolve(
        path.dirname(require.main.filename),
        'src/server',
        'cache'
    )
);
const was = require(
    path
    .resolve(
        path.dirname(require.main.filename),
        'src/server',
        'was'
    )
);

// Constructor.
function POST() {
    // Create events emitters for logging and data.
    EventEmitter.call(this);
};

// Process a request.
POST.prototype.process = function(config, request, response, root) {
    EventEmitter.call(this);
    var self = this;
    
    // Get client details.
    const address = request.socket.address();
    // Get requested URL.
    const requestURL = url.parse(
        request.url, true
    );

    // Perform URL re-writes.
    Object.keys(config.site.rewrite).forEach((key, index) => {
        if (config.site.rewrite[key].test(requestURL.path)) {
            const originalPath = requestURL.path;
            requestURL.path = requestURL
                .path
                .replace(
                    config.site.rewrite[key], key
                );
            requestURL.pathname = url.parse(
                    requestURL
                    .pathname
                    .replace(
                        config.site.rewrite[key], key
                    ),
                    true
                )
                .pathname;
            self.emit('log', {
                message: 'Rewrite path: ' +
                    originalPath +
                    ' to: ' +
                    requestURL.path,
                severity: 'info'
            });
        }
    });

    const trimmedPath = requestURL
        .pathname
        .split('/')
        .filter(Boolean)
        .join('/');
    const requestPath = trimmedPath === '/' ?
        path.join(root, trimmedPath) :
        path.resolve(root, trimmedPath);
        
    var form = new formidable.IncomingForm(),
        properfields = {};

    // Workaround for formidable not parsing multiple filed options.
    form.on('field', function(name, value) {
      if (!properfields[name]) {
        properfields[name] = value;
      } else {
        if (properfields[name].constructor.toString().indexOf("Array") > -1) { // is array
          properfields[name].push(value);
        } else { // not array
          var tmp = properfields[name];
          properfields[name] = [];
          properfields[name].push(tmp);
          properfields[name].push(value);
        }
      }
    });
        
    form.parse(request, function(error, fields, files) {
        // If the form data could not be parsed.
        if (error) {
          self.emit('log', {
              message: 'Could not parse form data from: ' +
                  address.address + ':' +
                  address.port +
                  ' requesting: ' +
                  requestURL.pathname,
              severity: 'warning'
          });
          self.emit('data', {
              status: 404,
              data: new stream.Readable({
                  read(size) {
                      this.push(null);
                  }
              }),
              type: 'text/plain'
          });
          return;
        }
        
        switch(trimmedPath) {
            case 'add':
                // Write the icon file.
                var iconPath = path.join(root, 'data/' + fields['service-name'] + '.png');
                var formFile = fs.createReadStream(files['service-icon'].path)
                    .on('open', () => {
                        formFile.pipe(
                            fs.createWriteStream(iconPath)
                                .on('error', (error) => {
                                    if (error) {
                                      self.emit('log', {
                                          message: 'Unable to create file at: ' + iconPath,
                                          severity: 'error'
                                      });
                                      self.emit('data', {
                                          status: 404,
                                          data: new stream.Readable({
                                              read(size) {
                                                  this.push(null);
                                              }
                                          }),
                                          type: 'text/plain'
                                      });
                                      return;
                                    }
                                })
                        );
                    })
                    .on('error', (error) => {
                        if (error) {
                          self.emit('log', {
                              message: 'Unable to create file at: ' + iconPath,
                              severity: 'error'
                          });
                          self.emit('data', {
                              status: 404,
                              data: new stream.Readable({
                                  read(size) {
                                      this.push(null);
                                  }
                              }),
                              type: 'text/plain'
                          });
                          return;
                        }
                    });
                    
                    // Update the data file.
                    fs.realpath(path.join(root, 'data/data.json'), (error, dataPath) => {
                        // If the path does not exist, then return early.
                        if (error) {
                          self.emit('log', {
                              message: 'Unable to access data path: ' + dataPath,
                              severity: 'error'
                          });
                          self.emit('data', {
                              status: 404,
                              data: new stream.Readable({
                                  read(size) {
                                      this.push(null);
                                  }
                              }),
                              type: 'text/plain'
                          });
                          return;
                        }
            
                        fs.readFile(dataPath, 'utf8', function(error, data) {
                            // Could not read data file.
                            if (error) {
                              self.emit('log', {
                                  message: 'Unable to read data file.',
                                  severity: 'error'
                              });
                              self.emit('data', {
                                  status: 404,
                                  data: new stream.Readable({
                                      read(size) {
                                          this.push(null);
                                      }
                                  }),
                                  type: 'text/plain'
                              });
                              return;
                            }
                
                            // Remove previous service if it exists.
                            var services = JSON.parse(data).filter((service) => {
                                return service.tooltip != fields['service-name'];
                            });
                
                            services.push({
                                image: '/data/' + fields['service-name'] + '.png',
                                width: '50',
                                height: '50',
                                url: fields['service-url'],
                                target: '_top',
                                tooltip: fields['service-name']
                            });
                
                            fs.writeFile(dataPath, JSON.stringify(services, null, 4), (error) => {
                                // Could not write data file.
                                if (error) {
                                  self.emit('log', {
                                      message: 'Unable to write data file.',
                                      severity: 'error'
                                  });
                                  self.emit('data', {
                                      status: 404,
                                      data: new stream.Readable({
                                          read(size) {
                                              this.push(null);
                                          }
                                      }),
                                      type: 'text/plain'
                                  });
                                  return;
                                }
                    
                                // Send the main site index.
                                self.emit('log', {
                                    message: 'Added new service.',
                                    severity: 'info'
                                });
                                self.emit('data', {
                                    status: 200,
                                    data: fs.createReadStream(path.join(root, config.site.index)),
                                    type: 'text/html'
                                });
                    
                            });
                
                        });

                    });
                break;
            case 'remove':
                fs.realpath(path.join(root, 'data/data.json'), (error, dataPath) => {
                    // If the path does not exist, then return early.
                    if (error) {
                      self.emit('log', {
                          message: 'Unable to access data path: ' + dataPath,
                          severity: 'error'
                      });
                      self.emit('data', {
                          status: 404,
                          data: new stream.Readable({
                              read(size) {
                                  this.push(null);
                              }
                          }),
                          type: 'text/plain'
                      });
                      return;
                    }
                    
                    fs.readFile(dataPath, 'utf8', function(error, data) {
                        // Could not read data file.
                        if (error) {
                          self.emit('log', {
                              message: 'Unable to read data file.',
                              severity: 'error'
                          });
                          self.emit('data', {
                              status: 404,
                              data: new stream.Readable({
                                  read(size) {
                                      this.push(null);
                                  }
                              }),
                              type: 'text/plain'
                          });
                          return;
                        }
        
                        // Remove all specified services and their corresponding icon files.
                        var currentServices = JSON.parse(data);
                        var services = [];
                        currentServices.forEach(currentService => {
                            if([ properfields['remove-services'] ].some((service) => service === currentService.tooltip)) {
                                var imagePath = path.join(root, currentService.image);
                                fs.stat(imagePath, (error, stats) => {
                                    // Image file does not exist.
                                    if (error) {
                                        self.emit('log', {
                                            message: 'Image file does not exist.',
                                            severity: 'warning'
                                        });
                                        self.emit('data', {
                                            status: 404,
                                            data: new stream.Readable({
                                                read(size) {
                                                    this.push(null);
                                                }
                                            }),
                                            type: 'text/plain'
                                        });
                                        return;
                                    }
                                    fs.unlink(imagePath, (error) => {
                                        if(error) {
                                            self.emit('log', {
                                                message: 'Could not remove image file.',
                                                severity: 'warning'
                                            });
                                            self.emit('data', {
                                                status: 404,
                                                data: new stream.Readable({
                                                    read(size) {
                                                        this.push(null);
                                                    }
                                                }),
                                                type: 'text/plain'
                                            });
                                        }
                                    });
                                });
                                return;
                            }
                            services.push(currentService);
                        });
                        
                        // Write the data file back.
                        fs.writeFile(dataPath, JSON.stringify(services, null, 4), (error) => {
                            // Could not write data file.
                            if (error) {
                              self.emit('log', {
                                  message: 'Unable to write data file.',
                                  severity: 'error'
                              });
                              self.emit('data', {
                                  status: 404,
                                  data: new stream.Readable({
                                      read(size) {
                                          this.push(null);
                                      }
                                  }),
                                  type: 'text/plain'
                              });
                              return;
                            }
            
                            // Send the main site index.
                            self.emit('log', {
                                message: 'Removed services.',
                                severity: 'info'
                            });
                            self.emit('data', {
                                status: 200,
                                data: fs.createReadStream(path.join(root, config.site.index)),
                                type: 'text/html'
                            });
            
                        });
                    
                    });
                });
                break;
            default:
                self.emit('log', {
                    message: 'No such path.',
                    severity: 'error'
                });
                self.emit('data', {
                    status: 404,
                    data: new stream.Readable({
                        read(size) {
                            this.push(null);
                        }
                    }),
                    type: 'text/plain'
                });
                return;
        }
    });

    return this;
};

util.inherits(POST, EventEmitter);
util.inherits(Cache, EventEmitter);
module.exports = POST;

Generated by GNU Enscript 1.6.5.90.