Merge branch 'iss-1376' into 'develop'
[akkoma] / lib / pleroma / web / admin_api / admin_api_controller.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Web.AdminAPI.AdminAPIController do
6 use Pleroma.Web, :controller
7 alias Pleroma.Activity
8 alias Pleroma.ModerationLog
9 alias Pleroma.Plugs.OAuthScopesPlug
10 alias Pleroma.User
11 alias Pleroma.UserInviteToken
12 alias Pleroma.Web.ActivityPub.ActivityPub
13 alias Pleroma.Web.ActivityPub.Relay
14 alias Pleroma.Web.AdminAPI.AccountView
15 alias Pleroma.Web.AdminAPI.Config
16 alias Pleroma.Web.AdminAPI.ConfigView
17 alias Pleroma.Web.AdminAPI.ModerationLogView
18 alias Pleroma.Web.AdminAPI.Report
19 alias Pleroma.Web.AdminAPI.ReportView
20 alias Pleroma.Web.AdminAPI.Search
21 alias Pleroma.Web.CommonAPI
22 alias Pleroma.Web.Endpoint
23 alias Pleroma.Web.MastodonAPI.StatusView
24 alias Pleroma.Web.Router
25
26 import Pleroma.Web.ControllerHelper, only: [json_response: 3]
27
28 require Logger
29
30 plug(
31 OAuthScopesPlug,
32 %{scopes: ["read:accounts"]}
33 when action in [:list_users, :user_show, :right_get, :invites]
34 )
35
36 plug(
37 OAuthScopesPlug,
38 %{scopes: ["write:accounts"]}
39 when action in [
40 :get_invite_token,
41 :revoke_invite,
42 :email_invite,
43 :get_password_reset,
44 :user_follow,
45 :user_unfollow,
46 :user_delete,
47 :users_create,
48 :user_toggle_activation,
49 :user_activate,
50 :user_deactivate,
51 :tag_users,
52 :untag_users,
53 :right_add,
54 :right_delete
55 ]
56 )
57
58 plug(
59 OAuthScopesPlug,
60 %{scopes: ["read:reports"]} when action in [:list_reports, :report_show]
61 )
62
63 plug(
64 OAuthScopesPlug,
65 %{scopes: ["write:reports"]}
66 when action in [:report_update_state, :report_respond]
67 )
68
69 plug(
70 OAuthScopesPlug,
71 %{scopes: ["read:statuses"]} when action == :list_user_statuses
72 )
73
74 plug(
75 OAuthScopesPlug,
76 %{scopes: ["write:statuses"]}
77 when action in [:status_update, :status_delete]
78 )
79
80 plug(
81 OAuthScopesPlug,
82 %{scopes: ["read"]}
83 when action in [:config_show, :migrate_to_db, :migrate_from_db, :list_log]
84 )
85
86 plug(
87 OAuthScopesPlug,
88 %{scopes: ["write"]}
89 when action in [:relay_follow, :relay_unfollow, :config_update]
90 )
91
92 @users_page_size 50
93
94 action_fallback(:errors)
95
96 def user_delete(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
97 user = User.get_cached_by_nickname(nickname)
98 User.delete(user)
99
100 ModerationLog.insert_log(%{
101 actor: admin,
102 subject: [user],
103 action: "delete"
104 })
105
106 conn
107 |> json(nickname)
108 end
109
110 def user_delete(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
111 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
112 User.delete(users)
113
114 ModerationLog.insert_log(%{
115 actor: admin,
116 subject: users,
117 action: "delete"
118 })
119
120 conn
121 |> json(nicknames)
122 end
123
124 def user_follow(%{assigns: %{user: admin}} = conn, %{
125 "follower" => follower_nick,
126 "followed" => followed_nick
127 }) do
128 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
129 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
130 User.follow(follower, followed)
131
132 ModerationLog.insert_log(%{
133 actor: admin,
134 followed: followed,
135 follower: follower,
136 action: "follow"
137 })
138 end
139
140 conn
141 |> json("ok")
142 end
143
144 def user_unfollow(%{assigns: %{user: admin}} = conn, %{
145 "follower" => follower_nick,
146 "followed" => followed_nick
147 }) do
148 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
149 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
150 User.unfollow(follower, followed)
151
152 ModerationLog.insert_log(%{
153 actor: admin,
154 followed: followed,
155 follower: follower,
156 action: "unfollow"
157 })
158 end
159
160 conn
161 |> json("ok")
162 end
163
164 def users_create(%{assigns: %{user: admin}} = conn, %{"users" => users}) do
165 changesets =
166 Enum.map(users, fn %{"nickname" => nickname, "email" => email, "password" => password} ->
167 user_data = %{
168 nickname: nickname,
169 name: nickname,
170 email: email,
171 password: password,
172 password_confirmation: password,
173 bio: "."
174 }
175
176 User.register_changeset(%User{}, user_data, need_confirmation: false)
177 end)
178 |> Enum.reduce(Ecto.Multi.new(), fn changeset, multi ->
179 Ecto.Multi.insert(multi, Ecto.UUID.generate(), changeset)
180 end)
181
182 case Pleroma.Repo.transaction(changesets) do
183 {:ok, users} ->
184 res =
185 users
186 |> Map.values()
187 |> Enum.map(fn user ->
188 {:ok, user} = User.post_register_action(user)
189
190 user
191 end)
192 |> Enum.map(&AccountView.render("created.json", %{user: &1}))
193
194 ModerationLog.insert_log(%{
195 actor: admin,
196 subjects: Map.values(users),
197 action: "create"
198 })
199
200 conn
201 |> json(res)
202
203 {:error, id, changeset, _} ->
204 res =
205 Enum.map(changesets.operations, fn
206 {current_id, {:changeset, _current_changeset, _}} when current_id == id ->
207 AccountView.render("create-error.json", %{changeset: changeset})
208
209 {_, {:changeset, current_changeset, _}} ->
210 AccountView.render("create-error.json", %{changeset: current_changeset})
211 end)
212
213 conn
214 |> put_status(:conflict)
215 |> json(res)
216 end
217 end
218
219 def user_show(conn, %{"nickname" => nickname}) do
220 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
221 conn
222 |> put_view(AccountView)
223 |> render("show.json", %{user: user})
224 else
225 _ -> {:error, :not_found}
226 end
227 end
228
229 def list_user_statuses(conn, %{"nickname" => nickname} = params) do
230 godmode = params["godmode"] == "true" || params["godmode"] == true
231
232 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
233 {_, page_size} = page_params(params)
234
235 activities =
236 ActivityPub.fetch_user_activities(user, nil, %{
237 "limit" => page_size,
238 "godmode" => godmode
239 })
240
241 conn
242 |> put_view(StatusView)
243 |> render("index.json", %{activities: activities, as: :activity})
244 else
245 _ -> {:error, :not_found}
246 end
247 end
248
249 def user_toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
250 user = User.get_cached_by_nickname(nickname)
251
252 {:ok, updated_user} = User.deactivate(user, !user.deactivated)
253
254 action = if user.deactivated, do: "activate", else: "deactivate"
255
256 ModerationLog.insert_log(%{
257 actor: admin,
258 subject: [user],
259 action: action
260 })
261
262 conn
263 |> put_view(AccountView)
264 |> render("show.json", %{user: updated_user})
265 end
266
267 def user_activate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
268 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
269 {:ok, updated_users} = User.deactivate(users, false)
270
271 ModerationLog.insert_log(%{
272 actor: admin,
273 subject: users,
274 action: "activate"
275 })
276
277 conn
278 |> put_view(AccountView)
279 |> render("index.json", %{users: Keyword.values(updated_users)})
280 end
281
282 def user_deactivate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
283 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
284 {:ok, updated_users} = User.deactivate(users, true)
285
286 ModerationLog.insert_log(%{
287 actor: admin,
288 subject: users,
289 action: "deactivate"
290 })
291
292 conn
293 |> put_view(AccountView)
294 |> render("index.json", %{users: Keyword.values(updated_users)})
295 end
296
297 def tag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
298 with {:ok, _} <- User.tag(nicknames, tags) do
299 ModerationLog.insert_log(%{
300 actor: admin,
301 nicknames: nicknames,
302 tags: tags,
303 action: "tag"
304 })
305
306 json_response(conn, :no_content, "")
307 end
308 end
309
310 def untag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
311 with {:ok, _} <- User.untag(nicknames, tags) do
312 ModerationLog.insert_log(%{
313 actor: admin,
314 nicknames: nicknames,
315 tags: tags,
316 action: "untag"
317 })
318
319 json_response(conn, :no_content, "")
320 end
321 end
322
323 def list_users(conn, params) do
324 {page, page_size} = page_params(params)
325 filters = maybe_parse_filters(params["filters"])
326
327 search_params = %{
328 query: params["query"],
329 page: page,
330 page_size: page_size,
331 tags: params["tags"],
332 name: params["name"],
333 email: params["email"]
334 }
335
336 with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)),
337 {:ok, users, count} <- filter_relay_user(users, count),
338 do:
339 conn
340 |> json(
341 AccountView.render("index.json",
342 users: users,
343 count: count,
344 page_size: page_size
345 )
346 )
347 end
348
349 defp filter_relay_user(users, count) do
350 filtered_users = Enum.reject(users, &relay_user?/1)
351 count = if Enum.any?(users, &relay_user?/1), do: length(filtered_users), else: count
352
353 {:ok, filtered_users, count}
354 end
355
356 defp relay_user?(user) do
357 user.ap_id == Relay.relay_ap_id()
358 end
359
360 @filters ~w(local external active deactivated is_admin is_moderator)
361
362 @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
363 defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
364
365 defp maybe_parse_filters(filters) do
366 filters
367 |> String.split(",")
368 |> Enum.filter(&Enum.member?(@filters, &1))
369 |> Enum.map(&String.to_atom(&1))
370 |> Enum.into(%{}, &{&1, true})
371 end
372
373 def right_add_multiple(%{assigns: %{user: admin}} = conn, %{
374 "permission_group" => permission_group,
375 "nicknames" => nicknames
376 })
377 when permission_group in ["moderator", "admin"] do
378 update = %{:"is_#{permission_group}" => true}
379
380 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
381
382 for u <- users, do: User.admin_api_update(u, update)
383
384 ModerationLog.insert_log(%{
385 action: "grant",
386 actor: admin,
387 subject: users,
388 permission: permission_group
389 })
390
391 json(conn, update)
392 end
393
394 def right_add_multiple(conn, _) do
395 render_error(conn, :not_found, "No such permission_group")
396 end
397
398 def right_add(%{assigns: %{user: admin}} = conn, %{
399 "permission_group" => permission_group,
400 "nickname" => nickname
401 })
402 when permission_group in ["moderator", "admin"] do
403 fields = %{:"is_#{permission_group}" => true}
404
405 {:ok, user} =
406 nickname
407 |> User.get_cached_by_nickname()
408 |> User.admin_api_update(fields)
409
410 ModerationLog.insert_log(%{
411 action: "grant",
412 actor: admin,
413 subject: [user],
414 permission: permission_group
415 })
416
417 json(conn, fields)
418 end
419
420 def right_add(conn, _) do
421 render_error(conn, :not_found, "No such permission_group")
422 end
423
424 def right_get(conn, %{"nickname" => nickname}) do
425 user = User.get_cached_by_nickname(nickname)
426
427 conn
428 |> json(%{
429 is_moderator: user.is_moderator,
430 is_admin: user.is_admin
431 })
432 end
433
434 def right_delete_multiple(
435 %{assigns: %{user: %{nickname: admin_nickname} = admin}} = conn,
436 %{
437 "permission_group" => permission_group,
438 "nicknames" => nicknames
439 }
440 )
441 when permission_group in ["moderator", "admin"] do
442 with false <- Enum.member?(nicknames, admin_nickname) do
443 update = %{:"is_#{permission_group}" => false}
444
445 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
446
447 for u <- users, do: User.admin_api_update(u, update)
448
449 ModerationLog.insert_log(%{
450 action: "revoke",
451 actor: admin,
452 subject: users,
453 permission: permission_group
454 })
455
456 json(conn, update)
457 else
458 _ -> render_error(conn, :forbidden, "You can't revoke your own admin/moderator status.")
459 end
460 end
461
462 def right_delete_multiple(conn, _) do
463 render_error(conn, :not_found, "No such permission_group")
464 end
465
466 def right_delete(
467 %{assigns: %{user: admin}} = conn,
468 %{
469 "permission_group" => permission_group,
470 "nickname" => nickname
471 }
472 )
473 when permission_group in ["moderator", "admin"] do
474 fields = %{:"is_#{permission_group}" => false}
475
476 {:ok, user} =
477 nickname
478 |> User.get_cached_by_nickname()
479 |> User.admin_api_update(fields)
480
481 ModerationLog.insert_log(%{
482 action: "revoke",
483 actor: admin,
484 subject: [user],
485 permission: permission_group
486 })
487
488 json(conn, fields)
489 end
490
491 def right_delete(%{assigns: %{user: %{nickname: nickname}}} = conn, %{"nickname" => nickname}) do
492 render_error(conn, :forbidden, "You can't revoke your own admin status.")
493 end
494
495 def relay_list(conn, _params) do
496 with {:ok, list} <- Relay.list() do
497 json(conn, %{relays: list})
498 else
499 _ ->
500 conn
501 |> put_status(500)
502 end
503 end
504
505 def relay_follow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
506 with {:ok, _message} <- Relay.follow(target) do
507 ModerationLog.insert_log(%{
508 action: "relay_follow",
509 actor: admin,
510 target: target
511 })
512
513 json(conn, target)
514 else
515 _ ->
516 conn
517 |> put_status(500)
518 |> json(target)
519 end
520 end
521
522 def relay_unfollow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
523 with {:ok, _message} <- Relay.unfollow(target) do
524 ModerationLog.insert_log(%{
525 action: "relay_unfollow",
526 actor: admin,
527 target: target
528 })
529
530 json(conn, target)
531 else
532 _ ->
533 conn
534 |> put_status(500)
535 |> json(target)
536 end
537 end
538
539 @doc "Sends registration invite via email"
540 def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) do
541 with true <-
542 Pleroma.Config.get([:instance, :invites_enabled]) &&
543 !Pleroma.Config.get([:instance, :registrations_open]),
544 {:ok, invite_token} <- UserInviteToken.create_invite(),
545 email <-
546 Pleroma.Emails.UserEmail.user_invitation_email(
547 user,
548 invite_token,
549 email,
550 params["name"]
551 ),
552 {:ok, _} <- Pleroma.Emails.Mailer.deliver(email) do
553 json_response(conn, :no_content, "")
554 end
555 end
556
557 @doc "Create an account registration invite token"
558 def create_invite_token(conn, params) do
559 opts = %{}
560
561 opts =
562 if params["max_use"],
563 do: Map.put(opts, :max_use, params["max_use"]),
564 else: opts
565
566 opts =
567 if params["expires_at"],
568 do: Map.put(opts, :expires_at, params["expires_at"]),
569 else: opts
570
571 {:ok, invite} = UserInviteToken.create_invite(opts)
572
573 json(conn, AccountView.render("invite.json", %{invite: invite}))
574 end
575
576 @doc "Get list of created invites"
577 def invites(conn, _params) do
578 invites = UserInviteToken.list_invites()
579
580 conn
581 |> put_view(AccountView)
582 |> render("invites.json", %{invites: invites})
583 end
584
585 @doc "Revokes invite by token"
586 def revoke_invite(conn, %{"token" => token}) do
587 with {:ok, invite} <- UserInviteToken.find_by_token(token),
588 {:ok, updated_invite} = UserInviteToken.update_invite(invite, %{used: true}) do
589 conn
590 |> put_view(AccountView)
591 |> render("invite.json", %{invite: updated_invite})
592 else
593 nil -> {:error, :not_found}
594 end
595 end
596
597 @doc "Get a password reset token (base64 string) for given nickname"
598 def get_password_reset(conn, %{"nickname" => nickname}) do
599 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
600 {:ok, token} = Pleroma.PasswordResetToken.create_token(user)
601
602 conn
603 |> json(%{
604 token: token.token,
605 link: Router.Helpers.reset_password_url(Endpoint, :reset, token.token)
606 })
607 end
608
609 @doc "Force password reset for a given user"
610 def force_password_reset(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
611 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
612
613 Enum.map(users, &User.force_password_reset_async/1)
614
615 ModerationLog.insert_log(%{
616 actor: admin,
617 subject: users,
618 action: "force_password_reset"
619 })
620
621 json_response(conn, :no_content, "")
622 end
623
624 def list_reports(conn, params) do
625 {page, page_size} = page_params(params)
626
627 params =
628 params
629 |> Map.put("type", "Flag")
630 |> Map.put("skip_preload", true)
631 |> Map.put("total", true)
632 |> Map.put("limit", page_size)
633 |> Map.put("offset", (page - 1) * page_size)
634
635 reports = ActivityPub.fetch_activities([], params, :offset)
636
637 conn
638 |> put_view(ReportView)
639 |> render("index.json", %{reports: reports})
640 end
641
642 def report_show(conn, %{"id" => id}) do
643 with %Activity{} = report <- Activity.get_by_id(id) do
644 conn
645 |> put_view(ReportView)
646 |> render("show.json", Report.extract_report_info(report))
647 else
648 _ -> {:error, :not_found}
649 end
650 end
651
652 def report_update_state(%{assigns: %{user: admin}} = conn, %{"id" => id, "state" => state}) do
653 with {:ok, report} <- CommonAPI.update_report_state(id, state) do
654 ModerationLog.insert_log(%{
655 action: "report_update",
656 actor: admin,
657 subject: report
658 })
659
660 conn
661 |> put_view(ReportView)
662 |> render("show.json", Report.extract_report_info(report))
663 end
664 end
665
666 def report_respond(%{assigns: %{user: user}} = conn, %{"id" => id} = params) do
667 with false <- is_nil(params["status"]),
668 %Activity{} <- Activity.get_by_id(id) do
669 params =
670 params
671 |> Map.put("in_reply_to_status_id", id)
672 |> Map.put("visibility", "direct")
673
674 {:ok, activity} = CommonAPI.post(user, params)
675
676 ModerationLog.insert_log(%{
677 action: "report_response",
678 actor: user,
679 subject: activity,
680 text: params["status"]
681 })
682
683 conn
684 |> put_view(StatusView)
685 |> render("show.json", %{activity: activity})
686 else
687 true ->
688 {:param_cast, nil}
689
690 nil ->
691 {:error, :not_found}
692 end
693 end
694
695 def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do
696 with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do
697 {:ok, sensitive} = Ecto.Type.cast(:boolean, params["sensitive"])
698
699 ModerationLog.insert_log(%{
700 action: "status_update",
701 actor: admin,
702 subject: activity,
703 sensitive: sensitive,
704 visibility: params["visibility"]
705 })
706
707 conn
708 |> put_view(StatusView)
709 |> render("show.json", %{activity: activity})
710 end
711 end
712
713 def status_delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do
714 with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
715 ModerationLog.insert_log(%{
716 action: "status_delete",
717 actor: user,
718 subject_id: id
719 })
720
721 json(conn, %{})
722 end
723 end
724
725 def list_log(conn, params) do
726 {page, page_size} = page_params(params)
727
728 log =
729 ModerationLog.get_all(%{
730 page: page,
731 page_size: page_size,
732 start_date: params["start_date"],
733 end_date: params["end_date"],
734 user_id: params["user_id"],
735 search: params["search"]
736 })
737
738 conn
739 |> put_view(ModerationLogView)
740 |> render("index.json", %{log: log})
741 end
742
743 def migrate_to_db(conn, _params) do
744 Mix.Tasks.Pleroma.Config.run(["migrate_to_db"])
745 json(conn, %{})
746 end
747
748 def migrate_from_db(conn, _params) do
749 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "true"])
750 json(conn, %{})
751 end
752
753 def config_show(conn, _params) do
754 configs = Pleroma.Repo.all(Config)
755
756 conn
757 |> put_view(ConfigView)
758 |> render("index.json", %{configs: configs})
759 end
760
761 def config_update(conn, %{"configs" => configs}) do
762 updated =
763 if Pleroma.Config.get([:instance, :dynamic_configuration]) do
764 updated =
765 Enum.map(configs, fn
766 %{"group" => group, "key" => key, "delete" => "true"} = params ->
767 {:ok, config} = Config.delete(%{group: group, key: key, subkeys: params["subkeys"]})
768 config
769
770 %{"group" => group, "key" => key, "value" => value} ->
771 {:ok, config} = Config.update_or_create(%{group: group, key: key, value: value})
772 config
773 end)
774 |> Enum.reject(&is_nil(&1))
775
776 Pleroma.Config.TransferTask.load_and_update_env()
777 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "false"])
778 updated
779 else
780 []
781 end
782
783 conn
784 |> put_view(ConfigView)
785 |> render("index.json", %{configs: updated})
786 end
787
788 def reload_emoji(conn, _params) do
789 Pleroma.Emoji.reload()
790
791 conn |> json("ok")
792 end
793
794 def errors(conn, {:error, :not_found}) do
795 conn
796 |> put_status(:not_found)
797 |> json(dgettext("errors", "Not found"))
798 end
799
800 def errors(conn, {:error, reason}) do
801 conn
802 |> put_status(:bad_request)
803 |> json(reason)
804 end
805
806 def errors(conn, {:param_cast, _}) do
807 conn
808 |> put_status(:bad_request)
809 |> json(dgettext("errors", "Invalid parameters"))
810 end
811
812 def errors(conn, _) do
813 conn
814 |> put_status(:internal_server_error)
815 |> json(dgettext("errors", "Something went wrong"))
816 end
817
818 defp page_params(params) do
819 {get_page(params["page"]), get_page_size(params["page_size"])}
820 end
821
822 defp get_page(page_string) when is_nil(page_string), do: 1
823
824 defp get_page(page_string) do
825 case Integer.parse(page_string) do
826 {page, _} -> page
827 :error -> 1
828 end
829 end
830
831 defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size
832
833 defp get_page_size(page_size_string) do
834 case Integer.parse(page_size_string) do
835 {page_size, _} -> page_size
836 :error -> @users_page_size
837 end
838 end
839 end