Example #1
0
 private function read($address)
 {
     step();
     $position = $address * $this->fullLength;
     step();
     return $this->storage->read($position, $this->fullLength);
 }
Example #2
0
 private function write($address, $key, $value = 1)
 {
     $position = $address * $this->fullLength;
     $key = $this->fix($key, $this->length);
     $value = $this->fix($value, $this->addressLength);
     $this->storage->write($position, $key . $value);
 }
Example #3
0
 private function updateParent($address, $value)
 {
     $position = ($address + 1) * $this->fullLength - $this->addressLength * 3;
     if ($value < 0) {
         $this->topAddress = $address;
     }
     $value = $this->fix($value, $this->addressLength);
     $this->storage->write($position, $value);
 }
 /**
  * Run the migrations.
  *
  * @return void
  */
 public function up()
 {
     Schema::table('files', function (Blueprint $table) {
         $table->boolean('has_thumbnail')->default(false);
     });
     FileStorage::chunk(100, function ($files) {
         foreach ($files as $file) {
             if ($file->hasThumb()) {
                 $file->has_thumbnail = true;
                 $file->save();
             }
         }
     });
 }
Example #5
0
 public function validateFileIntegrity($attribute, $file, $parameters)
 {
     if ($file instanceof \Symfony\Component\HttpFoundation\File\UploadedFile) {
         switch ($file->getClientMimeType()) {
             // For some reason, MP3 files routinely get scanned as octet-streams.
             // Attempt to evaluate it as music or a video.
             case "application/octet-stream":
             case "audio/mpeg":
             case "audio/mp3":
             case "video/mp4":
             case "video/flv":
             case "video/webm":
                 return FileStorage::probe($file);
             case "application/x-shockwave-flash":
                 // This is much slower than exif_imagetype but much more reliable with flash files.
                 return getimagesize($file->getPathname())['mime'] == "application/x-shockwave-flash";
             case "image/x-ms-bmp":
                 return exif_imagetype($file->getPathname()) == IMAGETYPE_BMP;
             case "image/gif":
                 return exif_imagetype($file->getPathname()) == IMAGETYPE_GIF;
             case "image/jpeg":
             case "image/jpg":
                 return exif_imagetype($file->getPathname()) == IMAGETYPE_JPEG;
             case "image/png":
                 return exif_imagetype($file->getPathname()) == IMAGETYPE_PNG;
             case "image/svg":
             case "image/svg+xml":
                 try {
                     $dom = new \DOMDocument();
                     $dom->Load($file->getPathname());
                     if ($dom->getElementsByTagName('script')->length > 0) {
                         return false;
                     }
                     return $dom->saveXML() !== false;
                 } catch (\Exception $error) {
                     return false;
                 }
                 // Things we allow but can't validate.
             // Things we allow but can't validate.
             case "application/epub+zip":
             case "application/pdf":
                 return true;
             default:
                 return false;
         }
     }
     return false;
     dd($value);
 }
 /**
  * Manage media.
  *
  * @return void
  */
 protected function handleMediaFiles()
 {
     $this->comment("    Pruning media data...");
     $mediaOrphanLife = (int) Settings::get('epheMediaPrune', 0);
     if ($mediaOrphanLife) {
         $carbonLife = Carbon::now()->subDays($mediaOrphanLife);
         $files = FileStorage::whereOrphan()->where('last_uploaded_at', '<=', $carbonLife)->get();
         $affected = 0;
         foreach ($files as $file) {
             if ($file->hasFile()) {
                 ++$affected;
                 $file->deleteFile();
             }
         }
         $this->comment("      Pruned {$affected} file(s).");
     }
 }
 /**
  * Run the migrations.
  *
  * @return void
  */
 public function up()
 {
     Schema::table('files', function (Blueprint $table) {
         $table->integer('file_width')->nullable()->after('filesize');
         $table->integer('file_height')->nullable()->after('file_width');
         $table->integer('thumbnail_width')->nullable()->after('has_thumbnail');
         $table->integer('thumbnail_height')->nullable()->after('thumbnail_width');
     });
     FileStorage::where('has_thumbnail', true)->where('mime', 'like', 'image/%')->chunk(100, function ($files) {
         echo "\tMeasuring 100 images.\n";
         foreach ($files as $file) {
             $image = (new ImageManager())->make($file->getFullPath());
             $file->file_height = $image->height();
             $file->file_width = $image->width();
             $thumb = (new ImageManager())->make($file->getFullPathThumb());
             $file->thumbnail_height = $thumb->height();
             $file->thumbnail_width = $thumb->width();
             $file->save();
         }
     });
 }
 /**
  * Delivers an image.
  *
  * @param  string  $hash
  * @param  string  $filename
  * @param  boolean  $thumbnail
  * @return Response
  */
 public function getImage($hash = false, $filename = false, $thumbnail = false)
 {
     if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
         header('HTTP/1.1 304 Not Modified');
         die;
     }
     if ($hash !== false && $filename !== false) {
         $FileStorage = FileStorage::getHash($hash);
         $storagePath = !$thumbnail ? $FileStorage->getPath() : $FileStorage->getPathThumb();
         $storagePathFull = !$thumbnail ? $FileStorage->getFullPath() : $FileStorage->getFullPathThumb();
         $cacheTime = 315360000;
         /// 10 years
         if ($FileStorage instanceof FileStorage && Storage::exists($storagePath)) {
             $responseSize = Storage::size($storagePath);
             $responseHeaders = ['Cache-Control' => "public, max-age={$cacheTime}, pre-check={$cacheTime}", 'Expires' => gmdate(DATE_RFC1123, time() + $cacheTime), 'Last-Modified' => gmdate(DATE_RFC1123, File::lastModified($storagePathFull)), 'Content-Disposition' => "inline", 'Content-Length' => $responseSize, 'Content-Type' => $FileStorage->mime, 'Filename' => $filename];
             $response = Response::stream(function () use($storagePathFull) {
                 readfile($storagePathFull);
             }, 200, $responseHeaders);
             return $response;
         }
     }
     return abort(404);
 }
