<commit_before>class ApiController < ApplicationController
  before_action :verify_key, :except => [:filter_generator]
  before_action :set_pagesize, :except => [:filter_generator]
  before_action :verify_write_token, :only => [:create_feedback, :report_post]
  skip_before_action :verify_authenticity_token, :only => [:create_feedback, :report_post]

  # Yes, this looks bad, but it actually works as a cache - we only have to calculate the bitstring for each filter once.
  @@filters = Hash.new { |h, k| h[k] = k.chars.map { |c| c.ord.to_s(2).rjust(8, '0') }.join('') }

  # Routes for developer use

  def filter_generator
  end

  # Read routes: Posts

  def posts
    filter = "\x00\x00\x00\x00\x00\x00\x00\x03sc\xc2\x80\x00\x00\x00\x00\x00"
    @posts = Post.where(:id => params[:ids].split(";")).select(select_fields(filter)).order(:id => :desc)
    @results = @posts.paginate(:page => params[:page], :per_page => @pagesize)
    @more = has_more?(params[:page], @results.count)
    render :formats => :json
  end

  def posts_by_feedback
    filter = "\x00\x00\x00\x00\x00\x00\x00\x03sc\xc2\x80\x00\x00\x00\x00\x00"
    @posts = Post.all.joins(:feedbacks).where(:feedbacks => { :feedback_type => params[:type] }).select(select_fields(filter)).order(:id => :desc)
    results = @posts.paginate(:page => params[:page], :per_page => @pagesize)
    render :json => { :items => results, :has_more => has_more?(params[:page], results.count) }
  end

  def posts_by_url
    filter = "\x00\x00\x00\x00\x00\x00\x00\x03sc\xc2\x80\x00\x00\x00\x00\x00"
    @posts = Post.where(:link => params[:urls].split(";")).select(select_fields(filter)).order(:id => :desc)
    results = @posts.paginate(:page => params[:page], :per_page => @pagesize)
    render :json => { :items => results, :has_more => has_more?(params[:page], results.count) }
  end

  def posts_by_site
    filter = "\x00\x00\x00\x00\x00\x00\x00\x03sc\xc2\x80\x00\x00\x00\x00\x00"
    @posts = Post.joins(:site).where(:sites => { :site_url => params[:site] }).select(select_fields(filter)).order(:id => :desc)
    results = @posts.paginate(:page => params[:page], :per_page => @pagesize)
    render :json => { :items => results, :has_more => has_more?(params[:page], results.count) }
  end

  def posts_by_daterange
    filter = "\x00\x00\x00\x00\x00\x00\x00\x03sc\xc2\x80\x00\x00\x00\x00\x00"
    @posts = Post.where(:created_at => DateTime.strptime(params[:from_date], '%s')..DateTime.strptime(params[:to_date], '%s'))
    results = @posts.select(select_fields(filter)).order(:id => :desc).paginate(:page => params[:page], :per_page => @pagesize)
    render :json => { :items => results, :has_more => has_more?(params[:page], results.count) }
  end

  def undeleted_posts
    filter = "\x00\x00\x00\x00\x00\x00\x00\x03sc\xc2\x80\x00\x00\x00\x00\x00"
    @posts = Post.left_outer_joins(:deletion_logs).where(:deletion_logs => { :id => nil }).select(select_fields(filter))
    results = @posts.order(:id => :desc).paginate(:page => params[:page], :per_page => @pagesize)
    render :json => { :items => results, :has_more => has_more?(params[:page], results.count) }
  end

  def post_feedback
    filter = "\x00\x00\x00\x00\xc2\xbd\x19\xc2\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00"
    @feedbacks = Feedback.where(:post_id => params[:id]).select(select_fields(filter)).order(:id => :desc)
    results = @feedbacks.paginate(:page => params[:page], :per_page => @pagesize)
    render :json => { :items => results, :has_more => has_more?(params[:page], results.count) }
  end

  def post_reasons
    filter = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x18\x00\x00\x00\x00\x00"
    @reasons = Reason.joins(:posts_reasons).where(:posts_reasons => { :post_id => params[:id] }).select(select_fields(filter)).order(:id => :desc)
    results = @reasons.paginate(:page => params[:page], :per_page => @pagesize)
    render :json => { :items => results, :has_more => has_more?(params[:page], results.count) }
  end

  def post_valid_feedback
    @post = Post.find params[:id]
    render :formats => :json
  end

  def search_posts
    filter = "\x00\x00\x00\x00\x00\x00\x00\x03sc\xc2\x80\x00\x00\x00\x00\x00"
    @posts = Post.all
    if params[:feedback_type].present?
      @posts = @posts.includes(:feedbacks).where(:feedbacks => { :feedback_type => params[:feedback_type] })
    end
    if params[:site].present?
      @posts = @posts.joins(:site).where(:sites => { :site_domain => params[:site] })
    end
    if params[:from_date].present?
      @posts = @posts.where('`posts`.`created_at` > ?', DateTime.strptime(params[:from_date], '%s'))
    end
    if params[:to_date].present?
      @posts = @posts.where('`posts`.`created_at` < ?', DateTime.strptime(params[:to_date], '%s'))
    end
    results = @posts.select(select_fields(filter)).order(:id => :desc).paginate(:page => params[:page], :per_page => @pagesize)
    render :json => { :items => results, :has_more => has_more?(params[:page], results.count) }
  end

  # Read routes: Reasons

  def reasons
    filter = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x18\x00\x00\x00\x00\x00"
    @reasons = Reason.where(:id => params[:ids].split(";")).select(select_fields(filter)).order(:id => :desc)
    results = @reasons.paginate(:page => params[:page], :per_page => @pagesize)
    render :json => { :items => results, :has_more => has_more?(params[:page], results.count) }
  end

  def reason_posts
    filter = "\x00\x00\x00\x00\x00\x00\x00\x03sc\xc2\x80\x00\x00\x00\x00\x00"
    @posts = Post.joins(:posts_reasons).where(:posts_reasons => { :reason_id => params[:id] }).select(select_fields(filter)).order(:id => :desc)
    results = @posts.paginate(:page => params[:page], :per_page => @pagesize)
    render :json => { :items => results, :has_more => has_more?(params[:page], results.count) }
  end

  # Read routes: BlacklistedWebsites

  def blacklisted_websites
    @websites = BlacklistedWebsite.active
    results = @websites.order(:id => :desc).paginate(:page => params[:page], :per_page => @pagesize)
    render :json => { :items => results, :has_more => has_more?(params[:page], results.count) }
  end

  # Read routes: Users

  def users_with_code_privs
    chat_ids = User.code_admins.pluck(:stackexchange_chat_id, :stackoverflow_chat_id, :meta_stackexchange_chat_id)

    items = {}
    ["stackexchange_chat_ids", "stackoverflow_chat_ids", "meta_stackexchange_chat_ids"].each_with_index do |name, index|
      items[name] = chat_ids.map { |a| a[index] }.select { |n| n.present? }
    end

    render :json => { :items => items }
  end

  # Read routes: Status

  def current_status
    render :json => { :last_ping => SmokeDetector.where.not(:location => params[:except]).maximum(:last_ping) }
  end

  # Write routes

  def create_feedback
    @post = Post.find params[:id]
    @feedback = Feedback.new(:user => @user, :post => @post, :api_key => @key)
    @feedback.feedback_type = params[:type]
    if @feedback.save
      if @feedback.is_positive?
        begin
          ActionCable.server.broadcast "smokedetector_messages", { blacklist: { uid: @post.stack_exchange_user.user_id.to_s, site: URI.parse(@post.stack_exchange_user.site.site_url).host, post: @post.link } }
        rescue
        end
      elsif @feedback.is_naa?
        begin
          ActionCable.server.broadcast "smokedetector_messages", { naa: { post_link: @post.link } }
        rescue
        end
      elsif @feedback.is_negative?
        begin
          ActionCable.server.broadcast "smokedetector_messages", { fp: { post_link: @post.link } }
        rescue
        end
      end
      unless Feedback.where(:post_id => @post.id, :feedback_type => @feedback.feedback_type).where.not(:id => @feedback.id).exists?
        ActionCable.server.broadcast "smokedetector_messages", { message: "#{@feedback.feedback_type} by #{@user.username}" + (@post.id == Post.last.id ? "" : " on [#{@post.title}](#{@post.link}) \[[MS](#{url_for(:controller => :posts, :action => :show, :id => @post.id)})]") }
      end
      render :json => @post.feedbacks, :status => 201
    else
      render :status => 500, :json => { :error_name => "failed", :error_code => 500, :error_message => "Feedback object failed to save." }
    end
  end

  def report_post
    # We don't create any posts here, just send them on to Smokey to do all the processing
    ActionCable.server.broadcast "smokedetector_messages", { report: { :user => @user.username, :post_link => params[:post_link] } }

    render :plain => "OK", :status => 201
  end

  private
    def verify_key
      @key = ApiKey.find_by_key(params[:key])
      unless params[:key].present? && @key.present?
        smokey = SmokeDetector.find_by_access_token(params[:key])
        unless smokey.present?
          render :status => 403, :json => { :error_name => "unauthenticated", :error_code => 403, :error_message => "No key was passed or the passed key is invalid." } and return
        end
      end
    end<commit_msg>Properly escape API feedback message, take 4<commit_after>class ApiController < ApplicationController
  before_action :verify_key, :except => [:filter_generator]
  before_action :set_pagesize, :except => [:filter_generator]
  before_action :verify_write_token, :only => [:create_feedback, :report_post]
  skip_before_action :verify_authenticity_token, :only => [:create_feedback, :report_post]

  # Yes, this looks bad, but it actually works as a cache - we only have to calculate the bitstring for each filter once.
  @@filters = Hash.new { |h, k| h[k] = k.chars.map { |c| c.ord.to_s(2).rjust(8, '0') }.join('') }

  # Routes for developer use

  def filter_generator
  end

  # Read routes: Posts

  def posts
    filter = "\x00\x00\x00\x00\x00\x00\x00\x03sc\xc2\x80\x00\x00\x00\x00\x00"
    @posts = Post.where(:id => params[:ids].split(";")).select(select_fields(filter)).order(:id => :desc)
    @results = @posts.paginate(:page => params[:page], :per_page => @pagesize)
    @more = has_more?(params[:page], @results.count)
    render :formats => :json
  end

  def posts_by_feedback
    filter = "\x00\x00\x00\x00\x00\x00\x00\x03sc\xc2\x80\x00\x00\x00\x00\x00"
    @posts = Post.all.joins(:feedbacks).where(:feedbacks => { :feedback_type => params[:type] }).select(select_fields(filter)).order(:id => :desc)
    results = @posts.paginate(:page => params[:page], :per_page => @pagesize)
    render :json => { :items => results, :has_more => has_more?(params[:page], results.count) }
  end

  def posts_by_url
    filter = "\x00\x00\x00\x00\x00\x00\x00\x03sc\xc2\x80\x00\x00\x00\x00\x00"
    @posts = Post.where(:link => params[:urls].split(";")).select(select_fields(filter)).order(:id => :desc)
    results = @posts.paginate(:page => params[:page], :per_page => @pagesize)
    render :json => { :items => results, :has_more => has_more?(params[:page], results.count) }
  end

  def posts_by_site
    filter = "\x00\x00\x00\x00\x00\x00\x00\x03sc\xc2\x80\x00\x00\x00\x00\x00"
    @posts = Post.joins(:site).where(:sites => { :site_url => params[:site] }).select(select_fields(filter)).order(:id => :desc)
    results = @posts.paginate(:page => params[:page], :per_page => @pagesize)
    render :json => { :items => results, :has_more => has_more?(params[:page], results.count) }
  end

  def posts_by_daterange
    filter = "\x00\x00\x00\x00\x00\x00\x00\x03sc\xc2\x80\x00\x00\x00\x00\x00"
    @posts = Post.where(:created_at => DateTime.strptime(params[:from_date], '%s')..DateTime.strptime(params[:to_date], '%s'))
    results = @posts.select(select_fields(filter)).order(:id => :desc).paginate(:page => params[:page], :per_page => @pagesize)
    render :json => { :items => results, :has_more => has_more?(params[:page], results.count) }
  end

  def undeleted_posts
    filter = "\x00\x00\x00\x00\x00\x00\x00\x03sc\xc2\x80\x00\x00\x00\x00\x00"
    @posts = Post.left_outer_joins(:deletion_logs).where(:deletion_logs => { :id => nil }).select(select_fields(filter))
    results = @posts.order(:id => :desc).paginate(:page => params[:page], :per_page => @pagesize)
    render :json => { :items => results, :has_more => has_more?(params[:page], results.count) }
  end

  def post_feedback
    filter = "\x00\x00\x00\x00\xc2\xbd\x19\xc2\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00"
    @feedbacks = Feedback.where(:post_id => params[:id]).select(select_fields(filter)).order(:id => :desc)
    results = @feedbacks.paginate(:page => params[:page], :per_page => @pagesize)
    render :json => { :items => results, :has_more => has_more?(params[:page], results.count) }
  end

  def post_reasons
    filter = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x18\x00\x00\x00\x00\x00"
    @reasons = Reason.joins(:posts_reasons).where(:posts_reasons => { :post_id => params[:id] }).select(select_fields(filter)).order(:id => :desc)
    results = @reasons.paginate(:page => params[:page], :per_page => @pagesize)
    render :json => { :items => results, :has_more => has_more?(params[:page], results.count) }
  end

  def post_valid_feedback
    @post = Post.find params[:id]
    render :formats => :json
  end

  def search_posts
    filter = "\x00\x00\x00\x00\x00\x00\x00\x03sc\xc2\x80\x00\x00\x00\x00\x00"
    @posts = Post.all
    if params[:feedback_type].present?
      @posts = @posts.includes(:feedbacks).where(:feedbacks => { :feedback_type => params[:feedback_type] })
    end
    if params[:site].present?
      @posts = @posts.joins(:site).where(:sites => { :site_domain => params[:site] })
    end
    if params[:from_date].present?
      @posts = @posts.where('`posts`.`created_at` > ?', DateTime.strptime(params[:from_date], '%s'))
    end
    if params[:to_date].present?
      @posts = @posts.where('`posts`.`created_at` < ?', DateTime.strptime(params[:to_date], '%s'))
    end
    results = @posts.select(select_fields(filter)).order(:id => :desc).paginate(:page => params[:page], :per_page => @pagesize)
    render :json => { :items => results, :has_more => has_more?(params[:page], results.count) }
  end

  # Read routes: Reasons

  def reasons
    filter = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x18\x00\x00\x00\x00\x00"
    @reasons = Reason.where(:id => params[:ids].split(";")).select(select_fields(filter)).order(:id => :desc)
    results = @reasons.paginate(:page => params[:page], :per_page => @pagesize)
    render :json => { :items => results, :has_more => has_more?(params[:page], results.count) }
  end

  def reason_posts
    filter = "\x00\x00\x00\x00\x00\x00\x00\x03sc\xc2\x80\x00\x00\x00\x00\x00"
    @posts = Post.joins(:posts_reasons).where(:posts_reasons => { :reason_id => params[:id] }).select(select_fields(filter)).order(:id => :desc)
    results = @posts.paginate(:page => params[:page], :per_page => @pagesize)
    render :json => { :items => results, :has_more => has_more?(params[:page], results.count) }
  end

  # Read routes: BlacklistedWebsites

  def blacklisted_websites
    @websites = BlacklistedWebsite.active
    results = @websites.order(:id => :desc).paginate(:page => params[:page], :per_page => @pagesize)
    render :json => { :items => results, :has_more => has_more?(params[:page], results.count) }
  end

  # Read routes: Users

  def users_with_code_privs
    chat_ids = User.code_admins.pluck(:stackexchange_chat_id, :stackoverflow_chat_id, :meta_stackexchange_chat_id)

    items = {}
    ["stackexchange_chat_ids", "stackoverflow_chat_ids", "meta_stackexchange_chat_ids"].each_with_index do |name, index|
      items[name] = chat_ids.map { |a| a[index] }.select { |n| n.present? }
    end

    render :json => { :items => items }
  end

  # Read routes: Status

  def current_status
    render :json => { :last_ping => SmokeDetector.where.not(:location => params[:except]).maximum(:last_ping) }
  end

  # Write routes

  def create_feedback
    @post = Post.find params[:id]
    @feedback = Feedback.new(:user => @user, :post => @post, :api_key => @key)
    @feedback.feedback_type = params[:type]
    if @feedback.save
      if @feedback.is_positive?
        begin
          ActionCable.server.broadcast "smokedetector_messages", { blacklist: { uid: @post.stack_exchange_user.user_id.to_s, site: URI.parse(@post.stack_exchange_user.site.site_url).host, post: @post.link } }
        rescue
        end
      elsif @feedback.is_naa?
        begin
          ActionCable.server.broadcast "smokedetector_messages", { naa: { post_link: @post.link } }
        rescue
        end
      elsif @feedback.is_negative?
        begin
          ActionCable.server.broadcast "smokedetector_messages", { fp: { post_link: @post.link } }
        rescue
        end
      end
      unless Feedback.where(:post_id => @post.id, :feedback_type => @feedback.feedback_type).where.not(:id => @feedback.id).exists?
        ActionCable.server.broadcast "smokedetector_messages", { message: "#{@feedback.feedback_type} by #{@user.username}" + (@post.id == Post.last.id ? "" : " on [#{@post.title}](#{@post.link}) \\[[MS](#{url_for(:controller => :posts, :action => :show, :id => @post.id)})]") }
      end
      render :json => @post.feedbacks, :status => 201
    else
      render :status => 500, :json => { :error_name => "failed", :error_code => 500, :error_message => "Feedback object failed to save." }
    end
  end

  def report_post
    # We don't create any posts here, just send them on to Smokey to do all the processing
    ActionCable.server.broadcast "smokedetector_messages", { report: { :user => @user.username, :post_link => params[:post_link] } }

    render :plain => "OK", :status => 201
  end

  private
    def verify_key
      @key = ApiKey.find_by_key(params[:key])
      unless params[:key].present? && @key.present?
        smokey = SmokeDetector.find_by_access_token(params[:key])
        unless smokey.present?
          render :status => 403, :json => { :error_name => "unauthenticated", :error_code => 403, :error_message => "No key was passed or the passed key is invalid." } and return
        end
      end
    end
{'fraction_non_alphanumeric': 0.17551885739790227, 'fraction_numerical': 0.03899799151975006, 'mean_word_length': 3.8895253682487727, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 253, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5428745', 'n_tokens_mistral': 6903, 'n_tokens_neox': 6426, 'n_words': 1147}