public function scopeWhereAuthorIP($query, $ip) { $ip = new IP($ip); return $query->where('author_ip', $ip->toSQL()); }
/** * Generates a snapshot in the database for the previous hour. * * @param \Carbon\Carbon $carbon A timestamp within the 0-60 minute block that is to be snapshotted. * @return array of new \App\Stats */ public function createStatsSnapshot(\Carbon\Carbon $carbon) { $carbonStart = $carbon->minute(0)->second(0); $carbonEnd = clone $carbonStart; $carbonEnd = $carbonEnd->addHour()->minute(0)->second(0)->subSecond(); $posts = $this->posts()->withTrashed()->where('created_at', '>=', $carbonStart)->where('created_at', '<=', $carbonEnd)->select('post_id', 'author_ip', 'reply_to')->get(); if ($posts->count() === 0) { return collect([]); } // Unique IPs. $authorsUnique = []; // Unique Post IDs. $postsUnique = []; // Unique \16 ranges. $rangesUnique = []; // Unique Thread Post IDs. $threadsUnique = []; foreach ($posts as $post) { $postsUnique[$post->post_id] = true; if (is_null($post->reply_to)) { $threadsUnique[$post->post_id] = false; } if (!is_null($post->author_ip)) { $ip = new IP($post->author_ip); if (!isset($authorsUnique[$ip->toText()])) { $authorsUnique[$ip->toText()] = $ip->toLong(); $range = new IP("{$ip->getStart()}/16"); $rangesUnique[$range->getStart()] = $range->toLong(); } } } // Save uniques $statsRows = []; $uniques = ['authors' => array_values($authorsUnique), 'posts' => array_keys($postsUnique), 'ranges' => array_values($rangesUnique), 'threads' => array_keys($threadsUnique)]; foreach ($uniques as $statsKey => $uniqueValues) { $statsBits = []; foreach ($uniqueValues as $uniqueValue) { $statsBits[] = ['unique' => (int) $uniqueValue]; } $statsRow = $this->stats()->updateOrCreate(['stats_time' => $carbonStart, 'stats_type' => $statsKey], ['counter' => count($statsBits)]); if (!$statsRow->exists) { $statsRow->save(); } $statsRow->uniques()->createMany($statsBits); $statsRows[] = $statsRow; } return collect($statsRows); }
/** * */ public function putMod(Request $request, Board $board, Post $post) { if (!$post->exists) { abort(404); } // Take trailing arguments, // compare them against a list of real actions, // intersect the liss to find the true commands. $actions = ["delete", "ban", "all", "global"]; $argList = func_get_args(); $modActions = array_intersect($actions, array_splice($argList, 2)); sort($modActions); $ban = in_array("ban", $modActions); $delete = in_array("delete", $modActions); $all = in_array("all", $modActions); $global = in_array("global", $modActions); if (!$ban) { return abort(404); } $validator = Validator::make(Input::all(), ['raw_ip' => 'required|boolean', 'ban_ip' => 'required_if:raw_ip,true|ip', 'ban_ip_range' => 'required|between:0,128', 'justification' => 'max:255', 'expires_days' => 'required|integer|min:0|max:' . $this->option('banMaxLength'), 'expires_hours' => 'required|integer|min:0|max:23', 'expires_minutes' => 'required|integer|min:0|max:59']); if (!$validator->passes()) { return redirect()->back()->withInput(Input::all())->withErrors($validator->errors()); } $banLengthStr = []; $expiresDays = Input::get('expires_days'); $expiresHours = Input::get('expires_hours'); $expiresMinutes = Input::get('expires_minutes'); if ($expiresDays > 0) { $banLengthStr[] = "{$expiresDays}d"; } if ($expiresHours > 0) { $banLengthStr[] = "{$expiresHours}h"; } if ($expiresMinutes > 0) { $banLengthStr[] = "{$expiresMinutes}m"; } if ($expiresDays == 0 && $expiresHours == 0 && $expiresMinutes == 0) { $banLengthStr[] = "Ø"; } $banLengthStr = implode($banLengthStr, " "); // If we're banning without the ability to view IP addresses, we will get our address directly from the post in human-readable format. $banIpAddr = $this->user->canViewRawIP() ? Input::get('ban_ip') : $post->getAuthorIpAsString(); // The CIDR is passed from our post parameters. By default, it is 32/128 for IPv4/IPv6 respectively. $banCidr = Input::get('ban_ip_range'); // This generates a range from start to finish. I.E. 192.168.1.3/22 becomes [192.168.0.0, 192.168.3.255]. // If we just pass the CDIR into the construct, we get 192.168.1.3-129.168.3.255 for some reason. $banCidrRange = IP::cidr_to_range("{$banIpAddr}/{$banCidr}"); // We then pass this range into the construct method. $banIp = new IP($banCidrRange[0], $banCidrRange[1]); $ban = new Ban(); $ban->ban_ip_start = $banIp->getStart(); $ban->ban_ip_end = $banIp->getEnd(); $ban->seen = false; $ban->created_at = $ban->freshTimestamp(); $ban->updated_at = clone $ban->created_at; $ban->expires_at = clone $ban->created_at; $ban->expires_at = $ban->expires_at->addDays($expiresDays); $ban->expires_at = $ban->expires_at->addHours($expiresHours); $ban->expires_at = $ban->expires_at->addMinutes($expiresMinutes); $ban->mod_id = $this->user->user_id; $ban->post_id = $post->post_id; $ban->ban_reason_id = null; $ban->justification = Input::get('justification'); if ($global) { if ($ban && !$this->user->canBanGlobally() || $delete && !$this->user->canDeleteGlobally()) { return abort(403); } if ($ban) { $ban->board_uri = null; $ban->save(); } $this->log('log.post.ban.global', $post, ["board_id" => $post->board_id, "board_uri" => $post->board_uri, "ip" => $post->getAuthorIpAsString(), "justification" => $ban->justification, "time" => $banLengthStr]); if ($delete) { $posts = Post::ipBinary($post->author_ip); $this->log('log.post.ban.delete', $post, ["board_id" => $post->board_id, "board_uri" => $post->board_uri, "posts" => $posts->count()]); $posts->delete(); return redirect($board->board_uri); } } else { if ($ban && !$board->canBan($this->user) || $delete && !$board->canDelete($this->user)) { return abort(403); } if ($ban) { $ban->board_uri = $post->board_uri; $ban->save(); } $this->log('log.post.ban.local', $post, ["board_id" => $post->board_id, "board_uri" => $post->board_uri, "ip" => $post->getAuthorIpAsString(), "justification" => $ban->justification, "time" => $banLengthStr]); if ($delete) { if ($all) { $posts = Post::ipBinary($post->author_ip)->where('board_uri', $board->board_uri); $this->log('log.post.ban.delete', $post, ["board_id" => $post->board_id, "board_uri" => $post->board_uri, "posts" => $posts->count()]); $posts->delete(); return redirect($board->board_uri); } else { $this->log('log.post.ban.delete', $post, ["board_id" => $post->board_id, "board_uri" => $post->board_uri, "posts" => 1]); $post->delete(); } } } Event::fire(new PostWasBanned($post)); Event::fire(new PostWasModerated($post, $this->user)); return back(); }
public function scopeWhereIPInBan($query, $ip) { $ip = new IP($ip); return $query->where(function ($query) use($ip) { $query->where('ban_ip_start', '<=', $ip->toSQL()); $query->where('ban_ip_end', '>=', $ip->toSQL()); }); }
/** * 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; }
/** * Validate the class instance. * This overrides the default invocation to provide additional rules after the controller is setup. * * @return void */ public function validate() { $board = $this->board; $thread = $this->thread; $user = $this->user; $ip = new IP($this->ip()); $carbon = new \Carbon\Carbon(); $validator = $this->getValidatorInstance(); $messages = $validator->errors(); $isReply = $this->thread instanceof Post; if ($isReply) { $floodTime = site_setting('postFloodTime'); // Check global flood. $nextPostTime = Carbon::createFromTimestamp(Cache::get('last_post_for_' . $ip->toLong(), 0) + $floodTime); if ($nextPostTime->isFuture()) { $timeDiff = $nextPostTime->diffInSeconds() + 1; $messages->add("flood", trans_choice("validation.custom.post_flood", $timeDiff, ['time_left' => $timeDiff])); $this->failedValidation($validator); return; } } else { $floodTime = site_setting('threadFloodTime'); // Check global flood. $nextPostTime = Carbon::createFromTimestamp(Cache::get('last_thread_for_' . $ip->toLong(), 0) + $floodTime); if ($nextPostTime->isFuture()) { $timeDiff = $nextPostTime->diffInSeconds() + 1; $messages->add("flood", trans_choice("validation.custom.thread_flood", $timeDiff, ['time_left' => $timeDiff])); $this->failedValidation($validator); return; } } // Board-level setting validaiton. $validator->sometimes('captcha', "required|captcha", function ($input) use($board) { return !$board->canPostWithoutCaptcha($this->user); }); if (!$validator->passes()) { $this->failedValidation($validator); } else { if (!$this->user->canAdminConfig() && $board->canPostWithoutCaptcha($this->user)) { // Check last post time for flood. $floodTime = site_setting('postFloodTime'); if ($floodTime > 0) { $lastPost = Post::getLastPostForIP(); if ($lastPost) { $floodTimer = clone $lastPost->created_at; $floodTimer->addSeconds($floodTime); if ($floodTimer->isFuture()) { $messages->add("flood", trans("validation.custom.post_flood", ['time_left' => $floodTimer->diffInSeconds()])); $this->failedValidation($validator); return; } } } } // Validate individual files being uploaded right now. $this->validateOriginality(); } if (count($validator->errors())) { $this->failedValidation($validator); } else { if (!$this->passesAuthorization()) { $this->failedAuthorization(); } } }