Example #9
0
 /**
  * Uploads a single file.
  *
  * @param  Request  $request
  * @param  Board  $board
  * @return json
  */
 public function putFile(Request $request, Board $board)
 {
     $input = Input::all();
     $rules = [];
     PostRequest::rulesForFiles($board, $rules);
     $rules['files'][] = "required";
     $validator = Validator::make($input, $rules);
     if (!$validator->passes()) {
         return json_encode(['errors' => $validator->errors()]);
     }
     $storage = new Collection();
     foreach ($input['files'] as $file) {
         $ip = new IP($request->ip());
         $uploadSize = (int) Cache::get("upstream_data_for_" . $ip->toLong(), 0);
         if ($uploadSize <= 52430000) {
             Cache::increment("upstream_data_for_" . $ip->toLong(), $file->getSize(), 2);
             $newStorage = FileStorage::storeUpload($file);
             $storage[$newStorage->hash] = $newStorage;
             Cache::decrement("upstream_data_for_" . $ip->toLong(), $file->getSize());
         } else {
             return abort(429);
         }
     }
     return $storage;
 }
Example #10
0
 /**
  * Pushes the post to the specified board, as a new thread or as a reply.
  * This autoatically handles concurrency issues. Creating a new reply without
  * using this method is forbidden by the `creating` event in ::boot.
  *
  *
  * @param  App\Board  &$board
  * @param  App\Post   &$thread
  * @return void
  */
 public function submitTo(Board &$board, &$thread = null)
 {
     $this->board_uri = $board->board_uri;
     $this->author_ip = new IP();
     $this->author_country = $board->getConfig('postsAuthorCountry', false) ? new Geolocation() : null;
     $this->reply_last = $this->freshTimestamp();
     $this->bumped_last = $this->reply_last;
     $this->setCreatedAt($this->reply_last);
     $this->setUpdatedAt($this->reply_last);
     if (!is_null($thread) && !$thread instanceof Post) {
         $thread = $board->getLocalThread($thread);
     }
     if ($thread instanceof Post) {
         $this->reply_to = $thread->post_id;
         $this->reply_to_board_id = $thread->board_id;
     }
     // Handle tripcode, if any.
     if (preg_match('/^([^#]+)?(##|#)(.+)$/', $this->author, $match)) {
         // Remove password from name.
         $this->author = $match[1];
         // Whether a secure tripcode was requested, currently unused.
         $secure_tripcode_requested = $match[2] == '##';
         // Convert password to tripcode, store tripcode hash in DB.
         $this->insecure_tripcode = ContentFormatter::formatInsecureTripcode($match[3]);
     }
     // Ensure we're using a valid flag.
     if (!$this->flag_id || !$board->hasFlag($this->flag_id)) {
         $this->flag_id = null;
     }
     // Store the post in the database.
     DB::transaction(function () use($board, $thread) {
         // The objective of this transaction is to prevent concurrency issues in the database
         // on the unique joint index [`board_uri`,`board_id`] which is generated procedurally
         // alongside the primary autoincrement column `post_id`.
         // First instruction is to add +1 to posts_total and set the last_post_at on the Board table.
         DB::table('boards')->where('board_uri', $this->board_uri)->increment('posts_total', 1, ['last_post_at' => $this->reply_last]);
         // Second, we record this value and lock the table.
         $boards = DB::table('boards')->where('board_uri', $this->board_uri)->lockForUpdate()->select('posts_total')->get();
         $posts_total = $boards[0]->posts_total;
         // Third, we store a unique checksum for this post for duplicate tracking.
         $board->checksums()->create(['checksum' => $this->getChecksum()]);
         // Optionally, we also expend the adventure.
         $adventure = BoardAdventure::getAdventure($board);
         if ($adventure) {
             $this->adventure_id = $adventure->adventure_id;
             $adventure->expended_at = $this->created_at;
             $adventure->save();
         }
         // We set our board_id and save the post.
         $this->board_id = $posts_total;
         $this->author_id = $this->makeAuthorId();
         $this->password = $this->makePassword($this->password);
         $this->save();
         // Optionally, the OP of this thread needs a +1 to reply count.
         if ($thread instanceof static) {
             // We're not using the Model for this because it fails under high volume.
             $threadNewValues = ['updated_at' => $thread->updated_at, 'reply_last' => $this->created_at, 'reply_count' => $thread->replies()->count(), 'reply_file_count' => $thread->replyFiles()->count()];
             if (!$this->isBumpless() && !$thread->isBumplocked()) {
                 $threadNewValues['bumped_last'] = $this->created_at;
             }
             DB::table('posts')->where('post_id', $thread->post_id)->update($threadNewValues);
         }
         // Queries and locks are handled automatically after this closure ends.
     });
     // Process uploads.
     $uploads = [];
     // Check file uploads.
     if (is_array($files = Input::file('files'))) {
         $uploads = array_filter($files);
         if (count($uploads) > 0) {
             foreach ($uploads as $uploadIndex => $upload) {
                 if (file_exists($upload->getPathname())) {
                     FileStorage::createAttachmentFromUpload($upload, $this);
                 }
             }
         }
     } else {
         if (is_array($files = Input::get('files'))) {
             $uniques = [];
             $hashes = $files['hash'];
             $names = $files['name'];
             $spoilers = isset($files['spoiler']) ? $files['spoiler'] : [];
             $storages = FileStorage::whereIn('hash', $hashes)->get();
             foreach ($hashes as $index => $hash) {
                 if (!isset($uniques[$hash])) {
                     $uniques[$hash] = true;
                     $storage = $storages->where('hash', $hash)->first();
                     if ($storage && !$storage->banned) {
                         $spoiler = isset($spoilers[$index]) ? $spoilers[$index] == 1 : false;
                         $upload = $storage->createAttachmentWithThis($this, $names[$index], $spoiler, false);
                         $upload->position = $index;
                         $uploads[] = $upload;
                     }
                 }
             }
             $this->attachmentLinks()->saveMany($uploads);
             FileStorage::whereIn('hash', $hashes)->increment('upload_count');
         }
     }
     // Finally fire event on OP, if it exists.
     if ($thread instanceof Post) {
         $thread->setRelation('board', $board);
         Event::fire(new ThreadNewReply($thread));
     }
     return $this;
 }
Example #11
0
 public function importInfinityPostAttachments(Post $model, Board &$board, $post)
 {
     $post_id = $model->post_id;
     if (!$post_id) {
         return 0;
     }
     $attachments = @json_decode($post->files, true);
     if (!is_array($attachments)) {
         return 0;
     }
     $aModels = [];
     foreach ($attachments as $aIndex => $attachment) {
         if (isset($attachment['error']) && $attachment['error']) {
             continue;
         }
         if (!isset($attachment['file_path']) || !isset($attachment['thumb_path'])) {
             continue;
         }
         $storage = null;
         $path = "{$this->targetLocation}/{$attachment['file_path']}";
         $thumb = "{$this->targetLocation}/{$attachment['thumb_path']}";
         if (file_exists($path)) {
             if (!isset($attachment['type'])) {
                 continue;
             }
             if (!isset($attachment['hash'])) {
                 $attachment['hash'] = md5(file_get_contents($path));
             }
             $storage = FileStorage::getHash($attachment['hash']);
             if (!$storage || (!file_exists($storage->getFullPath()) || !file_exists($storage->getFullPathThumb()))) {
                 $height = null;
                 $width = null;
                 if (isset($attachment['width']) && isset($attachment['height'])) {
                     $height = $attachment['height'];
                     $width = $attachment['width'];
                 }
                 if (!$storage) {
                     $storage = new FileStorage(['hash' => $attachment['hash'], 'banned' => false, 'filesize' => $attachment['size'], 'file_width' => $width, 'file_height' => $height, 'mime' => $attachment['type'], 'meta' => null, 'first_uploaded_at' => Carbon::now(), 'last_uploaded_at' => Carbon::now(), 'upload_count' => 1]);
                 }
                 Storage::makeDirectory($storage->getDirectory());
                 if (!file_exists($storage->getFullPath())) {
                     if (is_link($storage->getFullPath())) {
                         unlink($storage->getFullPath());
                     }
                     symlink($path, $storage->getFullPath());
                 }
                 if ($attachment['thumbwidth'] && file_exists($thumb)) {
                     $storage->has_thumbnail = true;
                     $storage->thumbnail_width = $attachment['thumbwidth'];
                     $storage->thumbnail_height = $attachment['thumbheight'];
                     Storage::makeDirectory($storage->getDirectoryThumb());
                     if (!file_exists($storage->getFullPathThumb())) {
                         if (is_link($storage->getFullPathThumb())) {
                             unlink($storage->getFullPathThumb());
                         }
                         symlink($thumb, $storage->getFullPathThumb());
                     }
                 }
                 $storage->save();
             }
             if ($storage && $storage->exists) {
                 $aModel = ['post_id' => $post_id, 'file_id' => $storage->file_id, 'filename' => $attachment['filename'], 'is_spoiler' => false, 'is_deleted' => false, 'position' => $aIndex];
                 $aModels[] = $aModel;
             } else {
                 ++$skips;
             }
         }
     }
     FileAttachment::insert($aModels);
     return count($aModels);
 }
 /**
  * Display existing assets.
  *
  * @return Response
  */
 public function putAssets(Request $request, Board $board)
 {
     if (!$board->canEditConfig($this->user)) {
         return abort(403);
     }
     $input = Input::all();
     $validator = Validator::make($input, ['asset_type' => ["required", "in:board_banner,file_deleted,file_none,file_spoiler"], 'new_board_banner' => ["required_if:asset_type,board_banner", "image", "image_size:<=300,<=100"]]);
     if (!$validator->passes()) {
         return redirect()->back()->withErrors($validator->errors());
     }
     // Fetch the asset.
     $upload = Input::file("new_{$input['asset_type']}");
     if (file_exists($upload->getPathname())) {
         $storage = FileStorage::storeUpload($upload);
         $asset = new BoardAsset();
         $asset->asset_type = "board_banner";
         $asset->board_uri = $board->board_uri;
         $asset->file_id = $storage->file_id;
         $asset->save();
     }
     return $this->view(static::VIEW_CONFIG, ['board' => $board, 'banners' => $board->getBanners(), 'tab' => "assets"]);
 }
 /**
  * Uploads a single file.
  *
  * @param  Request  $request
  * @param  Board  $board
  * @return json
  */
 public function putFile(Request $request, Board $board)
 {
     $input = Input::all();
     $rules = [];
     PostRequest::rulesForFiles($board, $rules);
     $rules['files'][] = "required";
     $validator = Validator::make($input, $rules);
     if (!$validator->passes()) {
         return json_encode(['errors' => $validator->errors()]);
     }
     $storage = new Collection();
     foreach ($input['files'] as $file) {
         $newStorage = FileStorage::storeUpload($file);
         $storage[$newStorage->hash] = $newStorage;
     }
     return $storage;
 }
Example #14
0
 /**
  * Delivers an image.
  *
  * @param  \App\FileAttachment  $attachment
  * @param  string  $filename
  * @param  boolean  $thumbnail
  * @return Response
  */
 public function getImage($hash = false, $filename = false, $thumbnail = false)
 {
     if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
         header('HTTP/1.1 304 Not Modified');
         die;
     }
     $FileStorage = null;
     if (is_string($hash) && is_string($filename)) {
         $FileStorage = FileStorage::getHash($hash);
     }
     if ($FileStorage instanceof FileStorage) {
         $storagePath = !$thumbnail ? $FileStorage->getPath() : $FileStorage->getPathThumb();
         $storagePathFull = !$thumbnail ? $FileStorage->getFullPath() : $FileStorage->getFullPathThumb();
         $cacheTime = 31536000;
         /// 1 year
         if ($thumbnail && (!file_exists($storagePathFull) || is_dir($storagePathFull))) {
             if (is_dir($storagePathFull)) {
                 unlink($storagePathFull);
             }
             $FileStorage->processThumb();
         }
         if (is_link($storagePathFull)) {
             if (!is_readable($storagePathFull)) {
                 abort(500, "Symlink file is unreadable.");
             }
             $storageExists = file_exists($storagePathFull);
         } else {
             $storageExists = Storage::exists($storagePath);
         }
         if ($storageExists) {
             ini_set("zlib.output_compression", "Off");
             $responseSize = filesize($storagePathFull);
             $responseCode = 200;
             $responseHeaders = ['Cache-Control' => "public, max-age={$cacheTime}, pre-check={$cacheTime}", 'Expires' => gmdate(DATE_RFC1123, time() + $cacheTime), 'Last-Modified' => gmdate(DATE_RFC1123, File::lastModified($storagePathFull)), 'Content-Disposition' => Request::get('disposition', "inline"), 'Content-Length' => $responseSize, 'Content-Type' => $FileStorage->mime, 'Filename' => urldecode($filename)];
             if ($thumbnail) {
                 if ($FileStorage->isImage()) {
                     $responseHeaders['Content-Type'] = Settings::get('attachmentThumbnailJpeg') ? "image/jpg" : "image/png";
                 } else {
                     if ($FileStorage->isVideo()) {
                         $responseHeaders['Content-Type'] = "image/jpg";
                     } else {
                         if ($FileStorage->isAudio()) {
                             $responseHeaders['Content-Type'] = "image/png";
                         }
                     }
                 }
             }
             // Determine if we can skip PHP content distribution.
             // This is hugely important.
             $xSendFile = false;
             // APACHE
             // Relies on the mod_xsendfile module.
             if (function_exists("apache_get_modules") && in_array("mod_xsendfile", apache_get_modules())) {
                 $xSendFile = true;
                 $responseHeaders['X-Sendfile'] = $storagePathFull;
             } else {
                 if (preg_match("/nginx\\/1(\\.[0-9]+)+/", $_SERVER['SERVER_SOFTWARE'])) {
                     $xSendFile = true;
                     $responseHeaders['X-Accel-Redirect'] = "/{$storagePath}";
                 } else {
                     if (preg_match("/lighttpd\\/1(\\.[0-9]+)+/", $_SERVER['SERVER_SOFTWARE'])) {
                         $xSendFile = true;
                         $responseHeaders['X-LIGHTTPD-send-file'] = $storagePathFull;
                     }
                 }
             }
             // Seek Audio and Video files.
             $responseStart = 0;
             $responseEnd = $responseSize - 1;
             if ($FileStorage->isVideo()) {
                 $responseHeaders['Accept-Ranges'] = "0-" . ($responseSize - 1);
                 if (isset($_SERVER['HTTP_RANGE'])) {
                     list(, $range) = explode('=', $_SERVER['HTTP_RANGE'], 2);
                     if (strpos($range, ',') !== false) {
                         return Response::make("Requested Range Not Satisfiable", 416, ['Content-Range' => "bytes {$responseStart}-{$responseEnd}/{$responseSize}"]);
                     }
                     if ($range == '-') {
                         $responseStart = $this->size - substr($range, 1);
                     } else {
                         $range = explode('-', $range);
                         $responseStart = $range[0];
                         $responseEnd = isset($range[1]) && is_numeric($range[1]) ? $range[1] : $responseEnd;
                     }
                     if ($responseStart > $responseEnd || $responseStart > $responseSize - 1 || $responseEnd >= $responseSize) {
                         return Response::make("Requested Range Not Satisfiable", 416, ['Content-Range' => "bytes {$responseStart}-{$responseEnd}/{$responseSize}"]);
                     }
                     $responseCode = 206;
                     $responseHeaders['Content-Length'] = $responseSize - $responseStart;
                     $responseHeaders['Content-Range'] = "bytes {$responseStart}-{$responseEnd}/{$responseSize}";
                     unset($responseHeaders['Accept-Ranges']);
                     unset($responseHeaders['Cache-Control']);
                     unset($responseHeaders['Content-Disposition']);
                     unset($responseHeaders['Expires']);
                 }
             }
             // Are we using the webserver to send files?
             if ($xSendFile) {
                 // Yes.
                 // Send an empty 200 response with the headers.
                 return Response::make("", $responseCode, $responseHeaders);
             } else {
                 // No.
                 // Get our hands dirty and stream the file.
                 return Response::make(function () use($storagePathFull, $responseStart, $responseEnd) {
                     if (!($responseStream = fopen($storagePathFull, 'rb'))) {
                         abort(500, "Could not open requested file.");
                     }
                     if ($responseStart > 0) {
                         fseek($responseStream, $responseStart);
                     }
                     $streamCurrent = 0;
                     while (!feof($responseStream) && $streamCurrent < $responseEnd && connection_status() == 0) {
                         echo fread($responseStream, min(1024 * 16, $responseEnd - $responseStart + 1));
                         $streamCurrent += 1024 * 16;
                     }
                     fclose($responseStream);
                 }, $responseCode, $responseHeaders);
             }
         }
     }
     return abort(404);
 }
Example #15
0
 /**
  * Delivers an image.
  *
  * @param  string  $hash
  * @param  string  $filename
  * @param  boolean  $thumbnail
  * @return Response
  */
 public function getImage($hash = false, $filename = false, $thumbnail = false)
 {
     if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
         header('HTTP/1.1 304 Not Modified');
         die;
     }
     if ($hash !== false && $filename !== false) {
         $FileStorage = FileStorage::getHash($hash);
         $storagePath = !$thumbnail ? $FileStorage->getPath() : $FileStorage->getPathThumb();
         $storagePathFull = !$thumbnail ? $FileStorage->getFullPath() : $FileStorage->getFullPathThumb();
         $cacheTime = 315360000;
         /// 10 years
         if ($FileStorage instanceof FileStorage && Storage::exists($storagePath)) {
             $responseSize = Storage::size($storagePath);
             $responseCode = 200;
             $responseHeaders = ['Cache-Control' => "public, max-age={$cacheTime}, pre-check={$cacheTime}", 'Expires' => gmdate(DATE_RFC1123, time() + $cacheTime), 'Last-Modified' => gmdate(DATE_RFC1123, File::lastModified($storagePathFull)), 'Content-Disposition' => "inline", 'Content-Length' => $responseSize, 'Content-Type' => $FileStorage->mime, 'Filename' => $filename];
             $responseStart = 0;
             $responseEnd = $responseSize - 1;
             if ($FileStorage->isVideo()) {
                 $responseHeaders['Accept-Ranges'] = "0-" . ($responseSize - 1);
                 if (isset($_SERVER['HTTP_RANGE'])) {
                     list(, $range) = explode('=', $_SERVER['HTTP_RANGE'], 2);
                     if (strpos($range, ',') !== false) {
                         return Response::make("Requested Range Not Satisfiable", 416, ['Content-Range' => "bytes {$responseStart}-{$responseEnd}/{$responseSize}"]);
                     }
                     if ($range == '-') {
                         $responseStart = $this->size - substr($range, 1);
                     } else {
                         $range = explode('-', $range);
                         $responseStart = $range[0];
                         $responseEnd = isset($range[1]) && is_numeric($range[1]) ? $range[1] : $responseEnd;
                     }
                     $responseEnd = $responseEnd > $responseEnd ? $responseEnd : $responseEnd;
                     if ($responseStart > $responseEnd || $responseStart > $responseSize - 1 || $responseEnd >= $responseSize) {
                         return Response::make("Requested Range Not Satisfiable", 416, ['Content-Range' => "bytes {$responseStart}-{$responseEnd}/{$responseSize}"]);
                     }
                     $responseCode = 206;
                     $responseHeaders['Content-Length'] = $responseSize - $responseStart;
                     $responseHeaders['Content-Range'] = "bytes {$responseStart}-{$responseEnd}/{$responseSize}";
                     unset($responseHeaders['Accept-Ranges']);
                     unset($responseHeaders['Cache-Control']);
                     unset($responseHeaders['Content-Disposition']);
                     unset($responseHeaders['Expires']);
                 }
             }
             return Response::stream(function () use($storagePathFull, $responseStart, $responseEnd) {
                 if (!($responseStream = fopen($storagePathFull, 'rb'))) {
                     abort(500, "Could not open requested file.");
                 }
                 if ($responseStart > 0) {
                     fseek($responseStream, $responseStart);
                 }
                 $streamCurrent = 0;
                 while (!feof($responseStream) && $streamCurrent < $responseEnd && connection_status() == 0) {
                     echo fread($responseStream, min(1024 * 16, $responseEnd - $responseStart + 1));
                     $streamCurrent += 1024 * 16;
                 }
                 fclose($responseStream);
             }, $responseCode, $responseHeaders);
         }
     }
     return abort(404);
 }
 /**
  * Add new assets.
  *
  * @return Response
  */
 public function putAssets(Request $request, Board $board)
 {
     if (!$board->canEditConfig($this->user)) {
         return abort(403);
     }
     if (!!Input::get('delete', false)) {
         return $this->deleteAssets($request, $board);
     }
     $input = Input::all();
     $assetType = Input::get('asset_type', false);
     $validator = Validator::make($input, ['asset_type' => ["required", "in:board_banner,board_banned,board_icon,file_deleted,file_spoiler"], 'new_board_banned' => ["required_if:asset_type,board_banned", "image", "image_size:100-500", "max:250"], 'new_board_banner' => ["required_if:asset_type,board_banner", "image", "image_size:<=300,<=100", "max:1024"], 'new_board_icon' => ["required_if:asset_type,board_icon", "image", "image_aspect:1", "image_size:64,64", "max:50"], 'new_file_deleted' => ["required_if:asset_type,file_deleted", "image", "image_size:100-500", "max:250"], 'new_file_spoiler' => ["required_if:asset_type,file_spoiler", "image", "image_size:100-500", "max:250"]]);
     if (!$validator->passes()) {
         return redirect()->back()->withErrors($validator->errors());
     }
     // Fetch the asset.
     $upload = Input::file("new_{$input['asset_type']}");
     $multiples = $assetType == "board_banner" || $assetType == "board_banned";
     if (file_exists($upload->getPathname())) {
         $storage = FileStorage::storeUpload($upload);
         if ($storage->exists) {
             if (!$multiples) {
                 $assets = $board->assets()->with('storage')->where('asset_type', $input['asset_type'])->get();
                 foreach ($assets as $asset) {
                     $asset->delete();
                     $asset->storage->challengeExistence();
                 }
             }
             $asset = new BoardAsset();
             $asset->asset_type = $input['asset_type'];
             $asset->board_uri = $board->board_uri;
             $asset->file_id = $storage->file_id;
             $asset->save();
         } else {
             return redirect()->back()->withErrors(["validation.custom.file_generic"]);
         }
     }
     Event::fire(new BoardWasModified($board));
     return $this->getAssets($board);
 }
 /**
  * Add new assets.
  *
  * @return Response
  */
 public function putAssets(Request $request, Board $board)
 {
     if (!$board->canEditConfig($this->user)) {
         return abort(403);
     }
     if (!!Input::get('delete', false)) {
         return $this->deleteAssets($request, $board);
     }
     $input = Input::all();
     $assetType = Input::get('asset_type', false);
     $validator = Validator::make($input, ['asset_type' => ["required", "in:board_banner,board_banned,board_icon,board_flags,file_deleted,file_spoiler"], 'new_board_banned' => ["required_if:asset_type,board_banned", "image", "image_size:100-500", "max:250"], 'new_board_banner' => ["required_if:asset_type,board_banner", "image", "image_size:<=300,<=100", "max:1024"], 'new_board_flags' => ["required_if:asset_type,board_flags", "array", "min:1", "max:500"], 'new_board_icon' => ["required_if:asset_type,board_icon", "image", "image_aspect:1", "image_size:64,64", "max:50"], 'new_file_deleted' => ["required_if:asset_type,file_deleted", "image", "image_size:100-500", "max:250"], 'new_file_spoiler' => ["required_if:asset_type,file_spoiler", "image", "image_size:100-500", "max:250"]]);
     if (!$validator->passes()) {
         return redirect()->back()->withErrors($validator->errors());
     }
     // Fetch the asset.
     $multiples = $assetType == "board_banner" || $assetType == "board_banned" || $assetType == "board_flags";
     if ($assetType == "board_flags") {
         $new = $input["new_{$input['asset_type']}"];
         $names = isset($new['name']) ? $new['name'] : [];
         $uploads = isset($new['file']) ? $new['file'] : [];
         $rules = [];
         $nameRules = ["required", "string", "between:1,128"];
         $imageRules = array_merge(["required"], BoardAsset::getRulesForFlags($board));
         foreach (range(0, count($uploads) - 1) as $index) {
             $rules["name.{$index}"] = $nameRules;
             $rules["file.{$index}"] = $imageRules;
         }
         $validator = Validator::make($new, $rules);
         if (!$validator->passes()) {
             return redirect()->back()->withErrors($validator->errors());
         }
     } else {
         $uploads = [Input::file("new_{$input['asset_type']}")];
     }
     foreach ((array) $uploads as $index => $upload) {
         if (file_exists($upload->getPathname())) {
             $storage = FileStorage::storeUpload($upload);
             if ($storage->exists) {
                 if (!$multiples) {
                     $assets = $board->assets()->with('storage')->where('asset_type', $input['asset_type'])->get();
                     foreach ($assets as $asset) {
                         $asset->delete();
                         $asset->storage->challengeExistence();
                     }
                 }
                 $asset = new BoardAsset();
                 $asset->asset_type = $input['asset_type'];
                 $asset->asset_name = isset($names[$index]) ? $names[$index] : null;
                 $asset->board_uri = $board->board_uri;
                 $asset->file_id = $storage->file_id;
                 $asset->save();
             } else {
                 return redirect()->back()->withErrors(["validation.custom.file_generic"]);
             }
         }
     }
     Event::fire(new BoardWasModified($board));
     return $this->getAssets($board);
 }
Example #18
0
 /**
  * Pushes the post to the specified board, as a new thread or as a reply.
  * This autoatically handles concurrency issues. Creating a new reply without
  * using this method is forbidden by the `creating` event in ::boot.
  *
  *
  * @param  App\Board  &$board
  * @param  App\Post   &$thread
  * @return void
  */
 public function submitTo(Board &$board, &$thread = null)
 {
     $this->board_uri = $board->board_uri;
     $this->author_ip = inet_pton(Request::ip());
     // Hash the salt because I honestly don't know if $this->save() is safe enough to parse unsanized information into.
     $this->author_salt = hash(env('APP_HASH'), Cookie::get('author_salt'));
     $this->author_country = $board->getConfig('postsAuthorCountry', false) ? new Geolocation() : null;
     $this->reply_last = $this->freshTimestamp();
     $this->bumped_last = $this->reply_last;
     $this->setCreatedAt($this->reply_last);
     $this->setUpdatedAt($this->reply_last);
     if (!is_null($thread) && !$thread instanceof Post) {
         $thread = $board->getLocalThread($thread);
         $this->reply_to = $thread->post_id;
         $this->reply_to_board_id = $thread->board_id;
     }
     // Handle tripcode, if any.
     if (preg_match('/^([^#]+)?(##|#)(.+)$/', $this->author, $match)) {
         // Remove password from name.
         $this->author = $match[1];
         // Whether a secure tripcode was requested, currently unused.
         $secure_tripcode_requested = $match[2] == '##';
         // Convert password to tripcode, store tripcode hash in DB.
         $this->insecure_tripcode = ContentFormatter::formatInsecureTripcode($match[3]);
     }
     // Store the post in the database.
     DB::transaction(function () use($board, $thread) {
         // The objective of this transaction is to prevent concurrency issues in the database
         // on the unique joint index [`board_uri`,`board_id`] which is generated procedurally
         // alongside the primary autoincrement column `post_id`.
         // First instruction is to add +1 to posts_total and set the last_post_at on the Board table.
         DB::table('boards')->where('board_uri', $this->board_uri)->increment('posts_total');
         DB::table('boards')->where('board_uri', $this->board_uri)->update(['last_post_at' => $this->created_at]);
         // Second, we record this value and lock the table.
         $boards = DB::table('boards')->where('board_uri', $this->board_uri)->lockForUpdate()->select('posts_total')->get();
         $posts_total = $boards[0]->posts_total;
         // Optionally, the OP of this thread needs a +1 to reply count.
         if ($thread instanceof Post) {
             if (!$this->isBumpless() && !$thread->isBumplocked()) {
                 $thread->bumped_last = $this->created_at;
                 // We explicitly set the updated_at to what it is now.
                 // If we didn't, this would change.
                 // We don't want that because it screws up the API and
                 // makes it think the OP post has had its content edited.
                 $thread->updated_at = $thread->updated_at;
             }
             $thread->reply_last = $this->created_at;
             $thread->reply_count += 1;
             $thread->save();
         }
         // Optionally, we also expend the adventure.
         $adventure = BoardAdventure::getAdventure($board);
         if ($adventure) {
             $this->adventure_id = $adventure->adventure_id;
             $adventure->expended_at = $this->created_at;
             $adventure->save();
         }
         // Finally, we set our board_id and save.
         $this->board_id = $posts_total;
         //$this->author_salt =
         $this->author_id = $this->makeAuthorId();
         $this->save();
         // Queries and locks are handled automatically after this closure ends.
     });
     // Process uploads.
     $uploads = [];
     // Check file uploads.
     if (is_array($files = Input::file('files'))) {
         $uploads = array_filter($files);
         if (count($uploads) > 0) {
             foreach ($uploads as $uploadIndex => $upload) {
                 if (file_exists($upload->getPathname())) {
                     FileStorage::createAttachmentFromUpload($upload, $this);
                 }
             }
         }
     } else {
         if (is_array($files = Input::get('files'))) {
             $uniques = [];
             $hashes = $files['hash'];
             $names = $files['name'];
             $spoilers = isset($files['spoiler']) ? $files['spoiler'] : [];
             $storages = FileStorage::whereIn('hash', $hashes)->get();
             foreach ($hashes as $index => $hash) {
                 if (!isset($uniques[$hash])) {
                     $uniques[$hash] = true;
                     $storage = $storages->where('hash', $hash)->first();
                     if ($storage && !$storage->banned) {
                         $spoiler = isset($spoilers[$index]) ? $spoilers[$index] == 1 : false;
                         $uploads[] = $storage->createAttachmentWithThis($this, $names[$index], $spoiler, false);
                     }
                 }
             }
             $this->attachmentLinks()->saveMany($uploads);
             FileStorage::whereIn('hash', $hashes)->increment('upload_count');
         }
     }
     // Finally fire event on OP, if it exists.
     if ($thread instanceof Post) {
         Event::fire(new ThreadNewReply($thread));
     }
 }
 protected function validateOriginality()
 {
     $board = $this->board;
     $thread = $this->thread;
     $user = $this->user;
     $input = $this->all();
     $validated = true;
     $validator = $this->getValidatorInstance();
     $messages = $validator->errors();
     // Process uploads.
     if (isset($input['files'])) {
         $uploads = $input['files'];
         if (count($uploads) > 0) {
             // Standard upload originality and integrity checks.
             if (!$this->dropzone) {
                 foreach ($uploads as $uploadIndex => $upload) {
                     // If a file is uploaded that has a specific filename, it breaks the process.
                     if (method_exists($upload, "getPathname") && !file_exists($upload->getPathname())) {
                         $validated = false;
                         $messages->add("files.{$uploadIndex}", trans("validation.custom.file_corrupt", ["filename" => $upload->getClientOriginalName()]));
                     }
                 }
             }
             if ($board->getConfig('originalityImages')) {
                 foreach ($uploads as $uploadIndex => $upload) {
                     if (!$upload instanceof UploadedFile) {
                         continue;
                     }
                     if ($board->getConfig('originalityImages') == "thread") {
                         if ($thread instanceof Post && ($originalPost = FileStorage::checkUploadExists($upload, $board, $thread))) {
                             $validated = false;
                             $messages->add("files.{$uploadIndex}", trans("validation.custom.unoriginal_image_thread", ["filename" => $upload->getClientOriginalName(), "url" => $originalPost->getURL()]));
                         }
                     } else {
                         if ($originalPost = FileStorage::checkUploadExists($upload, $board)) {
                             $validated = false;
                             $messages->add("files.{$uploadIndex}", trans("validation.custom.unoriginal_image_board", ["filename" => $upload->getClientOriginalName(), "url" => $originalPost->getURL()]));
                         }
                     }
                 }
             }
         } else {
             if ($board->getConfig('originalityImages')) {
                 foreach ($uploads['hash'] as $uploadIndex => $upload) {
                     if ($board->getConfig('originalityImages') == "thread") {
                         if ($thread instanceof Post && ($originalPost = FileStorage::checkHashExists($upload, $board, $thread))) {
                             $validated = false;
                             $messages->add("files.{$uploadIndex}", trans("validation.custom.unoriginal_image_thread", ["filename" => $uploads['name'][$uploadIndex], "url" => $originalPost->getURL()]));
                         }
                     } else {
                         if ($originalPost = FileStorage::checkHashExists($upload, $board)) {
                             $validated = false;
                             $messages->add("files.{$uploadIndex}", trans("validation.custom.unoriginal_image_board", ["filename" => $uploads['name'][$uploadIndex], "url" => $originalPost->getURL()]));
                         }
                     }
                 }
             }
         }
     }
     // Process body checksum for origianlity.
     $strictness = $board->getConfig('originalityPosts');
     if (isset($input['body']) && $strictness) {
         $checksum = Post::makeChecksum($input['body']);
         if ($strictness == "board" || $strictness == "boardr9k") {
             $checksums = PostChecksum::getChecksum($checksum, $board);
         } else {
             if ($strictness == "site" || $strictness == "siter9k") {
                 $checksums = PostChecksum::getChecksum($checksum);
             }
         }
         //dd($checksums);
         if ($checksums->count()) {
             $validated = false;
             $messages->add("body", trans("validation.custom.unoriginal_content"));
             // If we are in R9K mode, set $respectTheRobot property to to false.
             // This will trigger a Robot ban in failedValidation.
             $this->respectTheRobot = !($strictness == "boardr9k" || $strictness == "siter9k");
         }
     }
     if ($validated !== true) {
         $this->failedValidation($validator);
         return;
     }
 }
Example #20
0
 /**
  * Pushes the post to the specified board, as a new thread or as a reply.
  * This autoatically handles concurrency issues. Creating a new reply without
  * using this method is forbidden by the `creating` event in ::boot.
  *
  *
  * @param  App\Board  &$board
  * @param  App\Post   &$thread
  * @return void
  */
 public function submitTo(Board &$board, &$thread = null)
 {
     $this->board_uri = $board->board_uri;
     $this->author_ip = inet_pton(Request::ip());
     $this->reply_last = $this->freshTimestamp();
     $this->bumped_last = $this->reply_last;
     $this->setCreatedAt($this->reply_last);
     $this->setUpdatedAt($this->reply_last);
     if (!is_null($thread) && !$thread instanceof Post) {
         $thread = $board->getLocalThread($thread);
         $this->reply_to = $thread->post_id;
         $this->reply_to_board_id = $thread->board_id;
     }
     // Store the post in the database.
     DB::transaction(function () use($board, $thread) {
         // The objective of this transaction is to prevent concurrency issues in the database
         // on the unique joint index [`board_uri`,`board_id`] which is generated procedurally
         // alongside the primary autoincrement column `post_id`.
         // First instruction is to add +1 to posts_total and set the last_post_at on the Board table.
         DB::table('boards')->where('board_uri', $this->board_uri)->increment('posts_total');
         DB::table('boards')->where('board_uri', $this->board_uri)->update(['last_post_at' => $this->created_at]);
         // Second, we record this value and lock the table.
         $boards = DB::table('boards')->where('board_uri', $this->board_uri)->lockForUpdate()->select('posts_total')->get();
         $posts_total = $boards[0]->posts_total;
         // Optionally, the OP of this thread needs a +1 to reply count.
         if ($thread instanceof Post) {
             if (!$this->isBumpless() && !$thread->isBumplocked()) {
                 $thread->bumped_last = $this->created_at;
                 // We explicitly set the updated_at to what it is now.
                 // If we didn't, this would change.
                 // We don't want that because it screws up the API and
                 // makes it think the OP post has had its content edited.
                 $thread->updated_at = $thread->updated_at;
             }
             $thread->reply_last = $this->created_at;
             $thread->reply_count += 1;
             $thread->save();
         }
         // Optionally, we also expend the adventure.
         $adventure = BoardAdventure::getAdventure($board);
         if ($adventure) {
             $this->adventure_id = $adventure->adventure_id;
             $adventure->expended_at = $this->created_at;
             $adventure->save();
         }
         // Finally, we set our board_id and save.
         $this->board_id = $posts_total;
         $this->author_id = $this->makeAuthorId();
         $this->save();
         // Queries and locks are handled automatically after this closure ends.
     });
     // Process uploads.
     $uploads = [];
     if (is_array($files = Input::file('files'))) {
         $uploads = array_filter($files);
     }
     if (count($uploads) > 0) {
         foreach ($uploads as $uploadIndex => $upload) {
             if (file_exists($upload->getPathname())) {
                 FileStorage::createAttachment($upload, $this);
             }
         }
     }
     // Finally fire event on OP, if it exists.
     if ($thread instanceof Post) {
         Event::fire(new ThreadNewReply($thread));
     }
 }
Example #21
0
 /**
  * Pushes the post to the specified board, as a new thread or as a reply.
  * This autoatically handles concurrency issues. Creating a new reply without
  * using this method is forbidden by the `creating` event in ::boot.
  *
  *
  * @param  App\Board  &$board
  * @param  App\Post   &$thread
  * @return void
  */
 public function submitTo(Board &$board, &$thread = null)
 {
     $this->board_uri = $board->board_uri;
     $this->author_ip = Request::getClientIp();
     $this->reply_last = $this->freshTimestamp();
     $this->bumped_last = $this->reply_last;
     $this->setCreatedAt($this->reply_last);
     $this->setUpdatedAt($this->reply_last);
     if (!is_null($thread) && !$thread instanceof Post) {
         $thread = $board->getLocalThread($thread);
         $this->reply_to = $thread->post_id;
         $this->reply_to_board_id = $thread->board_id;
     }
     // Store attachments
     $uploads = [];
     if (is_array($files = Input::file('files'))) {
         $uploads = array_filter($files);
     }
     // Store the post in the database.
     DB::transaction(function () use($thread) {
         // The objective of this transaction is to prevent concurrency issues in the database
         // on the unique joint index [`board_uri`,`board_id`] which is generated procedurally
         // alongside the primary autoincrement column `post_id`.
         // First instruction is to add +1 to posts_total.
         DB::table('boards')->where('board_uri', $this->board_uri)->increment('posts_total');
         // Second, we record this value and lock the table.
         $boards = DB::table('boards')->where('board_uri', $this->board_uri)->lockForUpdate()->select('posts_total')->get();
         $posts_total = $boards[0]->posts_total;
         // Optionally, the OP of this thread needs a +1 to reply count.
         if ($thread instanceof Post) {
             if (!$this->isBumpless() && !$thread->isBumplocked()) {
                 $thread->bumped_last = $this->created_at;
             }
             $thread->reply_last = $this->created_at;
             $thread->reply_count += 1;
             $thread->save();
         }
         // Finally, we set our board_id and save.
         $this->board_id = $posts_total;
         $this->save();
         // Queries and locks are handled automatically after this closure ends.
     });
     // Process uploads.
     if (count($uploads) > 0) {
         foreach ($uploads as $uploadIndex => $upload) {
             if (file_exists($upload->getPathname())) {
                 $uploadName = urlencode($upload->getClientOriginalName());
                 $uploadExt = pathinfo($uploadName, PATHINFO_EXTENSION);
                 $fileName = basename($uploadName, "." . $uploadExt);
                 $fileExt = $upload->guessExtension();
                 $storage = FileStorage::storeUpload($upload);
                 $attachment = new FileAttachment();
                 $attachment->post_id = $this->post_id;
                 $attachment->file_id = $storage->file_id;
                 $attachment->filename = urlencode("{$fileName}.{$fileExt}");
                 $attachment->save();
             }
         }
     }
     // Finally fire event on OP, if it exists.
     if ($thread instanceof Post) {
         Event::fire(new ThreadNewReply($thread));
     }
 }